Customer Accounts
Optional customer accounts for session history, ratings, and faster future sessions
Customer Accounts
SavvySolve is designed for friction-free entry—customers can get help without creating an account. However, customers who create accounts gain access to their session history, can rate their solvers, and enjoy a smoother experience for future sessions.
Design Philosophy
The PRD emphasizes that the intake flow must be as simple as possible. Many customers seeking tech support are already frustrated; adding account creation would increase friction and abandonment.
Our solution: optional post-session sign-up. After submitting a ticket, customers see a prompt to create a free account. If they do, all their sessions (past and future) are automatically linked by phone number.
How It Works
Ticket Submission Flow
When a customer submits a ticket through the intake form, they provide:
- Name
- Phone number
- Email (optional)
- Problem description
- Device type
- Urgency level
No account is required. The ticket is created and a solver can claim it immediately.
// After successful submission, show the confirmation with sign-up CTA
if (isSubmitted && ticketId) {
return <TicketConfirmation ticketId={ticketId} phone={submittedPhone} />;
}The Sign-Up Prompt
After submission, the TicketConfirmation component displays:
- Success message with ticket ID
- Optional sign-up call-to-action
- Benefits of creating an account
// Build sign-up URL with return path and phone for pre-fill
const signUpUrl = `/sign-up?redirect_url=/account&phone=${encodeURIComponent(phone)}`;The phone number is passed to the sign-up page so Clerk can pre-fill it, making registration faster.
Auto-Linking by Phone
When a customer creates an account, the Clerk webhook automatically links all their previous tickets:
// Check if there are unlinked tickets with this phone number
const unlinkedTickets = await db.query.tickets.findMany({
where: isNull(tickets.customerId),
});
const matchingTickets = unlinkedTickets.filter(
(t) => t.customerInfo.phone === phone
);
if (matchingTickets.length > 0) {
// User has submitted tickets before - they're a customer
role = "customer";
}
// Link all matching tickets to the new customer
for (const ticket of matchingTickets) {
await db.update(tickets)
.set({ customerId: newUser.id })
.where(eq(tickets.id, ticket.id));
}This means:
- If a customer signs up with the same phone they used for tickets, those tickets are automatically linked
- Their session history is immediately available
- No manual linking required
Database Schema
The customer account feature adds a customerId column to the tickets table:
ALTER TABLE tickets ADD COLUMN customer_id UUID REFERENCES users(id) ON DELETE SET NULL;This creates a one-to-many relationship: one customer can have many tickets.
export const tickets = pgTable("tickets", {
// ... other fields
customerId: uuid("customer_id").references(() => users.id, {
onDelete: "set null",
}),
});Customer Dashboard
Authenticated customers can access /account to see their profile and session history.
Account Page (/account)
The main account page shows:
- Profile information (name, email, phone)
- Session statistics (total sessions, completed sessions)
- Recent sessions with status and ratings
Session History (/account/sessions)
A dedicated page for viewing all past sessions with:
- Expandable session cards
- Ticket description and status
- Solver name
- Duration and tier
- Rating given (if any)
- Infinite scroll pagination
// Get customer's session history with pagination
getSessions: protectedProcedure
.input(z.object({
limit: z.number().min(1).max(50).default(10),
cursor: z.string().uuid().optional(),
}))
.query(async ({ ctx, input }) => {
const user = await db.query.users.findFirst({
where: eq(users.clerkId, ctx.auth.userId),
});
// Get tickets with sessions, ratings, and solver info
const customerTickets = await db.query.tickets.findMany({
where: eq(tickets.customerId, user.id),
with: {
sessions: {
with: { solver: { with: { user: true } }, rating: true },
},
},
});
// ... flatten and return paginated sessions
})tRPC Procedures
The customersRouter provides these procedures:
| Procedure | Purpose |
|---|---|
getProfile | Get customer profile with session stats |
updateProfile | Update name or phone number |
getSessions | Get paginated session history |
getTickets | Get customer's tickets with sessions |
linkTicket | Manually link a ticket by ID + phone verification |
linkAllTicketsByPhone | Auto-link all tickets matching phone |
Role-Based Access
The customer routes use a role guard to ensure only customers (or higher roles) can access them:
export default async function CustomerLayout({ children }) {
// Require customer role to access these routes
await roleGuard("customer", "/");
return (
<div>
<header>...</header>
<main>{children}</main>
</div>
);
}The role hierarchy is: customer < solver < admin < owner. A solver can access customer routes, but a customer cannot access solver routes.
Testing
The customer account feature is covered by these test files:
| File | Coverage |
|---|---|
components/intake/TicketConfirmation.test.tsx | Sign-up CTA UI, link generation |
server/routers/customers.test.ts | All 6 tRPC procedures |
app/api/webhooks/clerk/route.test.ts | Auto-linking on sign-up |
describe("customersRouter", () => {
it("returns customer profile with stats", async () => {
mockUsersFindFirst.mockResolvedValueOnce(mockUser);
mockTicketsFindMany.mockResolvedValueOnce([
{ sessions: [{ status: "completed" }, { status: "active" }] },
]);
const result = await caller.getProfile();
expect(result.stats.totalSessions).toBe(2);
expect(result.stats.completedSessions).toBe(1);
});
});Future Enhancements
Potential improvements for customer accounts:
- Email notifications - Session updates, payment receipts
- Saved payment methods - Faster checkout for repeat customers
- Favorite solvers - Request specific solvers for future sessions
- Session transcripts - Download chat history from past sessions
Related Documentation
- Landing Page - The intake form flow
- Rating System - How customers rate sessions
- Clerk Integration - Authentication system