Add "revoked by landlord" state to /sign/[token] expired screen#3
Open
keywise-app wants to merge 3 commits into
Open
Add "revoked by landlord" state to /sign/[token] expired screen#3keywise-app wants to merge 3 commits into
keywise-app wants to merge 3 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposal: Add "revoked by landlord" state to /sign/[token] expired screen
Severity: medium · Route: /sign/[token]
Friction
When a landlord sends a lease to the wrong tenant (support ticket tkt_stub_1, May 14, 2026: "I sent the renewal to the wrong tenant — there's no way to recall it"), the current mitigating path is for the landlord to invalidate the token server-side. But from the tenant's perspective, the resulting UX at
/sign/[token]is identical for every expired/revoked link: thestate === 'expired'branch shows a ⏰ icon and "This signing link has expired. Please contact your landlord to request a new link."This is confusing in two directions: (a) a tenant who received a link sent to the wrong address doesn't understand why it doesn't work — they may assume the error is on their end and keep retrying; (b) a tenant waiting on a legitimate lease wonders if they did something wrong. The error message doesn't differentiate between a time-expired link and a landlord-revoked one, leaving the tenant without actionable next steps.
Proposed change
In
app/sign/[token]/page.tsx, update the API call to/api/sign-documentto return areasonfield in the 410 response:"expired"(link passed its time-to-live) vs."revoked"(landlord explicitly cancelled it). Then render two distinct states:This requires a small API change to the
sign-documentroute to surface the reason, but the frontend change is entirely contained in/sign/[token]/page.tsx.Why this matters
This sharpens Principle 5: Error Recovery — when things fail, the user is never stuck, and the message includes a plain-English next step. It also reflects Principle 2: Reversibility — landlords need a recall path, and the tenant UX must gracefully handle that recall rather than confusing the tenant. The affected population is small per incident but the confusion-per-incident is high (a tenant calling a landlord about a bad link is a real support burden).
What I changed
app/sign/[token]/page.tsx'revoked'to the state union typedata.reasonfrom the API response and sets'revoked'vs'expired'state accordingly (safe fallback to'expired'ifreasonis absent)revokedrender block: 🚫 "Document Recalled" with the copy from the proposalapp/api/sign-document/route.ts(GET handler only)reasonfield:"revoked"iftokenRow.revokedis truthy,"expired"otherwisetokenRow.revokedis a forward-compatible check — if the DB column doesn't exist yet, Supabase returnsundefined(falsy), so the response degrades gracefully toreason: "expired"with no breakageFiles touched
app/sign/[token]/page.tsx— addsrevokedstate, routes 410 response byreason, renders distinct revoked UI blockapp/api/sign-document/route.ts— surfacesreason: "revoked" | "expired"in the 410 JSON responseNotes
revokedcolumn onsigning_tokensdoesn't exist in the DB schema yet. The API change is written to degrade gracefully (always returns"expired"until a human adds arevoked boolean default falsecolumn via migration). The full recall flow (landlord UI + migration) can be a follow-up — this PR wires up the tenant-facing UX so it's ready the moment the backend is.