feat: Enhanced registration with username, name, and auto-subscription
Registration form now includes: - Username field (required, unique, validated) - Full name field (optional) - Auto-validates username format and availability - Auto-creates 'starter' subscription on signup Login form unchanged (email + password only)
This commit is contained in:
@@ -2,13 +2,15 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { createClient } from '@/lib/supabase/client';
|
import { createClient } from '@/lib/supabase/client';
|
||||||
import { LogIn, UserPlus, Mail, Lock, Loader2, AlertCircle } from 'lucide-react';
|
import { LogIn, UserPlus, Mail, Lock, Loader2, AlertCircle, User, AtSign } from 'lucide-react';
|
||||||
|
|
||||||
export default function AuthForm() {
|
export default function AuthForm() {
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
const [isLogin, setIsLogin] = useState(true);
|
const [isLogin, setIsLogin] = useState(true);
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [fullName, setFullName] = useState('');
|
||||||
const [rememberMe, setRememberMe] = useState(true);
|
const [rememberMe, setRememberMe] = useState(true);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -30,21 +32,67 @@ export default function AuthForm() {
|
|||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
// If remember-me is checked, session will persist (default Supabase behavior)
|
// If remember-me is checked, session will persist (default Supabase behavior)
|
||||||
// If not checked, clear session on browser close via localStorage flag
|
|
||||||
if (!rememberMe) {
|
if (!rememberMe) {
|
||||||
sessionStorage.setItem('dramlog_session_only', 'true');
|
sessionStorage.setItem('dramlog_session_only', 'true');
|
||||||
} else {
|
} else {
|
||||||
sessionStorage.removeItem('dramlog_session_only');
|
sessionStorage.removeItem('dramlog_session_only');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { error } = await supabase.auth.signUp({
|
// Validate username
|
||||||
|
if (!username.trim()) {
|
||||||
|
throw new Error('Bitte gib einen Benutzernamen ein.');
|
||||||
|
}
|
||||||
|
if (username.length < 3) {
|
||||||
|
throw new Error('Benutzername muss mindestens 3 Zeichen haben.');
|
||||||
|
}
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
|
||||||
|
throw new Error('Benutzername darf nur Buchstaben, Zahlen und Unterstriche enthalten.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if username is already taken
|
||||||
|
const { data: existingUser } = await supabase
|
||||||
|
.from('profiles')
|
||||||
|
.select('id')
|
||||||
|
.eq('username', username.toLowerCase())
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
throw new Error('Dieser Benutzername ist bereits vergeben.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create user with metadata
|
||||||
|
const { data: signUpData, error } = await supabase.auth.signUp({
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
options: {
|
options: {
|
||||||
emailRedirectTo: `${window.location.origin}/auth/callback`,
|
emailRedirectTo: `${window.location.origin}/auth/callback`,
|
||||||
|
data: {
|
||||||
|
username: username.toLowerCase(),
|
||||||
|
full_name: fullName.trim() || undefined,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
// After successful signup, create subscription with starter plan
|
||||||
|
if (signUpData.user) {
|
||||||
|
// Get starter plan ID
|
||||||
|
const { data: starterPlan } = await supabase
|
||||||
|
.from('subscription_plans')
|
||||||
|
.select('id')
|
||||||
|
.eq('name', 'starter')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (starterPlan) {
|
||||||
|
await supabase
|
||||||
|
.from('user_subscriptions')
|
||||||
|
.upsert({
|
||||||
|
user_id: signUpData.user.id,
|
||||||
|
plan_id: starterPlan.id,
|
||||||
|
}, { onConflict: 'user_id' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setMessage('Checke deine E-Mails, um dein Konto zu bestätigen!');
|
setMessage('Checke deine E-Mails, um dein Konto zu bestätigen!');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -71,6 +119,43 @@ export default function AuthForm() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
{/* Registration-only fields */}
|
||||||
|
{!isLogin && (
|
||||||
|
<>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-[10px] font-black uppercase tracking-widest text-zinc-400 ml-1">Benutzername</label>
|
||||||
|
<div className="relative">
|
||||||
|
<AtSign className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={18} />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, ''))}
|
||||||
|
placeholder="dein_username"
|
||||||
|
required
|
||||||
|
maxLength={20}
|
||||||
|
className="w-full pl-10 pr-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl focus:ring-1 focus:ring-orange-600 focus:border-transparent outline-none transition-all text-white placeholder:text-zinc-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="text-[10px] text-zinc-600 ml-1">Nur Kleinbuchstaben, Zahlen und _</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-[10px] font-black uppercase tracking-widest text-zinc-400 ml-1">Name <span className="text-zinc-600">(optional)</span></label>
|
||||||
|
<div className="relative">
|
||||||
|
<User className="absolute left-3 top-1/2 -translate-y-1/2 text-zinc-400" size={18} />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={fullName}
|
||||||
|
onChange={(e) => setFullName(e.target.value)}
|
||||||
|
placeholder="Max Mustermann"
|
||||||
|
maxLength={50}
|
||||||
|
className="w-full pl-10 pr-4 py-3 bg-zinc-950 border border-zinc-800 rounded-xl focus:ring-1 focus:ring-orange-600 focus:border-transparent outline-none transition-all text-white placeholder:text-zinc-600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-[10px] font-black uppercase tracking-widest text-zinc-400 ml-1">E-Mail</label>
|
<label className="text-[10px] font-black uppercase tracking-widest text-zinc-400 ml-1">E-Mail</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user