mirror of
https://github.com/zitadel/zitadel.git
synced 2026-08-19 01:14:48 -05:00
fix(login): prettier setup (#11798)
# Which Problems Are Solved Invalid formatting in the login codebase due to invalid prettier configuration # How the Problems Are Solved Fxed .prettierignore wildcards for login source files # Additional Changes Removed .prettierrc file because there is already a prettier.config.mjs file # Additional Context
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
*
|
||||
!constants
|
||||
!constants/**
|
||||
!src
|
||||
!src/**
|
||||
!locales
|
||||
!scripts/healthcheck.mjs
|
||||
!scripts/server.mjs
|
||||
!locales/**
|
||||
!scripts
|
||||
!scripts/**
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"printWidth": 125,
|
||||
"trailingComma": "all",
|
||||
"plugins": ["prettier-plugin-organize-imports"],
|
||||
"filepath": ""
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
printWidth: 80,
|
||||
printWidth: 125,
|
||||
tabWidth: 2,
|
||||
useTabs: false,
|
||||
semi: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as https from "node:https";
|
||||
import * as http from "node:http";
|
||||
import * as https from "node:https";
|
||||
|
||||
const scheme = process.env.ZITADEL_TLS_ENABLED === "true" ? "https" : "http";
|
||||
const port = process.env.PORT || "3000";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { readFileSync, accessSync, constants } from "node:fs";
|
||||
import { accessSync, constants, readFileSync } from "node:fs";
|
||||
import { createServer } from "node:https";
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
|
||||
@@ -21,8 +21,7 @@ async function loadSessions({ serviceConfig }: { serviceConfig: ServiceConfig })
|
||||
const cookieIds = await getAllSessionCookieIds();
|
||||
|
||||
if (cookieIds && cookieIds.length) {
|
||||
const response = await listSessions({ serviceConfig, ids: cookieIds.filter((id) => !!id) as string[],
|
||||
});
|
||||
const response = await listSessions({ serviceConfig, ids: cookieIds.filter((id) => !!id) as string[] });
|
||||
return response?.sessions ?? [];
|
||||
} else {
|
||||
console.info("No session cookie found.");
|
||||
@@ -41,7 +40,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig, });
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig });
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
@@ -49,8 +48,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
|
||||
let sessions = await loadSessions({ serviceConfig });
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
|
||||
@@ -24,8 +24,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
const _headers = await headers();
|
||||
const { serviceConfig } = getServiceConfig(_headers);
|
||||
|
||||
const { deviceAuthorizationRequest } = await getDeviceAuthorizationRequest({ serviceConfig, userCode,
|
||||
});
|
||||
const { deviceAuthorizationRequest } = await getDeviceAuthorizationRequest({ serviceConfig, userCode });
|
||||
|
||||
if (!deviceAuthorizationRequest) {
|
||||
return (
|
||||
@@ -37,14 +36,13 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig, });
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig });
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
|
||||
@@ -24,14 +24,13 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig, });
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig });
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
||||
@@ -24,14 +24,13 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig, });
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig });
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
@@ -51,7 +50,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
|
||||
{postErrorRedirectUrl && (
|
||||
<Link href={postErrorRedirectUrl}>
|
||||
<Button className="bg-primary-light-500 hover:bg-primary-light-400 dark:bg-primary-dark-500 dark:hover:bg-primary-dark-400 w-full rounded-md px-4 py-3 text-center transition-all">
|
||||
<Button className="w-full rounded-md bg-primary-light-500 px-4 py-3 text-center transition-all hover:bg-primary-light-400 dark:bg-primary-dark-500 dark:hover:bg-primary-dark-400">
|
||||
<Translated i18nKey="accountNotFound.backToLogin" namespace="idp" />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -20,11 +20,9 @@ export default async function Page(props: {
|
||||
const _headers = await headers();
|
||||
const { serviceConfig } = getServiceConfig(_headers);
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization });
|
||||
|
||||
const loginSettings = await getLoginSettings({ serviceConfig, organization,
|
||||
});
|
||||
const loginSettings = await getLoginSettings({ serviceConfig, organization });
|
||||
|
||||
let authMethods: AuthenticationMethodType[] = [];
|
||||
let user: User | undefined = undefined;
|
||||
@@ -39,8 +37,7 @@ export default async function Page(props: {
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
const userResponse = await getUserByID({ serviceConfig, userId,
|
||||
});
|
||||
const userResponse = await getUserByID({ serviceConfig, userId });
|
||||
if (userResponse) {
|
||||
user = userResponse.user;
|
||||
if (user?.type.case === "human") {
|
||||
@@ -52,8 +49,7 @@ export default async function Page(props: {
|
||||
}
|
||||
}
|
||||
|
||||
const authMethodsResponse = await listAuthenticationMethodTypes({ serviceConfig, userId,
|
||||
});
|
||||
const authMethodsResponse = await listAuthenticationMethodTypes({ serviceConfig, userId });
|
||||
if (authMethodsResponse.authMethodTypes) {
|
||||
authMethods = authMethodsResponse.authMethodTypes;
|
||||
}
|
||||
|
||||
@@ -24,14 +24,13 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig, });
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig });
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
@@ -51,7 +50,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
|
||||
{postErrorRedirectUrl && (
|
||||
<Link href={postErrorRedirectUrl}>
|
||||
<Button className="bg-primary-light-500 hover:bg-primary-light-400 dark:bg-primary-dark-500 dark:hover:bg-primary-dark-400 w-full rounded-md px-4 py-3 text-center transition-all">
|
||||
<Button className="w-full rounded-md bg-primary-light-500 px-4 py-3 text-center transition-all hover:bg-primary-light-400 dark:bg-primary-dark-500 dark:hover:bg-primary-dark-400">
|
||||
<Translated i18nKey="registrationFailed.backToLogin" namespace="idp" />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -11,15 +11,7 @@ export default async function Page(props: {
|
||||
params: Promise<{ provider: string }>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const {
|
||||
idpId,
|
||||
organization,
|
||||
link,
|
||||
requestId,
|
||||
postErrorRedirectUrl,
|
||||
linkToSessionId,
|
||||
linkFingerprint,
|
||||
} = searchParams;
|
||||
const { idpId, organization, link, requestId, postErrorRedirectUrl, linkToSessionId, linkFingerprint } = searchParams;
|
||||
|
||||
if (!idpId) {
|
||||
throw new Error("No idpId provided in searchParams");
|
||||
@@ -30,14 +22,13 @@ export default async function Page(props: {
|
||||
|
||||
let defaultOrganization;
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig, });
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig });
|
||||
if (org) {
|
||||
defaultOrganization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization: organization ?? defaultOrganization });
|
||||
|
||||
// return login failed if no linking or creation is allowed and no user was found
|
||||
return (
|
||||
|
||||
@@ -21,13 +21,11 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
const _headers = await headers();
|
||||
const { serviceConfig } = getServiceConfig(_headers);
|
||||
|
||||
const identityProviders = await getActiveIdentityProviders({ serviceConfig, orgId: organization,
|
||||
}).then((resp) => {
|
||||
const identityProviders = await getActiveIdentityProviders({ serviceConfig, orgId: organization }).then((resp) => {
|
||||
return resp.identityProviders;
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
||||
@@ -5,16 +5,16 @@ import { LanguageProvider } from "@/components/language-provider";
|
||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
import { Skeleton } from "@/components/skeleton";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import { Lato } from "next/font/google";
|
||||
import React, { Suspense } from "react";
|
||||
import ThemeSwitch from "@/components/theme-switch";
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import { LANGS, getLanguage } from "@/lib/i18n";
|
||||
import { getServiceConfig } from "@/lib/service-url";
|
||||
import { getAllowedLanguages } from "@/lib/zitadel";
|
||||
import { LANGS, getLanguage } from "@/lib/i18n";
|
||||
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||
import type { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { Lato } from "next/font/google";
|
||||
import { headers } from "next/headers";
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
const lato = Lato({
|
||||
weight: ["400", "700", "900"],
|
||||
@@ -70,7 +70,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
>
|
||||
<div className="relative mx-auto w-full max-w-[1100px] py-8">
|
||||
<div>{children}</div>
|
||||
<div className="flex flex-row items-center justify-end space-x-4 py-4 px-4 md:px-8 max-w-[440px] mx-auto md:max-w-full">
|
||||
<div className="mx-auto flex max-w-[440px] flex-row items-center justify-end space-x-4 px-4 py-4 md:max-w-full md:px-8">
|
||||
<LanguageSwitcher languages={languages} />
|
||||
<ThemeSwitch />
|
||||
</div>
|
||||
|
||||
@@ -57,16 +57,18 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
{loginSettings?.allowLocalAuthentication && <UsernameForm
|
||||
loginName={loginName}
|
||||
requestId={requestId}
|
||||
organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
|
||||
defaultOrganization={defaultOrganization}
|
||||
loginSettings={loginSettings}
|
||||
suffix={suffix}
|
||||
submit={submit}
|
||||
allowRegister={!!loginSettings?.allowRegister}
|
||||
></UsernameForm>}
|
||||
{loginSettings?.allowLocalAuthentication && (
|
||||
<UsernameForm
|
||||
loginName={loginName}
|
||||
requestId={requestId}
|
||||
organization={organization} // stick to "organization" as we still want to do user discovery based on the searchParams not the default organization, later the organization is determined by the found user
|
||||
defaultOrganization={defaultOrganization}
|
||||
loginSettings={loginSettings}
|
||||
suffix={suffix}
|
||||
submit={submit}
|
||||
allowRegister={!!loginSettings?.allowRegister}
|
||||
></UsernameForm>
|
||||
)}
|
||||
|
||||
{loginSettings?.allowExternalIdp && !!identityProviders?.length && (
|
||||
<div className="w-full pb-4 pt-6">
|
||||
|
||||
@@ -12,8 +12,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
|
||||
const { organization } = searchParams;
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
||||
@@ -26,12 +26,12 @@ export default async function Page(props: {
|
||||
const _headers = await headers();
|
||||
const { serviceConfig } = getServiceConfig(_headers);
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization,
|
||||
});
|
||||
const loginSettings = await getLoginSettings({ serviceConfig, organization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization });
|
||||
const loginSettings = await getLoginSettings({ serviceConfig, organization });
|
||||
|
||||
const session = await loadMostRecentSession({ serviceConfig, sessionParams: {
|
||||
const session = await loadMostRecentSession({
|
||||
serviceConfig,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
@@ -40,8 +40,7 @@ export default async function Page(props: {
|
||||
let totpResponse: RegisterTOTPResponse | undefined, error: Error | undefined;
|
||||
if (session && session.factors?.user?.id) {
|
||||
if (method === "time-based") {
|
||||
await registerTOTP({ serviceConfig, userId: session.factors.user.id,
|
||||
})
|
||||
await registerTOTP({ serviceConfig, userId: session.factors.user.id })
|
||||
.then((resp) => {
|
||||
if (resp) {
|
||||
totpResponse = resp;
|
||||
@@ -51,14 +50,12 @@ export default async function Page(props: {
|
||||
error = err;
|
||||
});
|
||||
} else if (method === "sms") {
|
||||
await addOTPSMS({ serviceConfig, userId: session.factors.user.id,
|
||||
}).catch((_error) => {
|
||||
await addOTPSMS({ serviceConfig, userId: session.factors.user.id }).catch((_error) => {
|
||||
// TODO: Throw this error?
|
||||
new Error("Could not add OTP via SMS");
|
||||
});
|
||||
} else if (method === "email") {
|
||||
await addOTPEmail({ serviceConfig, userId: session.factors.user.id,
|
||||
}).catch((_error) => {
|
||||
await addOTPEmail({ serviceConfig, userId: session.factors.user.id }).catch((_error) => {
|
||||
// TODO: Throw this error?
|
||||
new Error("Could not add OTP via Email");
|
||||
});
|
||||
|
||||
@@ -28,21 +28,21 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
// also allow no session to be found for userId-based flows
|
||||
let session: Session | undefined;
|
||||
if (loginName) {
|
||||
session = await loadMostRecentSession({ serviceConfig, sessionParams: {
|
||||
session = await loadMostRecentSession({
|
||||
serviceConfig,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization });
|
||||
|
||||
let user: User | undefined;
|
||||
let displayName: string | undefined;
|
||||
if (userId) {
|
||||
const userResponse = await getUserByID({ serviceConfig, userId,
|
||||
});
|
||||
const userResponse = await getUserByID({ serviceConfig, userId });
|
||||
user = userResponse.user;
|
||||
|
||||
if (user?.type.case === "human") {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { DynamicTheme } from "@/components/dynamic-theme";
|
||||
import { SetPasswordForm } from "@/components/set-password-form";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { UserAvatar } from "@/components/user-avatar";
|
||||
import { getServiceConfig } from "@/lib/service-url";
|
||||
import { UNKNOWN_USER_ID } from "@/lib/constants";
|
||||
import { getServiceConfig } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import {
|
||||
getBrandingSettings,
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
getUserByID,
|
||||
searchUsers,
|
||||
} from "@/lib/zitadel";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const t = await getTranslations("password");
|
||||
|
||||
@@ -31,25 +31,20 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
const { serviceConfig } = getServiceConfig(_headers);
|
||||
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig, });
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig });
|
||||
if (org) {
|
||||
organization = org.id;
|
||||
}
|
||||
}
|
||||
|
||||
const legal = await getLegalAndSupportSettings({ serviceConfig, organization,
|
||||
});
|
||||
const passwordComplexitySettings = await getPasswordComplexitySettings({ serviceConfig, organization,
|
||||
});
|
||||
const legal = await getLegalAndSupportSettings({ serviceConfig, organization });
|
||||
const passwordComplexitySettings = await getPasswordComplexitySettings({ serviceConfig, organization });
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization });
|
||||
|
||||
const loginSettings = await getLoginSettings({ serviceConfig, organization,
|
||||
});
|
||||
const loginSettings = await getLoginSettings({ serviceConfig, organization });
|
||||
|
||||
const identityProviders = await getActiveIdentityProviders({ serviceConfig, orgId: organization,
|
||||
}).then((resp) => {
|
||||
const identityProviders = await getActiveIdentityProviders({ serviceConfig, orgId: organization }).then((resp) => {
|
||||
return resp.identityProviders.filter((idp) => {
|
||||
return idp.options?.isAutoCreation || idp.options?.isCreationAllowed; // check if IDP allows to create account automatically or manual creation is allowed
|
||||
});
|
||||
@@ -105,21 +100,18 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{legal &&
|
||||
passwordComplexitySettings &&
|
||||
organization &&
|
||||
loginSettings.allowLocalAuthentication && (
|
||||
<RegisterForm
|
||||
idpCount={!loginSettings?.allowExternalIdp ? 0 : identityProviders.length}
|
||||
legal={legal}
|
||||
organization={organization}
|
||||
firstname={firstname}
|
||||
lastname={lastname}
|
||||
email={email}
|
||||
requestId={requestId}
|
||||
loginSettings={loginSettings}
|
||||
></RegisterForm>
|
||||
)}
|
||||
{legal && passwordComplexitySettings && organization && loginSettings.allowLocalAuthentication && (
|
||||
<RegisterForm
|
||||
idpCount={!loginSettings?.allowExternalIdp ? 0 : identityProviders.length}
|
||||
legal={legal}
|
||||
organization={organization}
|
||||
firstname={firstname}
|
||||
lastname={lastname}
|
||||
email={email}
|
||||
requestId={requestId}
|
||||
loginSettings={loginSettings}
|
||||
></RegisterForm>
|
||||
)}
|
||||
|
||||
{loginSettings?.allowExternalIdp && !!identityProviders.length && (
|
||||
<>
|
||||
|
||||
@@ -21,7 +21,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
const { serviceConfig } = getServiceConfig(_headers);
|
||||
|
||||
if (!organization) {
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig, });
|
||||
const org: Organization | null = await getDefaultOrg({ serviceConfig });
|
||||
if (org) {
|
||||
organization = org.id;
|
||||
}
|
||||
@@ -29,16 +29,12 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
|
||||
const missingData = !firstname || !lastname || !email || !organization;
|
||||
|
||||
const legal = await getLegalAndSupportSettings({ serviceConfig, organization,
|
||||
});
|
||||
const passwordComplexitySettings = await getPasswordComplexitySettings({ serviceConfig, organization,
|
||||
});
|
||||
const legal = await getLegalAndSupportSettings({ serviceConfig, organization });
|
||||
const passwordComplexitySettings = await getPasswordComplexitySettings({ serviceConfig, organization });
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization });
|
||||
|
||||
const loginSettings = await getLoginSettings({ serviceConfig, organization,
|
||||
});
|
||||
const loginSettings = await getLoginSettings({ serviceConfig, organization });
|
||||
|
||||
return missingData ? (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
||||
@@ -85,9 +85,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
}
|
||||
|
||||
const redirectUri = await resolveRedirectUri(
|
||||
requestId && sessionId
|
||||
? { sessionId, requestId }
|
||||
: { loginName: loginName ?? sessionFactors?.factors?.user?.loginName },
|
||||
requestId && sessionId ? { sessionId, requestId } : { loginName: loginName ?? sessionFactors?.factors?.user?.loginName },
|
||||
loginSettings?.defaultRedirectUri,
|
||||
);
|
||||
|
||||
|
||||
@@ -23,14 +23,15 @@ export default async function Page(props: { searchParams: Promise<Record<string
|
||||
const _headers = await headers();
|
||||
const { serviceConfig } = getServiceConfig(_headers);
|
||||
|
||||
const sessionFactors = await loadMostRecentSession({ serviceConfig, sessionParams: {
|
||||
const sessionFactors = await loadMostRecentSession({
|
||||
serviceConfig,
|
||||
sessionParams: {
|
||||
loginName,
|
||||
organization,
|
||||
},
|
||||
});
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization });
|
||||
|
||||
return (
|
||||
<DynamicTheme branding={branding}>
|
||||
|
||||
@@ -9,8 +9,8 @@ import { sendEmailCode, sendInviteEmailCode } from "@/lib/server/verify";
|
||||
import { getServiceConfig } from "@/lib/service-url";
|
||||
import { loadMostRecentSession } from "@/lib/session";
|
||||
import { getBrandingSettings, getLoginSettings, getUserByID, searchUsers } from "@/lib/zitadel";
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { HumanUser, User } from "@zitadel/proto/zitadel/user/v2/user_pb";
|
||||
import { Metadata } from "next";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
@@ -15,13 +15,13 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
|
||||
const { loginName, organization, userId } = searchParams;
|
||||
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization,
|
||||
});
|
||||
const branding = await getBrandingSettings({ serviceConfig, organization });
|
||||
|
||||
const sessionFactors = await loadMostRecentSession({ serviceConfig, sessionParams: { loginName, organization },
|
||||
}).catch((error) => {
|
||||
console.warn("Error loading session:", error);
|
||||
});
|
||||
const sessionFactors = await loadMostRecentSession({ serviceConfig, sessionParams: { loginName, organization } }).catch(
|
||||
(error) => {
|
||||
console.warn("Error loading session:", error);
|
||||
},
|
||||
);
|
||||
|
||||
const id = userId ?? sessionFactors?.factors?.user?.id;
|
||||
|
||||
@@ -29,8 +29,7 @@ export default async function Page(props: { searchParams: Promise<any> }) {
|
||||
throw Error("Failed to get user id");
|
||||
}
|
||||
|
||||
const userResponse = await getUserByID({ serviceConfig, userId: id,
|
||||
});
|
||||
const userResponse = await getUserByID({ serviceConfig, userId: id });
|
||||
|
||||
let user: User | undefined;
|
||||
let human: HumanUser | undefined;
|
||||
|
||||
@@ -5,13 +5,7 @@ import { Button } from "@/components/button";
|
||||
import { ThemeWrapper } from "@/components/theme-wrapper";
|
||||
import { Translated } from "@/components/translated";
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
||||
return (
|
||||
// global-error must include html and body tags
|
||||
<html>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { isRSCRequest, validateAuthRequest } from "@/lib/auth-utils";
|
||||
import { getAllSessions } from "@/lib/cookies";
|
||||
import { FlowInitiationParams, handleOIDCFlowInitiation, handleSAMLFlowInitiation } from "@/lib/server/flow-initiation";
|
||||
import { getServiceConfig } from "@/lib/service-url";
|
||||
import { validateAuthRequest, isRSCRequest } from "@/lib/auth-utils";
|
||||
import { handleOIDCFlowInitiation, handleSAMLFlowInitiation, FlowInitiationParams } from "@/lib/server/flow-initiation";
|
||||
import { listSessions, ServiceConfig } from "@/lib/zitadel";
|
||||
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { trace, metrics, SpanStatusCode } from "@opentelemetry/api";
|
||||
import { createLogger } from "@/lib/logger";
|
||||
import {
|
||||
recordAuthAttempt,
|
||||
recordAuthSuccess,
|
||||
recordAuthFailure,
|
||||
recordRequestStart,
|
||||
recordAuthSuccess,
|
||||
recordRequestEnd,
|
||||
recordRequestStart,
|
||||
recordSessionCreationDuration,
|
||||
} from "@/lib/metrics";
|
||||
import { metrics, SpanStatusCode, trace } from "@opentelemetry/api";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
const logger = createLogger("otel-test");
|
||||
|
||||
@@ -9,20 +9,14 @@ export async function GET() {
|
||||
const _headers = await headers();
|
||||
const { serviceConfig } = getServiceConfig(_headers);
|
||||
|
||||
const settingsService: Client<typeof SettingsService> =
|
||||
await createServiceForHost(SettingsService, serviceConfig);
|
||||
const settingsService: Client<typeof SettingsService> = await createServiceForHost(SettingsService, serviceConfig);
|
||||
|
||||
const settings = await settingsService
|
||||
.getSecuritySettings({})
|
||||
.then((resp) => (resp.settings ? resp.settings : undefined));
|
||||
const settings = await settingsService.getSecuritySettings({}).then((resp) => (resp.settings ? resp.settings : undefined));
|
||||
|
||||
const response = NextResponse.json({ settings }, { status: 200 });
|
||||
|
||||
// Add Cache-Control header to cache the response for up to 1 hour
|
||||
response.headers.set(
|
||||
"Cache-Control",
|
||||
"public, max-age=3600, stale-while-revalidate=86400",
|
||||
);
|
||||
response.headers.set("Cache-Control", "public, max-age=3600, stale-while-revalidate=86400");
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,7 @@ export function AddressBar({ domain }: Props) {
|
||||
return (
|
||||
<div className="flex items-center space-x-2 overflow-hidden p-3.5 lg:px-5 lg:py-3">
|
||||
<div className="text-gray-600">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z"
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
ExclamationTriangleIcon,
|
||||
InformationCircleIcon,
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { ExclamationTriangleIcon, InformationCircleIcon } from "@heroicons/react/24/outline";
|
||||
import { clsx } from "clsx";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
@@ -18,27 +15,19 @@ export enum AlertType {
|
||||
const yellow =
|
||||
"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";
|
||||
// const red =
|
||||
"border-red-600/40 dark:border-red-500/20 bg-red-200/30 text-red-600 dark:bg-red-700/20 dark:text-red-200";
|
||||
const neutral =
|
||||
"border-divider-light dark:border-divider-dark bg-black/5 text-gray-600 dark:bg-white/10 dark:text-gray-200";
|
||||
// "border-red-600/40 dark:border-red-500/20 bg-red-200/30 text-red-600 dark:bg-red-700/20 dark:text-red-200";
|
||||
const neutral = "border-divider-light dark:border-divider-dark bg-black/5 text-gray-600 dark:bg-white/10 dark:text-gray-200";
|
||||
|
||||
export function Alert({ children, type = AlertType.ALERT }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"flex scroll-px-40 flex-row items-center justify-center rounded-md border py-2 pr-2",
|
||||
{
|
||||
[yellow]: type === AlertType.ALERT,
|
||||
[neutral]: type === AlertType.INFO,
|
||||
},
|
||||
)}
|
||||
className={clsx("flex scroll-px-40 flex-row items-center justify-center rounded-md border py-2 pr-2", {
|
||||
[yellow]: type === AlertType.ALERT,
|
||||
[neutral]: type === AlertType.INFO,
|
||||
})}
|
||||
>
|
||||
{type === AlertType.ALERT && (
|
||||
<ExclamationTriangleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />
|
||||
)}
|
||||
{type === AlertType.INFO && (
|
||||
<InformationCircleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />
|
||||
)}
|
||||
{type === AlertType.ALERT && <ExclamationTriangleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />}
|
||||
{type === AlertType.INFO && <InformationCircleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />}
|
||||
<span className="w-full text-sm">{children}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,20 +7,10 @@ import { BadgeState, StateBadge } from "./state-badge";
|
||||
const cardClasses = (alreadyAdded: boolean) =>
|
||||
clsx(
|
||||
"relative bg-background-light-400 dark:bg-background-dark-400 group block space-y-1.5 rounded-md px-5 py-3 border border-divider-light dark:border-divider-dark transition-all ",
|
||||
alreadyAdded
|
||||
? "opacity-50 cursor-default"
|
||||
: "hover:shadow-lg hover:dark:bg-white/10",
|
||||
alreadyAdded ? "opacity-50 cursor-default" : "hover:shadow-lg hover:dark:bg-white/10",
|
||||
);
|
||||
|
||||
const LinkWrapper = ({
|
||||
alreadyAdded,
|
||||
children,
|
||||
link,
|
||||
}: {
|
||||
alreadyAdded: boolean;
|
||||
children: ReactNode;
|
||||
link: string;
|
||||
}) => {
|
||||
const LinkWrapper = ({ alreadyAdded, children, link }: { alreadyAdded: boolean; children: ReactNode; link: string }) => {
|
||||
return !alreadyAdded ? (
|
||||
<Link href={link} className={cardClasses(alreadyAdded)}>
|
||||
{children}
|
||||
@@ -33,12 +23,7 @@ const LinkWrapper = ({
|
||||
export const TOTP = (alreadyAdded: boolean, link: string) => {
|
||||
return (
|
||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center font-medium",
|
||||
alreadyAdded ? "opacity-50" : "",
|
||||
)}
|
||||
>
|
||||
<div className={clsx("flex items-center font-medium", alreadyAdded ? "opacity-50" : "")}>
|
||||
<svg
|
||||
className="mr-4 h-8 w-8 -translate-x-[2px] transform fill-current text-black dark:text-white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -61,12 +46,7 @@ export const TOTP = (alreadyAdded: boolean, link: string) => {
|
||||
export const U2F = (alreadyAdded: boolean, link: string) => {
|
||||
return (
|
||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center font-medium",
|
||||
alreadyAdded ? "" : "",
|
||||
)}
|
||||
>
|
||||
<div className={clsx("flex items-center font-medium", alreadyAdded ? "" : "")}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
@@ -95,12 +75,7 @@ export const U2F = (alreadyAdded: boolean, link: string) => {
|
||||
export const EMAIL = (alreadyAdded: boolean, link: string) => {
|
||||
return (
|
||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center font-medium",
|
||||
alreadyAdded ? "" : "",
|
||||
)}
|
||||
>
|
||||
<div className={clsx("flex items-center font-medium", alreadyAdded ? "" : "")}>
|
||||
<svg
|
||||
className="mr-4 h-8 w-8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -130,12 +105,7 @@ export const EMAIL = (alreadyAdded: boolean, link: string) => {
|
||||
export const SMS = (alreadyAdded: boolean, link: string) => {
|
||||
return (
|
||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center font-medium",
|
||||
alreadyAdded ? "" : "",
|
||||
)}
|
||||
>
|
||||
<div className={clsx("flex items-center font-medium", alreadyAdded ? "" : "")}>
|
||||
<svg
|
||||
className="mr-4 h-8 w-8"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -164,12 +134,7 @@ export const SMS = (alreadyAdded: boolean, link: string) => {
|
||||
export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
|
||||
return (
|
||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center font-medium",
|
||||
alreadyAdded ? "" : "",
|
||||
)}
|
||||
>
|
||||
<div className={clsx("flex items-center font-medium", alreadyAdded ? "" : "")}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
@@ -198,17 +163,8 @@ export const PASSKEYS = (alreadyAdded: boolean, link: string) => {
|
||||
export const PASSWORD = (alreadyAdded: boolean, link: string) => {
|
||||
return (
|
||||
<LinkWrapper key={link} alreadyAdded={alreadyAdded} link={link}>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex items-center font-medium",
|
||||
alreadyAdded ? "" : "",
|
||||
)}
|
||||
>
|
||||
<svg
|
||||
className="mr-4 h-7 w-8 fill-current"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<div className={clsx("flex items-center font-medium", alreadyAdded ? "" : "")}>
|
||||
<svg className="mr-4 h-7 w-8 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<title>form-textbox-password</title>
|
||||
<path d="M17,7H22V17H17V19A1,1 0 0,0 18,20H20V22H17.5C16.95,22 16,21.55 16,21C16,21.55 15.05,22 14.5,22H12V20H14A1,1 0 0,0 15,19V5A1,1 0 0,0 14,4H12V2H14.5C15.05,2 16,2.45 16,3C16,2.45 16.95,2 17.5,2H20V4H18A1,1 0 0,0 17,5V7M2,7H13V9H4V15H13V17H2V7M20,15V9H17V15H20M8.5,12A1.5,1.5 0 0,0 7,10.5A1.5,1.5 0 0,0 5.5,12A1.5,1.5 0 0,0 7,13.5A1.5,1.5 0 0,0 8.5,12M13,10.89C12.39,10.33 11.44,10.38 10.88,11C10.32,11.6 10.37,12.55 11,13.11C11.55,13.63 12.43,13.63 13,13.11V10.89Z" />
|
||||
</svg>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { render } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { Avatar, getInitials } from "./avatar";
|
||||
|
||||
// Mock next-themes
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { ColorShade, getColorHash } from "@/helpers/colors";
|
||||
import { useTheme } from "next-themes";
|
||||
import { getComponentRoundness } from "@/lib/theme";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
interface AvatarProps {
|
||||
name: string | null | undefined;
|
||||
|
||||
@@ -56,21 +56,14 @@ export const Boundary = ({
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute -top-2 flex space-x-1 text-[9px] uppercase leading-4 tracking-widest",
|
||||
{
|
||||
"left-3 lg:left-5": size === "small",
|
||||
"left-4 lg:left-9": size === "default",
|
||||
},
|
||||
)}
|
||||
className={clsx("absolute -top-2 flex space-x-1 text-[9px] uppercase leading-4 tracking-widest", {
|
||||
"left-3 lg:left-5": size === "small",
|
||||
"left-4 lg:left-9": size === "default",
|
||||
})}
|
||||
>
|
||||
{labels.map((label) => {
|
||||
return (
|
||||
<Label
|
||||
key={label}
|
||||
color={color}
|
||||
animateRerendering={animateRerendering}
|
||||
>
|
||||
<Label key={label} color={color} animateRerendering={animateRerendering}>
|
||||
{label}
|
||||
</Label>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { render } from "@testing-library/react";
|
||||
import { Button, ButtonSizes, ButtonVariants, ButtonColors, getButtonClasses } from "./button";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { Button, ButtonColors, ButtonSizes, ButtonVariants, getButtonClasses } from "./button";
|
||||
|
||||
describe("Button Component", () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { APPEARANCE_STYLES, getComponentRoundness, getThemeConfig } from "@/lib/theme";
|
||||
import { ThemeableProps } from "@/lib/themeUtils";
|
||||
import { clsx } from "clsx";
|
||||
import { ButtonHTMLAttributes, DetailedHTMLProps, forwardRef } from "react";
|
||||
import { ThemeableProps } from "@/lib/themeUtils";
|
||||
import { getThemeConfig, getComponentRoundness, APPEARANCE_STYLES } from "@/lib/theme";
|
||||
|
||||
export enum ButtonSizes {
|
||||
Small = "Small",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { render } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { Card } from "./card";
|
||||
|
||||
describe("Card Component", () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { APPEARANCE_STYLES, SPACING_STYLES, getComponentRoundness, getThemeConfig } from "@/lib/theme";
|
||||
import { clsx } from "clsx";
|
||||
import { HTMLAttributes, forwardRef, ReactNode } from "react";
|
||||
import { getThemeConfig, APPEARANCE_STYLES, SPACING_STYLES, getComponentRoundness } from "@/lib/theme";
|
||||
import { HTMLAttributes, ReactNode, forwardRef } from "react";
|
||||
|
||||
export interface CardProps extends HTMLAttributes<HTMLDivElement> {
|
||||
children: ReactNode;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { cleanup, render } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { ChangePasswordForm } from "./change-password-form";
|
||||
import { create } from "@zitadel/client";
|
||||
import { PasswordComplexitySettingsSchema } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { ChangePasswordForm } from "./change-password-form";
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ push: vi.fn() }),
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from "@/helpers/validators";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { checkSessionAndSetPassword, sendPassword } from "@/lib/server/password";
|
||||
import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
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 { useTranslations } from "next-intl";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { Alert } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { PasswordComplexity } from "./password-complexity";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
@@ -124,71 +124,71 @@ export function ChangePasswordForm({ passwordComplexitySettings, sessionId, logi
|
||||
<>
|
||||
{samlData && <AutoSubmitForm url={samlData.url} fields={samlData.fields} />}
|
||||
<form className="w-full">
|
||||
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
|
||||
<div className="">
|
||||
<TextInput
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
autoFocus
|
||||
required
|
||||
{...register("currentPassword", {
|
||||
required: t("change.required.currentPassword"),
|
||||
})}
|
||||
label={t("change.labels.currentPassword")}
|
||||
error={errors.currentPassword?.message as string}
|
||||
data-testid="password-change-current-text-input"
|
||||
/>
|
||||
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
|
||||
<div className="">
|
||||
<TextInput
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
autoFocus
|
||||
required
|
||||
{...register("currentPassword", {
|
||||
required: t("change.required.currentPassword"),
|
||||
})}
|
||||
label={t("change.labels.currentPassword")}
|
||||
error={errors.currentPassword?.message as string}
|
||||
data-testid="password-change-current-text-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="">
|
||||
<TextInput
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
{...register("password", {
|
||||
required: t("change.required.newPassword"),
|
||||
})}
|
||||
label={t("change.labels.newPassword")}
|
||||
error={errors.password?.message as string}
|
||||
data-testid="password-change-text-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="">
|
||||
<TextInput
|
||||
type="password"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
{...register("confirmPassword", {
|
||||
required: t("change.required.confirmPassword"),
|
||||
})}
|
||||
label={t("change.labels.confirmPassword")}
|
||||
error={errors.confirmPassword?.message as string}
|
||||
data-testid="password-change-confirm-text-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="">
|
||||
<TextInput
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
{...register("password", {
|
||||
required: t("change.required.newPassword"),
|
||||
})}
|
||||
label={t("change.labels.newPassword")}
|
||||
error={errors.password?.message as string}
|
||||
data-testid="password-change-text-input"
|
||||
|
||||
{passwordComplexitySettings && (
|
||||
<PasswordComplexity
|
||||
passwordComplexitySettings={passwordComplexitySettings}
|
||||
password={watchPassword}
|
||||
equals={!!watchPassword && watchPassword === watchConfirmPassword}
|
||||
/>
|
||||
)}
|
||||
|
||||
{error && <Alert>{error}</Alert>}
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center justify-between">
|
||||
<BackButton data-testid="back-button" />
|
||||
<Button
|
||||
type="submit"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !policyIsValid || !formState.isValid || watchPassword !== watchConfirmPassword}
|
||||
onClick={handleSubmit(submitChange)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="mr-2 h-5 w-5" />} <Translated i18nKey="change.submit" namespace="password" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="">
|
||||
<TextInput
|
||||
type="password"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
{...register("confirmPassword", {
|
||||
required: t("change.required.confirmPassword"),
|
||||
})}
|
||||
label={t("change.labels.confirmPassword")}
|
||||
error={errors.confirmPassword?.message as string}
|
||||
data-testid="password-change-confirm-text-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{passwordComplexitySettings && (
|
||||
<PasswordComplexity
|
||||
passwordComplexitySettings={passwordComplexitySettings}
|
||||
password={watchPassword}
|
||||
equals={!!watchPassword && watchPassword === watchConfirmPassword}
|
||||
/>
|
||||
)}
|
||||
|
||||
{error && <Alert>{error}</Alert>}
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center justify-between">
|
||||
<BackButton data-testid="back-button" />
|
||||
<Button
|
||||
type="submit"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !policyIsValid || !formState.isValid || watchPassword !== watchConfirmPassword}
|
||||
onClick={handleSubmit(submitChange)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="mr-2 h-5 w-5" />} <Translated i18nKey="change.submit" namespace="password" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,62 +1,44 @@
|
||||
import classNames from "clsx";
|
||||
import {
|
||||
DetailedHTMLProps,
|
||||
forwardRef,
|
||||
InputHTMLAttributes,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
import { DetailedHTMLProps, forwardRef, InputHTMLAttributes, useEffect, useState } from "react";
|
||||
|
||||
export type CheckboxProps = DetailedHTMLProps<
|
||||
InputHTMLAttributes<HTMLInputElement>,
|
||||
HTMLInputElement
|
||||
> & {
|
||||
export type CheckboxProps = DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> & {
|
||||
checked: boolean;
|
||||
disabled?: boolean;
|
||||
onChangeVal?: (checked: boolean) => void;
|
||||
};
|
||||
|
||||
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
function Checkbox(
|
||||
{
|
||||
className = "",
|
||||
checked = false,
|
||||
disabled = false,
|
||||
onChangeVal,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const [enabled, setEnabled] = useState<boolean>(checked);
|
||||
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Checkbox(
|
||||
{ className = "", checked = false, disabled = false, onChangeVal, children, ...props },
|
||||
ref,
|
||||
) {
|
||||
const [enabled, setEnabled] = useState<boolean>(checked);
|
||||
|
||||
useEffect(() => {
|
||||
setEnabled(checked);
|
||||
}, [checked]);
|
||||
useEffect(() => {
|
||||
setEnabled(checked);
|
||||
}, [checked]);
|
||||
|
||||
return (
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex h-5 items-center">
|
||||
<div className="box-sizing block">
|
||||
<input
|
||||
ref={ref}
|
||||
checked={enabled}
|
||||
onChange={(event) => {
|
||||
setEnabled(event.target?.checked);
|
||||
onChangeVal && onChangeVal(event.target?.checked);
|
||||
}}
|
||||
disabled={disabled}
|
||||
type="checkbox"
|
||||
className={classNames(
|
||||
"form-checkbox rounded border-gray-300 text-primary-light-500 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 focus:ring-offset-0 dark:text-primary-dark-500",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
return (
|
||||
<div className="relative flex items-start">
|
||||
<div className="flex h-5 items-center">
|
||||
<div className="box-sizing block">
|
||||
<input
|
||||
ref={ref}
|
||||
checked={enabled}
|
||||
onChange={(event) => {
|
||||
setEnabled(event.target?.checked);
|
||||
onChangeVal && onChangeVal(event.target?.checked);
|
||||
}}
|
||||
disabled={disabled}
|
||||
type="checkbox"
|
||||
className={classNames(
|
||||
"form-checkbox rounded border-gray-300 text-primary-light-500 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 focus:ring-offset-0 dark:text-primary-dark-500",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
LoginSettings,
|
||||
PasskeysType,
|
||||
} from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { LoginSettings, PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { PASSKEYS, PASSWORD } from "./auth-methods";
|
||||
import { Translated } from "./translated";
|
||||
@@ -12,25 +9,20 @@ type Props = {
|
||||
loginSettings: LoginSettings | undefined;
|
||||
};
|
||||
|
||||
export function ChooseAuthenticatorToLogin({
|
||||
authMethods,
|
||||
params,
|
||||
loginSettings,
|
||||
}: Props) {
|
||||
export function ChooseAuthenticatorToLogin({ authMethods, params, loginSettings }: Props) {
|
||||
return (
|
||||
<>
|
||||
{authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
||||
loginSettings?.allowLocalAuthentication && (
|
||||
<div className="ztdl-p">
|
||||
<Translated i18nKey="chooseAlternativeMethod" namespace="idp" />
|
||||
</div>
|
||||
)}
|
||||
{authMethods.includes(AuthenticationMethodType.PASSWORD) && loginSettings?.allowLocalAuthentication && (
|
||||
<div className="ztdl-p">
|
||||
<Translated i18nKey="chooseAlternativeMethod" namespace="idp" />
|
||||
</div>
|
||||
)}
|
||||
<div className="grid w-full grid-cols-1 gap-5 pt-4">
|
||||
{authMethods.includes(AuthenticationMethodType.PASSWORD) &&
|
||||
loginSettings?.allowLocalAuthentication &&
|
||||
PASSWORD(false, "/password?" + params)}
|
||||
{authMethods.includes(AuthenticationMethodType.PASSKEY) &&
|
||||
loginSettings?.allowLocalAuthentication &&
|
||||
loginSettings?.allowLocalAuthentication &&
|
||||
loginSettings?.passkeysType == PasskeysType.ALLOWED &&
|
||||
PASSKEYS(false, "/passkey?" + params)}
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { skipMFAAndContinueWithNextUrl } from "@/lib/server/session";
|
||||
import { LoginSettings, SecondFactorType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { AuthenticationMethodType } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { EMAIL, SMS, TOTP, U2F } from "./auth-methods";
|
||||
import { Translated } from "./translated";
|
||||
import { useState } from "react";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { Alert } from "./alert";
|
||||
import { EMAIL, SMS, TOTP, U2F } from "./auth-methods";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
type Props = {
|
||||
userId: string;
|
||||
|
||||
@@ -11,13 +11,7 @@ type Props = {
|
||||
userMethods: AuthenticationMethodType[];
|
||||
};
|
||||
|
||||
export function ChooseSecondFactor({
|
||||
loginName,
|
||||
sessionId,
|
||||
requestId,
|
||||
organization,
|
||||
userMethods,
|
||||
}: Props) {
|
||||
export function ChooseSecondFactor({ loginName, sessionId, requestId, organization, userMethods }: Props) {
|
||||
const params = new URLSearchParams({});
|
||||
|
||||
if (loginName) {
|
||||
@@ -38,14 +32,10 @@ export function ChooseSecondFactor({
|
||||
{userMethods.map((method, i) => {
|
||||
return (
|
||||
<div key={"method-" + i}>
|
||||
{method === AuthenticationMethodType.TOTP &&
|
||||
TOTP(false, "/otp/time-based?" + params)}
|
||||
{method === AuthenticationMethodType.U2F &&
|
||||
U2F(false, "/u2f?" + params)}
|
||||
{method === AuthenticationMethodType.OTP_EMAIL &&
|
||||
EMAIL(false, "/otp/email?" + params)}
|
||||
{method === AuthenticationMethodType.OTP_SMS &&
|
||||
SMS(false, "/otp/sms?" + params)}
|
||||
{method === AuthenticationMethodType.TOTP && TOTP(false, "/otp/time-based?" + params)}
|
||||
{method === AuthenticationMethodType.U2F && U2F(false, "/u2f?" + params)}
|
||||
{method === AuthenticationMethodType.OTP_EMAIL && EMAIL(false, "/otp/email?" + params)}
|
||||
{method === AuthenticationMethodType.OTP_SMS && SMS(false, "/otp/sms?" + params)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -28,9 +28,7 @@ export function ConsentScreen({
|
||||
|
||||
async function denyDeviceAuth() {
|
||||
setLoading(true);
|
||||
const response = await completeDeviceAuthorization(
|
||||
deviceAuthorizationRequestId,
|
||||
)
|
||||
const response = await completeDeviceAuthorization(deviceAuthorizationRequestId)
|
||||
.catch(() => {
|
||||
setError("Could not register user");
|
||||
return;
|
||||
@@ -59,8 +57,7 @@ export function ConsentScreen({
|
||||
const description = t(translationKey);
|
||||
|
||||
// Check if the key itself is returned and provide a fallback
|
||||
const resolvedDescription =
|
||||
description === translationKey ? "" : description;
|
||||
const resolvedDescription = description === translationKey ? "" : description;
|
||||
|
||||
return (
|
||||
<li
|
||||
@@ -74,11 +71,7 @@ export function ConsentScreen({
|
||||
</ul>
|
||||
|
||||
<p className="ztdl-p text-left text-xs">
|
||||
<Translated
|
||||
i18nKey="request.disclaimer"
|
||||
namespace="device"
|
||||
data={{ appName: appName }}
|
||||
/>
|
||||
<Translated i18nKey="request.disclaimer" namespace="device" data={{ appName: appName }} />
|
||||
</p>
|
||||
|
||||
{error && (
|
||||
@@ -101,12 +94,7 @@ export function ConsentScreen({
|
||||
<span className="flex-grow"></span>
|
||||
|
||||
<Link href={nextUrl}>
|
||||
<Button
|
||||
data-testid="submit-button"
|
||||
type="submit"
|
||||
className="self-end"
|
||||
variant={ButtonVariants.Primary}
|
||||
>
|
||||
<Button data-testid="submit-button" type="submit" className="self-end" variant={ButtonVariants.Primary}>
|
||||
<Translated i18nKey="device.request.submit" namespace="device" />
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
ClipboardDocumentCheckIcon,
|
||||
ClipboardIcon,
|
||||
} from "@heroicons/react/20/solid";
|
||||
import { ClipboardDocumentCheckIcon, ClipboardIcon } from "@heroicons/react/20/solid";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
@@ -30,11 +27,7 @@ export function CopyToClipboard({ value }: Props) {
|
||||
className="text-primary-light-500 dark:text-primary-dark-500"
|
||||
onClick={() => setCopied(true)}
|
||||
>
|
||||
{!copied ? (
|
||||
<ClipboardIcon className="h-5 w-5" />
|
||||
) : (
|
||||
<ClipboardDocumentCheckIcon className="h-5 w-5" />
|
||||
)}
|
||||
{!copied ? <ClipboardIcon className="h-5 w-5" /> : <ClipboardDocumentCheckIcon className="h-5 w-5" />}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,23 +3,9 @@ export function DefaultTags() {
|
||||
return (
|
||||
<>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link
|
||||
href="/favicon/apple-touch-icon.png"
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
/>
|
||||
<link
|
||||
href="/favicon/favicon-32x32.png"
|
||||
rel="icon"
|
||||
sizes="32x32"
|
||||
type="image/png"
|
||||
/>
|
||||
<link
|
||||
href="/favicon/favicon-16x16.png"
|
||||
rel="icon"
|
||||
sizes="16x16"
|
||||
type="image/png"
|
||||
/>
|
||||
<link href="/favicon/apple-touch-icon.png" rel="apple-touch-icon" sizes="180x180" />
|
||||
<link href="/favicon/favicon-32x32.png" rel="icon" sizes="32x32" type="image/png" />
|
||||
<link href="/favicon/favicon-16x16.png" rel="icon" sizes="16x16" type="image/png" />
|
||||
<link href="/favicon/site.webmanifest" rel="manifest" />
|
||||
{/* <link
|
||||
color="#000000"
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import { Alert } from "@/components/alert";
|
||||
import { getDeviceAuthorizationRequest } from "@/lib/server/oidc";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { Logo } from "@/components/logo";
|
||||
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
||||
import React, { ReactNode, Children } from "react";
|
||||
import { ThemeWrapper } from "./theme-wrapper";
|
||||
import { Card } from "./card";
|
||||
import { useResponsiveLayout } from "@/lib/theme-hooks";
|
||||
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
||||
import React, { Children, ReactNode } from "react";
|
||||
import { Card } from "./card";
|
||||
import { ThemeWrapper } from "./theme-wrapper";
|
||||
|
||||
/**
|
||||
* DynamicTheme component handles layout switching between traditional top-to-bottom
|
||||
@@ -49,12 +49,12 @@ export function DynamicTheme({
|
||||
const hasLeftRightStructure = childArray.length === 2;
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto w-full max-w-[1100px] py-4 px-8">
|
||||
<div className="relative mx-auto w-full max-w-[1100px] px-8 py-4">
|
||||
<Card>
|
||||
<div className="flex min-h-[400px]">
|
||||
{/* Left side: First child + branding */}
|
||||
<div className="flex w-1/2 flex-col justify-center p-4 lg:p-8 bg-gradient-to-br from-primary-50 to-primary-100 dark:from-primary-900/20 dark:to-primary-800/20">
|
||||
<div className="max-w-[440px] mx-auto space-y-8">
|
||||
<div className="from-primary-50 to-primary-100 dark:from-primary-900/20 dark:to-primary-800/20 flex w-1/2 flex-col justify-center bg-gradient-to-br p-4 lg:p-8">
|
||||
<div className="mx-auto max-w-[440px] space-y-8">
|
||||
{/* Logo and branding */}
|
||||
{branding && (
|
||||
<Logo
|
||||
@@ -67,9 +67,9 @@ export function DynamicTheme({
|
||||
|
||||
{/* First child content (title, description) - only if we have left/right structure */}
|
||||
{hasLeftRightStructure && (
|
||||
<div className="space-y-4 text-left flex flex-col items-start">
|
||||
<div className="flex flex-col items-start space-y-4 text-left">
|
||||
{/* Apply larger styling to the content */}
|
||||
<div className="space-y-6 [&_h1]:text-4xl [&_h1]:lg:text-4xl [&_h1]:text-left [&_h1]:text-gray-900 [&_h1]:dark:text-white [&_h1]:leading-tight [&_p]:text-left [&_p]:leading-relaxed [&_p]:text-gray-700 [&_p]:dark:text-gray-300">
|
||||
<div className="space-y-6 [&_h1]:text-left [&_h1]:text-4xl [&_h1]:leading-tight [&_h1]:text-gray-900 [&_h1]:dark:text-white [&_h1]:lg:text-4xl [&_p]:text-left [&_p]:leading-relaxed [&_p]:text-gray-700 [&_p]:dark:text-gray-300">
|
||||
{leftContent}
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,10 +96,10 @@ export function DynamicTheme({
|
||||
const hasMultipleChildren = childArray.length > 1;
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto w-full max-w-[440px] py-4 px-4">
|
||||
<div className="relative mx-auto w-full max-w-[440px] px-4 py-4">
|
||||
<Card>
|
||||
<div className="mx-auto flex flex-col items-center space-y-8">
|
||||
<div className="relative flex flex-row items-center justify-center -mb-4">
|
||||
<div className="relative -mb-4 flex flex-row items-center justify-center">
|
||||
{branding && (
|
||||
<Logo
|
||||
lightSrc={branding.lightTheme?.logoUrl}
|
||||
@@ -113,7 +113,7 @@ export function DynamicTheme({
|
||||
{hasMultipleChildren ? (
|
||||
<>
|
||||
{/* Title and description - center aligned */}
|
||||
<div className="w-full text-center flex flex-col items-center mb-4">{titleContent}</div>
|
||||
<div className="mb-4 flex w-full flex-col items-center text-center">{titleContent}</div>
|
||||
|
||||
{/* Form content - left aligned */}
|
||||
<div className="w-full">{formContent}</div>
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { ArrowRightIcon } from "@heroicons/react/24/solid";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export const ExternalLink = ({
|
||||
children,
|
||||
href,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
href: string;
|
||||
}) => {
|
||||
export const ExternalLink = ({ children, href }: { children: ReactNode; href: string }) => {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { processIDPCallback } from "@/lib/server/idp-intent";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Alert } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { Spinner } from "./spinner";
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { APPEARANCE_STYLES, getComponentRoundness, getThemeConfig } from "@/lib/theme";
|
||||
import { clsx } from "clsx";
|
||||
import { Loader2Icon } from "lucide-react";
|
||||
import { ButtonHTMLAttributes, DetailedHTMLProps, forwardRef } from "react";
|
||||
import { useFormStatus } from "react-dom";
|
||||
import { getComponentRoundness, getThemeConfig, APPEARANCE_STYLES } from "@/lib/theme";
|
||||
|
||||
export type SignInWithIdentityProviderProps = DetailedHTMLProps<
|
||||
ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
|
||||
@@ -4,33 +4,26 @@ import { forwardRef } from "react";
|
||||
import { Translated } from "../translated";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
export const SignInWithApple = forwardRef<
|
||||
HTMLButtonElement,
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithApple(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
export const SignInWithApple = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
|
||||
function SignInWithApple(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="flex h-12 w-12 items-center justify-center">
|
||||
<div className="h-6 w-6">
|
||||
<svg viewBox="0 0 170 170" fill="currentColor">
|
||||
<title>Apple Logo</title>
|
||||
<path d="M150.37 130.25c-2.45 5.66-5.35 10.87-8.71 15.66-4.58 6.53-8.33 11.05-11.22 13.56-4.48 4.12-9.28 6.23-14.42 6.35-3.69 0-8.14-1.05-13.32-3.18-5.197-2.12-9.973-3.17-14.34-3.17-4.58 0-9.492 1.05-14.746 3.17-5.262 2.13-9.501 3.24-12.742 3.35-4.929.21-9.842-1.96-14.746-6.52-3.13-2.73-7.045-7.41-11.735-14.04-5.032-7.08-9.169-15.29-12.41-24.65-3.471-10.11-5.211-19.9-5.211-29.378 0-10.857 2.346-20.221 7.045-28.068 3.693-6.303 8.606-11.275 14.755-14.925s12.793-5.51 19.948-5.629c3.915 0 9.049 1.211 15.429 3.591 6.362 2.388 10.447 3.599 12.238 3.599 1.339 0 5.877-1.416 13.57-4.239 7.275-2.618 13.415-3.702 18.445-3.275 13.63 1.1 23.87 6.473 30.68 16.153-12.19 7.386-18.22 17.731-18.1 31.002.11 10.337 3.86 18.939 11.23 25.769 3.34 3.17 7.07 5.62 11.22 7.36-.9 2.61-1.85 5.11-2.86 7.51zM119.11 7.24c0 8.102-2.96 15.667-8.86 22.669-7.12 8.324-15.732 13.134-25.071 12.375a25.222 25.222 0 0 1-.188-3.07c0-7.778 3.386-16.102 9.399-22.908 3.002-3.446 6.82-6.311 11.45-8.597 4.62-2.252 8.99-3.497 13.1-3.71.12 1.083.17 2.166.17 3.24z" />
|
||||
</svg>
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="flex h-12 w-12 items-center justify-center">
|
||||
<div className="h-6 w-6">
|
||||
<svg viewBox="0 0 170 170" fill="currentColor">
|
||||
<title>Apple Logo</title>
|
||||
<path d="M150.37 130.25c-2.45 5.66-5.35 10.87-8.71 15.66-4.58 6.53-8.33 11.05-11.22 13.56-4.48 4.12-9.28 6.23-14.42 6.35-3.69 0-8.14-1.05-13.32-3.18-5.197-2.12-9.973-3.17-14.34-3.17-4.58 0-9.492 1.05-14.746 3.17-5.262 2.13-9.501 3.24-12.742 3.35-4.929.21-9.842-1.96-14.746-6.52-3.13-2.73-7.045-7.41-11.735-14.04-5.032-7.08-9.169-15.29-12.41-24.65-3.471-10.11-5.211-19.9-5.211-29.378 0-10.857 2.346-20.221 7.045-28.068 3.693-6.303 8.606-11.275 14.755-14.925s12.793-5.51 19.948-5.629c3.915 0 9.049 1.211 15.429 3.591 6.362 2.388 10.447 3.599 12.238 3.599 1.339 0 5.877-1.416 13.57-4.239 7.275-2.618 13.415-3.702 18.445-3.275 13.63 1.1 23.87 6.473 30.68 16.153-12.19 7.386-18.22 17.731-18.1 31.002.11 10.337 3.86 18.939 11.23 25.769 3.34 3.17 7.07 5.62 11.22 7.36-.9 2.61-1.85 5.11-2.86 7.51zM119.11 7.24c0 8.102-2.96 15.667-8.86 22.669-7.12 8.324-15.732 13.134-25.071 12.375a25.222 25.222 0 0 1-.188-3.07c0-7.778 3.386-16.102 9.399-22.908 3.002-3.446 6.82-6.311 11.45-8.597 4.62-2.252 8.99-3.497 13.1-3.71.12 1.083.17 2.166.17 3.24z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithApple" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
});
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : <Translated i18nKey="signInWithApple" namespace="idp" />}</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -4,39 +4,26 @@ import { forwardRef } from "react";
|
||||
import { Translated } from "../translated";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
export const SignInWithAzureAd = forwardRef<
|
||||
HTMLButtonElement,
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithAzureAd(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
export const SignInWithAzureAd = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
|
||||
function SignInWithAzureAd(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="flex h-12 w-12 items-center justify-center p-[10px]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="21"
|
||||
height="21"
|
||||
viewBox="0 0 21 21"
|
||||
className="h-full w-full"
|
||||
>
|
||||
<path fill="#f25022" d="M1 1H10V10H1z"></path>
|
||||
<path fill="#00a4ef" d="M1 11H10V20H1z"></path>
|
||||
<path fill="#7fba00" d="M11 1H20V10H11z"></path>
|
||||
<path fill="#ffb900" d="M11 11H20V20H11z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithAzureAD" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="flex h-12 w-12 items-center justify-center p-[10px]">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="21" viewBox="0 0 21 21" className="h-full w-full">
|
||||
<path fill="#f25022" d="M1 1H10V10H1z"></path>
|
||||
<path fill="#00a4ef" d="M1 11H10V20H1z"></path>
|
||||
<path fill="#7fba00" d="M11 1H20V10H11z"></path>
|
||||
<path fill="#ffb900" d="M11 11H20V20H11z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : <Translated i18nKey="signInWithAzureAD" namespace="idp" />}</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -3,19 +3,13 @@
|
||||
import { forwardRef } from "react";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
export const SignInWithGeneric = forwardRef<
|
||||
HTMLButtonElement,
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithGeneric(props, ref) {
|
||||
const {
|
||||
children,
|
||||
name = "",
|
||||
className = "h-[50px] pl-20",
|
||||
...restProps
|
||||
} = props;
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref} className={className}>
|
||||
{children ? children : <span>{name}</span>}
|
||||
</BaseButton>
|
||||
);
|
||||
});
|
||||
export const SignInWithGeneric = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
|
||||
function SignInWithGeneric(props, ref) {
|
||||
const { children, name = "", className = "h-[50px] pl-20", ...restProps } = props;
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref} className={className}>
|
||||
{children ? children : <span>{name}</span>}
|
||||
</BaseButton>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -7,12 +7,7 @@ import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
function GitHubLogo() {
|
||||
return (
|
||||
<>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 1024 1024"
|
||||
className="hidden h-8 w-8 dark:block"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 1024 1024" className="hidden h-8 w-8 dark:block">
|
||||
<path
|
||||
fill="#fafafa"
|
||||
fillRule="evenodd"
|
||||
@@ -20,12 +15,7 @@ function GitHubLogo() {
|
||||
clipRule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 1024 1024"
|
||||
className="block h-8 w-8 dark:hidden"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 1024 1024" className="block h-8 w-8 dark:hidden">
|
||||
<path
|
||||
fill="#1B1F23"
|
||||
fillRule="evenodd"
|
||||
@@ -37,28 +27,21 @@ function GitHubLogo() {
|
||||
);
|
||||
}
|
||||
|
||||
export const SignInWithGithub = forwardRef<
|
||||
HTMLButtonElement,
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithGithub(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
export const SignInWithGithub = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
|
||||
function SignInWithGithub(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="mx-2 my-2 flex items-center justify-center">
|
||||
<GitHubLogo />
|
||||
</div>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithGithub" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="mx-2 my-2 flex items-center justify-center">
|
||||
<GitHubLogo />
|
||||
</div>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : <Translated i18nKey="signInWithGithub" namespace="idp" />}</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -4,50 +4,38 @@ import { forwardRef } from "react";
|
||||
import { Translated } from "../translated";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
export const SignInWithGitlab = forwardRef<
|
||||
HTMLButtonElement,
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithGitlab(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
export const SignInWithGitlab = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
|
||||
function SignInWithGitlab(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="flex h-12 w-12 items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={25}
|
||||
height={24}
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
fill="#e24329"
|
||||
d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5z"
|
||||
/>
|
||||
<path
|
||||
fill="#fc6d26"
|
||||
d="m24.507 9.5-.034-.09a11.44 11.44 0 0 0-4.56 2.051l-7.447 5.632 4.742 3.584 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5z"
|
||||
/>
|
||||
<path
|
||||
fill="#fca326"
|
||||
d="m7.707 20.677 2.56 1.935 1.555 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935-4.743-3.584-4.755 3.584z"
|
||||
/>
|
||||
<path
|
||||
fill="#fc6d26"
|
||||
d="M5.01 11.461a11.43 11.43 0 0 0-4.56-2.05L.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 4.745-3.584-7.444-5.632z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithGitlab" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="flex h-12 w-12 items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={25} height={24} fill="none">
|
||||
<path
|
||||
fill="#e24329"
|
||||
d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5z"
|
||||
/>
|
||||
<path
|
||||
fill="#fc6d26"
|
||||
d="m24.507 9.5-.034-.09a11.44 11.44 0 0 0-4.56 2.051l-7.447 5.632 4.742 3.584 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5z"
|
||||
/>
|
||||
<path
|
||||
fill="#fca326"
|
||||
d="m7.707 20.677 2.56 1.935 1.555 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935-4.743-3.584-4.755 3.584z"
|
||||
/>
|
||||
<path
|
||||
fill="#fc6d26"
|
||||
d="M5.01 11.461a11.43 11.43 0 0 0-4.56-2.05L.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 4.745-3.584-7.444-5.632z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : <Translated i18nKey="signInWithGitlab" namespace="idp" />}</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -4,63 +4,51 @@ import { forwardRef } from "react";
|
||||
import { Translated } from "../translated";
|
||||
import { BaseButton, SignInWithIdentityProviderProps } from "./base-button";
|
||||
|
||||
export const SignInWithGoogle = forwardRef<
|
||||
HTMLButtonElement,
|
||||
SignInWithIdentityProviderProps
|
||||
>(function SignInWithGoogle(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
export const SignInWithGoogle = forwardRef<HTMLButtonElement, SignInWithIdentityProviderProps>(
|
||||
function SignInWithGoogle(props, ref) {
|
||||
const { children, name, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="flex h-12 w-12 items-center justify-center">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlSpace="preserve"
|
||||
id="Capa_1"
|
||||
viewBox="0 0 150 150"
|
||||
>
|
||||
<style>
|
||||
{
|
||||
".st0{fill:#1a73e8}.st1{fill:#ea4335}.st2{fill:#4285f4}.st3{fill:#fbbc04}.st4{fill:#34a853}.st5{fill:#4caf50}.st6{fill:#1e88e5}.st7{fill:#e53935}.st8{fill:#c62828}.st9{fill:#fbc02d}.st10{fill:#1565c0}.st11{fill:#2e7d32}.st16{clip-path:url(#SVGID_2_)}.st17{fill:#188038}.st18,.st19{opacity:.2;fill:#fff;enable-background:new}.st19{opacity:.3;fill:#0d652d}.st20{clip-path:url(#SVGID_4_)}.st21{opacity:.3;fill:url(#_45_shadow_1_);enable-background:new}.st22{clip-path:url(#SVGID_6_)}.st23{fill:#fa7b17}.st24,.st25,.st26{opacity:.3;fill:#174ea6;enable-background:new}.st25,.st26{fill:#a50e0e}.st26{fill:#e37400}.st27{fill:url(#Finish_mask_1_)}.st28{fill:#fff}.st29{fill:#0c9d58}.st30,.st31{opacity:.2;fill:#004d40;enable-background:new}.st31{fill:#3e2723}.st32{fill:#ffc107}.st33{fill:#1a237e;enable-background:new}.st33,.st34{opacity:.2}.st35{fill:#1a237e}.st36{fill:url(#SVGID_7_)}.st37{fill:#fbbc05}.st38{clip-path:url(#SVGID_9_);fill:#e53935}.st39{clip-path:url(#SVGID_11_);fill:#fbc02d}.st40{clip-path:url(#SVGID_13_);fill:#e53935}.st41{clip-path:url(#SVGID_15_);fill:#fbc02d}"
|
||||
}
|
||||
</style>
|
||||
<path
|
||||
d="M120 76.1c0-3.1-.3-6.3-.8-9.3H75.9v17.7h24.8c-1 5.7-4.3 10.7-9.2 13.9l14.8 11.5C115 101.8 120 90 120 76.1z"
|
||||
style={{
|
||||
fill: "#4280ef",
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M75.9 120.9c12.4 0 22.8-4.1 30.4-11.1L91.5 98.4c-4.1 2.8-9.4 4.4-15.6 4.4-12 0-22.1-8.1-25.8-18.9L34.9 95.6c7.8 15.5 23.6 25.3 41 25.3z"
|
||||
style={{
|
||||
fill: "#34a353",
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M50.1 83.8c-1.9-5.7-1.9-11.9 0-17.6L34.9 54.4c-6.5 13-6.5 28.3 0 41.2l15.2-11.8z"
|
||||
style={{
|
||||
fill: "#f6b704",
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M75.9 47.3c6.5-.1 12.9 2.4 17.6 6.9L106.6 41c-8.3-7.8-19.3-12-30.7-11.9-17.4 0-33.2 9.8-41 25.3l15.2 11.8c3.7-10.9 13.8-18.9 25.8-18.9z"
|
||||
style={{
|
||||
fill: "#e54335",
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">
|
||||
{name ? (
|
||||
name
|
||||
) : (
|
||||
<Translated i18nKey="signInWithGoogle" namespace="idp" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<BaseButton {...restProps} ref={ref}>
|
||||
<div className="flex h-12 w-12 items-center justify-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlSpace="preserve" id="Capa_1" viewBox="0 0 150 150">
|
||||
<style>
|
||||
{
|
||||
".st0{fill:#1a73e8}.st1{fill:#ea4335}.st2{fill:#4285f4}.st3{fill:#fbbc04}.st4{fill:#34a853}.st5{fill:#4caf50}.st6{fill:#1e88e5}.st7{fill:#e53935}.st8{fill:#c62828}.st9{fill:#fbc02d}.st10{fill:#1565c0}.st11{fill:#2e7d32}.st16{clip-path:url(#SVGID_2_)}.st17{fill:#188038}.st18,.st19{opacity:.2;fill:#fff;enable-background:new}.st19{opacity:.3;fill:#0d652d}.st20{clip-path:url(#SVGID_4_)}.st21{opacity:.3;fill:url(#_45_shadow_1_);enable-background:new}.st22{clip-path:url(#SVGID_6_)}.st23{fill:#fa7b17}.st24,.st25,.st26{opacity:.3;fill:#174ea6;enable-background:new}.st25,.st26{fill:#a50e0e}.st26{fill:#e37400}.st27{fill:url(#Finish_mask_1_)}.st28{fill:#fff}.st29{fill:#0c9d58}.st30,.st31{opacity:.2;fill:#004d40;enable-background:new}.st31{fill:#3e2723}.st32{fill:#ffc107}.st33{fill:#1a237e;enable-background:new}.st33,.st34{opacity:.2}.st35{fill:#1a237e}.st36{fill:url(#SVGID_7_)}.st37{fill:#fbbc05}.st38{clip-path:url(#SVGID_9_);fill:#e53935}.st39{clip-path:url(#SVGID_11_);fill:#fbc02d}.st40{clip-path:url(#SVGID_13_);fill:#e53935}.st41{clip-path:url(#SVGID_15_);fill:#fbc02d}"
|
||||
}
|
||||
</style>
|
||||
<path
|
||||
d="M120 76.1c0-3.1-.3-6.3-.8-9.3H75.9v17.7h24.8c-1 5.7-4.3 10.7-9.2 13.9l14.8 11.5C115 101.8 120 90 120 76.1z"
|
||||
style={{
|
||||
fill: "#4280ef",
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M75.9 120.9c12.4 0 22.8-4.1 30.4-11.1L91.5 98.4c-4.1 2.8-9.4 4.4-15.6 4.4-12 0-22.1-8.1-25.8-18.9L34.9 95.6c7.8 15.5 23.6 25.3 41 25.3z"
|
||||
style={{
|
||||
fill: "#34a353",
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M50.1 83.8c-1.9-5.7-1.9-11.9 0-17.6L34.9 54.4c-6.5 13-6.5 28.3 0 41.2l15.2-11.8z"
|
||||
style={{
|
||||
fill: "#f6b704",
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M75.9 47.3c6.5-.1 12.9 2.4 17.6 6.9L106.6 41c-8.3-7.8-19.3-12-30.7-11.9-17.4 0-33.2 9.8-41 25.3l15.2 11.8c3.7-10.9 13.8-18.9 25.8-18.9z"
|
||||
style={{
|
||||
fill: "#e54335",
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{children ? (
|
||||
children
|
||||
) : (
|
||||
<span className="ml-4">{name ? name : <Translated i18nKey="signInWithGoogle" namespace="idp" />}</span>
|
||||
)}
|
||||
</BaseButton>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { TextInput } from "./input";
|
||||
|
||||
describe("TextInput Component", () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { getComponentRoundness } from "@/lib/theme";
|
||||
import { CheckCircleIcon } from "@heroicons/react/24/solid";
|
||||
import { clsx } from "clsx";
|
||||
import { ChangeEvent, DetailedHTMLProps, forwardRef, InputHTMLAttributes, ReactNode } from "react";
|
||||
import { getComponentRoundness } from "@/lib/theme";
|
||||
|
||||
export type TextInputProps = DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> & {
|
||||
label: string;
|
||||
|
||||
@@ -5,9 +5,5 @@ import { ReactNode } from "react";
|
||||
export async function LanguageProvider({ children }: { children: ReactNode }) {
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
);
|
||||
return <NextIntlClientProvider messages={messages}>{children}</NextIntlClientProvider>;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { setLanguageCookie } from "@/lib/cookies";
|
||||
import { Lang } from "@/lib/i18n";
|
||||
import { getThemeConfig, getComponentRoundness, APPEARANCE_STYLES } from "@/lib/theme";
|
||||
import { APPEARANCE_STYLES, getComponentRoundness, getThemeConfig } from "@/lib/theme";
|
||||
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from "@headlessui/react";
|
||||
import { CheckIcon, ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||
import clsx from "clsx";
|
||||
@@ -57,7 +57,7 @@ export function LanguageSwitcher({ languages }: { languages: Lang[] }) {
|
||||
anchor="bottom"
|
||||
transition
|
||||
className={clsx(
|
||||
`w-[var(--button-width)] border border-black/5 bg-background-light-500 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none dark:border-white/5 dark:bg-background-dark-500 rounded-md`,
|
||||
`w-[var(--button-width)] rounded-md border border-black/5 bg-background-light-500 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none dark:border-white/5 dark:bg-background-dark-500`,
|
||||
"transition duration-100 ease-in data-[leave]:data-[closed]:opacity-0",
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -11,7 +11,5 @@ export function LayoutProviders({ children }: Props) {
|
||||
const { resolvedTheme } = useTheme();
|
||||
const isDark = resolvedTheme === "dark";
|
||||
|
||||
return (
|
||||
<div className={`${isDark ? "ui-dark" : "ui-light"} `}>{children}</div>
|
||||
);
|
||||
return <div className={`${isDark ? "ui-dark" : "ui-light"} `}>{children}</div>;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,7 @@ describe("LDAPUsernamePasswordForm", () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test("should autofocus the username input on mount", () => {
|
||||
const { getByTestId } = render(
|
||||
<LDAPUsernamePasswordForm idpId="idp-1" link={false} />,
|
||||
);
|
||||
const { getByTestId } = render(<LDAPUsernamePasswordForm idpId="idp-1" link={false} />);
|
||||
expect(getByTestId("username-text-input")).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,9 +23,7 @@ describe("LoginOTP", () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test("should autofocus the code input on mount", () => {
|
||||
const { getByTestId } = render(
|
||||
<LoginOTP host={null} method="time-based" />,
|
||||
);
|
||||
const { getByTestId } = render(<LoginOTP host={null} method="time-based" />);
|
||||
expect(getByTestId("code-text-input")).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { completeFlowOrGetUrl } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { updateOrCreateSession } from "@/lib/server/session";
|
||||
import { create } from "@zitadel/client";
|
||||
import { RequestChallengesSchema } from "@zitadel/proto/zitadel/session/v2/challenge_pb";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Alert, AlertType } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { completeFlowOrGetUrl } from "@/lib/client";
|
||||
|
||||
// either loginName or sessionId must be provided
|
||||
type Props = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, expect, test, vi, beforeEach } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { LoginPasskey } from "./login-passkey";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { LoginPasskey } from "./login-passkey";
|
||||
|
||||
// Mock next/navigation
|
||||
const mockPush = vi.fn();
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { sendPasskey } from "@/lib/server/passkeys";
|
||||
import { updateOrCreateSession } from "@/lib/server/session";
|
||||
import { create, JsonObject } from "@zitadel/client";
|
||||
import { RequestChallengesSchema, 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 { useTranslations } from "next-intl";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { Alert } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
// either loginName or sessionId must be provided
|
||||
type Props = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { afterEach, describe, expect, test } from "vitest";
|
||||
import { PasswordComplexity } from "./password-complexity";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
|
||||
describe("<PasswordComplexity/>", () => {
|
||||
const messages = {
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import {
|
||||
lowerCaseValidator,
|
||||
numberValidator,
|
||||
symbolValidator,
|
||||
upperCaseValidator,
|
||||
} from "@/helpers/validators";
|
||||
import { Translated } from "@/components/translated";
|
||||
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from "@/helpers/validators";
|
||||
import { PasswordComplexitySettings } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Translated } from "@/components/translated";
|
||||
|
||||
type Props = {
|
||||
passwordComplexitySettings: PasswordComplexitySettings;
|
||||
@@ -26,11 +21,7 @@ function CheckIcon({ title }: { title: string }) {
|
||||
role="img"
|
||||
>
|
||||
<title>{title}</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4.5 12.75l6 6 9-13.5"
|
||||
/>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -47,30 +38,17 @@ function CrossIcon({ title }: { title: string }) {
|
||||
role="img"
|
||||
>
|
||||
<title>{title}</title>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function renderIcon(matched: boolean, t: ReturnType<typeof useTranslations>) {
|
||||
return matched ? (
|
||||
<CheckIcon title={t("complexity.matches")} />
|
||||
) : (
|
||||
<CrossIcon title={t("complexity.doesNotMatch")} />
|
||||
);
|
||||
return matched ? <CheckIcon title={t("complexity.matches")} /> : <CrossIcon title={t("complexity.doesNotMatch")} />;
|
||||
}
|
||||
const desc =
|
||||
"text-14px leading-4 text-input-light-label dark:text-input-dark-label";
|
||||
const desc = "text-14px leading-4 text-input-light-label dark:text-input-dark-label";
|
||||
|
||||
export function PasswordComplexity({
|
||||
passwordComplexitySettings,
|
||||
password,
|
||||
equals,
|
||||
}: Props) {
|
||||
export function PasswordComplexity({ passwordComplexitySettings, password, equals }: Props) {
|
||||
const t = useTranslations("password");
|
||||
const hasMinLength = password?.length >= passwordComplexitySettings.minLength;
|
||||
const hasSymbol = symbolValidator(password);
|
||||
|
||||
@@ -19,12 +19,7 @@ describe("PasswordForm", () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test("should autofocus the password input on mount", () => {
|
||||
const { getByTestId } = render(
|
||||
<PasswordForm
|
||||
loginSettings={undefined}
|
||||
loginName="test@example.com"
|
||||
/>,
|
||||
);
|
||||
const { getByTestId } = render(<PasswordForm loginSettings={undefined} loginName="test@example.com" />);
|
||||
expect(getByTestId("password-text-input")).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { resetPassword, sendPassword } from "@/lib/server/password";
|
||||
import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Alert, AlertType } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs = {
|
||||
password: string;
|
||||
|
||||
@@ -27,10 +27,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
|
||||
const hasPrivacyLink = !!legal?.privacyPolicyLink;
|
||||
|
||||
// Check that all required checkboxes are accepted
|
||||
return (
|
||||
(!hasTosLink || newState.tosAccepted) &&
|
||||
(!hasPrivacyLink || newState.privacyPolicyAccepted)
|
||||
);
|
||||
return (!hasTosLink || newState.tosAccepted) && (!hasPrivacyLink || newState.privacyPolicyAccepted);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -29,16 +29,12 @@ describe("RegisterFormIDPIncomplete", () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test("should autofocus the username input when idpUserName is not provided", () => {
|
||||
const { getByTestId } = render(
|
||||
<RegisterFormIDPIncomplete {...defaultProps} />,
|
||||
);
|
||||
const { getByTestId } = render(<RegisterFormIDPIncomplete {...defaultProps} />);
|
||||
expect(getByTestId("username-text-input")).toHaveFocus();
|
||||
});
|
||||
|
||||
test("should autofocus the firstname input when idpUserName is provided", () => {
|
||||
const { getByTestId } = render(
|
||||
<RegisterFormIDPIncomplete {...defaultProps} idpUserName="existing-user" />,
|
||||
);
|
||||
const { getByTestId } = render(<RegisterFormIDPIncomplete {...defaultProps} idpUserName="existing-user" />);
|
||||
expect(getByTestId("firstname-text-input")).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { registerUserAndLinkToIDP } from "@/lib/server/register";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { useState } from "react";
|
||||
import { registerUserAndLinkToIDP } from "@/lib/server/register";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { Alert } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { cleanup, render } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { RegisterForm } from "./register-form";
|
||||
import { create } from "@zitadel/client";
|
||||
import { LegalAndSupportSettingsSchema } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { RegisterForm } from "./register-form";
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ push: vi.fn() }),
|
||||
@@ -26,13 +26,7 @@ describe("RegisterForm", () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test("should autofocus the firstname input on mount", () => {
|
||||
const { getByTestId } = render(
|
||||
<RegisterForm
|
||||
legal={defaultLegal}
|
||||
organization="org-1"
|
||||
idpCount={0}
|
||||
/>,
|
||||
);
|
||||
const { getByTestId } = render(<RegisterForm legal={defaultLegal} organization="org-1" idpCount={0} />);
|
||||
expect(getByTestId("firstname-text-input")).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { registerUser } from "@/lib/server/register";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { registerUser } from "@/lib/server/register";
|
||||
import { LegalAndSupportSettings } from "@zitadel/proto/zitadel/settings/v2/legal_settings_pb";
|
||||
import { LoginSettings, PasskeysType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { Alert, AlertType } from "./alert";
|
||||
import { AuthenticationMethod, AuthenticationMethodRadio, methods } from "./authentication-method-radio";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { PrivacyPolicyCheckboxes } from "./privacy-policy-checkboxes";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
|
||||
import { registerPasskeyLink, verifyPasskeyRegistration } from "@/lib/server/passkeys";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Alert } from "./alert";
|
||||
import { BackButton } from "./back-button";
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { coerceToArrayBuffer, coerceToBase64Url } from "@/helpers/base64";
|
||||
import { completeFlowOrGetUrl } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { addU2F, verifyU2F } from "@/lib/server/u2f";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { RegisterU2FResponse } from "@zitadel/proto/zitadel/user/v2/user_service_pb";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { Alert } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { completeFlowOrGetUrl } from "@/lib/client";
|
||||
|
||||
type Props = {
|
||||
loginName?: string;
|
||||
|
||||
@@ -17,13 +17,7 @@ export function SelfServiceMenu() {
|
||||
return (
|
||||
<div className="flex w-full flex-col space-y-2">
|
||||
{list.map((menuitem, index) => {
|
||||
return (
|
||||
<SelfServiceItem
|
||||
link={menuitem.link}
|
||||
key={"self-service-" + index}
|
||||
name={menuitem.name}
|
||||
/>
|
||||
);
|
||||
return <SelfServiceItem link={menuitem.link} key={"self-service-" + index} name={menuitem.name} />;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -11,13 +11,7 @@ import { Avatar } from "./avatar";
|
||||
import { isSessionValid } from "./session-item";
|
||||
import { Translated } from "./translated";
|
||||
|
||||
export function SessionClearItem({
|
||||
session,
|
||||
reload,
|
||||
}: {
|
||||
session: Session;
|
||||
reload: () => void;
|
||||
}) {
|
||||
export function SessionClearItem({ session, reload }: { session: Session; reload: () => void }) {
|
||||
const currentLocale = useLocale();
|
||||
moment.locale(currentLocale === "zh" ? "zh-cn" : currentLocale);
|
||||
|
||||
@@ -65,9 +59,7 @@ export function SessionClearItem({
|
||||
|
||||
<div className="flex flex-col items-start overflow-hidden">
|
||||
<span className="">{session.factors?.user?.displayName}</span>
|
||||
<span className="text-ellipsis text-xs opacity-80">
|
||||
{session.factors?.user?.loginName}
|
||||
</span>
|
||||
<span className="text-ellipsis text-xs opacity-80">{session.factors?.user?.loginName}</span>
|
||||
{valid ? (
|
||||
<span className="text-ellipsis text-xs opacity-80">
|
||||
{verifiedAt && (
|
||||
@@ -81,9 +73,7 @@ export function SessionClearItem({
|
||||
) : (
|
||||
verifiedAt && (
|
||||
<span className="text-ellipsis text-xs opacity-80">
|
||||
expired{" "}
|
||||
{session.expirationDate &&
|
||||
moment(timestampDate(session.expirationDate)).fromNow()}
|
||||
expired {session.expirationDate && moment(timestampDate(session.expirationDate)).fromNow()}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { sendLoginname } from "@/lib/server/loginname";
|
||||
import { clearSession, continueWithSession, ContinueWithSessionCommand } from "@/lib/server/session";
|
||||
import { XCircleIcon } from "@heroicons/react/24/outline";
|
||||
@@ -10,10 +11,9 @@ import moment from "moment";
|
||||
import { useLocale } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
import React, { useState } from "react";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { Avatar } from "./avatar";
|
||||
import { Translated } from "./translated";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
export function isSessionValid(session: Partial<Session>): {
|
||||
valid: boolean;
|
||||
|
||||
@@ -20,12 +20,8 @@ export function SessionsList({ sessions, requestId }: Props) {
|
||||
.filter((session) => session?.factors?.user?.loginName)
|
||||
// sort by change date descending
|
||||
.sort((a, b) => {
|
||||
const dateA = a.changeDate
|
||||
? timestampDate(a.changeDate).getTime()
|
||||
: 0;
|
||||
const dateB = b.changeDate
|
||||
? timestampDate(b.changeDate).getTime()
|
||||
: 0;
|
||||
const dateA = a.changeDate ? timestampDate(a.changeDate).getTime() : 0;
|
||||
const dateB = b.changeDate ? timestampDate(b.changeDate).getTime() : 0;
|
||||
return dateB - dateA;
|
||||
})
|
||||
// TODO: add sorting to move invalid sessions to the bottom
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { cleanup, render } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { SetPasswordForm } from "./set-password-form";
|
||||
import { create } from "@zitadel/client";
|
||||
import { PasswordComplexitySettingsSchema } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { SetPasswordForm } from "./set-password-form";
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ push: vi.fn() }),
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from "@/helpers/validators";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { changePassword, resetPassword, sendPassword } from "@/lib/server/password";
|
||||
import { create } from "@zitadel/client";
|
||||
import { ChecksSchema } from "@zitadel/proto/zitadel/session/v2/session_service_pb";
|
||||
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 { useTranslations } from "next-intl";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { Alert, AlertType } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { PasswordComplexity } from "./password-complexity";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
@@ -175,95 +175,95 @@ export function SetPasswordForm({
|
||||
<>
|
||||
{samlData && <AutoSubmitForm url={samlData.url} fields={samlData.fields} />}
|
||||
<form className="w-full">
|
||||
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
|
||||
{codeRequired && (
|
||||
<Alert type={AlertType.INFO}>
|
||||
<div className="flex flex-row">
|
||||
<span className="mr-auto flex-1 text-left">
|
||||
<Translated i18nKey="set.noCodeReceived" namespace="password" />
|
||||
</span>
|
||||
<button
|
||||
aria-label="Resend OTP Code"
|
||||
disabled={loading}
|
||||
type="button"
|
||||
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
|
||||
onClick={() => {
|
||||
resendCode();
|
||||
}}
|
||||
data-testid="resend-button"
|
||||
>
|
||||
<Translated i18nKey="set.resend" namespace="password" />
|
||||
</button>
|
||||
<div className="mb-4 grid grid-cols-1 gap-4 pt-4">
|
||||
{codeRequired && (
|
||||
<Alert type={AlertType.INFO}>
|
||||
<div className="flex flex-row">
|
||||
<span className="mr-auto flex-1 text-left">
|
||||
<Translated i18nKey="set.noCodeReceived" namespace="password" />
|
||||
</span>
|
||||
<button
|
||||
aria-label="Resend OTP Code"
|
||||
disabled={loading}
|
||||
type="button"
|
||||
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
|
||||
onClick={() => {
|
||||
resendCode();
|
||||
}}
|
||||
data-testid="resend-button"
|
||||
>
|
||||
<Translated i18nKey="set.resend" namespace="password" />
|
||||
</button>
|
||||
</div>
|
||||
</Alert>
|
||||
)}
|
||||
{codeRequired && (
|
||||
<div>
|
||||
<TextInput
|
||||
type="text"
|
||||
autoFocus
|
||||
required
|
||||
{...register("code", {
|
||||
required: t("set.required.code"),
|
||||
})}
|
||||
label={t("set.labels.code")}
|
||||
autoComplete="one-time-code"
|
||||
error={errors.code?.message as string}
|
||||
data-testid="code-text-input"
|
||||
/>
|
||||
</div>
|
||||
</Alert>
|
||||
)}
|
||||
{codeRequired && (
|
||||
)}
|
||||
<div>
|
||||
<TextInput
|
||||
type="text"
|
||||
autoFocus
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
autoFocus={!codeRequired}
|
||||
required
|
||||
{...register("code", {
|
||||
required: t("set.required.code"),
|
||||
{...register("password", {
|
||||
required: t("set.required.newPassword"),
|
||||
})}
|
||||
label={t("set.labels.code")}
|
||||
autoComplete="one-time-code"
|
||||
error={errors.code?.message as string}
|
||||
data-testid="code-text-input"
|
||||
label={t("set.labels.newPassword")}
|
||||
error={errors.password?.message as string}
|
||||
data-testid="password-set-text-input"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextInput
|
||||
type="password"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
{...register("confirmPassword", {
|
||||
required: t("set.required.confirmPassword"),
|
||||
})}
|
||||
label={t("set.labels.confirmPassword")}
|
||||
error={errors.confirmPassword?.message as string}
|
||||
data-testid="password-set-confirm-text-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{passwordComplexitySettings && (
|
||||
<PasswordComplexity
|
||||
passwordComplexitySettings={passwordComplexitySettings}
|
||||
password={watchPassword}
|
||||
equals={!!watchPassword && watchPassword === watchConfirmPassword}
|
||||
/>
|
||||
)}
|
||||
<div>
|
||||
<TextInput
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
autoFocus={!codeRequired}
|
||||
required
|
||||
{...register("password", {
|
||||
required: t("set.required.newPassword"),
|
||||
})}
|
||||
label={t("set.labels.newPassword")}
|
||||
error={errors.password?.message as string}
|
||||
data-testid="password-set-text-input"
|
||||
/>
|
||||
|
||||
{error && <Alert>{error}</Alert>}
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center justify-between">
|
||||
<BackButton data-testid="back-button" />
|
||||
<Button
|
||||
type="submit"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !policyIsValid || !formState.isValid || watchPassword !== watchConfirmPassword}
|
||||
onClick={handleSubmit(submitPassword)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="mr-2 h-5 w-5" />} <Translated i18nKey="set.submit" namespace="password" />
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<TextInput
|
||||
type="password"
|
||||
required
|
||||
autoComplete="new-password"
|
||||
{...register("confirmPassword", {
|
||||
required: t("set.required.confirmPassword"),
|
||||
})}
|
||||
label={t("set.labels.confirmPassword")}
|
||||
error={errors.confirmPassword?.message as string}
|
||||
data-testid="password-set-confirm-text-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{passwordComplexitySettings && (
|
||||
<PasswordComplexity
|
||||
passwordComplexitySettings={passwordComplexitySettings}
|
||||
password={watchPassword}
|
||||
equals={!!watchPassword && watchPassword === watchConfirmPassword}
|
||||
/>
|
||||
)}
|
||||
|
||||
{error && <Alert>{error}</Alert>}
|
||||
|
||||
<div className="mt-8 flex w-full flex-row items-center justify-between">
|
||||
<BackButton data-testid="back-button" />
|
||||
<Button
|
||||
type="submit"
|
||||
variant={ButtonVariants.Primary}
|
||||
disabled={loading || !policyIsValid || !formState.isValid || watchPassword !== watchConfirmPassword}
|
||||
onClick={handleSubmit(submitPassword)}
|
||||
data-testid="submit-button"
|
||||
>
|
||||
{loading && <Spinner className="mr-2 h-5 w-5" />} <Translated i18nKey="set.submit" namespace="password" />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { cleanup, render } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { SetRegisterPasswordForm } from "./set-register-password-form";
|
||||
import { create } from "@zitadel/client";
|
||||
import { PasswordComplexitySettingsSchema } from "@zitadel/proto/zitadel/settings/v2/password_settings_pb";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { SetRegisterPasswordForm } from "./set-register-password-form";
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ push: vi.fn() }),
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
"use client";
|
||||
|
||||
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from "@/helpers/validators";
|
||||
import { registerUser } from "@/lib/server/register";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { registerUser } 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 { useTranslations } from "next-intl";
|
||||
import { FieldValues, useForm } from "react-hook-form";
|
||||
import { Alert } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { PasswordComplexity } from "./password-complexity";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs =
|
||||
| {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { redirectToIdp } from "@/lib/server/idp";
|
||||
import { IdentityProvider, IdentityProviderType } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { ReactNode, useActionState } from "react";
|
||||
import { Alert } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { SignInWithIdentityProviderProps } from "./idps/base-button";
|
||||
import { SignInWithApple } from "./idps/sign-in-with-apple";
|
||||
import { SignInWithAzureAd } from "./idps/sign-in-with-azure-ad";
|
||||
@@ -13,7 +14,6 @@ import { SignInWithGithub } from "./idps/sign-in-with-github";
|
||||
import { SignInWithGitlab } from "./idps/sign-in-with-gitlab";
|
||||
import { SignInWithGoogle } from "./idps/sign-in-with-google";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
export interface SignInWithIDPProps {
|
||||
children?: ReactNode;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clsx } from "clsx";
|
||||
import { getComponentRoundness, getThemeConfig, SPACING_STYLES } from "@/lib/theme";
|
||||
import { ThemeableProps } from "@/lib/themeUtils";
|
||||
import { getThemeConfig, SPACING_STYLES, getComponentRoundness } from "@/lib/theme";
|
||||
import { clsx } from "clsx";
|
||||
|
||||
interface SkeletonCardProps extends ThemeableProps {
|
||||
isLoading?: boolean;
|
||||
|
||||
@@ -28,12 +28,6 @@ const getBadgeClasses = (state: BadgeState, evenPadding: boolean) =>
|
||||
"p-[2px]": evenPadding,
|
||||
});
|
||||
|
||||
export function StateBadge({
|
||||
state = BadgeState.Success,
|
||||
evenPadding = false,
|
||||
children,
|
||||
}: StateBadgeProps) {
|
||||
return (
|
||||
<span className={`${getBadgeClasses(state, evenPadding)}`}>{children}</span>
|
||||
);
|
||||
export function StateBadge({ state = BadgeState.Success, evenPadding = false, children }: StateBadgeProps) {
|
||||
return <span className={`${getBadgeClasses(state, evenPadding)}`}>{children}</span>;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,7 @@ import { clsx } from "clsx";
|
||||
import Link from "next/link";
|
||||
import { useSelectedLayoutSegment } from "next/navigation";
|
||||
|
||||
export const Tab = ({
|
||||
path,
|
||||
item: { slug, text },
|
||||
}: {
|
||||
path: string;
|
||||
item: Item;
|
||||
}) => {
|
||||
export const Tab = ({ path, item: { slug, text } }: { path: string; item: Item }) => {
|
||||
const segment = useSelectedLayoutSegment();
|
||||
const href = slug ? path + "/" + slug : path;
|
||||
const isActive =
|
||||
@@ -24,8 +18,7 @@ export const Tab = ({
|
||||
<Link
|
||||
href={href}
|
||||
className={clsx("mr-2 mt-2 rounded-lg px-3 py-1 text-sm font-medium", {
|
||||
"bg-gray-700 text-gray-100 hover:bg-gray-500 hover:text-white":
|
||||
!isActive,
|
||||
"bg-gray-700 text-gray-100 hover:bg-gray-500 hover:text-white": !isActive,
|
||||
"bg-blue-500 text-white": isActive,
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -4,12 +4,7 @@ import { ReactNode } from "react";
|
||||
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<ThemeP
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
storageKey="cp-theme"
|
||||
value={{ dark: "dark" }}
|
||||
>
|
||||
<ThemeP attribute="class" defaultTheme="system" storageKey="cp-theme" value={{ dark: "dark" }}>
|
||||
{children}
|
||||
</ThemeP>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { APPEARANCE_STYLES, getComponentRoundness, getThemeConfig } from "@/lib/theme";
|
||||
import { MoonIcon, SunIcon } from "@heroicons/react/24/outline";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useEffect, useState } from "react";
|
||||
import { getThemeConfig, getComponentRoundness, APPEARANCE_STYLES } from "@/lib/theme";
|
||||
|
||||
function getThemeToggleRoundness() {
|
||||
return getComponentRoundness("themeSwitch");
|
||||
@@ -51,14 +51,14 @@ export default function ThemeSwitch() {
|
||||
return (
|
||||
<div className={`flex space-x-1 p-1 ${toggleRoundness} ${cardAppearance}`}>
|
||||
<button
|
||||
className={`w-8 h-8 flex flex-row items-center justify-center ${toggleRoundness} transition-colors ${getSelectedButtonStyle(theme === "light")}`}
|
||||
className={`flex h-8 w-8 flex-row items-center justify-center ${toggleRoundness} transition-colors ${getSelectedButtonStyle(theme === "light")}`}
|
||||
onClick={() => setTheme("light")}
|
||||
aria-label="Switch to light mode"
|
||||
>
|
||||
<SunIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<button
|
||||
className={`w-8 h-8 flex flex-row items-center justify-center ${toggleRoundness} transition-colors ${getSelectedButtonStyle(theme === "dark")}`}
|
||||
className={`flex h-8 w-8 flex-row items-center justify-center ${toggleRoundness} transition-colors ${getSelectedButtonStyle(theme === "dark")}`}
|
||||
onClick={() => setTheme("dark")}
|
||||
aria-label="Switch to dark mode"
|
||||
>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import { setTheme } from "@/helpers/colors";
|
||||
import { BrandingSettings } from "@zitadel/proto/zitadel/settings/v2/branding_settings_pb";
|
||||
import { ReactNode, useEffect } from "react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { ReactNode, useEffect } from "react";
|
||||
|
||||
type Props = {
|
||||
branding: BrandingSettings | undefined;
|
||||
|
||||
@@ -27,9 +27,7 @@ describe("TotpRegister", () => {
|
||||
afterEach(cleanup);
|
||||
|
||||
test("should autofocus the code input on mount", () => {
|
||||
const { getByTestId } = render(
|
||||
<TotpRegister uri="otpauth://totp/test" secret="SECRET" />,
|
||||
);
|
||||
const { getByTestId } = render(<TotpRegister uri="otpauth://totp/test" secret="SECRET" />);
|
||||
expect(getByTestId("code-text-input")).toHaveFocus();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { completeFlowOrGetUrl } from "@/lib/client";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { verifyTOTP } from "@/lib/server/verify";
|
||||
import { LoginSettings } from "@zitadel/proto/zitadel/settings/v2/login_settings_pb";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { QRCodeSVG } from "qrcode.react";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { Alert } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { CopyToClipboard } from "./copy-to-clipboard";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { completeFlowOrGetUrl } from "@/lib/client";
|
||||
|
||||
type Inputs = {
|
||||
code: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Avatar } from "@/components/avatar";
|
||||
import { getComponentRoundness } from "@/lib/theme";
|
||||
import { ChevronDownIcon } from "@heroicons/react/24/outline";
|
||||
import Link from "next/link";
|
||||
import { getComponentRoundness } from "@/lib/theme";
|
||||
|
||||
// Helper function to get user avatar container roundness from theme
|
||||
function getUserAvatarRoundness(): string {
|
||||
|
||||
@@ -19,13 +19,7 @@ describe("UsernameForm", () => {
|
||||
|
||||
test("should autofocus the loginName input on mount", () => {
|
||||
const { getByTestId } = render(
|
||||
<UsernameForm
|
||||
loginName=""
|
||||
requestId={undefined}
|
||||
loginSettings={undefined}
|
||||
submit={false}
|
||||
allowRegister={false}
|
||||
/>,
|
||||
<UsernameForm loginName="" requestId={undefined} loginSettings={undefined} submit={false} allowRegister={false} />,
|
||||
);
|
||||
expect(getByTestId("username-text-input")).toHaveFocus();
|
||||
});
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { sendLoginname } from "@/lib/server/loginname";
|
||||
import { handleServerActionResponse } from "@/lib/client-utils";
|
||||
import { sendLoginname } from "@/lib/server/loginname";
|
||||
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";
|
||||
import { Alert } from "./alert";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
import { BackButton } from "./back-button";
|
||||
import { Button, ButtonVariants } from "./button";
|
||||
import { TextInput } from "./input";
|
||||
import { Spinner } from "./spinner";
|
||||
import { Translated } from "./translated";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { AutoSubmitForm } from "./auto-submit-form";
|
||||
|
||||
type Inputs = {
|
||||
loginName: string;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user