This commit is contained in:
2026-08-24 04:03:44 +02:00
parent 8a690a0464
commit 7bafd7a322
9 changed files with 391 additions and 53 deletions
+11 -2
View File
@@ -124,17 +124,25 @@ export const en: MessageDict = {
"auth.register.haveAccount": "Already have an account?",
"auth.register.signIn": "Sign in",
"auth.invite.title": "Accept invite",
"auth.invite.verifyTitle": "Join with your account",
"auth.invite.recoverTitle": "Restore access",
"auth.invite.setPasswordTitle": "Set password",
"auth.invite.description": "Set your password to join the company. Your admin assigned either Member (day-to-day work) or Admin (team and billing).",
"auth.invite.description": "Choose a password to join this company. You also get your own workspace you can switch to anytime.",
"auth.invite.verifyDescription": "This email already has a Descrybe account. Enter your current password to join this company. You keep your own workspace and can switch between companies.",
"auth.invite.recoverDescription": "This email has an account with no active company (for example after being removed). Choose a new password to rejoin — you also get your own workspace.",
"auth.invite.setPasswordDescription": "Choose a password for your migrated Descrybe account (at least 8 characters).",
"auth.invite.checking": "Checking invite…",
"auth.invite.forEmail": "Invite for {email}.",
"auth.invite.linkRecognized": "Invite link recognized. Enter a password below to continue — the secret is not shown on this page.",
"auth.invite.verifyLinkRecognized": "Invite link recognized. Enter your current account password below — the invite secret is not shown on this page.",
"auth.invite.recoverLinkRecognized": "Invite link recognized. Choose a new password below to restore access — the invite secret is not shown on this page.",
"auth.invite.resetLinkRecognized": "Reset link recognized. Enter a password below to continue — the secret is not shown on this page.",
"auth.invite.tokenLabel": "Invite token",
"auth.invite.resetTokenLabel": "Reset token",
"auth.invite.tokenHelp": "Paste the token from your invite email. It is masked in this field.",
"auth.invite.passwordHint": "At least 8 characters. No other complexity rules.",
"auth.invite.passwordHintVerify": "Use the password you already use to sign in to Descrybe.",
"auth.invite.passwordHintRecover": "At least 8 characters. This replaces your previous password.",
"auth.invite.submit": "Accept invite",
"auth.invite.setPasswordSubmit": "Set password",
"auth.invite.accepting": "Accepting…",
@@ -143,6 +151,7 @@ export const en: MessageDict = {
"auth.invite.verifySetPasswordFailed": "Could not verify set-password link",
"auth.invite.acceptFailed": "Could not accept invite",
"auth.invite.setPasswordFailed": "Could not set password",
"auth.invite.wrongPassword": "That password does not match this account. Enter the password you use to sign in, or ask an admin for a new invite if you were removed and need to set a new password.",
"auth.invite.expired": "This invite is invalid or expired. Ask your company admin to send a new invite, then open the new link (or paste the new token below).",
"auth.invite.setPasswordExpired": "This set-password link is invalid or expired. Ask a company admin to re-issue a set-password link to this email, then open the new link (or paste the new token below).",
"auth.invite.emailMismatchDefault": "You're signed in as a different email than this invite.",
@@ -150,7 +159,7 @@ export const en: MessageDict = {
"auth.invite.adminUsersLink": "Admin → Users",
"auth.invite.doneTitle": "You're on the team",
"auth.invite.doneSetPasswordTitle": "Password saved",
"auth.invite.doneDescription": "Your account is ready. Next, open the dashboard to work with feeds and products, or review company settings.",
"auth.invite.doneDescription": "You're on the invited team and have your own workspace. Switch companies anytime from the header.",
"auth.invite.doneSetPasswordDescription": "Sign in with your email and new password to open your workspace.",
"auth.invite.openDashboard": "Open dashboard",
"auth.invite.companySettings": "Company settings",
+39 -5
View File
@@ -25,6 +25,7 @@
session_email?: string;
valid: boolean;
mismatch: boolean;
password_mode?: "set" | "verify" | "recover";
};
let token = $state("");
@@ -44,6 +45,8 @@
let signingOut = $state(false);
/** Admin HMAC emails use mode=set-password; migrator invites use the invite token path. */
let mode = $state<"invite" | "set-password">("invite");
/** How accept-invite treats the password field for an existing/new account. */
let passwordMode = $state<"set" | "verify" | "recover">("set");
const errorId = "accept-invite-form-error";
const passwordHintId = "accept-invite-password-hint";
@@ -84,10 +87,20 @@
if (preview.mode === "set-password" || preview.mode === "invite") {
mode = preview.mode;
}
if (
preview.password_mode === "set" ||
preview.password_mode === "verify" ||
preview.password_mode === "recover"
) {
passwordMode = preview.password_mode;
} else if (preview.mode === "invite") {
passwordMode = "set";
}
} catch (err) {
mismatch = false;
inviteEmail = "";
sessionEmail = "";
passwordMode = "set";
error =
err instanceof ApiError
? err.message
@@ -142,6 +155,7 @@
done = true;
} catch (err) {
if (applyMismatchFromError(err)) return;
const code = apiErrorCode(err);
const raw = err instanceof ApiError ? err.message : "";
const expired =
/invalid or expired/i.test(raw) || /token invalid or expired/i.test(raw);
@@ -150,6 +164,8 @@
mode === "set-password"
? i18n.t("auth.invite.setPasswordExpired")
: i18n.t("auth.invite.expired");
} else if (code === "invalid_credentials" && mode === "invite") {
error = i18n.t("auth.invite.wrongPassword");
} else {
error =
raw ||
@@ -274,12 +290,20 @@
<CardTitle level={1}
>{mode === "set-password"
? i18n.t("auth.invite.setPasswordTitle")
: i18n.t("auth.invite.title")}</CardTitle
: passwordMode === "verify"
? i18n.t("auth.invite.verifyTitle")
: passwordMode === "recover"
? i18n.t("auth.invite.recoverTitle")
: i18n.t("auth.invite.title")}</CardTitle
>
<CardDescription>
{mode === "set-password"
? i18n.t("auth.invite.setPasswordDescription")
: i18n.t("auth.invite.description")}
: passwordMode === "verify"
? i18n.t("auth.invite.verifyDescription")
: passwordMode === "recover"
? i18n.t("auth.invite.recoverDescription")
: i18n.t("auth.invite.description")}
</CardDescription>
</CardHeader>
<CardContent>
@@ -310,7 +334,11 @@
<p class="rounded-md border bg-muted/40 px-3 py-2 text-sm text-text-muted">
{mode === "set-password"
? i18n.t("auth.invite.resetLinkRecognized")
: i18n.t("auth.invite.linkRecognized")}
: passwordMode === "verify"
? i18n.t("auth.invite.verifyLinkRecognized")
: passwordMode === "recover"
? i18n.t("auth.invite.recoverLinkRecognized")
: i18n.t("auth.invite.linkRecognized")}
</p>
{:else}
<div class="space-y-2">
@@ -359,7 +387,9 @@
id="password"
type="password"
name="password"
autocomplete="new-password"
autocomplete={mode === "invite" && passwordMode === "verify"
? "current-password"
: "new-password"}
required
minlength={8}
aria-invalid={error ? "true" : undefined}
@@ -367,7 +397,11 @@
bind:value={password}
/>
<p id={passwordHintId} class="text-xs text-text-muted">
{i18n.t("auth.invite.passwordHint")}
{mode === "invite" && passwordMode === "verify"
? i18n.t("auth.invite.passwordHintVerify")
: mode === "invite" && passwordMode === "recover"
? i18n.t("auth.invite.passwordHintRecover")
: i18n.t("auth.invite.passwordHint")}
</p>
</div>