diff --git a/src/components/LoginPage.tsx b/src/components/LoginPage.tsx index f466604..3bc1086 100644 --- a/src/components/LoginPage.tsx +++ b/src/components/LoginPage.tsx @@ -1,32 +1,61 @@ import React, { useState } from 'react'; -import { Database, Loader2, LogIn } from 'lucide-react'; -import { signIn } from '../lib/auth'; +import { Database, Loader2, LogIn, UserPlus } from 'lucide-react'; +import { signIn, signUp } from '../lib/auth'; interface LoginPageProps { onLogin: () => void; } export function LoginPage({ onLogin }: LoginPageProps) { + const [mode, setMode] = useState<'signin' | 'signup'>('signin'); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const [successMsg, setSuccessMsg] = useState(null); + + const switchMode = (next: 'signin' | 'signup') => { + setMode(next); + setError(null); + setSuccessMsg(null); + setPassword(''); + setConfirmPassword(''); + }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - setLoading(true); setError(null); + setSuccessMsg(null); + if (mode === 'signup' && password !== confirmPassword) { + setError('Passwords do not match.'); + return; + } + if (mode === 'signup' && password.length < 8) { + setError('Password must be at least 8 characters.'); + return; + } + + setLoading(true); try { - await signIn(email.trim(), password); - onLogin(); + if (mode === 'signup') { + await signUp(email.trim(), password); + setSuccessMsg('Account created! Check your email to confirm, then sign in.'); + switchMode('signin'); + } else { + await signIn(email.trim(), password); + onLogin(); + } } catch (err: any) { - setError(err.message || 'Login failed.'); + setError(err.message || 'Something went wrong.'); } finally { setLoading(false); } }; + const isSignUp = mode === 'signup'; + return (
@@ -35,7 +64,31 @@ export function LoginPage({ onLogin }: LoginPageProps) {

CRAZE Data Quality

-

Sign in with your CRAZE account

+

+ {isSignUp ? 'Create your CRAZE account' : 'Sign in with your CRAZE account'} +

+
+ + {/* Mode toggle */} +
+ +
@@ -44,6 +97,11 @@ export function LoginPage({ onLogin }: LoginPageProps) { {error} )} + {successMsg && ( +
+ {successMsg} +
+ )}
@@ -70,18 +128,34 @@ export function LoginPage({ onLogin }: LoginPageProps) { />
+ {isSignUp && ( +
+ + setConfirmPassword(e.target.value)} + placeholder="••••••••" + required + className="w-full bg-slate-900 border border-slate-700 rounded-md px-3 py-2.5 text-sm text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors" + /> +
+ )} +

- Access restricted to @craze.de accounts + Access restricted to @craze-group.com accounts

diff --git a/src/lib/auth.ts b/src/lib/auth.ts index ba62e0f..525756a 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -8,6 +8,26 @@ export interface AuthSession { user: { id: string; email: string }; } +export async function signUp(email: string, password: string): Promise { + if (!email.toLowerCase().endsWith(ALLOWED_DOMAIN)) { + throw new Error(`Only ${ALLOWED_DOMAIN} email addresses are allowed.`); + } + + const response = await fetch(`${SUPABASE_URL}/auth/v1/signup`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'apikey': SUPABASE_ANON_KEY, + }, + body: JSON.stringify({ email, password }), + }); + + if (!response.ok) { + const err = await response.json().catch(() => ({})); + throw new Error(err.error_description || err.message || 'Registration failed.'); + } +} + export async function signIn(email: string, password: string): Promise { if (!email.toLowerCase().endsWith(ALLOWED_DOMAIN)) { throw new Error(`Only ${ALLOWED_DOMAIN} email addresses are allowed.`);