diff --git a/components/AuthGate.tsx b/components/AuthGate.tsx new file mode 100644 index 0000000..697ea46 --- /dev/null +++ b/components/AuthGate.tsx @@ -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 = ({ children }) => { + const [session, setSession] = useState(null); + const [mode, setMode] = useState('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} + + + ); + } + + // Email sent confirmation + if (mode === 'check_email') { + return ( +
+
+
📬
+

Check your email

+

+ Confirmation link sent to {email}.
+ Click it to activate your account. +

+ +
+
+ ); + } + + // Initial loading + if (mode === 'loading') { + return ( +
+
+
+ ); + } + + const isSignUp = mode === 'signup'; + + return ( +
+
+ {/* Logo */} +
+ +
+ +
+

+ {isSignUp ? 'Create account' : 'Welcome back'} +

+

+ {isSignUp ? 'Sign up to access the dashboard' : 'Sign in to your account'} +

+ +
+
+ + 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" + /> +
+ +
+ + 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" + /> +
+ + {error && ( +
+ {error} +
+ )} + + +
+ +
+ +
+
+
+
+ ); +}; diff --git a/index.tsx b/index.tsx index a28080f..03c097b 100644 --- a/index.tsx +++ b/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import ErrorBoundary from './components/ErrorBoundary'; +import { AuthGate } from './components/AuthGate'; const rootElement = document.getElementById('root'); if (!rootElement) { @@ -11,6 +12,8 @@ if (!rootElement) { const root = ReactDOM.createRoot(rootElement); root.render( - + + + ); \ No newline at end of file diff --git a/services/authClient.ts b/services/authClient.ts new file mode 100644 index 0000000..ad560b5 --- /dev/null +++ b/services/authClient.ts @@ -0,0 +1,14 @@ +/// +import { createClient } from '@supabase/supabase-js'; + +const url = process.env.SUPABASE_URL || ''; +const key = process.env.SUPABASE_ANON_KEY || ''; + +export const supabaseAuth = createClient(url, key, { + auth: { + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: true, + storageKey: 'craze-auth-session', + }, +});