Files
hackerspace.sh/app/(app)/me/notification-prefs.tsx
T
Moheeb Zara 5d153b4d68 In-app notification inbox, consolidate /profile into /me, theme realign
In-app notification center (Phase 5, migration 051):
- notifications.read_at + partial unread index. /me Notifications is now
  an inbox: unread styling, click to expand the message + mark read,
  "mark all as read", unread count. markNotificationsRead is a service-
  client write scoped to the caller's own member_id. Always-on channel:
  email-muted ('skipped') notifications still appear in-app.

Consolidate /profile into /me:
- Delete app/(app)/profile/*; /me is the single member page. Port the
  conflict-of-interest privileged-role nudge + last-disclosed date into
  /me's profile editor. Remove the "My profile" sidebar + command-palette
  entries; rename the /me nav label to "My membership". Repoint the
  recruitment empty-state link to /me.

Theme realign:
- Revert the rounded-lg cards introduced in the prior /me visual pass to
  rounded (the app norm, matching the former /profile). Route /me section
  headers through the SectionTitle primitive.

Regression guard:
- integration/auth-embed.test.ts runs the auth layout's
  space_members.select('*, spaces(*)') embed through PostgREST and fails
  on PGRST201, so a future junction table cannot silently break auth again.
2026-05-19 22:51:44 -07:00

53 lines
1.8 KiB
TypeScript

'use client'
import { useState, useTransition } from 'react'
import { toast } from 'sonner'
import { Switch } from '@/components/ui/switch'
import {
CATEGORY_META,
type PrefMap,
type NotificationCategory,
} from '@/lib/notifications-prefs-logic'
import { setMyNotificationPreference } from '@/lib/actions'
// Per-category email opt-out toggles. Optimistic: the switch flips immediately
// and reverts if the server action fails. Billing is never shown (always on).
export function NotificationPrefs({ initial }: { initial: PrefMap }) {
const [prefs, setPrefs] = useState<PrefMap>(initial)
const [pending, startTransition] = useTransition()
function toggle(category: NotificationCategory, next: boolean) {
const prev = prefs[category] ?? true
setPrefs(p => ({ ...p, [category]: next }))
startTransition(async () => {
const res = await setMyNotificationPreference({ category, enabled: next })
if ('error' in res) {
setPrefs(p => ({ ...p, [category]: prev }))
toast.error(res.error)
}
})
}
return (
<ul className="divide-y rounded border border-border">
{CATEGORY_META.map(meta => {
const enabled = prefs[meta.category] ?? true
return (
<li key={meta.category} className="p-4 flex items-center justify-between gap-4">
<div className="min-w-0">
<span className="font-sans text-sm text-foreground">{meta.label}</span>
<p className="font-sans text-xs text-muted-foreground mt-0.5">{meta.description}</p>
</div>
<Switch
checked={enabled}
disabled={pending}
onCheckedChange={next => toggle(meta.category, next)}
aria-label={`Email me about ${meta.label}`}
/>
</li>
)
})}
</ul>
)
}