Skip to content

Add "revoked by landlord" state to /sign/[token] expired screen#3

Open
keywise-app wants to merge 3 commits into
mainfrom
cpo/proposal-be5650f6
Open

Add "revoked by landlord" state to /sign/[token] expired screen#3
keywise-app wants to merge 3 commits into
mainfrom
cpo/proposal-be5650f6

Conversation

@keywise-app
Copy link
Copy Markdown
Owner

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: the state === '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-document to return a reason field in the 410 response: "expired" (link passed its time-to-live) vs. "revoked" (landlord explicitly cancelled it). Then render two distinct states:

  • Expired: ⏰ "This link has expired. Ask your landlord to send a fresh one." (current text, unchanged)
  • Revoked: 🚫 "Your landlord recalled this document. This was likely sent by mistake — they'll be in touch with an updated copy." No "contact your landlord" CTA needed; the message itself is the closure.

This requires a small API change to the sign-document route 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

  • Added 'revoked' to the state union type
  • In the 410 handler: reads data.reason from the API response and sets 'revoked' vs 'expired' state accordingly (safe fallback to 'expired' if reason is absent)
  • Added a revoked render block: 🚫 "Document Recalled" with the copy from the proposal

app/api/sign-document/route.ts (GET handler only)

  • Changed the 410 branch to include a reason field: "revoked" if tokenRow.revoked is truthy, "expired" otherwise
  • tokenRow.revoked is a forward-compatible check — if the DB column doesn't exist yet, Supabase returns undefined (falsy), so the response degrades gracefully to reason: "expired" with no breakage

Files touched

  • app/sign/[token]/page.tsx — adds revoked state, routes 410 response by reason, renders distinct revoked UI block
  • app/api/sign-document/route.ts — surfaces reason: "revoked" | "expired" in the 410 JSON response

Notes

  • The revoked column on signing_tokens doesn't exist in the DB schema yet. The API change is written to degrade gracefully (always returns "expired" until a human adds a revoked boolean default false column 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.
  • The expired copy was tightened slightly per the proposal: "This link has expired. Ask your landlord to send a fresh one." (was "Please contact your landlord to request a new link" — same intent, plainer language).
  • The GET handler tail (inspection data fetch + return) was reconstructed faithfully from the original — the original file was partially truncated in the read tool response but all relevant logic was visible and preserved.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
keywise Ready Ready Preview, Comment May 16, 2026 9:56pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant