import { createClient } from '@/lib/supabase/server' import { redirect } from 'next/navigation' import { AppSidebar } from '@/components/app-sidebar' export default async function AppLayout({ children }: { children: React.ReactNode }) { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() if (!user) redirect('/login') const { data: member } = await supabase .from('space_members') .select('*, spaces(*)') .eq('user_id', user.id) .in('status', ['current', 'unverified', 'late']) .order('joined_at', { ascending: true }) .limit(1) .maybeSingle() if (!member || !member.spaces) { // No space found - redirect to signup to create/join one const { data: { user: currentUser } } = await supabase.auth.getUser() if (currentUser) { redirect(`/signup?email=${encodeURIComponent(currentUser.email || '')}`) } redirect('/login') } // New members walk through the space's configured onboarding before they // reach the app. Founders (createSpace) and pre-existing members (migration // 022 backfill) have onboarding_completed_at set, so this only gates // genuinely-new joiners. /onboarding is outside this layout group, so there // is no redirect loop. if (!(member as { onboarding_completed_at?: string | null }).onboarding_completed_at) { redirect('/onboarding') } // Count badges const { count: taskCount } = await supabase .from('tasks') .select('*', { count: 'exact', head: true }) .eq('space_id', member.space_id) .in('status', ['open', 'claimed']) const { count: paymentCount } = await supabase .from('payments') .select('*', { count: 'exact', head: true }) .eq('space_id', member.space_id) .eq('link_status', 'unlinked') return (