Files
hackerspace.sh/lib/actions/tiers.ts
T
Moheeb ZaraandClaude Opus 4.8 33b2475e67 fix: revalidate /customize (not /settings) after customize-panel mutations
The invites/onboarding/areas/roles/tiers admin panels moved to /customize, but
their actions still revalidated the old /settings route, so the /customize
Router Cache went stale after a write. Point them at /customize (keeping the
other cross-page revalidations).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-04 19:10:52 -07:00

104 lines
3.6 KiB
TypeScript

'use server'
import { revalidatePath } from 'next/cache'
import { createClient } from '@/lib/supabase/server'
import { requireMemberWithRole, parseInput } from '@/lib/auth-helpers'
import { createTierSchema, updateTierSchema, uuidSchema } from '@/lib/validations'
const TIER_RW_ROLES = ['admin', 'board', 'treasurer'] as const
export async function createTier(input: {
slug: string
name: string
description?: string | null
monthly_price_cents: number
billing_cadence?: 'monthly' | 'quarterly' | 'annual' | 'one_time' | 'custom'
sort_order?: number
}) {
const v = parseInput(createTierSchema, input)
if (!v.ok) return { error: v.error }
const supabase = await createClient()
const auth = await requireMemberWithRole(supabase, TIER_RW_ROLES, 'Treasurer, board, or admin access required')
if (!auth.ok) return { error: auth.error }
const { member } = auth
const { data, error } = await supabase
.from('space_tiers')
.insert({
space_id: member.space_id,
slug: v.data.slug,
name: v.data.name,
description: v.data.description ?? null,
monthly_price_cents: v.data.monthly_price_cents,
billing_cadence: v.data.billing_cadence ?? 'monthly',
sort_order: v.data.sort_order ?? 100,
is_system: false,
})
.select('id')
.single()
if (error) return { error: error.message }
revalidatePath('/customize')
return { id: data.id }
}
export async function updateTier(tierId: string, updates: {
name?: string
description?: string | null
monthly_price_cents?: number
billing_cadence?: 'monthly' | 'quarterly' | 'annual' | 'one_time' | 'custom'
sort_order?: number
is_archived?: boolean
}) {
const idCheck = parseInput(uuidSchema, tierId)
if (!idCheck.ok) return { error: 'Invalid tier ID' }
const v = parseInput(updateTierSchema, updates)
if (!v.ok) return { error: v.error }
const supabase = await createClient()
const auth = await requireMemberWithRole(supabase, TIER_RW_ROLES, 'Treasurer, board, or admin access required')
if (!auth.ok) return { error: auth.error }
const { member } = auth
const patch: Record<string, unknown> = {}
if (v.data.name !== undefined) patch.name = v.data.name
if (v.data.description !== undefined) patch.description = v.data.description
if (v.data.monthly_price_cents !== undefined) patch.monthly_price_cents = v.data.monthly_price_cents
if (v.data.billing_cadence !== undefined) patch.billing_cadence = v.data.billing_cadence
if (v.data.sort_order !== undefined) patch.sort_order = v.data.sort_order
if (v.data.is_archived !== undefined) patch.is_archived = v.data.is_archived
const { error } = await supabase
.from('space_tiers')
.update(patch)
.eq('id', idCheck.data)
.eq('space_id', member.space_id)
if (error) return { error: error.message }
revalidatePath('/customize')
return { success: true as const }
}
export async function deleteTier(tierId: string) {
const v = parseInput(uuidSchema, tierId)
if (!v.ok) return { error: 'Invalid tier ID' }
const supabase = await createClient()
// RLS blocks deletion of system tiers; admin-only.
const auth = await requireMemberWithRole(supabase, ['admin'], 'Admin access required')
if (!auth.ok) return { error: auth.error }
const { member } = auth
const { error, count } = await supabase
.from('space_tiers')
.delete({ count: 'exact' })
.eq('id', v.data)
.eq('space_id', member.space_id)
if (error) return { error: error.message }
if (count === 0) return { error: 'Cannot delete a built-in tier. Archive it instead.' }
revalidatePath('/customize')
return { success: true as const }
}