i18n, cleanup layout

This commit is contained in:
peintnermax
2024-10-10 16:15:10 +02:00
parent b5e81d426f
commit b8be02a278
18 changed files with 148 additions and 269 deletions

View File

@@ -78,6 +78,60 @@
"submit": "Continue"
}
},
"passkey": {
"verify": {
"title": "Authenticate with a passkey",
"description": "Your device will ask for your fingerprint, face, or screen lock",
"usePassword": "Use password",
"submit": "Continue"
},
"set": {
"title": "Setup a passkey",
"description": "Your device will ask for your fingerprint, face, or screen lock",
"info": {
"description": "A passkey is an authentication method on a device like your fingerprint, Apple FaceID or similar. ",
"link": "Passwordless Authentication"
},
"skip": "Skip",
"submit": "Continue"
}
},
"u2f": {
"verify": {
"title": "Verify 2-Factor",
"description": "Verify your account with your device."
},
"set": {
"title": "Set up 2-Factor",
"description": "Set up a device as a second factor.",
"submit": "Continue"
}
},
"register": {
"title": "Register",
"description": "Create your ZITADEL account.",
"selectMethod": "Select the method you would like to authenticate",
"agreeTo": "To register you must agree to the terms and conditions",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"submit": "Continue",
"password": {
"title": "Set Password",
"description": "Set the password for your account",
"submit": "Continue"
}
},
"signedin": {
"title": "Welcome {user}!",
"description": "You are signed in."
},
"verify": {
"title": "Verify user",
"description": "Enter the Code provided in the verification email.",
"userIdMissing": "No userId provided!",
"resendCode": "Resend code",
"submit": "Continue"
},
"error": {
"unknownContext": "Could not get the context of the user. Make sure to enter the username first or provide a loginName as searchParam.",
"sessionExpired": "You need to have a valid session in order to continue."

View File

@@ -1,7 +1,5 @@
import "@/styles/globals.scss";
import { AddressBar } from "@/components/address-bar";
import { GlobalNav } from "@/components/global-nav";
import { LanguageSwitcher } from "@/components/language-switcher";
import { Theme } from "@/components/theme";
import { ThemeProvider } from "@/components/theme-provider";
@@ -23,16 +21,9 @@ export default async function RootLayout({
}: {
children: ReactNode;
}) {
const locale = (await getLocale()) ?? "en";
const locale = await getLocale();
const messages = await getMessages();
// later only shown with dev mode enabled
const showNav = process.env.DEBUG === "true";
let domain = process.env.ZITADEL_API_URL;
domain = domain ? domain.replace("https://", "") : "acme.com";
return (
<html
lang={locale}
@@ -44,35 +35,15 @@ export default async function RootLayout({
<ThemeProvider>
<NextIntlClientProvider messages={messages}>
<div
className={`h-screen overflow-y-scroll bg-background-light-600 dark:bg-background-dark-600 ${
showNav
? "bg-[url('/grid-light.svg')] dark:bg-[url('/grid-dark.svg')]"
: ""
}`}
className={`h-screen overflow-y-scroll bg-background-light-600 dark:bg-background-dark-600`}
>
{showNav ? (
<GlobalNav />
) : (
<div className="absolute bottom-0 right-0 flex flex-row p-4 items-center space-x-4">
<LanguageSwitcher />
<Theme />
</div>
)}
<div className="absolute bottom-0 right-0 flex flex-row p-4 items-center space-x-4">
<LanguageSwitcher />
<Theme />
</div>
<div
className={`${
showNav ? "lg:pl-72" : ""
} pb-4 flex flex-col justify-center h-full`}
>
<div className={`pb-4 flex flex-col justify-center h-full`}>
<div className="mx-auto max-w-[440px] space-y-8 pt-20 lg:py-8 w-full">
{showNav && (
<div className="rounded-lg bg-vc-border-gradient dark:bg-dark-vc-border-gradient p-px shadow-lg shadow-black/5 dark:shadow-black/20">
<div className="rounded-lg bg-background-light-400 dark:bg-background-dark-500">
<AddressBar domain={domain} />
</div>
</div>
)}
{children}
</div>
</div>

View File

@@ -1,45 +0,0 @@
import { demos } from "@/lib/demos";
import Link from "next/link";
export default function Page() {
return (
<div className="rounded-lg bg-vc-border-gradient dark:bg-dark-vc-border-gradient p-px shadow-lg shadow-black/5 dark:shadow-black/20 mb-10">
<div className="rounded-lg bg-background-light-400 dark:bg-background-dark-500 px-8 py-12">
<div className="space-y-8">
<h1 className="text-xl font-medium">Pages</h1>
<div className="space-y-10">
{demos.map((section) => {
return (
<div key={section.name} className="space-y-5">
<div className="text-xs font-semibold uppercase tracking-wider text-gray-500">
{section.name}
</div>
<div className="grid grid-cols-1 gap-5 lg:grid-cols-2">
{section.items.map((item) => {
return (
<Link
href={`/${item.slug}`}
key={item.name}
className="bg-background-light-400 dark:bg-background-dark-400 group block space-y-1.5 rounded-md px-5 py-3 hover:shadow-lg hover:dark:bg-white/10 border border-divider-light dark:border-divider-dark transition-all "
>
<div className="font-medium">{item.name}</div>
{item.description ? (
<div className="line-clamp-3 text-sm text-text-light-secondary-500 dark:text-text-dark-secondary-500">
{item.description}
</div>
) : null}
</Link>
);
})}
</div>
</div>
);
})}
</div>
</div>
</div>
</div>
);
}

View File

@@ -5,16 +5,16 @@ import { UserAvatar } from "@/components/user-avatar";
import { getSessionCookieById } from "@/lib/cookies";
import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings, getSession } from "@/lib/zitadel";
const title = "Authenticate with a passkey";
const description =
"Your device will ask for your fingerprint, face, or screen lock";
import { getLocale, getTranslations } from "next-intl/server";
export default async function Page({
searchParams,
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "passkey" });
const { loginName, altPassword, authRequestId, organization, sessionId } =
searchParams;
@@ -39,7 +39,7 @@ export default async function Page({
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>{title}</h1>
<h1>{t("verify.title")}</h1>
{sessionFactors && (
<UserAvatar
@@ -49,10 +49,10 @@ export default async function Page({
searchParams={searchParams}
></UserAvatar>
)}
<p className="ztdl-p mb-6 block">{description}</p>
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
{!(loginName || sessionId) && (
<Alert>Provide your active session as loginName param</Alert>
<Alert>{t("error:unknownContext")}</Alert>
)}
{(loginName || sessionId) && (

View File

@@ -4,12 +4,16 @@ import { RegisterPasskey } from "@/components/register-passkey";
import { UserAvatar } from "@/components/user-avatar";
import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings } from "@/lib/zitadel";
import { getLocale, getTranslations } from "next-intl/server";
export default async function Page({
searchParams,
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "passkey" });
const { loginName, prompt, organization, authRequestId } = searchParams;
const session = await loadMostRecentSession({
@@ -17,19 +21,12 @@ export default async function Page({
organization,
});
const title = !!prompt
? "Authenticate with a passkey"
: "Use your passkey to confirm it's really you";
const description = !!prompt
? "When set up, you will be able to authenticate without a password."
: "Your device will ask for your fingerprint, face, or screen lock";
const branding = await getBrandingSettings(organization);
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>{title}</h1>
<h1>{t("set.title")}</h1>
{session && (
<UserAvatar
@@ -39,28 +36,24 @@ export default async function Page({
searchParams={searchParams}
></UserAvatar>
)}
<p className="ztdl-p mb-6 block">{description}</p>
<p className="ztdl-p mb-6 block">{t("set.description")}</p>
<Alert type={AlertType.INFO}>
<span>
A passkey is an authentication method on a device like your
fingerprint, Apple FaceID or similar.
{t("set.info.description")}
<a
className="text-primary-light-500 dark:text-primary-dark-500 hover:text-primary-light-300 hover:dark:text-primary-dark-300"
target="_blank"
href="https://zitadel.com/docs/guides/manage/user/reg-create-user#with-passwordless"
>
Passwordless Authentication
{t("set.info.link")}
</a>
</span>
</Alert>
{!session && (
<div className="py-4">
<Alert>
Could not get the context of the user. Make sure to enter the
username first or provide a loginName as searchParam.
</Alert>
<Alert>{t("error:unknownContext")}</Alert>
</div>
)}

View File

@@ -6,12 +6,16 @@ import {
getLegalAndSupportSettings,
getPasswordComplexitySettings,
} from "@/lib/zitadel";
import { getLocale, getTranslations } from "next-intl/server";
export default async function Page({
searchParams,
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "register" });
const { firstname, lastname, email, organization, authRequestId } =
searchParams;
@@ -30,8 +34,8 @@ export default async function Page({
return setPassword ? (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Set Password</h1>
<p className="ztdl-p">Set the password for your account</p>
<h1>{t("password.title")}</h1>
<p className="ztdl-p">{t("description")}</p>
{legal && passwordComplexitySettings && (
<SetPasswordForm
@@ -48,8 +52,8 @@ export default async function Page({
) : (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Register</h1>
<p className="ztdl-p">Create your ZITADEL account.</p>
<h1>{t("title")}</h1>
<p className="ztdl-p">{t("description")}</p>
{legal && passwordComplexitySettings && (
<RegisterFormWithoutPassword

View File

@@ -8,6 +8,7 @@ import {
CreateCallbackRequestSchema,
SessionSchema,
} from "@zitadel/proto/zitadel/oidc/v2/oidc_service_pb";
import { getLocale, getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
async function loadSession(loginName: string, authRequestId?: string) {
@@ -39,6 +40,9 @@ async function loadSession(loginName: string, authRequestId?: string) {
}
export default async function Page({ searchParams }: { searchParams: any }) {
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "signedin" });
const { loginName, authRequestId, organization } = searchParams;
const sessionFactors = await loadSession(loginName, authRequestId);
@@ -47,8 +51,10 @@ export default async function Page({ searchParams }: { searchParams: any }) {
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>{`Welcome ${sessionFactors?.factors?.user?.displayName}`}</h1>
<p className="ztdl-p mb-6 block">You are signed in.</p>
<h1>
{t("title", { user: sessionFactors?.factors?.user?.displayName })}
</h1>
<p className="ztdl-p mb-6 block">{t("description")}</p>
<UserAvatar
loginName={loginName ?? sessionFactors?.factors?.user?.loginName}

View File

@@ -5,12 +5,16 @@ import { UserAvatar } from "@/components/user-avatar";
import { getSessionCookieById } from "@/lib/cookies";
import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings, getSession } from "@/lib/zitadel";
import { getLocale, getTranslations } from "next-intl/server";
export default async function Page({
searchParams,
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "u2f" });
const { loginName, authRequestId, sessionId, organization } = searchParams;
const branding = await getBrandingSettings(organization);
@@ -34,7 +38,7 @@ export default async function Page({
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Verify 2-Factor</h1>
<h1>{t("verify.title")}</h1>
{sessionFactors && (
<UserAvatar
@@ -44,12 +48,10 @@ export default async function Page({
searchParams={searchParams}
></UserAvatar>
)}
<p className="ztdl-p mb-6 block">
Verify your account with your device.
</p>
<p className="ztdl-p mb-6 block">{t("verify.description")}</p>
{!(loginName || sessionId) && (
<Alert>Provide your active session as loginName param</Alert>
<Alert>{t("error:unknownContext")}</Alert>
)}
{(loginName || sessionId) && (

View File

@@ -4,12 +4,16 @@ import { RegisterU2f } from "@/components/register-u2f";
import { UserAvatar } from "@/components/user-avatar";
import { loadMostRecentSession } from "@/lib/session";
import { getBrandingSettings } from "@/lib/zitadel";
import { getLocale, getTranslations } from "next-intl/server";
export default async function Page({
searchParams,
}: {
searchParams: Record<string | number | symbol, string | undefined>;
}) {
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "u2f" });
const { loginName, organization, authRequestId, checkAfter } = searchParams;
const sessionFactors = await loadMostRecentSession({
@@ -17,16 +21,12 @@ export default async function Page({
organization,
});
const title = "Use your passkey to confirm it's really you";
const description =
"Your device will ask for your fingerprint, face, or screen lock";
const branding = await getBrandingSettings(organization);
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>{title}</h1>
<h1>{t("set.title")}</h1>
{sessionFactors && (
<UserAvatar
@@ -36,14 +36,11 @@ export default async function Page({
searchParams={searchParams}
></UserAvatar>
)}
<p className="ztdl-p mb-6 block">{description}</p>
<p className="ztdl-p mb-6 block">{t("set.description")}</p>
{!sessionFactors && (
<div className="py-4">
<Alert>
Could not get the context of the user. Make sure to enter the
username first or provide a loginName as searchParam.
</Alert>
<Alert>{t("error:unknownContext")}</Alert>
</div>
)}

View File

@@ -3,8 +3,12 @@ import { DynamicTheme } from "@/components/dynamic-theme";
import { VerifyEmailForm } from "@/components/verify-email-form";
import { getBrandingSettings, getLoginSettings } from "@/lib/zitadel";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import { getLocale, getTranslations } from "next-intl/server";
export default async function Page({ searchParams }: { searchParams: any }) {
const locale = getLocale();
const t = await getTranslations({ locale, namespace: "verify" });
const {
userId,
loginName,
@@ -22,17 +26,12 @@ export default async function Page({ searchParams }: { searchParams: any }) {
return (
<DynamicTheme branding={branding}>
<div className="flex flex-col items-center space-y-4">
<h1>Verify user</h1>
<p className="ztdl-p mb-6 block">
Enter the Code provided in the verification email.
</p>
<h1>{t("title")}</h1>
<p className="ztdl-p mb-6 block">{t("description")}</p>
{!userId && (
<div className="py-4">
<Alert>
Could not get the context of the user. Make sure to provide a
userId as searchParam.
</Alert>
<Alert>{t("error:unknownContext")}</Alert>
</div>
)}
@@ -50,7 +49,7 @@ export default async function Page({ searchParams }: { searchParams: any }) {
) : (
<div className="w-full flex flex-row items-center justify-center border border-yellow-600/40 dark:border-yellow-500/20 bg-yellow-200/30 text-yellow-600 dark:bg-yellow-700/20 dark:text-yellow-200 rounded-md py-2 scroll-px-40">
<ExclamationTriangleIcon className="h-5 w-5 mr-2" />
<span className="text-center text-sm">No userId provided!</span>
<span className="text-center text-sm">{t("userIdMissing")}</span>
</div>
)}
</div>

View File

@@ -1,120 +0,0 @@
"use client";
import { ZitadelLogo } from "@/components/zitadel-logo";
import { demos, type Item } from "@/lib/demos";
import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/solid";
import { clsx } from "clsx";
import Link from "next/link";
import { usePathname, useSelectedLayoutSegment } from "next/navigation";
import { useState } from "react";
import { Theme } from "./theme";
export function GlobalNav() {
const [isOpen, setIsOpen] = useState(false);
const close = () => setIsOpen(false);
return (
<div className="fixed top-0 z-10 flex w-full flex-col border-b border-divider-light dark:border-divider-dark bg-white/80 dark:bg-black/80 lg:bottom-0 lg:z-auto lg:w-72 lg:border-r">
<div className="flex h-14 items-center py-4 px-4 lg:h-auto">
<Link
href="/"
className="group flex w-full items-center space-x-2.5"
onClick={close}
>
<div className="">
<ZitadelLogo />
</div>
<h2 className="text-blue-500 font-bold uppercase transform translate-y-2 text-sm">
Login
</h2>
</Link>
</div>
<div className="absolute right-0 top-0 flex flex-row items-center lg:hidden">
<Theme />
<button
type="button"
className="group flex h-14 items-center space-x-2 px-4"
onClick={() => setIsOpen(!isOpen)}
>
<div className="font-medium text-text-light-secondary-500 group-hover:text-text-light-500 dark:text-text-dark-secondary-500 dark:group-hover:text-text-dark-500">
Menu
</div>
{isOpen ? (
<XMarkIcon className="block w-6 " />
) : (
<Bars3Icon className="block w-6 " />
)}
</button>
</div>
<div
className={clsx(
"overflow-y-auto lg:static lg:flex lg:flex-col justify-between h-full",
{
"fixed inset-x-0 bottom-0 top-14 mt-px bg-white/80 dark:bg-black/80 backdrop-blur-lg":
isOpen,
hidden: !isOpen,
},
)}
>
<nav
className={`space-y-6 px-4 py-5 ${
isOpen ? "text-center lg:text-left" : ""
}`}
>
{demos.map((section) => {
return (
<div key={section.name}>
<div className="mb-2 px-3 text-[11px] font-bold uppercase tracking-wider text-black/40 dark:text-white/40">
<div>{section.name}</div>
</div>
<div className="space-y-1">
{section.items.map((item) => (
<GlobalNavItem key={item.slug} item={item} close={close} />
))}
</div>
</div>
);
})}
</nav>
<div className="flex flex-row p-4">
<Theme />
</div>
</div>
</div>
);
}
function GlobalNavItem({
item,
close,
}: {
item: Item;
close: () => false | void;
}) {
const segment = useSelectedLayoutSegment();
const pathname = usePathname();
const isActive = `/${item.slug}` === pathname;
return (
<Link
onClick={close}
href={`/${item.slug}`}
className={clsx(
"block rounded-md px-3 py-2 text-[15px] font-medium text-text-light-500 dark:text-text-dark-500 opacity-60 dark:opacity-60",
{
"hover:opacity-100 hover:dark:opacity-100": !isActive,
"text-text-light-500 dark:text-text-dark-500 opacity-100 dark:opacity-100 font-semibold":
isActive,
},
)}
>
{item.name}
</Link>
);
}

View File

@@ -8,6 +8,7 @@ import {
UserVerificationRequirement,
} from "@zitadel/proto/zitadel/session/v2/challenge_pb";
import { Checks } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { Alert } from "./alert";
@@ -33,6 +34,8 @@ export function LoginPasskey({
organization,
login = true,
}: Props) {
const t = useTranslations("passkey");
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
@@ -232,7 +235,7 @@ export function LoginPasskey({
);
}}
>
use password
{t("verify.usePassword")}
</Button>
) : (
<BackButton />
@@ -267,7 +270,7 @@ export function LoginPasskey({
}}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
continue
{t("verify.submit")}
</Button>
</div>
</div>

View File

@@ -1,5 +1,6 @@
"use client";
import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useState } from "react";
import { Checkbox } from "./checkbox";
@@ -15,6 +16,7 @@ type AcceptanceState = {
};
export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
const t = useTranslations("register");
const [acceptanceState, setAcceptanceState] = useState<AcceptanceState>({
tosAccepted: false,
privacyPolicyAccepted: false,
@@ -23,7 +25,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
return (
<>
<p className="flex flex-row items-center text-text-light-secondary-500 dark:text-text-dark-secondary-500 mt-4 text-sm">
To register you must agree to the terms and conditions
{t("agreeTo")}
{legal?.helpLink && (
<span>
<Link href={legal.helpLink} target="_blank">
@@ -62,9 +64,8 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
<div className="mr-4 w-[28rem]">
<p className="text-sm text-text-light-500 dark:text-text-dark-500">
Agree&nbsp;
<Link href={legal.tosLink} className="underline" target="_blank">
Terms of Service
{t("termsOfService")}
</Link>
</p>
</div>
@@ -87,13 +88,12 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
<div className="mr-4 w-[28rem]">
<p className="text-sm text-text-light-500 dark:text-text-dark-500">
Agree&nbsp;
<Link
href={legal.privacyPolicyLink}
className="underline"
target="_blank"
>
Privacy Policy
{t("privacyPolicy")}
</Link>
</p>
</div>

View File

@@ -2,6 +2,7 @@
import { registerUser, RegisterUserResponse } from "@/lib/server/register";
import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { FieldValues, useForm } from "react-hook-form";
@@ -41,6 +42,8 @@ export function RegisterFormWithoutPassword({
organization,
authRequestId,
}: Props) {
const t = useTranslations("register");
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
defaultValues: {
@@ -160,7 +163,7 @@ export function RegisterFormWithoutPassword({
)}
<p className="mt-4 ztdl-p mb-6 block text-text-light-secondary-500 dark:text-text-dark-secondary-500">
Select the method you would like to authenticate
{t("selectMethod")}
</p>
<div className="pb-4">
@@ -187,7 +190,7 @@ export function RegisterFormWithoutPassword({
)}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
continue
{t("submit")}
</Button>
</div>
</form>

View File

@@ -2,6 +2,7 @@
import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
import { registerPasskeyLink, verifyPasskey } from "@/lib/server/passkeys";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
@@ -25,6 +26,8 @@ export function RegisterPasskey({
organization,
authRequestId,
}: Props) {
const t = useTranslations("passkey");
const { handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
});
@@ -173,7 +176,7 @@ export function RegisterPasskey({
continueAndLogin();
}}
>
skip
{t("set.skip")}
</Button>
) : (
<BackButton />
@@ -188,7 +191,7 @@ export function RegisterPasskey({
onClick={handleSubmit(submitRegisterAndContinue)}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
continue
{t("set.submit")}
</Button>
</div>
</form>

View File

@@ -3,6 +3,7 @@
import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
import { addU2F, verifyU2F } from "@/lib/server/u2f";
import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { Alert } from "./alert";
@@ -25,6 +26,8 @@ export function RegisterU2f({
authRequestId,
checkAfter,
}: Props) {
const t = useTranslations("u2f");
const [error, setError] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
@@ -198,7 +201,7 @@ export function RegisterU2f({
onClick={submitRegisterAndContinue}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
continue
{t("set.submit")}
</Button>
</div>
</form>

View File

@@ -8,6 +8,7 @@ import {
} from "@/helpers/validators";
import { registerUser, RegisterUserResponse } from "@/lib/server/register";
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { FieldValues, useForm } from "react-hook-form";
@@ -42,6 +43,8 @@ export function SetPasswordForm({
organization,
authRequestId,
}: Props) {
const t = useTranslations("register");
const { register, handleSubmit, watch, formState } = useForm<Inputs>({
mode: "onBlur",
defaultValues: {
@@ -186,7 +189,7 @@ export function SetPasswordForm({
onClick={handleSubmit(submitRegister)}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
continue
{t("password.submit")}
</Button>
</div>
</form>

View File

@@ -3,6 +3,7 @@
import { Alert } from "@/components/alert";
import { resendVerifyEmail, verifyUserByEmail } from "@/lib/server/email";
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
@@ -35,6 +36,8 @@ export function VerifyEmailForm({
sessionId,
loginSettings,
}: Props) {
const t = useTranslations("verify");
const { register, handleSubmit, formState } = useForm<Inputs>({
mode: "onBlur",
defaultValues: {
@@ -123,7 +126,7 @@ export function VerifyEmailForm({
onClick={() => resendCode()}
variant={ButtonVariants.Secondary}
>
resend code
{t("resendCode")}
</Button>
<span className="flex-grow"></span>
<Button
@@ -134,7 +137,7 @@ export function VerifyEmailForm({
onClick={handleSubmit(submitCodeAndContinue)}
>
{loading && <Spinner className="h-5 w-5 mr-2" />}
continue
{t("submit")}
</Button>
</div>
</form>