Parent epic
Epic E6: tutor onboarding and tutor directory (P0) — #37
Objective
Implement the admin-side tutor approval workflow: a Server Action that sets tutor_profiles.approved = true, writes an audit log, and ensures that approved tutors immediately become available for matching.
Background
From docs/MVP.md section 10.2:
"admin approval required before tutor can be matched"
From docs/ARCHITECTURE.md section 5.4:
"tutor_profiles.approved (bool, default false)"
The approval state gates the tutor from appearing in matching. In E7 (admin matching), the tutor filter query must include approved = true.
Approval workflow steps
From docs/OPS.md section 4 Workflow C (matching → tutor confirmation):
"pick tutor based on subject, level, availability, timezone compatibility. Message tutor privately: confirm availability. If tutor accepts: create match in platform."
Before matching, the admin must approve the tutor. This workflow:
- Tutor submits application (T6.1) —
approved = false
- Admin reviews at
/admin/tutors
- Admin clicks "Approve" → Server Action fires
tutor_profiles.approved = true
- Audit log written
- Tutor appears in E7 matching filter
Server Action: approveOrRevokeTutor
Location: app/admin/actions.ts (or app/admin/tutors/actions.ts)
'use server'
import { createAdminClient } from '@/lib/supabase/admin'
import { revalidatePath } from 'next/cache'
export async function approveTutor(tutorUserId: string, adminUserId: string) {
const admin = createAdminClient()
await admin
.from('tutor_profiles')
.update({ approved: true, updated_at: new Date().toISOString() })
.eq('tutor_user_id', tutorUserId)
await admin.from('audit_logs').insert([{
actor_user_id: adminUserId,
action: 'tutor_approved',
entity_type: 'tutor_profile',
entity_id: tutorUserId,
details: {}
}])
revalidatePath('/admin/tutors')
}
export async function revokeTutorApproval(tutorUserId: string, adminUserId: string) {
const admin = createAdminClient()
await admin
.from('tutor_profiles')
.update({ approved: false, updated_at: new Date().toISOString() })
.eq('tutor_user_id', tutorUserId)
await admin.from('audit_logs').insert([{
actor_user_id: adminUserId,
action: 'tutor_approval_revoked',
entity_type: 'tutor_profile',
entity_id: tutorUserId,
details: {}
}])
revalidatePath('/admin/tutors')
}
Acceptance criteria
E7 matching filter dependency
In E7 (T7.2), when the admin selects a tutor, the query must filter by approved = true:
const { data: tutors } = await adminClient
.from('tutor_profiles')
.select('tutor_user_id, bio, timezone, tutor_subjects(...), user_profiles(display_name)')
.eq('approved', true)
.contains_any('subject filter...')
This is the critical linkage between E6 and E7.
Definition of done
References
docs/ARCHITECTURE.md — section 5.4 (tutor_profiles.approved), section 6.6 (audit log)
docs/MVP.md — section 9.1 (tutor verification), section 10.3 (admin — tutor approval)
docs/OPS.md — section 4 Workflow C (matching begins after approval)
Parent epic
Epic E6: tutor onboarding and tutor directory (P0) — #37
Objective
Implement the admin-side tutor approval workflow: a Server Action that sets
tutor_profiles.approved = true, writes an audit log, and ensures that approved tutors immediately become available for matching.Background
From
docs/MVP.mdsection 10.2:From
docs/ARCHITECTURE.mdsection 5.4:The approval state gates the tutor from appearing in matching. In E7 (admin matching), the tutor filter query must include
approved = true.Approval workflow steps
From
docs/OPS.mdsection 4 Workflow C (matching → tutor confirmation):Before matching, the admin must approve the tutor. This workflow:
approved = false/admin/tutorstutor_profiles.approved = trueServer Action:
approveOrRevokeTutorLocation:
app/admin/actions.ts(orapp/admin/tutors/actions.ts)Acceptance criteria
approveTutorServer Actiontutor_profiles.approved = truein databaseactor_user_id,action = 'tutor_approved',entity_id = tutorUserIdrevokeTutorApprovalServer ActionE7 matching filter dependency
In E7 (T7.2), when the admin selects a tutor, the query must filter by
approved = true:This is the critical linkage between E6 and E7.
Definition of done
approveTutorServer Action exists and setsapproved = truerevokeTutorApprovalServer Action exists and setsapproved = falserevalidatePath('/admin/tutors')approved = trueReferences
docs/ARCHITECTURE.md— section 5.4 (tutor_profiles.approved), section 6.6 (audit log)docs/MVP.md— section 9.1 (tutor verification), section 10.3 (admin — tutor approval)docs/OPS.md— section 4 Workflow C (matching begins after approval)