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.
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
import { INVITE_ROLES } from './invite-logic'
|
||||
import { MUTEABLE_CATEGORIES } from './notifications-prefs-logic'
|
||||
import { DUES_LINK_PLATFORMS, isSafeDuesUrl } from './dues-payments-logic'
|
||||
|
||||
/**
|
||||
* Accepts every date-ish string the app produces and normalizes to a
|
||||
@@ -1021,6 +1023,34 @@ export const stripeSettingsSchema = z.object({
|
||||
prices: z.record(z.string().trim().max(255)).optional().default({}),
|
||||
})
|
||||
|
||||
// ─── Notification preferences ────────────────────────────────────────────────
|
||||
|
||||
// Only muteable categories are settable; billing is membership-critical and
|
||||
// never user-controllable, so it is excluded from the enum (a request to set
|
||||
// it is rejected at the boundary).
|
||||
export const notificationPreferenceSchema = z.object({
|
||||
category: z.enum(MUTEABLE_CATEGORIES as unknown as [string, ...string[]]),
|
||||
enabled: z.boolean(),
|
||||
})
|
||||
|
||||
// ─── Dues payment methods (admin-configured external links) ──────────────────
|
||||
|
||||
// One external pay-here URL per platform. url must be absolute https (no
|
||||
// http downgrade, no javascript:/data: scheme) since it is rendered as a
|
||||
// member-clickable anchor. instructions is an optional memo hint (e.g. "put
|
||||
// your member name in the note") to help the treasurer reconcile later.
|
||||
export const duesPaymentMethodSchema = z.object({
|
||||
platform: z.enum(DUES_LINK_PLATFORMS as unknown as [string, ...string[]]),
|
||||
url: z
|
||||
.string()
|
||||
.trim()
|
||||
.max(500)
|
||||
.refine(isSafeDuesUrl, 'Enter an absolute https:// URL'),
|
||||
instructions: z.string().trim().max(300).optional().nullable(),
|
||||
is_active: z.boolean().optional().default(true),
|
||||
sort_order: z.number().int().min(0).max(999).optional().default(0),
|
||||
})
|
||||
|
||||
// Helper type exports
|
||||
export type CreateTaskInput = z.infer<typeof createTaskSchema>
|
||||
export type CreateProjectInput = z.infer<typeof createProjectSchema>
|
||||
|
||||
Reference in New Issue
Block a user