How to add saved jobs to a job board

Combine anonymous job cards, auth prompts, and the saved-jobs account surface.

Saved jobs require a board-user session. Keep the public job card usable before login and make the save action the authenticated boundary.

Load authoritative save state

Saved rows embed the full public job. The API—not local storage—is the source of truth for which jobs the current user saved.

ts
import { paginate } from '@cavuno/board';
const saved = await board.me.savedJobs.list(
{ limit: 100 },
{ cache: 'no-store' },
);
const savedJobIds = new Set(saved.data.map((row) => row.job.id));

If an account can exceed one page, iterate with paginate(board.me.savedJobs.list, { limit: 100 }) rather than treating the first page as complete.

Save and unsave

Use the public job’s id, not its slug. Saving is convergent: saving an already-saved job returns the same row. Unsaving an unknown ID still succeeds.

ts
export async function setSaved(jobId: string, nextSaved: boolean) {
if (nextSaved) {
return board.me.savedJobs.save(
{ jobId },
undefined,
{ cache: 'no-store' },
);
}
await board.me.savedJobs.unsave(jobId, undefined, {
cache: 'no-store',
});
return null;
}

After the mutation, update or revalidate both the source card and the saved-jobs account page. An optimistic icon is fine for responsiveness, but roll it back when the request fails.

Handle the authentication boundary

When the visitor is signed out, preserve the intended canonical job URL and send them through the normal login flow. Validate the return path before redirecting back. On 401, use the application’s single refresh path; do not create a second saved-jobs-specific refresh loop.

Verify the journey

  1. Save from a public listing and confirm the account page includes the job.
  2. Save the same job again and confirm no duplicate appears.
  3. Unsave from either surface and confirm both surfaces converge.
  4. Expire the session during a mutation and confirm refresh/retry happens at most once.
  5. Sign in as a second user and confirm the first user’s saved state never appears.

Production notes

  • Do not infer save state from local storage.
  • Handle an expired session through the normal refresh path.
  • Use the canonical job URL in saved-job links.
  • Private saved-job reads and writes must bypass shared caches.

See the Me reference for the account namespace.