diff --git a/src/app/admin/onboarding/page.test.tsx b/src/app/admin/onboarding/page.test.tsx index ca5b2d7..6be4d04 100644 --- a/src/app/admin/onboarding/page.test.tsx +++ b/src/app/admin/onboarding/page.test.tsx @@ -229,6 +229,63 @@ describe('AdminOnboardingPage', () => { expect(screen.getByText(/this field is required by the barazo platform/i)).toBeInTheDocument() }) + it('shows ToS URL input when field type is tos_acceptance', async () => { + const user = userEvent.setup() + render() + await user.click(screen.getByRole('button', { name: /add field/i })) + // Default type is custom_text -- no ToS URL input yet + expect(screen.queryByLabelText(/terms of service url/i)).not.toBeInTheDocument() + // Switch to tos_acceptance + await user.selectOptions(screen.getByLabelText(/field type/i), 'tos_acceptance') + expect(screen.getByLabelText(/terms of service url/i)).toBeInTheDocument() + }) + + it('hides ToS URL input for other field types', async () => { + const user = userEvent.setup() + render() + await user.click(screen.getByRole('button', { name: /add field/i })) + await user.selectOptions(screen.getByLabelText(/field type/i), 'custom_text') + expect(screen.queryByLabelText(/terms of service url/i)).not.toBeInTheDocument() + }) + + it('saves tosUrl in config when provided', async () => { + let capturedBody: Record | null = null + server.use( + http.post('/api/admin/onboarding-fields', async ({ request }) => { + capturedBody = (await request.json()) as Record + return HttpResponse.json( + { + id: 'new-field-1', + communityDid: 'did:plc:test', + fieldType: 'tos_acceptance', + label: 'Accept ToS', + description: '', + isMandatory: true, + sortOrder: 3, + source: 'admin', + config: capturedBody?.['config'] ?? null, + createdAt: '2026-02-15T12:00:00.000Z', + updatedAt: '2026-02-15T12:00:00.000Z', + }, + { status: 201 } + ) + }) + ) + const user = userEvent.setup() + render() + await user.click(screen.getByRole('button', { name: /add field/i })) + await user.selectOptions(screen.getByLabelText(/field type/i), 'tos_acceptance') + await user.type(screen.getByLabelText(/^label$/i), 'Accept ToS') + await user.type(screen.getByLabelText(/terms of service url/i), 'https://example.com/tos') + await user.click(screen.getByRole('button', { name: /^save$/i })) + await waitFor(() => { + expect(capturedBody).not.toBeNull() + }) + expect(capturedBody).toMatchObject({ + config: { tosUrl: 'https://example.com/tos' }, + }) + }) + it('passes axe accessibility check', async () => { const { container } = render() await waitFor(() => { diff --git a/src/components/admin/onboarding/onboarding-field-form.tsx b/src/components/admin/onboarding/onboarding-field-form.tsx index bad0cc1..5cc902f 100644 --- a/src/components/admin/onboarding/onboarding-field-form.tsx +++ b/src/components/admin/onboarding/onboarding-field-form.tsx @@ -102,6 +102,34 @@ export function OnboardingFieldForm({ placeholder="Additional context or instructions for users" /> + {editing.fieldType === 'tos_acceptance' && ( +
+ + + onChange({ + ...editing, + config: e.target.value + ? { ...editing.config, tosUrl: e.target.value } + : (() => { + const { tosUrl: _, ...rest } = editing.config ?? {} + return Object.keys(rest).length > 0 ? rest : null + })(), + }) + } + className="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground" + placeholder="https://example.com/terms" + /> +

+ Link displayed alongside the checkbox so users can review your terms. +

+
+ )}
) - case 'tos_acceptance': + case 'tos_acceptance': { + const tosUrl = typeof field.config?.tosUrl === 'string' ? field.config.tosUrl : null + return ( +
+ onChange(e.target.checked)} + className="mt-1 h-4 w-4 rounded border-border" + /> +
+ + {tosUrl && ( + + Read full Terms of Service + + )} +
+
+ ) + } + case 'custom_checkbox': return (
diff --git a/src/components/onboarding-modal.test.tsx b/src/components/onboarding-modal.test.tsx index e13f946..e1a06a6 100644 --- a/src/components/onboarding-modal.test.tsx +++ b/src/components/onboarding-modal.test.tsx @@ -196,6 +196,61 @@ describe('OnboardingModal', () => { expect(screen.getByRole('button', { name: /continue/i })).toBeEnabled() }) + it('renders a ToS link when config.tosUrl is set', () => { + render( + + ) + const link = screen.getByRole('link', { name: /read full terms of service/i }) + expect(link).toHaveAttribute('href', 'https://example.com/tos') + expect(link).toHaveAttribute('target', '_blank') + expect(link).toHaveAttribute('rel', 'noopener noreferrer') + }) + + it('does not render a link when config.tosUrl is absent', () => { + render( + + ) + expect(screen.queryByRole('link')).not.toBeInTheDocument() + }) + + it('passes axe accessibility check for tos_acceptance with tosUrl', async () => { + const { container } = render( + + ) + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + it('passes axe accessibility check', async () => { const { container } = render() const results = await axe(container)