Files
hackerspace.sh/app/(app)/ops/page.tsx
T
Moheeb ZaraandClaude Opus 4.8 b1edb5c460 types: clear remaining tsc errors across 41 files (76→14, behavior-preserving)
Per-file type-only fixes surfaced after the Supabase client was correctly
typed. All fixes preserve runtime behavior; no any/ts-ignore/ts-expect-error.

- lib/supabase/client.ts: annotate the browser client as SupabaseClient<Database>
  (same ssr/js drift as server.ts; fixes it centrally for all callers).
- Supabase embedded-select joins whose FK metadata is missing from the
  generated types are bridged with `as unknown as <target>` + a TODO to remove
  after regenerating types/database.ts.
- setState optimistic-update variance and raw <select> enum values cast to the
  precise element/union type; .insert payload fields (Json, enums) given the
  precise column type via satisfies/field casts.
- Added narrowing guards where a value was legitimately nullable (comms channel,
  public form page) without changing the intended path.

The 14 remaining errors are confined to the four files fixed in Phase B (real
bugs + the tautological actions.test rewrite). 639 tests still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-01 18:56:11 -07:00

66 lines
3.2 KiB
TypeScript

import { createClient } from '@/lib/supabase/server'
import { OpsClient } from './ops-client'
import type { Tables } from '@/types/database'
export default async function OpsPage() {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) return null
const { data: member } = await supabase
.from('space_members').select('space_id, role, display_name, user_id').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single()
if (!member) return null
const isAdminBoard = member.role === 'admin' || member.role === 'board'
// Writing/managing secrets and the ACL stays admin/board only.
const canManageAcl = isAdminBoard
// The secrets list is always queried; secrets_select RLS filters it to the
// rows this member may see (admin/board, OR the ops.secrets.read role
// permission, OR a per-secret ops_acl entry). Only metadata is selected
// here; plaintext is fetched on demand via revealSecret().
const [{ data: kbEntries }, { data: areaLeads }, secretsResult, { data: customRoles }, { data: acl }, { data: canReadSecretsPerm }] = await Promise.all([
supabase.from('knowledge_base').select('*').eq('space_id', member.space_id).order('is_pinned', { ascending: false }).order('created_at', { ascending: false }),
supabase.from('area_leads').select('*').eq('space_id', member.space_id).order('area_name'),
supabase.from('secrets').select('id, title, area, created_at, label, description, icon, space_id, created_by, updated_at, category, notes, encryption_version').eq('space_id', member.space_id),
supabase.from('space_custom_roles').select('slug, name').eq('space_id', member.space_id),
supabase.from('ops_acl').select('entity_type, entity_id, role').eq('space_id', member.space_id),
supabase.rpc('user_has_permission', { uid: user.id, sid: member.space_id, perm: 'ops.secrets.read' }),
])
// Show the Secrets section to admin/board, to a holder of the
// ops.secrets.read permission, or to anyone RLS returned at least one
// (ACL-granted) secret for.
const canSeeSecrets = isAdminBoard || !!canReadSecretsPerm || (secretsResult.data?.length ?? 0) > 0
// Selectable ACL roles: built-in roles, custom-role slugs, and area-lead
// sentinels labelled by area name.
const aclRoleOptions = [
{ value: 'treasurer', label: 'treasurer' },
{ value: 'member', label: 'member' },
{ value: 'associate', label: 'associate' },
...((customRoles ?? []).map(c => ({ value: c.slug, label: c.name }))),
...((areaLeads ?? []).map(al => ({ value: `area_lead:${al.id}`, label: `${al.area_name} lead` }))),
]
const aclByEntity: Record<string, string[]> = {}
for (const row of acl ?? []) {
const k = `${row.entity_type}:${row.entity_id}`
;(aclByEntity[k] ??= []).push(row.role)
}
return (
<OpsClient
member={member as Tables<'space_members'>}
spaceId={member.space_id}
kbEntries={kbEntries ?? []}
areaLeads={areaLeads ?? []}
secrets={(secretsResult.data ?? []) as Tables<'secrets'>[]}
canSeeSecrets={canSeeSecrets}
canManageAcl={canManageAcl}
aclRoleOptions={aclRoleOptions}
aclByEntity={aclByEntity}
/>
)
}