Files
hackerspace.sh/app/(app)/profile/affiliations-form.tsx
T
Moheeb Zara ab8c22ae1d Add notification preferences and alternate dues payment options
Notification preferences (migration 048):
- Per-member opt-out of muteable categories (bookings, classes, forms,
  admin alerts); billing is always-on. Opt-out model, default on.
- Dispatcher reads prefs and marks muted outbox rows 'skipped' instead
  of sending; pure isMuted logic, fail-open on a prefs lookup error.
- notification_preferences table (default-deny RLS); /me toggles and
  self-view via validated service-client actions.

Alternate dues payment methods (migration 049):
- Admin sets external pay-here URLs per platform (PayPal/Zeffy/Venmo)
  in /settings; members see active ones on /me and pay off-platform;
  the treasurer reconciles manually (platform pre-tagged). https-only.
- dues_payment_methods table (SELECT space member, write admin/board).

/me visual pass:
- Restore the chip/preset profile editor (skills/interests/willing-to/
  affiliations); promote ChipInput and presets to shared modules.
- Gate the Stripe pay-dues button on whether the space has Stripe
  configured; show external links or a contact-admin note otherwise.
2026-05-19 20:26:32 -07:00

65 lines
2.3 KiB
TypeScript

'use client'
import { useState, useTransition } from 'react'
import { useRouter } from 'next/navigation'
import { discloseAffiliations } from '@/lib/actions'
import { ChipInput } from '@/components/chip-input'
export function AffiliationsForm({ initial }: { initial: { affiliations: string[] } }) {
const router = useRouter()
const [affiliations, setAffiliations] = useState<string[]>(initial.affiliations)
const [error, setError] = useState('')
const [status, setStatus] = useState<'idle' | 'saved'>('idle')
const [isPending, startTransition] = useTransition()
function handleSubmit(e: React.FormEvent) {
e.preventDefault()
setError('')
setStatus('idle')
startTransition(async () => {
const r = await discloseAffiliations({ affiliations })
if ('error' in r && r.error) setError(r.error)
else {
setStatus('saved')
router.refresh()
}
})
}
return (
<form onSubmit={handleSubmit} className="bg-card rounded border border-border p-5 space-y-4">
<div>
<label className="font-mono text-[10px] tracking-widest text-muted-foreground uppercase block mb-2">
Outside affiliations
</label>
<ChipInput
values={affiliations}
onChange={setAffiliations}
placeholder='add an affiliation and press enter, e.g. "ACME Robotics, contractor"'
maxLength={200}
maxItems={50}
/>
<p className="font-mono text-[10px] text-muted-foreground mt-1">
Organizations, businesses, or roles outside this space that could intersect with space
decisions. Visible to all members. Submitting stamps your disclosure date.
</p>
</div>
{error && <p className="font-mono text-xs text-red-500">{error}</p>}
{status === 'saved' && !error && (
<p className="font-mono text-xs text-primary">Disclosed. Your timestamp is updated.</p>
)}
<div className="flex items-center justify-end">
<button
type="submit"
disabled={isPending}
className="bg-primary text-white font-sans text-sm font-medium px-4 py-2 rounded hover:bg-primary/90 transition disabled:opacity-50"
>
{isPending ? 'Disclosing...' : 'Disclose now'}
</button>
</div>
</form>
)
}