import { redirect } from 'next/navigation' import { createClient } from '@/lib/supabase/server' import { PageHeader, PageTitle } from '@/components/ui/page-title' import { Badge } from '@/components/ui/badge' import { certificationStatus, CERT_STATUS_LABEL, type CertStatus, } from '@/lib/certifications-logic' import { PERMISSIONS, PERMISSION_CODES } from '@/lib/permissions-catalog' import { getMyClassSignups } from '@/lib/actions' import { SIGNUP_STATUS_LABEL, SESSION_STATUS_LABEL } from '@/lib/classes-logic' export const dynamic = 'force-dynamic' type Grant = { id: string granted_at: string expires_at: string | null revoked_at: string | null revoked_reason: string | null note: string | null certifications: { name: string; description: string | null; validity_months: number | null } | null } const STATUS_VARIANT: Record = { active: 'default', expiring_soon: 'outline', expired: 'outline', revoked: 'outline', } export default async function MePage() { const supabase = await createClient() const { data: { user }, } = await supabase.auth.getUser() if (!user) redirect('/login') const { data: member } = await supabase .from('space_members') .select('id, space_id, role, display_name') .eq('user_id', user.id) .in('status', ['current', 'unverified', 'late']) .maybeSingle() if (!member) redirect('/signup') const { data: grantsRaw } = await supabase .from('member_certifications') .select( 'id, granted_at, expires_at, revoked_at, revoked_reason, note, certifications(name, description, validity_months)', ) .eq('space_id', member.space_id) .eq('member_id', member.id) .order('granted_at', { ascending: false }) const grants = (grantsRaw ?? []) as unknown as Grant[] // Effective permissions (read-only view). admin implicitly holds everything. let heldCodes: string[] = [] if (member.role === 'admin') { heldCodes = [...PERMISSION_CODES] } else { const { data: roles } = await supabase.rpc('user_effective_roles', { uid: user.id, sid: member.space_id, }) const subjects = (roles ?? []) as string[] if (subjects.length > 0) { const { data: perms } = await supabase .from('space_role_permissions') .select('permission') .eq('space_id', member.space_id) .in('subject', subjects) heldCodes = Array.from(new Set((perms ?? []).map(p => p.permission as string))) } } const held = PERMISSIONS.filter(p => heldCodes.includes(p.code)) const signupRes = await getMyClassSignups() type ClassSignup = { id: string status: string attended: boolean signed_up_at: string class_sessions: | { starts_at: string; ends_at: string | null; location: string | null; status: string; classes: { title: string } | { title: string }[] | null } | { starts_at: string; ends_at: string | null; location: string | null; status: string; classes: { title: string } | { title: string }[] | null }[] | null } const classSignups: ClassSignup[] = 'data' in signupRes ? (signupRes.data as ClassSignup[]) : [] const sessionOf = (cs: ClassSignup) => Array.isArray(cs.class_sessions) ? cs.class_sessions[0] : cs.class_sessions const titleOf = (cs: ClassSignup) => { const sess = sessionOf(cs) const c = sess ? (Array.isArray(sess.classes) ? sess.classes[0] : sess.classes) : null return c?.title ?? 'Class' } return ( <> My certifications & access

Certifications

{grants.length === 0 ? (

You have no certifications yet. An instructor can award one to you.

) : (
    {grants.map(g => { const status = certificationStatus(g) return (
  • {g.certifications?.name ?? 'Certification'} {CERT_STATUS_LABEL[status]}
    {g.certifications?.description && (

    {g.certifications.description}

    )}

    granted {new Date(g.granted_at).toLocaleDateString()} {g.expires_at ? ` · expires ${new Date(g.expires_at).toLocaleDateString()}` : ' · no expiry'} {g.revoked_at ? ` · revoked ${new Date(g.revoked_at).toLocaleDateString()}` : ''}

    {g.revoked_at && g.revoked_reason && (

    Reason: {g.revoked_reason}

    )}
  • ) })}
)}

My permissions

{held.length === 0 ? (

You have the standard member access for this space. No extra permissions are granted to your role.

) : (
    {held.map(p => (
  • {p.label}
  • ))}
)}

Permissions are set per role by a space admin. This is a read-only view.

My classes

{classSignups.length === 0 ? (

You have not signed up for any classes. Browse what’s on at{' '} Classes.

) : (
    {classSignups.map(cs => { const sess = sessionOf(cs) return (
  • {titleOf(cs)} {SIGNUP_STATUS_LABEL[cs.status] ?? cs.status} {cs.attended && Attended}
    {sess && (

    {new Date(sess.starts_at).toLocaleString()} {sess.location ? ` · ${sess.location}` : ''} {' · '} {SESSION_STATUS_LABEL[sess.status] ?? sess.status}

    )}
  • ) })}
)}
) }