Skip to content

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. ConciergeRequest model + ConciergeType (BASIC/SPECIALIZED/URGENT) + ConciergeStatus (PENDING/IN_PROGRESS/FULFILLED/CANCELLED) enums (migration 20260620235023_epic23_concierge; User.conciergeRequests back-relation). services/concierge/index.ts: CONCIERGE_COSTS is the single source of truth for prices (BASIC 5k / SPECIALIZED 10k / URGENT 25k) — exposed to the FE via the conciergeOptions query so amounts are never hard-coded (decision: centralized for easy future updates). submitConciergeRequest creates the request + deductCasTars in one transaction (insufficient balance → INSUFFICIENT_BALANCE, full rollback); myConciergeRequests; admin adminListConciergeRequests (paginated, status filter), updateConciergeStatus, fulfillConciergeRequest (cleans candidates, sets FULFILLED + result JSON, fires the pre-staged CONCIERGE_FULFILLED notification). Decision: NOT free for any tier — CasTars is the only gate (any authed user can submit). GraphQL types/queries/mutations in resolvers/concierge.ts; authz-matrix updated (409 tests). BE 1499 tests green (15 new in concierge.test.ts); typecheck clean; schema.graphql re-exported.

Files:

  • Edit: prisma/schema.prisma — add ConciergeRequest model
  • 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 from conciergeOptions (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; pings castarsBus after submit so the balance widget refreshes; INSUFFICIENT_BALANCE → tailored toast). Admin: AdminConciergePage rebuilt from the stub — paginated queue with status filter, inline status Select, and a fulfil Modal (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); i18n concierge.* + expanded adminConcierge.* + pageTitle.concierge in 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 PENDING state. 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.