passkey translations

This commit is contained in:
Pas 2025-12-29 16:31:28 -07:00
parent b7e08b505f
commit 4a8c756eb0
5 changed files with 51 additions and 42 deletions

View file

@ -158,7 +158,8 @@
"customPassphrasePlaceholder": "Enter your custom passphrase", "customPassphrasePlaceholder": "Enter your custom passphrase",
"useCustomPassphrase": "Use Custom Passphrase", "useCustomPassphrase": "Use Custom Passphrase",
"invalidPassphraseCharacters": "Invalid passphrase characters. Only English letters, numbers 1-10, and normal symbols are allowed.", "invalidPassphraseCharacters": "Invalid passphrase characters. Only English letters, numbers 1-10, and normal symbols are allowed.",
"passphraseTooShort": "Passphrase must be at least 8 characters long." "passphraseTooShort": "Passphrase must be at least 8 characters long.",
"usePasskeyInstead": "Use passkey instead"
}, },
"hasAccount": "Already have an account? <0>Login here.</0>", "hasAccount": "Already have an account? <0>Login here.</0>",
"login": { "login": {
@ -168,7 +169,10 @@
"passphrasePlaceholder": "Passphrase", "passphrasePlaceholder": "Passphrase",
"submit": "Login", "submit": "Login",
"title": "Login to your account", "title": "Login to your account",
"validationError": "Incorrect or incomplete passphrase /ᐠ. .ᐟ\\" "validationError": "Incorrect or incomplete passphrase /ᐠ. .ᐟ\\",
"usePasskey": "Use passkey",
"or": "or",
"noBackendUrl": "No backend URL"
}, },
"register": { "register": {
"information": { "information": {
@ -209,7 +213,9 @@
"passphraseLabel": "Your 12-word passphrase", "passphraseLabel": "Your 12-word passphrase",
"recaptchaFailed": "ReCaptcha validation failed", "recaptchaFailed": "ReCaptcha validation failed",
"register": "Create account", "register": "Create account",
"title": "Confirm your passphrase" "title": "Confirm your passphrase",
"passkeyDescription": "Please authenticate with your passkey to complete registration.",
"authenticatePasskey": "Authenticate with Passkey"
} }
}, },
"errors": { "errors": {

View file

@ -156,7 +156,12 @@ export function decryptData(data: string, secret: Uint8Array) {
// Passkey/WebAuthn utilities // Passkey/WebAuthn utilities
export function isPasskeySupported(): boolean { export function isPasskeySupported(): boolean {
// Passkeys require HTTPS
const isSecureContext =
typeof window !== "undefined" && window.location.protocol === "https:";
return ( return (
isSecureContext &&
typeof navigator !== "undefined" && typeof navigator !== "undefined" &&
"credentials" in navigator && "credentials" in navigator &&
"create" in navigator.credentials && "create" in navigator.credentials &&

View file

@ -117,8 +117,40 @@ export function LoginFormPart(props: LoginFormPartProps) {
{t("auth.login.description")} {t("auth.login.description")}
</LargeCardText> </LargeCardText>
<div className="space-y-4"> <div className="space-y-4">
<AuthInputBox
label={t("auth.deviceNameLabel") ?? undefined}
value={device}
onChange={setDevice}
placeholder={t("auth.deviceNamePlaceholder") ?? undefined}
/>
<AuthInputBox
label={t("auth.login.passphraseLabel") ?? undefined}
value={mnemonic}
autoComplete="username"
name="username"
onChange={setMnemonic}
placeholder={t("auth.login.passphrasePlaceholder") ?? undefined}
passwordToggleable
/>
{(result.error || passkeyResult.error) &&
!result.loading &&
!passkeyResult.loading ? (
<p className="text-authentication-errorText">
{result.error?.message || passkeyResult.error?.message}
</p>
) : null}
{isPasskeySupported() && ( {isPasskeySupported() && (
<div> <div className="relative mb-4">
<div className="relative my-4">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-authentication-border/50" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-authentication-bg text-authentication-text">
{t("auth.login.or")}
</span>
</div>
</div>
<Button <Button
theme="secondary" theme="secondary"
onClick={() => executePasskey(device)} onClick={() => executePasskey(device)}
@ -131,42 +163,10 @@ export function LoginFormPart(props: LoginFormPartProps) {
className="w-full" className="w-full"
> >
<Icon icon={Icons.LOCK} className="mr-2" /> <Icon icon={Icons.LOCK} className="mr-2" />
{t("auth.login.usePasskey") ?? "Use passkey"} {t("auth.login.usePasskey")}
</Button> </Button>
<div className="relative my-4">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-authentication-border/50" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-authentication-bg text-authentication-text">
{t("auth.login.or") ?? "or"}
</span>
</div>
</div>
</div> </div>
)} )}
<AuthInputBox
label={t("auth.login.passphraseLabel") ?? undefined}
value={mnemonic}
autoComplete="username"
name="username"
onChange={setMnemonic}
placeholder={t("auth.login.passphrasePlaceholder") ?? undefined}
passwordToggleable
/>
<AuthInputBox
label={t("auth.deviceNameLabel") ?? undefined}
value={device}
onChange={setDevice}
placeholder={t("auth.deviceNamePlaceholder") ?? undefined}
/>
{(result.error || passkeyResult.error) &&
!result.loading &&
!passkeyResult.loading ? (
<p className="text-authentication-errorText">
{result.error?.message || passkeyResult.error?.message}
</p>
) : null}
</div> </div>
<LargeCardButtons> <LargeCardButtons>

View file

@ -80,7 +80,7 @@ export function PassphraseGeneratePart(props: PassphraseGeneratePartProps) {
className="w-full" className="w-full"
> >
<Icon icon={Icons.LOCK} className="mr-2" /> <Icon icon={Icons.LOCK} className="mr-2" />
{t("auth.generate.usePasskeyInstead") ?? "Use passkey instead"} {t("auth.generate.usePasskeyInstead")}
</Button> </Button>
{passkeyResult.error && ( {passkeyResult.error && (
<p className="mt-2 text-authentication-errorText text-sm text-center"> <p className="mt-2 text-authentication-errorText text-sm text-center">

View file

@ -188,8 +188,7 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
icon={<Icon icon={Icons.CIRCLE_CHECK} />} icon={<Icon icon={Icons.CIRCLE_CHECK} />}
title={t("auth.verify.title")} title={t("auth.verify.title")}
> >
{t("auth.verify.passkeyDescription") ?? {t("auth.verify.passkeyDescription")}
"Please authenticate with your passkey to complete registration."}
</LargeCardText> </LargeCardText>
{passkeyResult.error ? ( {passkeyResult.error ? (
<p className="mt-3 text-authentication-errorText"> <p className="mt-3 text-authentication-errorText">
@ -203,8 +202,7 @@ export function VerifyPassphrase(props: VerifyPassphraseProps) {
onClick={() => authenticatePasskeyFn()} onClick={() => authenticatePasskeyFn()}
> >
<Icon icon={Icons.LOCK} className="mr-2" /> <Icon icon={Icons.LOCK} className="mr-2" />
{t("auth.verify.authenticatePasskey") ?? {t("auth.verify.authenticatePasskey")}
"Authenticate with Passkey"}
</Button> </Button>
</LargeCardButtons> </LargeCardButtons>
</form> </form>