feat(signup): community guidelines agreement and policies routes

Introduces /community-guidelines and /policies/{privacy,terms,[slug]} pages,
swaps the signup/invite checkbox from agreedToTerms to agreedToGuidelines,
adds Member.agreement.acceptedAt, and stamps the field when a Helcim
customer is created.
This commit is contained in:
Jennie Robinson Faber 2026-04-18 17:06:10 +01:00
parent e0d11e47f4
commit c5e901ed24
13 changed files with 1292 additions and 54 deletions

View file

@ -80,7 +80,8 @@ describe('helcimCustomerSchema', () => {
it('accepts valid customer data', () => {
const result = helcimCustomerSchema.safeParse({
name: 'Jane Doe',
email: 'jane@example.com'
email: 'jane@example.com',
agreedToGuidelines: true
})
expect(result.success).toBe(true)
})
@ -88,7 +89,8 @@ describe('helcimCustomerSchema', () => {
it('lowercases email', () => {
const result = helcimCustomerSchema.safeParse({
name: 'Jane',
email: 'JANE@Example.COM'
email: 'JANE@Example.COM',
agreedToGuidelines: true
})
expect(result.success).toBe(true)
expect(result.data.email).toBe('jane@example.com')
@ -97,7 +99,8 @@ describe('helcimCustomerSchema', () => {
it('rejects invalid email', () => {
const result = helcimCustomerSchema.safeParse({
name: 'Jane',
email: 'not-an-email'
email: 'not-an-email',
agreedToGuidelines: true
})
expect(result.success).toBe(false)
})
@ -106,11 +109,29 @@ describe('helcimCustomerSchema', () => {
const result = helcimCustomerSchema.safeParse({
name: 'Jane',
email: 'jane@example.com',
agreedToGuidelines: true,
role: 'admin'
})
expect(result.success).toBe(true)
expect(result.data).not.toHaveProperty('role')
})
it('rejects missing agreedToGuidelines', () => {
const result = helcimCustomerSchema.safeParse({
name: 'Jane',
email: 'jane@example.com'
})
expect(result.success).toBe(false)
})
it('rejects agreedToGuidelines:false', () => {
const result = helcimCustomerSchema.safeParse({
name: 'Jane',
email: 'jane@example.com',
agreedToGuidelines: false
})
expect(result.success).toBe(false)
})
})
describe('helcimSubscriptionSchema', () => {
@ -297,14 +318,16 @@ describe('seriesTicketPurchaseSchema', () => {
it('accepts valid series ticket purchase', () => {
const result = seriesTicketPurchaseSchema.safeParse({
name: 'Buyer',
email: 'buyer@example.com'
email: 'buyer@example.com',
ticketType: 'member'
})
expect(result.success).toBe(true)
})
it('rejects missing name', () => {
const result = seriesTicketPurchaseSchema.safeParse({
email: 'buyer@example.com'
email: 'buyer@example.com',
ticketType: 'member'
})
expect(result.success).toBe(false)
})

View file

@ -6,7 +6,8 @@ import {
memberProfileUpdateSchema,
eventRegistrationSchema,
paymentVerifySchema,
adminEventCreateSchema
adminEventCreateSchema,
seriesTicketPurchaseSchema
} from '../../../server/utils/schemas.js'
import { validateBody } from '../../../server/utils/validateBody.js'
@ -183,6 +184,42 @@ describe('memberProfileUpdateSchema', () => {
})
})
describe('seriesTicketPurchaseSchema', () => {
const validBody = {
name: 'Test User',
email: 'test@example.com',
ticketType: 'member'
}
it('accepts member, public, and guest ticket types', () => {
for (const ticketType of ['member', 'public', 'guest']) {
const result = seriesTicketPurchaseSchema.safeParse({ ...validBody, ticketType })
expect(result.success).toBe(true)
}
})
it('rejects a body missing ticketType (closes pricing-mismatch gap)', () => {
const { ticketType, ...rest } = validBody
const result = seriesTicketPurchaseSchema.safeParse(rest)
expect(result.success).toBe(false)
})
it('rejects null ticketType', () => {
const result = seriesTicketPurchaseSchema.safeParse({ ...validBody, ticketType: null })
expect(result.success).toBe(false)
})
it('rejects an unknown ticketType value', () => {
const result = seriesTicketPurchaseSchema.safeParse({ ...validBody, ticketType: 'vip' })
expect(result.success).toBe(false)
})
it('rejects empty-string ticketType', () => {
const result = seriesTicketPurchaseSchema.safeParse({ ...validBody, ticketType: '' })
expect(result.success).toBe(false)
})
})
// --- validateBody integration tests ---
describe('validateBody', () => {