mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:05:24 +02:00
feat: add Supabase email auth with signup/login gate
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
76d901fcde
commit
47e2513b79
@@ -0,0 +1,193 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import type { Session } from '@supabase/supabase-js';
|
||||
import { supabaseAuth } from '../services/authClient';
|
||||
import CrazeLogo from './CrazeLogo';
|
||||
|
||||
type AuthMode = 'login' | 'signup' | 'check_email' | 'loading';
|
||||
|
||||
interface AuthGateProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const AuthGate: React.FC<AuthGateProps> = ({ children }) => {
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [mode, setMode] = useState<AuthMode>('loading');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
supabaseAuth.auth.getSession().then(({ data }) => {
|
||||
setSession(data.session);
|
||||
setMode(data.session ? 'loading' : 'login');
|
||||
});
|
||||
|
||||
const { data: listener } = supabaseAuth.auth.onAuthStateChange((_event, sess) => {
|
||||
setSession(sess);
|
||||
if (sess) setMode('loading');
|
||||
});
|
||||
|
||||
return () => listener.subscription.unsubscribe();
|
||||
}, []);
|
||||
|
||||
const handleSignUp = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setSubmitting(true);
|
||||
const { error: err } = await supabaseAuth.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: { emailRedirectTo: window.location.origin },
|
||||
});
|
||||
setSubmitting(false);
|
||||
if (err) {
|
||||
setError(err.message);
|
||||
} else {
|
||||
setMode('check_email');
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
setSubmitting(true);
|
||||
const { error: err } = await supabaseAuth.auth.signInWithPassword({ email, password });
|
||||
setSubmitting(false);
|
||||
if (err) setError(err.message);
|
||||
};
|
||||
|
||||
const handleSignOut = async () => {
|
||||
await supabaseAuth.auth.signOut();
|
||||
setSession(null);
|
||||
setMode('login');
|
||||
setEmail('');
|
||||
setPassword('');
|
||||
};
|
||||
|
||||
// Session active — render app
|
||||
if (session) {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<button
|
||||
onClick={handleSignOut}
|
||||
className="fixed top-3 right-16 z-50 text-[10px] text-slate-600 hover:text-slate-400 font-bold uppercase tracking-wider transition-colors hidden md:block"
|
||||
title="Sign out"
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Email sent confirmation
|
||||
if (mode === 'check_email') {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-slate-950 p-4">
|
||||
<div className="w-full max-w-sm text-center">
|
||||
<div className="text-5xl mb-6">📬</div>
|
||||
<h2 className="text-xl font-black text-white mb-2">Check your email</h2>
|
||||
<p className="text-sm text-slate-400 mb-6">
|
||||
Confirmation link sent to <span className="text-white font-bold">{email}</span>.<br />
|
||||
Click it to activate your account.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setMode('login')}
|
||||
className="text-xs text-slate-500 hover:text-slate-300 underline transition-colors"
|
||||
>
|
||||
Back to login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Initial loading
|
||||
if (mode === 'loading') {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-slate-950">
|
||||
<div className="w-10 h-10 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const isSignUp = mode === 'signup';
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-slate-950 p-4">
|
||||
<div className="w-full max-w-sm">
|
||||
{/* Logo */}
|
||||
<div className="h-12 w-48 mx-auto mb-8">
|
||||
<CrazeLogo />
|
||||
</div>
|
||||
|
||||
<div className="bg-slate-900 border border-white/10 rounded-2xl shadow-2xl p-6">
|
||||
<h2 className="text-lg font-black text-white mb-1">
|
||||
{isSignUp ? 'Create account' : 'Welcome back'}
|
||||
</h2>
|
||||
<p className="text-xs text-slate-500 mb-6">
|
||||
{isSignUp ? 'Sign up to access the dashboard' : 'Sign in to your account'}
|
||||
</p>
|
||||
|
||||
<form onSubmit={isSignUp ? handleSignUp : handleLogin} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-slate-400 mb-1.5 uppercase tracking-wider">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
required
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
placeholder="you@example.com"
|
||||
className="w-full bg-slate-800 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-white placeholder-slate-600
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-xs font-bold text-slate-400 mb-1.5 uppercase tracking-wider">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
required
|
||||
minLength={6}
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
placeholder="••••••••"
|
||||
className="w-full bg-slate-800 border border-white/10 rounded-lg px-3 py-2.5 text-sm text-white placeholder-slate-600
|
||||
focus:outline-none focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/50 transition-all"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-500/10 border border-red-500/20 rounded-lg px-3 py-2 text-xs text-red-400 font-medium">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
className="w-full py-2.5 bg-indigo-600 hover:bg-indigo-500 disabled:bg-indigo-800 disabled:cursor-not-allowed
|
||||
text-white font-black text-sm rounded-lg transition-all active:scale-[0.98] shadow-lg shadow-indigo-500/20"
|
||||
>
|
||||
{submitting ? '...' : isSignUp ? 'Create account' : 'Sign in'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<button
|
||||
onClick={() => { setMode(isSignUp ? 'login' : 'signup'); setError(''); }}
|
||||
className="text-xs text-slate-500 hover:text-slate-300 transition-colors"
|
||||
>
|
||||
{isSignUp ? 'Already have an account? Sign in' : "Don't have an account? Sign up"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user