Appearance
Epic 5 — Job Board
BE-JOB-001 — Extend Job schema with full spec fields
- [x] Implemented
Files:
- Edit:
prisma/schema.prisma - Edit:
src/graphql/schema/index.ts— expandJob,CreateJobInput,UpdateJobInput,JobFilterInput
New fields to add:
prisma
projectName String?
ageMin Int?
ageMax Int?
gender String[] @default([])
languages Json? // [{ language, fluency, accent }]
experienceLevel String?
auditionRequired Boolean @default(false)
paymentCurrency String @default("USD")
locationCity String?
locationCountry String?
unionRequired String?Expand JobFilterInput: add genre, ageMin/Max, language, location, commitmentType, experienceLevel, unionStatus filter fields.
BE-JOB-002 — Job Application model & pipeline
- [x] Implemented
Files:
- Edit:
prisma/schema.prisma— addApplicationmodel - Run:
pnpm db:migrate - Edit:
src/graphql/schema/index.ts— addApplicationtype,applyToJobmutation,myApplicationsquery,jobApplications(jobId)query (producer only) - Create:
src/graphql/resolvers/application.ts - Edit:
src/graphql/resolvers/index.ts - Create:
src/services/job/application.ts
Schema:
prisma
model Application {
id String @id @default(cuid())
jobId String
job Job @relation(fields: [jobId], references: [id], onDelete: Cascade)
talentId String
talent TalentProfile @relation(fields: [talentId], references: [id], onDelete: Cascade)
status ApplicationStatus @default(PENDING)
coverNote String?
reelUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([jobId, talentId])
@@map("applications")
}
enum ApplicationStatus {
PENDING
REVIEWED
SHORTLISTED
REJECTED
HIRED
}Mutations: applyToJob(jobId, coverNote?, reelUrl?), updateApplicationStatus(id, status) (producer)
Queries: myApplications: [Application!]! (talent), jobApplications(jobId: ID!): [Application!]! (producer)
BE-JOB-003 — Producer swipe actions (shortlist / skip / block)
- [x] Implemented
Files:
- Edit:
prisma/schema.prisma— addTalentInteractionmodel - Edit:
src/graphql/schema/index.ts— add mutations - Create:
src/graphql/resolvers/interaction.ts - Create:
src/services/job/interactions.ts
Schema:
prisma
model TalentInteraction {
id String @id @default(cuid())
producerId String
talentId String
jobId String?
type InteractionType
expiresAt DateTime?
createdAt DateTime @default(now())
@@unique([producerId, talentId, jobId])
@@map("talent_interactions")
}
enum InteractionType {
SHORTLISTED
SKIPPED
BLOCKED
}Mutations: swipeTalent(talentId, jobId, action: SHORTLIST | SKIP | BLOCK): Boolean!
Blocked interactions expire after 90 days (set expiresAt). Discover feed excludes talents with active interactions.
BE-JOB-004 — Job posting missing spec fields
- [x] Implemented
Files:
- Edit:
prisma/schema.prisma— add columns toJobandJobRequirements - Run:
pnpm db:migrate - Edit:
src/graphql/schema/index.ts— extendJob,CreateJobInput,UpdateJobInput - Edit:
src/graphql/resolvers/job.ts - Edit:
src/__tests__/resolvers/job.test.ts
Fields to add:
prisma
// On Job model
rehearsalStartDate DateTime?
rehearsalEndDate DateTime?
responseTimeEstimate String? // e.g. "Within 48 hours"
contactPerson String? // name of casting agent if different from producer
contactPersonEmail String?
contractType String? // FREELANCE | FULL_TIME | PART_TIME | TEMPORARY | ONE_TIME | RETAINER
auditionCount Int? // how many rounds of audition if auditionRequired = true
requiredMaterials String[] @default([]) // e.g. ["headshot", "demo reel", "cover letter"]
otherPhysicalReqs String? // freetext for additional physical requirementsNotes:
contractTypeis more granular than the existingCommitmentTypeenum — both fields coexist.requiredMaterialsdrives what the talent sees on the application form (show/hide fields conditionally).rehearsalStartDate/rehearsalEndDateare separate fromstartDate/endDate(production dates).
FE-JOBS-001 — Job listings page (talent view)
- [x] Implemented — superseded by FE-JOBS-006 (role-dispatched
/jobs+ match grid)
Files:
- Edit:
apps/app/src/pages/jobs/JobsPage.tsx— replace placeholder - Create:
apps/app/src/lib/queries/jobs.ts—JOBS_QUERY,JOB_QUERY - Create:
apps/app/src/hooks/useJobs.ts - Create:
apps/app/src/hooks/useJob.ts
Description: Replace "Jobs board coming soon" with a real list using JobCard (DS). Shows filter bar at top (production type, remote, payment type). Infinite scroll with React Query useInfiniteQuery. Shows Skeleton during load. Shows EmptyState when no results. Matches "Listagem de vagas" frame in Figma.
Superseded: the
/jobsroute is now role-dispatched (see FE-JOBS-006). The talent view becomesTalentJobsPage, backed by the newjobsForTalentquery (BE-JOB-005) which returns a per-job match score. The originalJobsPage.tsxis removed.
FE-JOBS-002 — Job detail page (talent view)
- [x] Implemented
Files:
- Create:
apps/app/src/pages/jobs/JobDetailPage.tsx - Edit:
apps/app/src/App.tsx— add route/jobs/:id
Description: Full job detail view. Sections: header (title, producer, production type), compensation, commitment, requirements, description, location, deadline. Apply button triggers applyToJob mutation with optional cover note. Uses DS components. Matches "Detalhes" screens in Figma.
FE-JOBS-003 — Job posting form (producer)
- [x] Implemented
Files:
- Create:
apps/app/src/pages/jobs/CreateJobPage.tsx - Create:
apps/app/src/pages/jobs/steps/JobBasicStep.tsx - Create:
apps/app/src/pages/jobs/steps/JobRequirementsStep.tsx - Create:
apps/app/src/pages/jobs/steps/JobCompensationStep.tsx - Create:
apps/app/src/pages/jobs/steps/JobReviewStep.tsx - Create:
apps/app/src/hooks/useCreateJob.ts - Edit:
apps/app/src/App.tsx— add route/jobs/create(producer-only)
Description: Multi-step form to post a job. Step 1: Basic info (title, project name, production type, genre). Step 2: Requirements (talent category, skills, experience level, age range, gender, languages, work authorization, union). Step 3: Compensation & logistics (payment type/amount, commitment, location/remote, start/end dates, deadline). Step 4: Review & publish. All DS components.
FE-JOBS-004 — Producer job management dashboard
- [x] Implemented — superseded by FE-JOBS-007 (producer view moves to
/jobsas a table)
Files:
- Create:
apps/app/src/pages/jobs/ProducerJobsPage.tsx - Create:
apps/app/src/pages/jobs/JobApplicationsPage.tsx— list applicants for a job - Create:
apps/app/src/hooks/useProducerJobs.ts - Create:
apps/app/src/hooks/useJobApplications.ts
Description: Producer view of their posted jobs with status badges (OPEN/CLOSED/DRAFT), applicant count, deadline. Tap a job → see applicants list. Each applicant shows TalentCard (compact) with status chip and action buttons (Shortlist / Reject / Hire). Producer can update application status here.
Superseded: the producer view moves from
/jobs/manageto/jobs(role-dispatched). The grid is replaced by a DS data table — see FE-JOBS-007./jobs/managebecomes a redirect.
FE-JOBS-005 — Extend job detail & posting form for new spec fields
- [x] Implemented
Files:
- Edit:
apps/app/src/pages/jobs/JobDetailPage.tsx— show rehearsal dates, required materials, contact person, physical reqs, contract type - Edit:
apps/app/src/pages/jobs/steps/JobBasicStep.tsx— add contract type, contact person, contact email - Edit:
apps/app/src/pages/jobs/steps/JobCompensationStep.tsx— add rehearsal start/end date pickers, response time estimate - Edit:
apps/app/src/pages/jobs/steps/JobRequirementsStep.tsx— add required materials checklist, other physical requirements field, audition count input - Edit:
apps/app/src/lib/queries/jobs.ts— add new fields to JOB_QUERY
Job detail view additions:
- Rehearsal dates (if set) shown alongside production dates
- Required application materials shown as a checklist (so talent knows what to prepare before applying)
- Contact person name/email if different from the producer
- Other physical requirements as a note block
- Contract type badge alongside commitment type
Job posting form additions:
JobBasicStep: contract type dropdown (Freelance / Full-Time / Part-Time / Temporary / One-time / Retainer), contact person name + email fieldsJobRequirementsStep: audition count input (shown whenauditionRequired = true), required materials MultiSelect (Headshot / Demo Reel / Cover Letter / References / Portfolio / Showreel), other physical requirements textareaJobCompensationStep: rehearsal start/end date pickers, response time estimate input (free text: "Within 48 hours")
BE-JOB-005 — Talent job match feed (jobsForTalent)
- [x] Implemented
Files:
- Edit:
src/graphql/schema/index.ts— addJobMatchFeedItem,JobMatchConnection,jobsForTalentquery - Edit:
src/graphql/resolvers/job.ts—jobsForTalentresolver - Create:
src/services/ai/jobMatching.ts— talent-vs-job scoring (mirror ofdiscoverFeed's talent scoring, inverted) - Edit:
src/__tests__/resolvers/job.test.ts
Description: Talent-facing analogue of discoverFeed. Returns a paginated, scored list of open jobs ranked for the calling talent. Uses the same 9-factor model from BE-DISCOVER-001 with the perspective inverted (rank jobs for one talent, instead of talents for one job). Excludes jobs the talent has already applied to.
GraphQL:
graphql
type JobMatchFeedItem {
job: Job!
score: Float! # 0-1
matchReasons: [String!]!
}
type JobMatchConnection {
edges: [JobMatchFeedItem!]!
pageInfo: PageInfo!
}
extend type Query {
jobsForTalent(first: Int, after: String, filter: JobFilterInput): JobMatchConnection!
}Notes:
- Default
first = 10(matches the frontend grid page size). - Cursor pagination — same convention as the existing
jobs(...)resolver. - Resolver enforces that the caller has an active talent profile; errors otherwise.
BE-PETJOB-001 — PetJob model + CRUD
- [x] Implemented
Files:
- Edit:
prisma/schema.prisma— newPetJobmodel - Run:
pnpm db:migrate - Edit:
src/graphql/schema/index.ts—PetJobtype,CreatePetJobInput,UpdatePetJobInput,PetJobFilterInput,PetJobConnection - Create:
src/graphql/resolvers/petJob.ts - Create:
src/services/petJob/index.ts - Edit:
src/graphql/resolvers/index.ts - Edit:
src/__tests__/resolvers/petJob.test.ts
Schema (initial — mirror of Job plus pet-specific requirement fields):
prisma
model PetJob {
id String @id @default(cuid())
producerId String
producer ProducerProfile @relation(fields: [producerId], references: [id], onDelete: Cascade)
title String
productionType String?
productionSubtype String?
genre String?
description String?
// Pet-specific requirements
species String[] @default([]) // dog, cat, horse, etc.
breeds String[] @default([])
ageMonthsMin Int?
ageMonthsMax Int?
trainedSkills String[] @default([]) // sit, stay, fetch, agility, on-leash, etc.
experienceLevel String? // novice | intermediate | professional
coatColors String[] @default([])
sizeRange String? // small | medium | large | xl
vaccinationRequired Boolean @default(false)
// Logistics (mirror of Job)
paymentType PaymentType
paymentAmount Float?
paymentCurrency String @default("USD")
location String?
isRemote Boolean @default(false)
startDate DateTime?
endDate DateTime?
applicationDeadline DateTime?
status JobStatus @default(OPEN)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
applications PetJobApplication[]
@@map("pet_jobs")
}Mutations: createPetJob, updatePetJob, closePetJob
Queries: petJob(id), petJobs(first, after, filter)
Notes:
PetJobApplication(apply with a specific pet) is a follow-up — out of scope for this ticket. Tracked separately when the apply flow is built.PaymentTypeandJobStatusenums are reused from the existingJobmodel.
BE-PETJOB-002 — Pet-owner pet-job match feed (petJobsForOwner)
- [x] Implemented
Files:
- Edit:
src/graphql/schema/index.ts— addPetJobMatchFeedItem,PetJobMatchConnection,petJobsForOwnerquery - Edit:
src/graphql/resolvers/petJob.ts— resolver - Create:
src/services/ai/petJobMatching.ts— scoring service - Edit:
src/__tests__/resolvers/petJob.test.ts
Description: Pet-owner-facing match feed. Scores PetJobs against the pet-owner's pets (best-pet-match score per job) and returns a paginated, ranked list.
Scoring factors:
- Species match (hard filter — score 0 if no pet matches species)
- Breed match (any owned pet)
- Age window match
- Trained-skills overlap
- Size match
- Vaccination compliance (when required)
- Availability vs
startDate - Location proximity / remote
GraphQL:
graphql
type PetJobMatchFeedItem {
petJob: PetJob!
score: Float!
matchReasons: [String!]!
bestMatchingPetId: ID # which of the owner's pets scored highest
}
type PetJobMatchConnection {
edges: [PetJobMatchFeedItem!]!
pageInfo: PageInfo!
}
extend type Query {
petJobsForOwner(first: Int, after: String, filter: PetJobFilterInput): PetJobMatchConnection!
}BE-JOB-006 — jobListing union for /jobs/:id
- [x] Implemented
Files:
- Edit:
src/graphql/schema/index.ts— addJobListingunion - Edit:
src/graphql/resolvers/job.ts—jobListingresolver - Edit:
src/__tests__/resolvers/job.test.ts
GraphQL:
graphql
union JobListing = Job | PetJob
extend type Query {
jobListing(id: ID!): JobListing
}Description: Single resolver for the role+type-aware /jobs/:id page. Looks up the ID in Job first, then PetJob. Returns null if neither match (frontend renders 404). Frontend dispatches by __typename and active profile.
Notes:
- Existing
job(id)andpetJob(id)queries stay — admin and internal callers use them directly. - IDs across
JobandPetJobare CUIDs so the namespace risk is negligible, but the resolver order (Job → PetJob) is deterministic.
FE-JOBS-006 — Role-dispatched /jobs + Talent match grid
- [x] Implemented — supersedes FE-JOBS-001
Files:
- Create:
apps/app/src/pages/jobs/JobsRouter.tsx— dispatches by active profile type - Create:
apps/app/src/pages/jobs/TalentJobsPage.tsx— infinite-scroll grid with match % - Create:
apps/app/src/hooks/useJobsForTalent.ts—useInfiniteQuery, 10/page - Edit:
apps/app/src/lib/queries/jobs.ts— addJOBS_FOR_TALENT_QUERY+JobMatchFeedItemfragment - Delete:
apps/app/src/pages/jobs/JobsPage.tsx - Edit:
apps/app/src/App.tsx—/jobs→<JobsRouter />;/jobs/manage→<Navigate to="/jobs" replace /> - Edit:
packages/design-system/src/components/JobCard.tsx— accept optionalmatchScore+matchReasons(rendered as a progress ring or chip)
Description: /jobs is now role-aware. For talent profiles, it renders TalentJobsPage: a responsive grid (1/2/3 cols) of JobCards with the per-job match percentage shown prominently. First page is 10 items via useInfiniteQuery, additional pages loaded on scroll using IntersectionObserver. Filter bar (production type, remote, payment type) above the grid persists query state in the URL.
Empty / loading / error states: DS Skeleton grid during initial load; DS EmptyState when no jobs match the filter; toast on network error.
FE-JOBS-007 — Producer jobs table at /jobs
- [x] Implemented — supersedes FE-JOBS-004
Files:
- Create:
apps/app/src/pages/jobs/ProducerJobsPage.tsx(rewrite — was list, now table) - Edit:
apps/app/src/hooks/useProducerJobs.ts— page/pageSize args (server-side pagination) - Edit:
apps/app/src/lib/queries/jobs.ts—MY_JOBS_QUERYwith pageInfo - Edit:
apps/app/src/App.tsx— drop/jobs/manage(route folded intoJobsRouter) - Add:
packages/design-system/src/components/JobsTable.tsxif not already in DS
Description: When a producer hits /jobs, JobsRouter renders this table. Columns: Title, Status badge, Applications count, Casting submissions count, Posted, Deadline, Actions (... menu: View, Edit, Close, Generate flier, View castings). Default sort: most recent first. Pagination controls below the table per pagination rule. Row click → /jobs/:id (producer view per FE-JOBS-009).
FE-JOBS-008 — Pet-owner match grid at /jobs
- [x] Implemented
Files:
- Create:
apps/app/src/pages/jobs/PetOwnerJobsPage.tsx - Create:
apps/app/src/hooks/usePetJobsForOwner.ts - Edit:
apps/app/src/lib/queries/petJobs.ts(new file) —PET_JOBS_FOR_OWNER_QUERY+ fragment - Edit:
packages/design-system/src/components/JobCard.tsx— addPetJobCardvariant (or passkind: 'pet') showing species/breed instead of skills
Description: Pet-owner variant of TalentJobsPage. Same grid + infinite-scroll layout, but driven by petJobsForOwner and shows pet-specific match reasons (e.g., "Bella matches the breed and trained skills"). When the owner has multiple pets, the card highlights the best-matching pet by name.
FE-JOBS-009 — Role+type-aware /jobs/:id detail
- [x] Implemented — single
JobListingPagedispatches by union__typename; role-aware behavior lives insideJobDetailPage/PetJobDetailPagerather than separate view subcomponents.
Files:
- Edit:
apps/app/src/pages/jobs/JobDetailPage.tsx— fetch viajobListing(id)union - Create:
apps/app/src/pages/jobs/views/TalentJobView.tsx— apply flow (extracted from currentJobDetailPage) - Create:
apps/app/src/pages/jobs/views/ProducerJobView.tsx— castings tab, applicants tab, manage actions - Create:
apps/app/src/pages/jobs/views/PetOwnerPetJobView.tsx— apply-with-pet flow - Edit:
apps/app/src/lib/queries/jobs.ts—JOB_LISTING_QUERYreturning the union with__typename
Description: Single page resolves the listing via the union, then dispatches by __typename (Job vs PetJob) and active profile (producer / talent / pet-owner). Each view is a self-contained subcomponent — no role logic intermixed at the page level beyond the dispatcher.
Dispatch matrix:
| Profile | Job | PetJob |
|---|---|---|
| Producer (owner) | ProducerJobView | ProducerPetJobView (future) |
| Producer (other) | Read-only TalentJobView | Read-only view |
| Talent | TalentJobView (apply) | 404 (talent can't apply to pet jobs) |
| Pet owner | 404 | PetOwnerPetJobView (apply with pet) |
Open follow-ups: ProducerPetJobView is deferred until BE-PETJOB-001 + an application model for pet jobs lands.
BE-JOB-007 — Job wizard schema extension
- [x] Implemented
Files:
- Edit:
prisma/schema.prisma—Jobmodel gains the wizard fields - Run:
pnpm db:migrate→20260601220244_add_job_wizard_fields - Edit:
src/graphql/schema/index.ts—Job,CreateJobInput,UpdateJobInput; newWorkModeenum - Edit:
src/graphql/validation/index.ts— Zod tightened - Edit:
src/graphql/resolvers/job.ts—withWorkModeMirrorkeeps the legacyisRemotefilter in sync
Fields added to Job: projectName, talentCategory, auditionRequired, accentsDialects[], materialsRequired[], applicationInstructions, contactPersonName, contactPersonEmail, contractType, salaryMin, salaryMax, workMode (IN_PERSON | REMOTE | HYBRID), recordingDates Json.
BE-JOB-008 — Job ownership enforcement on updateJob / deleteJob
- [x] Implemented
Files:
- Edit:
src/graphql/resolvers/job.ts— fetch the producer profile fromctx.user.sub, compare tojob.producerId, throwFORBIDDENotherwise - Edit:
src/__tests__/resolvers/job.test.ts— three new ownership cases (success, non-owner, missing job)
Before this fix, any producer could update or delete any job. PetJob mutations already had the check; this brings Jobs to parity.
BE-JOB-009 — Application contact + submitted materials
- [x] Implemented
Files:
- Edit:
prisma/schema.prisma—ApplicationgainsapplicantEmail,applicantPhone,submittedMaterials Json - Run:
pnpm db:migrate→20260602004355_add_application_contact_materials - Edit:
src/graphql/schema/index.ts— extendApplicationtype, addApplicationMaterialInput,applyToJobaccepts the new variables - Edit:
src/graphql/validation/index.ts— Zod for new fields incl. email + URL validation - Edit:
src/graphql/resolvers/job.ts—applyToJobwrites the new columns
submittedMaterials shape: Array<{ material: string; mediaItemId?: string; uploadedUrl?: string }> — one entry per Job.materialsRequired slot, pointing either to a portfolio item or a fresh upload.
BE-PETJOB-003 — PetJob wizard schema parity
- [x] Implemented
Files:
- Edit:
prisma/schema.prisma—PetJobgains the same wizard fields asJob(minus the talent-specific ones) - Run:
pnpm db:migrate→20260601231109_add_pet_job_wizard_fields - Edit:
src/graphql/schema/index.ts—PetJob,CreatePetJobInput,UpdatePetJobInput - Edit:
src/graphql/resolvers/petJob.ts—withPetJobWorkModeMirrormirrors the Job pattern
Fields added: projectName, auditionRequired, salaryMin, salaryMax, contractType, workMode, recordingDates, materialsRequired[], applicationInstructions, contactPersonName, contactPersonEmail. talentCategory and accentsDialects intentionally omitted (don't apply to pets).
FE-JOBS-010 — Multi-stage job creation wizard (6 stages)
- [x] Implemented
Files:
- Rewrite:
apps/app/src/pages/jobs/JobCreatePage.tsxas a 6-stage wizard - Edit:
apps/app/src/hooks/useProducerJobs.ts— extendCreateJobInput - Edit:
apps/app/src/lib/queries/jobs.ts— extendJOB_FRAGMENT - Edit:
apps/app/src/hooks/useJobs.ts—JobTS type +WorkMode - Edit:
packages/i18n/locales/{en,pt,es}/app.json— fulljobCreate.*block
Stages: Basic information → Production details → Job description → Specific requirements → Logistics & Compensation → Application process. Reuses Card, Chip, Input, Select, Textarea, MultiSelect from @castyou/design-system. Header shows a thin progress bar + STAGE n of 6 / <name> + Next: <next> preview. Free-form ChipInput for genre / skills / accents / materials; three-button workMode selector; salary min/max pair; materials suggestions; structured-casting toggle preserved on Stage 6.
FE-JOBS-011 — Job edit page (reuse wizard)
- [x] Implemented
Files:
- Edit:
apps/app/src/pages/jobs/JobCreatePage.tsx— accept{ mode, initialData, jobId }props - Create:
apps/app/src/pages/jobs/JobEditPage.tsx— fetches viauseJob, mapsJob → CreateJobInput, renders the wizard in edit mode - Edit:
apps/app/src/App.tsx—/jobs/:id/editroute - Edit:
apps/app/src/pages/jobs/ProducerJobsPage.tsx— Edit row action onJobsTab
In edit mode, the header reads "Edit job", the submit button reads "Save changes", and the structured-casting toggle is hidden (casting map has its own dedicated /jobs/:id/casting-map/edit page).
FE-JOBS-012 — Apply-to-job 3-stage wizard
- [x] Implemented
Files:
- Create:
apps/app/src/pages/jobs/JobApplyPage.tsx - Edit:
apps/app/src/lib/queries/jobs.ts—APPLY_TO_JOB_MUTATIONaccepts new variables; newGET_ME_FOR_APPLYquery for prefill - Edit:
apps/app/src/hooks/useJob.ts—useApplyToJobaccepts anApplyInputobject - Edit:
apps/app/src/App.tsx—/jobs/:id/applyroute - Edit:
apps/app/src/pages/jobs/JobDetailPage.tsx— Apply Now navigates to the wizard (replaces the modal) - Edit:
packages/i18n/locales/{en,pt,es}/app.json—jobApply.*block
Stages: Contact info + library/upload → Match each Job.materialsRequired slot to a chosen media item → Review + Send. Uses the existing GET_MEDIA_UPLOAD_URL upload pipeline for fresh files; ties them to the Application.submittedMaterials JSON column via uploadedUrl, or attaches existing portfolio items via mediaItemId.
FE-JOBS-013 — Inline applicants panel on job detail
- [x] Implemented
Files:
- Edit:
apps/app/src/pages/jobs/JobDetailPage.tsx— newProducerApplicantsPanelcomponent, rendered whenactiveProfile === 'PRODUCER' | 'AGENCY' - Edit:
apps/app/src/pages/jobs/PetJobDetailPage.tsx— same panel pattern for pet jobs
Backend's jobApplications(jobId) resolver enforces ownership; non-owners get FORBIDDEN and the panel hides itself silently. Each row shows avatar → name → primaryCategory → cover note → status badge → per-row status Select. The panel also surfaces applicantEmail / applicantPhone (as mailto: / tel: links) and the submittedMaterials list with links to uploaded files or "From portfolio" / "Not provided" indicators.
FE-JOBS-014 — Route-based Jobs / Pet jobs tabs
- [x] Implemented
Files:
- Edit:
apps/app/src/pages/jobs/ProducerJobsPage.tsx— tab state moved fromuseStatetouseSearchParams
The Pet jobs tab encodes as ?tab=pets; Jobs is the default (no param). Clicking a tab pushes a history entry, so browser Back from a detail page returns to the previously active tab.
FE-JOBS-015 — Flier thumbnails in producer jobs list
- [x] Implemented
Files:
- Edit:
apps/app/src/pages/jobs/ProducerJobsPage.tsx—Thumbnailcomponent layered fallback:flierUrlimage →FlierThumbnailcanvas → empty placeholder - Pet jobs table gets the same thumbnail column
The AI-generated flierSpec renders as a tiny canvas via the existing FlierThumbnail component. Empty placeholders for rows without either field.
FE-PETJOB-001 — Pet job creation + edit wizards
- [x] Implemented
Files:
- Create:
apps/app/src/pages/jobs/PetJobCreatePage.tsx— 6-stage wizard with pet-specific stages - Create:
apps/app/src/pages/jobs/PetJobEditPage.tsx - Edit:
apps/app/src/hooks/useProducerJobs.ts—useCreatePetJob,useUpdatePetJobhooks +CreatePetJobInput - Edit:
apps/app/src/lib/queries/petJobs.ts—PET_JOB_FRAGMENT,CREATE_PET_JOB_MUTATION,UPDATE_PET_JOB_MUTATION - Edit:
apps/app/src/hooks/usePetJobsForOwner.ts—PetJobTS type extended - Edit:
apps/app/src/App.tsx—/pet-jobs/createand/pet-jobs/:id/editroutes - Edit:
apps/app/src/pages/jobs/ProducerJobsPage.tsx— header "Post job" button switches to "Post pet job" when the Pet jobs tab is active; Edit row action onPetJobsTab - Edit:
packages/i18n/locales/{en,pt,es}/app.json—petJobCreate.*block
Pet-specific stages: Stage 3 "Pet description" — species multi-select, breeds chips, trained skills chips, novice/intermediate/professional experience levels. Stage 4 "Pet requirements" — age in months, size range (small/medium/large/xl), coat colors, vaccination required toggle, audition required toggle, number of roles.
FE-PETJOB-002 — PetJobDetailPage parity with JobDetailPage
- [x] Implemented
Files:
- Rewrite:
apps/app/src/pages/jobs/PetJobDetailPage.tsx
Same purple gradient hero, info-pill row (Payment / Work model / Openings), flyer section, structured bullet sections (Pet requirements, Required trained skills), Apply gradient pill button (wired to ApplyWithPetModal), ProducerCard at the bottom, and an inline ProducerApplicantsPanel for the job owner (wrapped in the same surface-card styling).