'use client' import { useState, useEffect } from 'react' import { useRouter } from 'next/navigation' import Link from 'next/link' import { createClient } from '@/lib/supabase/client' import { createSpace, joinSpace } from '@/lib/auth-actions' function generateInviteCode() { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' const prefix = 'HSL' const year = new Date().getFullYear() let code = '' for (let i = 0; i < 4; i++) { code += chars.charAt(Math.floor(Math.random() * chars.length)) } return `${prefix}-${year}-${code}` } export default function SignupPage() { const router = useRouter() const [step, setStep] = useState<'account' | 'space'>('account') const [isReturningUser, setIsReturningUser] = useState(false) const [mode, setMode] = useState<'create' | 'join' | null>(null) const [fullName, setFullName] = useState('') const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [spaceName, setSpaceName] = useState('') const [spaceSlug, setSpaceSlug] = useState('') const [spaceCity, setSpaceCity] = useState('') const [inviteCode, setInviteCode] = useState('') const [error, setError] = useState('') const [loading, setLoading] = useState(false) useEffect(() => { const checkAuth = async () => { const supabase = createClient() const { data: { user } } = await supabase.auth.getUser() if (user) { setEmail(user.email || '') setFullName(user.user_metadata?.full_name || '') setIsReturningUser(true) setStep('space') } } checkAuth() }, []) async function handleAccountSubmit(e: React.FormEvent) { e.preventDefault() if (!mode) { setError('Please select create or join a space'); return } setError('') setStep('space') } async function handleSpaceSubmit(e: React.FormEvent) { e.preventDefault() if (!mode) return setLoading(true) setError('') // Returning user — already has a session, just create/join if (isReturningUser) { const result = mode === 'create' ? await createSpace({ spaceName, spaceSlug, spaceCity, displayName: fullName }) : await joinSpace({ inviteCode, displayName: fullName }) if (result.error) { setError(result.error) setLoading(false) return } // Hard navigate so server layout re-reads cookies fresh window.location.href = '/dashboard' return } // New user — sign up; trigger will create space/member const supabase = createClient() if (mode === 'join') { // Invite code is validated server-side in joinSpace (uses admin client, bypasses RLS). // Do NOT pre-validate here with the regular client — RLS blocks unauthenticated lookups. } const metadata: Record = { full_name: fullName, space_action: mode, ...(mode === 'create' ? { space_name: spaceName, space_slug: spaceSlug, space_city: spaceCity, invite_code: generateInviteCode() } : { join_invite_code: inviteCode.trim().toUpperCase() } ), } const { data: authData, error: authError } = await supabase.auth.signUp({ email, password, options: { emailRedirectTo: `${window.location.origin}/auth/callback`, data: metadata, }, }) if (authError) { setError(authError.message); setLoading(false); return } // If Supabase returns a session immediately it means email confirmation is // disabled — the user is fully signed up AND signed in in one step. // We still need to create the space/member record via the server action. if (authData.session) { // Manually set the session so server-side cookies are flushed before // we call the server action (which needs auth.getUser() to succeed). const supabaseClient = createClient() await supabaseClient.auth.setSession({ access_token: authData.session.access_token, refresh_token: authData.session.refresh_token, }) const result = mode === 'create' ? await createSpace({ spaceName, spaceSlug, spaceCity, displayName: fullName }) : await joinSpace({ inviteCode, displayName: fullName }) if (result.error) { setError(result.error) setLoading(false) return } // Hard navigate so the server re-reads cookies and picks up the session window.location.href = '/dashboard' return } // Email confirmation is required — redirect to the confirm page router.push('/signup/confirm?email=' + encodeURIComponent(email)) } return (
{'{'} hackerspace.sh {'}'}

{step === 'account' ? 'Create your account' : mode === 'create' ? 'Set up your Space' : mode === 'join' ? 'Join a Space' : 'Choose an option'} {isReturningUser && step === 'space' && ( ({email}) )}

{step === 'account' ? (
setFullName(e.target.value)} placeholder="Moheeb Zara" required className="w-full bg-[#232323] border border-[#333] text-white font-mono text-sm rounded px-3 py-2.5 placeholder:text-zinc-600 focus:outline-none focus:border-[#d4f53c] transition" />
setEmail(e.target.value)} placeholder="you@space.org" required className="w-full bg-[#232323] border border-[#333] text-white font-mono text-sm rounded px-3 py-2.5 placeholder:text-zinc-600 focus:outline-none focus:border-[#d4f53c] transition" />
setPassword(e.target.value)} placeholder="min 8 characters" required minLength={8} className="w-full bg-[#232323] border border-[#333] text-white font-mono text-sm rounded px-3 py-2.5 placeholder:text-zinc-600 focus:outline-none focus:border-[#d4f53c] transition" />

What are you doing?

{error &&

{error}

}
) : (
{/* Mode picker - show inline for returning users who haven't selected yet */} {!mode && (
)} {mode === 'create' ? (
{ setSpaceName(e.target.value) setSpaceSlug( e.target.value.toLowerCase().replace(/\s+/g, '').replace(/[^a-z0-9]/g, '') ) }} placeholder="HeatSync Labs" required className="w-full bg-[#232323] border border-[#333] text-white font-mono text-sm rounded px-3 py-2.5 placeholder:text-zinc-600 focus:outline-none focus:border-[#d4f53c] transition" />
setSpaceSlug(e.target.value.toLowerCase().replace(/[^a-z0-9]/g, ''))} placeholder="heatsynclabs" required pattern="[a-z0-9]+" title="Lowercase letters and numbers only" className="w-full bg-[#232323] border border-[#333] text-white font-mono text-sm rounded px-3 py-2.5 placeholder:text-zinc-600 focus:outline-none focus:border-[#d4f53c] transition" />
setSpaceCity(e.target.value)} placeholder="Mesa, AZ" className="w-full bg-[#232323] border border-[#333] text-white font-mono text-sm rounded px-3 py-2.5 placeholder:text-zinc-600 focus:outline-none focus:border-[#d4f53c] transition" />

You'll be the admin of this space.

Default channels (general, announcements, random) will be created automatically.

) : (
setInviteCode(e.target.value.toUpperCase())} placeholder="HSL-2025-XXXX" required className="w-full bg-[#232323] border border-[#333] text-white font-mono text-sm rounded px-3 py-2.5 placeholder:text-zinc-600 focus:outline-none focus:border-[#d4f53c] transition" />

Ask your space admin for the invite code from Settings.

)} {error &&

{error}

}
{!isReturningUser && ( )} {mode && ( )}
)} {!isReturningUser && (

already have an account?{' '} sign in →

)}
) }