Epic E12: Policies, safety, and reliability#106
Conversation
T12.1: Full /policies page (reschedule, no-show, refund/expiry, package terms, privacy) T12.2: /tutor/conduct code of conduct page + required acknowledgement checkbox on tutor form T12.3: /admin/audit audit log page (recent 200 events, human-readable labels, actor/entity/details) T12.4: /admin/analytics dashboard (7 metric cards: active students/tutors, upcoming/missed/unmarked sessions, pending payments/tutors) - Admin nav + dashboard updated with Audit Log and Analytics links - README updated to reflect E12 features Closes #78, Closes #79, Closes #80, Closes #81, Closes #77 Co-authored-by: Taleef7 <89072337+Taleef7@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Implements Epic E12 launch-readiness features by adding public-facing policy/safety pages and admin operational visibility pages, plus wiring the tutor conduct acknowledgement into the tutor profile form.
Changes:
- Add public
/policiespage and public/tutor/conductcode-of-conduct page. - Require tutors to acknowledge the code of conduct in the tutor profile/application form via Zod validation + UI checkbox.
- Add admin-only
/admin/audit(recent audit events) and/admin/analytics(count-based health metrics), plus navigation entry points.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
lib/validators/tutor.ts |
Adds conductAcknowledged requirement to tutor profile Zod schema. |
app/tutor/profile/TutorProfileForm.tsx |
Adds conduct acknowledgement checkbox linking to /tutor/conduct. |
app/tutor/conduct/page.tsx |
New public tutor code-of-conduct page content. |
app/policies/page.tsx |
Replaces TODO with full public policies page content. |
app/admin/page.tsx |
Adds admin dashboard cards for Audit Log and Analytics. |
app/admin/layout.tsx |
Adds nav links to /admin/audit and /admin/analytics. |
app/admin/audit/page.tsx |
New admin audit log page querying audit_logs and rendering recent events. |
app/admin/analytics/page.tsx |
New admin analytics dashboard with parallel count queries and metric cards. |
README.md |
Updates “after E11” to “after E12” and documents new E12 features. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const detailsStr = log.details | ||
| ? Object.entries(log.details) | ||
| .map(([k, v]) => `${k}: ${String(v)}`) | ||
| .join(' · ') | ||
| : '—' |
There was a problem hiding this comment.
detailsStr stringifies values with String(v), which turns objects into [object Object] (e.g. match_details_updated can include a schedule_pattern object). This makes the “Details” column lose the most useful context. Consider formatting non-primitive values with JSON.stringify (with a small length cap) so nested details remain readable.
There was a problem hiding this comment.
Fixed in 7e81337. Non-primitive values (objects like schedule_pattern) are now serialized with JSON.stringify and capped at 80 characters with a trailing …, so nested details remain readable instead of showing [object Object].
| .select('tutor_user_id', { count: 'exact', head: true }) | ||
| .eq('approved', false), | ||
| ]) | ||
|
|
There was a problem hiding this comment.
The analytics counts ignore query errors: if any of these Supabase requests fail, count becomes null and the UI silently shows 0, which can mask outages and lead to incorrect operational decisions. Consider checking each response’s error and rendering an explicit error state (or throwing) when any metric query fails.
| const firstErrorResult = | |
| activeStudents.error || | |
| activeTutors.error || | |
| upcomingSessions.error || | |
| missedSessions.error || | |
| unmarkedSessions.error || | |
| pendingPayments.error || | |
| pendingTutors.error | |
| if (firstErrorResult) { | |
| throw new Error( | |
| `Failed to load admin analytics metrics: ${ | |
| typeof firstErrorResult.message === 'string' | |
| ? firstErrorResult.message | |
| : 'Unknown Supabase error' | |
| }`, | |
| ) | |
| } |
There was a problem hiding this comment.
Fixed in 7e81337. After Promise.all resolves, the page now checks activeStudents.error || activeTutors.error || … and throws an explicit Error if any query failed. Next.js will render the nearest error boundary rather than silently displaying zeros.
| conductAcknowledged: z.literal(true, { | ||
| error: 'You must read and agree to the CorvEd Tutor Code of Conduct', | ||
| }), |
There was a problem hiding this comment.
z.literal(true, { error: ... }) uses an unsupported option key (error). This will either fail TypeScript compilation or silently fall back to Zod’s default message, so the form won’t show the intended validation error. Use a supported option (e.g., required_error/invalid_type_error where applicable) or switch to z.boolean().refine(v => v, { message: ... }) to guarantee the custom message is surfaced when unchecked.
| conductAcknowledged: z.literal(true, { | |
| error: 'You must read and agree to the CorvEd Tutor Code of Conduct', | |
| }), | |
| conductAcknowledged: z | |
| .boolean() | |
| .refine((v) => v, { | |
| message: 'You must read and agree to the CorvEd Tutor Code of Conduct', | |
| }), |
There was a problem hiding this comment.
Fixed in 7e81337. Switched to z.boolean().refine((v) => v, { message: '…' }) so the custom validation message is correctly surfaced when the checkbox is unchecked.
| <label className="flex items-start gap-3 cursor-pointer"> | ||
| <input | ||
| type="checkbox" | ||
| aria-required="true" | ||
| aria-invalid={errors.conductAcknowledged ? 'true' : 'false'} | ||
| {...register('conductAcknowledged')} | ||
| className="mt-0.5 h-4 w-4 rounded border-zinc-300 text-indigo-600 focus:ring-indigo-500" | ||
| /> | ||
| <span className="text-sm text-zinc-700 dark:text-zinc-300"> | ||
| I have read and agree to the{' '} | ||
| <Link | ||
| href="/tutor/conduct" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="font-semibold text-indigo-600 hover:text-indigo-700 underline underline-offset-2 dark:text-indigo-400 dark:hover:text-indigo-300" | ||
| > | ||
| CorvEd Tutor Code of Conduct | ||
| </Link>{' '} |
There was a problem hiding this comment.
Because the /tutor/conduct link is inside the <label>, clicking the link will also toggle the checkbox (label click behavior). This makes it easy to accidentally “acknowledge” without intending to, and is confusing for users. Consider using htmlFor with an id on the checkbox and keeping the link outside the label (or stop propagation on the link) so only direct checkbox/label text clicks toggle it.
There was a problem hiding this comment.
Fixed in 7e81337. The link is now outside the <label> element. The checkbox has an explicit id="conductAcknowledged" and the label uses htmlFor="conductAcknowledged", so only clicking the checkbox or its label text toggles it — clicking "View Code of Conduct →" opens the conduct page without touching the checkbox.
| // Human-readable labels for known audit actions | ||
| const AUDIT_ACTION_LABELS: Record<string, string> = { | ||
| payment_marked_paid: '💳 Payment marked paid', | ||
| payment_rejected: '❌ Payment rejected', | ||
| tutor_approved: '✅ Tutor approved', | ||
| tutor_approval_revoked: '🚫 Tutor approval revoked', | ||
| tutor_assigned: '🎓 Tutor assigned to request', | ||
| tutor_reassigned: '🔄 Tutor reassigned', | ||
| sessions_generated: '📅 Sessions generated', | ||
| session_rescheduled: '📅 Session rescheduled', | ||
| session_status_updated: '📝 Session status updated', | ||
| meet_link_updated: '🔗 Meet link updated', | ||
| } |
There was a problem hiding this comment.
AUDIT_ACTION_LABELS doesn’t match the action strings that are actually written to audit_logs elsewhere (e.g. actions include payment_marked_rejected and match_details_updated, but this map has payment_rejected and no entry for match_details_updated). As a result, the audit log UI will show raw action keys for common events instead of the intended human-readable labels. Align the keys in this map with the action values inserted throughout the codebase (or standardize the inserted action names).
There was a problem hiding this comment.
Fixed in 7e81337. The label map now uses the actual action strings written to audit_logs throughout the codebase:
payment_marked_rejected(waspayment_rejected)match_details_updated(wasmeet_link_updated)
The map now covers all 10 real action strings: payment_marked_paid, payment_marked_rejected, tutor_approved, tutor_approval_revoked, tutor_assigned, tutor_reassigned, sessions_generated, session_rescheduled, session_status_updated, match_details_updated.
|
@copilot apply changes based on the comments in this thread |
- Align AUDIT_ACTION_LABELS with actual action strings (payment_marked_rejected, match_details_updated; remove non-existent payment_rejected, meet_link_updated) - Format non-primitive detail values with JSON.stringify (80-char cap) instead of String(), so nested objects like schedule_pattern remain readable - Throw explicit error when any analytics metric query fails instead of silently showing 0 - Switch conductAcknowledged from z.literal(true) to z.boolean().refine() so custom error message is correctly surfaced - Fix label/link UX: move 'View Code of Conduct' link outside <label> so clicking it doesn't accidentally toggle the checkbox Co-authored-by: Taleef7 <89072337+Taleef7@users.noreply.github.com>
Implements all four tasks under Epic E12 — foundational trust, safety, and operational visibility features required before public launch.
Summary
/policiespage with all locked MVP policy sections/tutor/conductcode-of-conduct page with required acknowledgement in the tutor application form/admin/auditaudit log view/admin/analyticsoperational health dashboardChanges
T12.1 —
/policiespage (app/policies/page.tsx)docs/MVP.md §5: reschedule (24h cutoff, exceptions), no-show table (student/tutor/late-join outcomes), refund/expiry (no carryover, admin discretion), package terms, privacy basicsT12.2 — Tutor code of conduct (
app/tutor/conduct/page.tsx)z.boolean().refine()so Zod surfaces the custom error message when uncheckedid="conductAcknowledged") is bound viahtmlForon the label text; the "View Code of Conduct →" link is placed outside the<label>so clicking it does not accidentally toggle the checkboxT12.3 — Admin audit log (
app/admin/audit/page.tsx)audit_logstable (created in E5 migration) for 200 most recent events, newest-firstpayment_marked_paid,payment_marked_rejected,tutor_approved,tutor_approval_revoked,tutor_assigned,tutor_reassigned,sessions_generated,session_rescheduled,session_status_updated,match_details_updated), entity type, truncated UUID, details summary; timestamps in PKTschedule_patternobjects) are serialized withJSON.stringifyand capped at 80 characters so nested details remain readableT12.4 — Admin analytics dashboard (
app/admin/analytics/page.tsx)force-dynamicpage with 7 parallel count queries (no row data transferred): active students, active tutors, upcoming sessions (next 7d), missed sessions (last 7d), unmarked past sessions, pending payments, pending tutor applicationsSupporting
lib/validators/tutor.tsupdated withconductAcknowledgedfield usingz.boolean().refine()Testing
Notes
audit_logstable already existed from the E5 packages/payments migration — no new migration neededAUDIT_ACTION_LABELSkeys are aligned with the actual action strings written throughout the codebase (verified by grepping allaudit_logsinsert sites)💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.