Solver Applications
Public application system for prospective solvers to join the SavvySolve platform
Solver Applications
The solver applications feature provides a structured process for recruiting new solvers. Prospective solvers submit applications through a public form, which admins review and approve before the applicant can create their account.
How It Works
The application workflow follows these stages:
- Application Submission - Prospective solver fills out the multi-step application form at
/apply - Admin Review - Platform admins review applications in the admin dashboard
- Approval/Rejection - Admin approves or rejects with optional notes
- Account Creation - Approved applicants sign up via Clerk, automatically becoming solvers
- Onboarding - New solvers complete onboarding checklist before going live
This flow ensures quality control while keeping the barrier to entry reasonable for qualified candidates.
Application Form
The public application form at /apply collects information across four steps:
Step 1: Contact Information
Basic contact details for communication and identity verification.
export const solverApplications = pgTable("solver_applications", {
id: uuid("id").primaryKey().defaultRandom(),
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
phone: varchar("phone", { length: 50 }).notNull(),
// ...
});Step 2: Experience & Motivation
Free-text fields capturing the applicant's background in tech support and why they want to join SavvySolve.
Step 3: Availability
A weekly schedule grid where applicants indicate their typical available hours. This helps match solvers with demand patterns.
export interface ApplicationAvailability {
monday: string[];
tuesday: string[];
wednesday: string[];
thursday: string[];
friday: string[];
saturday: string[];
sunday: string[];
}Each day contains an array of time slots (e.g., ["morning", "afternoon", "evening"]).
Step 4: Technical Skills Assessment
Self-reported comfort levels across common tech support categories:
export interface TechSkillsAssessment {
mobileDevices: number; // 1-5
computers: number;
networking: number;
software: number;
printers: number;
email: number;
security: number;
}Database Schema
Applications are stored with full audit trail:
export const applicationStatusEnum = pgEnum("application_status", [
"pending",
"approved",
"rejected",
"withdrawn",
]);
export const solverApplications = pgTable("solver_applications", {
id: uuid("id").primaryKey().defaultRandom(),
// Contact info
name: varchar("name", { length: 255 }).notNull(),
email: varchar("email", { length: 255 }).notNull().unique(),
phone: varchar("phone", { length: 50 }).notNull(),
// Experience
experience: text("experience").notNull(),
motivation: text("motivation").notNull(),
// Structured data
availability: jsonb("availability").$type<ApplicationAvailability>().notNull(),
techSkills: jsonb("tech_skills").$type<TechSkillsAssessment>().notNull(),
// Review workflow
status: applicationStatusEnum("status").notNull().default("pending"),
reviewedBy: uuid("reviewed_by"),
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
reviewNotes: text("review_notes"),
// Link to user after approval + signup
userId: uuid("user_id"),
// Timestamps
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
});API Reference
The applications router provides these tRPC procedures:
applications.submit
Public procedure for submitting new applications.
// Input schema
z.object({
name: z.string().min(2).max(255),
email: z.string().email(),
phone: z.string().min(10),
experience: z.string().min(50),
motivation: z.string().min(50),
availability: availabilitySchema,
techSkills: techSkillsSchema,
})
// Returns
{ success: true, applicationId: string }applications.list (Admin)
Lists applications with optional status filtering and pagination.
// Input
z.object({
status: z.enum(["pending", "approved", "rejected", "withdrawn"]).optional(),
limit: z.number().min(1).max(100).optional().default(20),
offset: z.number().min(0).optional().default(0),
})
// Returns
{
applications: SolverApplication[],
total: number,
hasMore: boolean,
}applications.get (Admin)
Retrieves a single application by ID with full details.
applications.approve (Admin)
Approves an application, optionally with notes.
// Input
z.object({
applicationId: z.string().uuid(),
notes: z.string().optional(),
})applications.reject (Admin)
Rejects an application with required notes explaining the decision.
// Input
z.object({
applicationId: z.string().uuid(),
notes: z.string().min(10),
})applications.getPendingCount (Admin)
Returns the count of pending applications for sidebar badges.
Admin Review Interface
Admins access the application review interface at /admin/applications. The interface provides:
- Filtering - View all, pending, approved, or rejected applications
- Pagination - Navigate through applications
- Detail View - Modal showing full application with availability grid and skills chart
- Actions - Approve or reject with notes
The admin sidebar shows a badge with the pending application count, alerting admins to new applications that need review.
Clerk Webhook Integration
When an approved applicant creates their account via Clerk, the webhook handler automatically:
- Checks if the email matches an approved application
- Creates the user record with
role: "solver" - Creates the solver record with
status: "onboarding" - Links the application to the new user
// Check if this email has an approved solver application
const approvedApplication = await db.query.solverApplications.findFirst({
where: and(
eq(solverApplications.email, email),
eq(solverApplications.status, "approved")
),
});
if (approvedApplication) {
role = "solver";
// Link application to user after insert
}This eliminates manual intervention—approved applicants automatically become solvers upon signup.
Security Considerations
- Application submission is public (no auth required)
- Email uniqueness prevents duplicate applications
- Admin-only procedures use
adminProceduremiddleware - Review actions are audited with reviewer ID and timestamp
Related Documentation
- Solver Onboarding - Post-approval onboarding checklist
- Role-Based Access Control - Permission system
- Admin Dashboard - Admin interface overview