diff --git a/login/apps/login/locales/de.json b/login/apps/login/locales/de.json
index 75897a628e..7d3173bb94 100644
--- a/login/apps/login/locales/de.json
+++ b/login/apps/login/locales/de.json
@@ -6,7 +6,9 @@
"title": "Konten",
"description": "Wählen Sie das Konto aus, das Sie verwenden möchten.",
"addAnother": "Ein weiteres Konto hinzufügen",
- "noResults": "Keine Konten gefunden"
+ "noResults": "Keine Konten gefunden",
+ "verified": "verifiziert",
+ "expired": "abgelaufen"
},
"logout": {
"title": "Logout",
diff --git a/login/apps/login/locales/en.json b/login/apps/login/locales/en.json
index 9f95403063..eeecdd023f 100644
--- a/login/apps/login/locales/en.json
+++ b/login/apps/login/locales/en.json
@@ -6,7 +6,9 @@
"title": "Accounts",
"description": "Select the account you want to use.",
"addAnother": "Add another account",
- "noResults": "No accounts found"
+ "noResults": "No accounts found",
+ "verified": "verified",
+ "expired": "expired"
},
"logout": {
"title": "Logout",
diff --git a/login/apps/login/locales/es.json b/login/apps/login/locales/es.json
index fe88bb94c6..558dbd03b5 100644
--- a/login/apps/login/locales/es.json
+++ b/login/apps/login/locales/es.json
@@ -4,9 +4,11 @@
},
"accounts": {
"title": "Cuentas",
- "description": "Selecciona la cuenta que deseas usar.",
+ "description": "Seleccione la cuenta que desea utilizar.",
"addAnother": "Agregar otra cuenta",
- "noResults": "No se encontraron cuentas"
+ "noResults": "No se encontraron cuentas",
+ "verified": "verificado",
+ "expired": "expirado"
},
"logout": {
"title": "Cerrar sesión",
diff --git a/login/apps/login/locales/it.json b/login/apps/login/locales/it.json
index 1229a1a4c0..cc5a0b8c4f 100644
--- a/login/apps/login/locales/it.json
+++ b/login/apps/login/locales/it.json
@@ -4,9 +4,11 @@
},
"accounts": {
"title": "Account",
- "description": "Seleziona l'account che desideri utilizzare.",
+ "description": "Seleziona l'account che vuoi utilizzare.",
"addAnother": "Aggiungi un altro account",
- "noResults": "Nessun account trovato"
+ "noResults": "Nessun account trovato",
+ "verified": "verificato",
+ "expired": "scaduto"
},
"logout": {
"title": "Esci",
diff --git a/login/apps/login/locales/pl.json b/login/apps/login/locales/pl.json
index 9fea6a19fa..cd418d5c8c 100644
--- a/login/apps/login/locales/pl.json
+++ b/login/apps/login/locales/pl.json
@@ -6,7 +6,9 @@
"title": "Konta",
"description": "Wybierz konto, którego chcesz użyć.",
"addAnother": "Dodaj kolejne konto",
- "noResults": "Nie znaleziono kont"
+ "noResults": "Nie znaleziono kont",
+ "verified": "zweryfikowany",
+ "expired": "wygasł"
},
"logout": {
"title": "Wyloguj się",
diff --git a/login/apps/login/locales/ru.json b/login/apps/login/locales/ru.json
index e745f1ae59..e5f735db94 100644
--- a/login/apps/login/locales/ru.json
+++ b/login/apps/login/locales/ru.json
@@ -6,7 +6,9 @@
"title": "Аккаунты",
"description": "Выберите аккаунт, который хотите использовать.",
"addAnother": "Добавить другой аккаунт",
- "noResults": "Аккаунты не найдены"
+ "noResults": "Аккаунты не найдены",
+ "verified": "проверенный",
+ "expired": "истёк"
},
"logout": {
"title": "Выход",
diff --git a/login/apps/login/locales/zh.json b/login/apps/login/locales/zh.json
index 5a9cb3a4eb..73af30ab97 100644
--- a/login/apps/login/locales/zh.json
+++ b/login/apps/login/locales/zh.json
@@ -4,9 +4,11 @@
},
"accounts": {
"title": "账户",
- "description": "选择您想使用的账户。",
+ "description": "选择您要使用的账户。",
"addAnother": "添加另一个账户",
- "noResults": "未找到账户"
+ "noResults": "未找到账户",
+ "verified": "已验证",
+ "expired": "已过期"
},
"logout": {
"title": "注销",
diff --git a/login/apps/login/package.json b/login/apps/login/package.json
index 655aeb8f28..e45dbae7f9 100644
--- a/login/apps/login/package.json
+++ b/login/apps/login/package.json
@@ -25,6 +25,7 @@
"dependencies": {
"@headlessui/react": "^2.1.9",
"@heroicons/react": "2.1.3",
+ "@radix-ui/react-tooltip": "^1.2.7",
"@tailwindcss/forms": "0.5.7",
"@vercel/analytics": "^1.2.2",
"@zitadel/client": "latest",
diff --git a/login/apps/login/src/app/(login)/accounts/page.tsx b/login/apps/login/src/app/(login)/accounts/page.tsx
index cebcd014bb..e4e6b387dc 100644
--- a/login/apps/login/src/app/(login)/accounts/page.tsx
+++ b/login/apps/login/src/app/(login)/accounts/page.tsx
@@ -15,12 +15,12 @@ import { headers } from "next/headers";
import Link from "next/link";
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
- const ids: (string | undefined)[] = await getAllSessionCookieIds();
+ const cookieIds = await getAllSessionCookieIds();
- if (ids && ids.length) {
+ if (cookieIds && cookieIds.length) {
const response = await listSessions({
serviceUrl,
- ids: ids.filter((id) => !!id) as string[],
+ ids: cookieIds.filter((id) => !!id) as string[],
});
return response?.sessions ?? [];
} else {
diff --git a/login/apps/login/src/app/(login)/layout.tsx b/login/apps/login/src/app/(login)/layout.tsx
index 0a75a920f1..dbce9804c9 100644
--- a/login/apps/login/src/app/(login)/layout.tsx
+++ b/login/apps/login/src/app/(login)/layout.tsx
@@ -5,6 +5,7 @@ import { LanguageSwitcher } from "@/components/language-switcher";
import { Skeleton } from "@/components/skeleton";
import { Theme } from "@/components/theme";
import { ThemeProvider } from "@/components/theme-provider";
+import * as Tooltip from "@radix-ui/react-tooltip";
import { Analytics } from "@vercel/analytics/react";
import { Lato } from "next/font/google";
import { ReactNode, Suspense } from "react";
@@ -24,36 +25,38 @@ export default async function RootLayout({
-
-
- }
- >
-
-
-
- {children}
-
-
-
+
+
+
diff --git a/login/apps/login/src/app/(login)/logout/page.tsx b/login/apps/login/src/app/(login)/logout/page.tsx
index 853461d7d6..76e4dc3800 100644
--- a/login/apps/login/src/app/(login)/logout/page.tsx
+++ b/login/apps/login/src/app/(login)/logout/page.tsx
@@ -12,12 +12,12 @@ import { Organization } from "@zitadel/proto/zitadel/org/v2/org_pb";
import { headers } from "next/headers";
async function loadSessions({ serviceUrl }: { serviceUrl: string }) {
- const ids: (string | undefined)[] = await getAllSessionCookieIds();
+ const cookieIds = await getAllSessionCookieIds();
- if (ids && ids.length) {
+ if (cookieIds && cookieIds.length) {
const response = await listSessions({
serviceUrl,
- ids: ids.filter((id) => !!id) as string[],
+ ids: cookieIds.filter((id) => !!id) as string[],
});
return response?.sessions ?? [];
} else {
diff --git a/login/apps/login/src/app/(login)/otp/[method]/page.tsx b/login/apps/login/src/app/(login)/otp/[method]/page.tsx
index af4159d62e..94164c757f 100644
--- a/login/apps/login/src/app/(login)/otp/[method]/page.tsx
+++ b/login/apps/login/src/app/(login)/otp/[method]/page.tsx
@@ -41,17 +41,13 @@ export default async function Page(props: {
const { method } = params;
const session = sessionId
- ? await loadSessionById(serviceUrl, sessionId, organization)
+ ? await loadSessionById(sessionId, organization)
: await loadMostRecentSession({
serviceUrl,
sessionParams: { loginName, organization },
});
- async function loadSessionById(
- host: string,
- sessionId: string,
- organization?: string,
- ) {
+ async function loadSessionById(sessionId: string, organization?: string) {
const recent = await getSessionCookieById({ sessionId, organization });
return getSession({
serviceUrl,
diff --git a/login/apps/login/src/app/(login)/otp/[method]/set/page.tsx b/login/apps/login/src/app/(login)/otp/[method]/set/page.tsx
index f74093ce8e..b43bb973f5 100644
--- a/login/apps/login/src/app/(login)/otp/[method]/set/page.tsx
+++ b/login/apps/login/src/app/(login)/otp/[method]/set/page.tsx
@@ -66,7 +66,6 @@ export default async function Page(props: {
error = err;
});
} else if (method === "sms") {
- // does not work
await addOTPSMS({
serviceUrl,
userId: session.factors.user.id,
@@ -74,7 +73,6 @@ export default async function Page(props: {
error = new Error("Could not add OTP via SMS");
});
} else if (method === "email") {
- // works
await addOTPEmail({
serviceUrl,
userId: session.factors.user.id,
@@ -106,6 +104,7 @@ export default async function Page(props: {
paramsToContinue.append("requestId", requestId);
}
urlToContinue = `/otp/${method}?` + paramsToContinue;
+
// immediately check the OTP on the next page if sms or email was set up
if (["email", "sms"].includes(method)) {
return redirect(urlToContinue);
diff --git a/login/apps/login/src/components/session-item.tsx b/login/apps/login/src/components/session-item.tsx
index f189d05828..7faeade9b0 100644
--- a/login/apps/login/src/components/session-item.tsx
+++ b/login/apps/login/src/components/session-item.tsx
@@ -3,6 +3,7 @@
import { sendLoginname } from "@/lib/server/loginname";
import { clearSession, continueWithSession } from "@/lib/server/session";
import { XCircleIcon } from "@heroicons/react/24/outline";
+import * as Tooltip from "@radix-ui/react-tooltip";
import { Timestamp, timestampDate } from "@zitadel/client";
import { Session } from "@zitadel/proto/zitadel/session/v2/session_pb";
import moment from "moment";
@@ -10,6 +11,7 @@ import { useLocale } from "next-intl";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { Avatar } from "./avatar";
+import { Translated } from "./translated";
export function isSessionValid(session: Partial
): {
valid: boolean;
@@ -66,91 +68,107 @@ export function SessionItem({
const router = useRouter();
return (
-
+
+ {valid && session.expirationDate && (
+
+
+ Expires {moment(timestampDate(session.expirationDate)).fromNow()}
+
+
+
+ )}
+
);
}
diff --git a/login/apps/login/src/lib/cookies.ts b/login/apps/login/src/lib/cookies.ts
index 45463d7a4d..71008de24e 100644
--- a/login/apps/login/src/lib/cookies.ts
+++ b/login/apps/login/src/lib/cookies.ts
@@ -242,7 +242,7 @@ export async function getSessionCookieByLoginName({
*/
export async function getAllSessionCookieIds(
cleanup: boolean = false,
-): Promise {
+): Promise {
const cookiesList = await cookies();
const stringifiedCookie = cookiesList.get("sessions");
diff --git a/login/apps/login/src/lib/server/cookie.ts b/login/apps/login/src/lib/server/cookie.ts
index 841fc06b3a..7f87f49731 100644
--- a/login/apps/login/src/lib/server/cookie.ts
+++ b/login/apps/login/src/lib/server/cookie.ts
@@ -55,10 +55,21 @@ export async function createSessionAndUpdateCookie(command: {
const _headers = await headers();
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
+ let sessionLifetime = command.lifetime;
+
+ if (!sessionLifetime) {
+ console.warn("No session lifetime provided, using default of 24 hours.");
+
+ sessionLifetime = {
+ seconds: BigInt(24 * 60 * 60), // 24 hours
+ nanos: 0,
+ } as Duration; // for usecases where the lifetime is not specified (user discovery)
+ }
+
const createdSession = await createSessionFromChecks({
serviceUrl,
checks: command.checks,
- lifetime: command.lifetime,
+ lifetime: sessionLifetime,
});
if (createdSession) {
@@ -126,11 +137,24 @@ export async function createSessionForIdpAndUpdateCookie({
const _headers = await headers();
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
+ let sessionLifetime = lifetime;
+
+ if (!sessionLifetime) {
+ console.warn(
+ "No IDP session lifetime provided, using default of 24 hours.",
+ );
+
+ sessionLifetime = {
+ seconds: BigInt(24 * 60 * 60), // 24 hours
+ nanos: 0,
+ } as Duration;
+ }
+
const createdSession = await createSessionForUserIdAndIdpIntent({
serviceUrl,
userId,
idpIntent,
- lifetime,
+ lifetime: sessionLifetime,
}).catch((error: ErrorDetail | CredentialsCheckError) => {
console.error("Could not set session", error);
if ("failedAttempts" in error && error.failedAttempts) {
@@ -190,41 +214,41 @@ export type SessionWithChallenges = Session & {
challenges: Challenges | undefined;
};
-export async function setSessionAndUpdateCookie(
- recentCookie: CustomCookieData,
- checks?: Checks,
- challenges?: RequestChallenges,
- requestId?: string,
- lifetime?: Duration,
-) {
+export async function setSessionAndUpdateCookie(command: {
+ recentCookie: CustomCookieData;
+ checks?: Checks;
+ challenges?: RequestChallenges;
+ requestId?: string;
+ lifetime: Duration;
+}) {
const _headers = await headers();
const { serviceUrl } = getServiceUrlFromHeaders(_headers);
return setSession({
serviceUrl,
- sessionId: recentCookie.id,
- sessionToken: recentCookie.token,
- challenges,
- checks,
- lifetime,
+ sessionId: command.recentCookie.id,
+ sessionToken: command.recentCookie.token,
+ challenges: command.challenges,
+ checks: command.checks,
+ lifetime: command.lifetime,
})
.then((updatedSession) => {
if (updatedSession) {
const sessionCookie: CustomCookieData = {
- id: recentCookie.id,
+ id: command.recentCookie.id,
token: updatedSession.sessionToken,
- creationTs: recentCookie.creationTs,
- expirationTs: recentCookie.expirationTs,
+ creationTs: command.recentCookie.creationTs,
+ expirationTs: command.recentCookie.expirationTs,
// just overwrite the changeDate with the new one
changeTs: updatedSession.details?.changeDate
? `${timestampMs(updatedSession.details.changeDate)}`
: "",
- loginName: recentCookie.loginName,
- organization: recentCookie.organization,
+ loginName: command.recentCookie.loginName,
+ organization: command.recentCookie.organization,
};
- if (requestId) {
- sessionCookie.requestId = requestId;
+ if (command.requestId) {
+ sessionCookie.requestId = command.requestId;
}
return getSession({
diff --git a/login/apps/login/src/lib/server/otp.ts b/login/apps/login/src/lib/server/otp.ts
deleted file mode 100644
index f3d4a1536a..0000000000
--- a/login/apps/login/src/lib/server/otp.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-"use server";
-
-import { setSessionAndUpdateCookie } from "@/lib/server/cookie";
-import { create } from "@zitadel/client";
-import {
- CheckOTPSchema,
- ChecksSchema,
- CheckTOTPSchema,
-} from "@zitadel/proto/zitadel/session/v2/session_service_pb";
-import { headers } from "next/headers";
-import {
- getMostRecentSessionCookie,
- getSessionCookieById,
- getSessionCookieByLoginName,
-} from "../cookies";
-import { getServiceUrlFromHeaders } from "../service-url";
-import { getLoginSettings } from "../zitadel";
-
-export type SetOTPCommand = {
- loginName?: string;
- sessionId?: string;
- organization?: string;
- requestId?: string;
- code: string;
- method: string;
-};
-
-export async function setOTP(command: SetOTPCommand) {
- const _headers = await headers();
- const { serviceUrl } = getServiceUrlFromHeaders(_headers);
-
- const recentSession = command.sessionId
- ? await getSessionCookieById({ sessionId: command.sessionId }).catch(
- (error) => {
- return Promise.reject(error);
- },
- )
- : command.loginName
- ? await getSessionCookieByLoginName({
- loginName: command.loginName,
- organization: command.organization,
- }).catch((error) => {
- return Promise.reject(error);
- })
- : await getMostRecentSessionCookie().catch((error) => {
- return Promise.reject(error);
- });
-
- const checks = create(ChecksSchema, {});
-
- if (command.method === "time-based") {
- checks.totp = create(CheckTOTPSchema, {
- code: command.code,
- });
- } else if (command.method === "sms") {
- checks.otpSms = create(CheckOTPSchema, {
- code: command.code,
- });
- } else if (command.method === "email") {
- checks.otpEmail = create(CheckOTPSchema, {
- code: command.code,
- });
- }
-
- const loginSettings = await getLoginSettings({
- serviceUrl,
- organization: command.organization,
- });
-
- return setSessionAndUpdateCookie(
- recentSession,
- checks,
- undefined,
- command.requestId,
- loginSettings?.secondFactorCheckLifetime,
- ).then((session) => {
- return {
- sessionId: session.id,
- factors: session.factors,
- challenges: session.challenges,
- };
- });
-}
diff --git a/login/apps/login/src/lib/server/passkeys.ts b/login/apps/login/src/lib/server/passkeys.ts
index 60aa2c92b4..ca603471ab 100644
--- a/login/apps/login/src/lib/server/passkeys.ts
+++ b/login/apps/login/src/lib/server/passkeys.ts
@@ -211,19 +211,27 @@ export async function sendPasskey(command: SendPasskeyCommand) {
organization,
});
- const lifetime = checks?.webAuthN
+ let lifetime = checks?.webAuthN
? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
: checks?.otpEmail || checks?.otpSms
? loginSettings?.secondFactorCheckLifetime
: undefined;
- const session = await setSessionAndUpdateCookie(
- recentSession,
+ if (!lifetime) {
+ console.warn("No passkey lifetime provided, defaulting to 24 hours");
+
+ lifetime = {
+ seconds: BigInt(60 * 60 * 24), // default to 24 hours
+ nanos: 0,
+ } as Duration;
+ }
+
+ const session = await setSessionAndUpdateCookie({
+ recentCookie: recentSession,
checks,
- undefined,
requestId,
lifetime,
- );
+ });
if (!session || !session?.factors?.user?.id) {
return { error: "Could not update session" };
diff --git a/login/apps/login/src/lib/server/password.ts b/login/apps/login/src/lib/server/password.ts
index 5c6fb03aa5..013255d06f 100644
--- a/login/apps/login/src/lib/server/password.ts
+++ b/login/apps/login/src/lib/server/password.ts
@@ -16,7 +16,7 @@ import {
setPassword,
setUserPassword,
} from "@/lib/zitadel";
-import { ConnectError, create } from "@zitadel/client";
+import { ConnectError, create, Duration } from "@zitadel/client";
import { createUserServiceClient } from "@zitadel/client/v2";
import {
Checks,
@@ -152,14 +152,32 @@ export async function sendPassword(command: UpdateSessionCommand) {
// this is a fake error message to hide that the user does not even exist
return { error: "Could not verify password" };
} else {
+ loginSettings = await getLoginSettings({
+ serviceUrl,
+ organization: sessionCookie.organization,
+ });
+
+ if (!loginSettings) {
+ return { error: "Could not load login settings" };
+ }
+
+ let lifetime = loginSettings.passwordCheckLifetime;
+
+ if (!lifetime) {
+ console.warn("No password lifetime provided, defaulting to 24 hours");
+ lifetime = {
+ seconds: BigInt(60 * 60 * 24), // default to 24 hours
+ nanos: 0,
+ } as Duration;
+ }
+
try {
- session = await setSessionAndUpdateCookie(
- sessionCookie,
- command.checks,
- undefined,
- command.requestId,
- loginSettings?.passwordCheckLifetime,
- );
+ session = await setSessionAndUpdateCookie({
+ recentCookie: sessionCookie,
+ checks: command.checks,
+ requestId: command.requestId,
+ lifetime,
+ });
} catch (error: any) {
if ("failedAttempts" in error && error.failedAttempts) {
const lockoutSettings = await getLockoutSettings({
diff --git a/login/apps/login/src/lib/server/session.ts b/login/apps/login/src/lib/server/session.ts
index d04d1d25b4..957c89ad81 100644
--- a/login/apps/login/src/lib/server/session.ts
+++ b/login/apps/login/src/lib/server/session.ts
@@ -154,19 +154,27 @@ export async function updateSession(options: UpdateSessionCommand) {
organization,
});
- const lifetime = checks?.webAuthN
+ let lifetime = checks?.webAuthN
? loginSettings?.multiFactorCheckLifetime // TODO different lifetime for webauthn u2f/passkey
: checks?.otpEmail || checks?.otpSms
? loginSettings?.secondFactorCheckLifetime
: undefined;
- const session = await setSessionAndUpdateCookie(
- recentSession,
+ if (!lifetime) {
+ console.warn("No lifetime provided for session, defaulting to 24 hours");
+ lifetime = {
+ seconds: BigInt(60 * 60 * 24), // default to 24 hours
+ nanos: 0,
+ } as Duration;
+ }
+
+ const session = await setSessionAndUpdateCookie({
+ recentCookie: recentSession,
checks,
challenges,
requestId,
lifetime,
- );
+ });
if (!session) {
return { error: "Could not update session" };
diff --git a/login/apps/login/src/lib/zitadel.ts b/login/apps/login/src/lib/zitadel.ts
index bad773092c..5e4583f8af 100644
--- a/login/apps/login/src/lib/zitadel.ts
+++ b/login/apps/login/src/lib/zitadel.ts
@@ -298,7 +298,7 @@ export async function createSessionFromChecks({
}: {
serviceUrl: string;
checks: Checks;
- lifetime?: Duration;
+ lifetime: Duration;
}) {
const sessionService: Client =
await createServiceForHost(SessionService, serviceUrl);
@@ -320,7 +320,7 @@ export async function createSessionForUserIdAndIdpIntent({
idpIntentId?: string | undefined;
idpIntentToken?: string | undefined;
};
- lifetime?: Duration;
+ lifetime: Duration;
}) {
const sessionService: Client =
await createServiceForHost(SessionService, serviceUrl);
@@ -355,7 +355,7 @@ export async function setSession({
sessionToken: string;
challenges: RequestChallenges | undefined;
checks?: Checks;
- lifetime?: Duration;
+ lifetime: Duration;
}) {
const sessionService: Client =
await createServiceForHost(SessionService, serviceUrl);
diff --git a/login/apps/login/turbo.json b/login/apps/login/turbo.json
index f05cd41d01..db0e93d5ed 100644
--- a/login/apps/login/turbo.json
+++ b/login/apps/login/turbo.json
@@ -8,6 +8,9 @@
"build:login:standalone": {
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
+ "dev": {
+ "dependsOn": ["@zitadel/client#build", "@zitadel/proto#generate"]
+ },
"test": {
"dependsOn": ["@zitadel/client#build"]
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a8a22a41ce..901b332f2d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -421,6 +421,9 @@ importers:
'@heroicons/react':
specifier: 2.1.3
version: 2.1.3(react@19.1.0)
+ '@radix-ui/react-tooltip':
+ specifier: ^1.2.7
+ version: 1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@tailwindcss/forms':
specifier: 0.5.7
version: 0.5.7(tailwindcss@3.4.14)
@@ -4259,6 +4262,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-tooltip@1.2.7':
+ resolution: {integrity: sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-use-callback-ref@1.1.0':
resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
peerDependencies:
@@ -4398,6 +4414,19 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-visually-hidden@1.2.3':
+ resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
@@ -19496,6 +19525,15 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
'@radix-ui/react-avatar@1.1.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-context': 1.1.1(@types/react@19.1.2)(react@18.3.1)
@@ -19548,6 +19586,12 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-context@1.1.1(@types/react@19.1.2)(react@18.3.1)':
dependencies:
react: 18.3.1
@@ -19560,6 +19604,12 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-context@1.1.2(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-direction@1.1.0(@types/react@19.1.2)(react@18.3.1)':
dependencies:
react: 18.3.1
@@ -19585,6 +19635,19 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
'@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.1
@@ -19676,6 +19739,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-id@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-popover@1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.1
@@ -19753,6 +19823,24 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/rect': 1.1.1
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
'@radix-ui/react-portal@1.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -19783,6 +19871,16 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
'@radix-ui/react-presence@1.1.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.1(@types/react@19.1.2)(react@18.3.1)
@@ -19803,6 +19901,16 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
'@radix-ui/react-primitive@2.0.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-slot': 1.1.1(@types/react@19.1.2)(react@18.3.1)
@@ -19830,6 +19938,15 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
@@ -19885,6 +20002,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-slot@1.2.3(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-tabs@1.1.12(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
@@ -19921,6 +20045,26 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-tooltip@1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.2
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-popper': 1.2.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
'@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.1.2)(react@18.3.1)':
dependencies:
react: 18.3.1
@@ -19933,6 +20077,12 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.1.2)(react@18.3.1)':
dependencies:
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.2)(react@18.3.1)
@@ -19948,6 +20098,14 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.2)(react@19.1.0)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.2)(react@18.3.1)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@18.3.1)
@@ -19955,6 +20113,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.1.2)(react@18.3.1)':
dependencies:
'@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.1.2)(react@18.3.1)
@@ -19969,6 +20134,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.1.2)(react@18.3.1)':
dependencies:
react: 18.3.1
@@ -19981,6 +20153,12 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-use-previous@1.1.0(@types/react@19.1.2)(react@18.3.1)':
dependencies:
react: 18.3.1
@@ -20001,6 +20179,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/rect': 1.1.1
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-use-size@1.1.0(@types/react@19.1.2)(react@18.3.1)':
dependencies:
'@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.1.2)(react@18.3.1)
@@ -20015,6 +20200,13 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
+ '@radix-ui/react-use-size@1.1.1(@types/react@19.1.2)(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.2)(react@19.1.0)
+ react: 19.1.0
+ optionalDependencies:
+ '@types/react': 19.1.2
+
'@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-primitive': 2.0.1(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -20024,6 +20216,15 @@ snapshots:
'@types/react': 19.1.2
'@types/react-dom': 19.1.2(@types/react@19.1.2)
+ '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ optionalDependencies:
+ '@types/react': 19.1.2
+ '@types/react-dom': 19.1.2(@types/react@19.1.2)
+
'@radix-ui/rect@1.1.0': {}
'@radix-ui/rect@1.1.1': {}