diff --git a/frontend/src/surfaces/operations/sponsor-docs.js b/frontend/src/surfaces/operations/sponsor-docs.js index 9ff3ce9f..24310c99 100644 --- a/frontend/src/surfaces/operations/sponsor-docs.js +++ b/frontend/src/surfaces/operations/sponsor-docs.js @@ -242,6 +242,9 @@ export function createSponsorDocPage({ } else if (err.status === 429) { phase = "gate"; gateError = `Too many failed access code attempts. Please wait ${err.retryAfter || 60} seconds and try again.`; + } else if (err.status === 403 && err.code === "staff_only_document") { + phase = "gate"; + gateError = "This document is for staff only. Use \"Staff sign in\" above with your DataTalks.Club account; an access code cannot open it."; } else if (err.status === 403) { saveStoredCode(docId, ""); phase = "gate"; diff --git a/frontend/test/sponsor-docs.test.mjs b/frontend/test/sponsor-docs.test.mjs index 560363fa..bd4d4992 100644 --- a/frontend/test/sponsor-docs.test.mjs +++ b/frontend/test/sponsor-docs.test.mjs @@ -737,3 +737,16 @@ test("guest portal shows dual zones and saves a time choice before required mate assert.equal(JSON.parse(ordinarySave[1].body).timeChoiceIntent, undefined, "ordinary biography/material saves do not reconfirm the stored slot choice"); h.controller.destroy(); }); + +test("a valid code on a staff-only doc says it is staff only and keeps the stored code", async () => { + const h = harness({ + request: async () => { + throw Object.assign(new Error("This document is for staff only."), { status: 403, code: "staff_only_document" }); + }, + }); + await h.controller.start(); + + assert.match(h.root.textContent, /for staff only/); + assert.ok(!/Invalid access code/.test(h.root.textContent)); + h.controller.destroy(); +});