Appearance
Epic 23 — Personal Concierge
CasTars-powered casting requests handled by the CasTyou team. Referenced in spec "AFTER THOUGHTS #3".
BE-CONCIERGE-001 — Personal Concierge request model
- [x] Done
✅ SHIPPED 2026-06-20.
ConciergeRequestmodel +ConciergeType(BASIC/SPECIALIZED/URGENT) +ConciergeStatus(PENDING/IN_PROGRESS/FULFILLED/CANCELLED) enums (migration20260620235023_epic23_concierge;User.conciergeRequestsback-relation).services/concierge/index.ts:CONCIERGE_COSTSis the single source of truth for prices (BASIC 5k / SPECIALIZED 10k / URGENT 25k) — exposed to the FE via theconciergeOptionsquery so amounts are never hard-coded (decision: centralized for easy future updates).submitConciergeRequestcreates the request +deductCasTarsin one transaction (insufficient balance →INSUFFICIENT_BALANCE, full rollback);myConciergeRequests; adminadminListConciergeRequests(paginated, status filter),updateConciergeStatus,fulfillConciergeRequest(cleans candidates, sets FULFILLED + result JSON, fires the pre-stagedCONCIERGE_FULFILLEDnotification). Decision: NOT free for any tier — CasTars is the only gate (any authed user can submit). GraphQL types/queries/mutations inresolvers/concierge.ts; authz-matrix updated (409 tests). BE 1499 tests green (15 new inconcierge.test.ts); typecheck clean;schema.graphqlre-exported.
Files:
- Edit:
prisma/schema.prisma— addConciergeRequestmodel - Run:
pnpm db:migrate - Edit:
src/graphql/schema/index.ts - Create:
src/graphql/resolvers/concierge.ts
Schema:
prisma
model ConciergeRequest {
id String @id @default(cuid())
userId String
requestType ConciergeType // BASIC | SPECIALIZED | URGENT
description String
starsCost Int
status ConciergeStatus @default(PENDING)
// PENDING | IN_PROGRESS | FULFILLED | CANCELLED
adminNotes String?
result Json? // { candidates: [{ name, link, notes }] }
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("concierge_requests")
}
enum ConciergeType { BASIC SPECIALIZED URGENT }
enum ConciergeStatus { PENDING IN_PROGRESS FULFILLED CANCELLED }Costs:
BASIC→ 5,000 CasTars ("Need a dog actor" / "Need a male singer in LA")SPECIALIZED→ 10,000 CasTars ("Need a female hula-hoop artist")URGENT→ 25,000 CasTars ("Need a one-legged skateboarder available tomorrow in NYC")
Mutations: submitConciergeRequest(type, description) — deducts CasTars, creates request.
Admin mutations: updateConciergeStatus(id, status, adminNotes?), fulfillConciergeRequest(id, result)
Queries: myConciergeRequests: [ConciergeRequest!]!
FE-CONCIERGE-001 — Personal Concierge UI
- [x] Done
✅ SHIPPED 2026-06-20.
pages/concierge/ConciergePage.tsx(route/concierge, inside ProtectedShellLayout): tier picker rendering cost + example use-cases fromconciergeOptions(no hard-coded amounts), description textarea, submit → success toast ("Our team is on it!") + optimistic prepend, and a "My requests" list with status badges, team notes, and fulfilled candidate links.hooks/useConcierge.ts(loads options + own requests; pingscastarsBusafter submit so the balance widget refreshes; INSUFFICIENT_BALANCE → tailored toast). Admin:AdminConciergePagerebuilt from the stub — paginated queue with status filter, inline statusSelect, and a fulfilModal(dynamic candidate rows + note →fulfillConciergeRequest);hooks/useAdminConcierge.ts. Entry point: a Concierge CTA card on the CasTars hub (/castars).lib/queries/concierge.ts; routes +pageTitles(concierge); i18nconcierge.*+ expandedadminConcierge.*+pageTitle.conciergein en/pt/es. Tests:ConciergePage.test.tsx(5) +AdminConciergePage.test.tsx(3). FE app 593 green; typecheck + eslint clean.
Files:
- Create:
apps/app/src/pages/concierge/ConciergePage.tsx - Create:
apps/app/src/hooks/useConcierge.ts
Description:
- Request type picker (Basic / Specialized / Urgent) with CasTars cost and example use cases shown per type.
- Freetext description field.
- Submit → CasTars deducted, request enters
PENDINGstate. Confirmation screen: "Our team is on it!" - "My Requests" list: status chips, admin notes when fulfilled, result candidates shown as links.
- Admin panel extension: new "Concierge" tab in the admin area to view/manage all requests, update status, and submit results.