mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 10:36:44 +00:00
i18n, cleanup layout
This commit is contained in:
@@ -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."
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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) && (
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) && (
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
<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
|
||||
<Link
|
||||
href={legal.privacyPolicyLink}
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
Privacy Policy
|
||||
{t("privacyPolicy")}
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user