Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions portal/app/account/ChangeHandleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export default function ChangeHandleModal({ currentHandle, token, onClose, onSuc
try {
await updateHandle(trimmed, token);
} catch (err) {
if (err instanceof ApiError && err.status === 402) {
setError("Custom handles require Pro or above.");
if (err instanceof ApiError && err.status === 403) {
setError("Verify your email first, then choose your handle.");
} else if (err instanceof ApiError && err.status === 409) {
setError("That handle is taken.");
} else if (err instanceof ApiError && err.status === 422) {
Expand Down
34 changes: 20 additions & 14 deletions portal/app/account/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ export default function AccountPage() {
setAccountId(me.account_id);
setAccountEmail(me.email);
setAccountHandle(me.handle);
// Authoritative over the JWT claim above: the token predates a
// verification the user may have completed since it was issued, and
// this value now decides whether the handle control is offered.
setEmailVerified(me.email_verified);
} catch { /* non-critical */ }

// Trial expired modal
Expand Down Expand Up @@ -442,12 +446,13 @@ export default function AccountPage() {
<div>
<p className="text-xs text-zinc-500 uppercase tracking-widest mb-1">Handle</p>
<p className="text-sm text-white">{accountHandle ? `@${accountHandle}` : "—"}</p>
{displayTier === "free" && (
<div className="mt-1">
<p className="text-xs text-zinc-500">Custom handles are a Pro feature.</p>
<p className="text-xs text-zinc-500">Others can still reach you at this handle.</p>
</div>
)}
<div className="mt-1">
<p className="text-xs text-zinc-500">A custom handle makes you findable by people outside your teams.</p>
<p className="text-xs text-zinc-500">Your current handle already works for anyone you give it to.</p>
{emailVerified === false && (
<p className="text-xs text-amber-300 mt-1">Verify your email to choose a custom handle.</p>
)}
</div>
</div>
<div className="flex gap-2 shrink-0">
{accountHandle && (
Expand All @@ -458,14 +463,15 @@ export default function AccountPage() {
{handleCopied ? "Copied" : "Copy"}
</button>
)}
{displayTier != null && displayTier !== "free" && (
<button
onClick={() => setShowChangeHandle(true)}
className="px-4 py-2 rounded-xl border border-[#1e1e2e] hover:border-zinc-600 text-zinc-400 hover:text-white text-sm font-semibold transition-colors"
>
Change handle
</button>
)}
{/* Disabled with the reason rather than hidden — hiding it is
what made the old tier gate unreadable. */}
<button
onClick={() => setShowChangeHandle(true)}
disabled={emailVerified === false}
className="px-4 py-2 rounded-xl border border-[#1e1e2e] hover:border-zinc-600 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:border-[#1e1e2e] text-zinc-400 hover:text-white disabled:hover:text-zinc-400 text-sm font-semibold transition-colors"
>
Change handle
</button>
</div>
</div>
{/* Email + password row */}
Expand Down