diff --git a/Cargo.lock b/Cargo.lock
index 8c7a11a..2c3aa0a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -314,6 +314,17 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "axum-client-ip"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8ba1af5b620232acf37f2eb6d22151ea465491e0b4c25f552d1990f64ec5a67"
+dependencies = [
+ "axum",
+ "client-ip",
+ "serde",
+]
+
[[package]]
name = "axum-core"
version = "0.5.6"
@@ -723,6 +734,15 @@ dependencies = [
"inout",
]
+[[package]]
+name = "client-ip"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39d2056bf065c8b4bce5a8898d40e175211ff4410add2a84d695845d3937c729"
+dependencies = [
+ "http",
+]
+
[[package]]
name = "cmac"
version = "0.7.2"
@@ -1142,6 +1162,20 @@ dependencies = [
"syn 2.0.114",
]
+[[package]]
+name = "dashmap"
+version = "6.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+ "hashbrown 0.14.5",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
[[package]]
name = "data-encoding"
version = "2.10.0"
@@ -1586,6 +1620,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
[[package]]
name = "foreign-types"
version = "0.3.2"
@@ -1610,6 +1650,16 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "forwarded-header-value"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
+dependencies = [
+ "nonempty",
+ "thiserror 1.0.69",
+]
+
[[package]]
name = "fred"
version = "10.1.0"
@@ -1735,6 +1785,12 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
+[[package]]
+name = "futures-timer"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
+
[[package]]
name = "futures-util"
version = "0.3.32"
@@ -1830,6 +1886,29 @@ version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
+[[package]]
+name = "governor"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9efcab3c1958580ff1f25a2a41be1668f7603d849bb63af523b208a3cc1223b8"
+dependencies = [
+ "cfg-if",
+ "dashmap",
+ "futures-sink",
+ "futures-timer",
+ "futures-util",
+ "getrandom 0.3.4",
+ "hashbrown 0.16.1",
+ "nonzero_ext",
+ "parking_lot",
+ "portable-atomic",
+ "quanta",
+ "rand 0.9.2",
+ "smallvec",
+ "spinning_top",
+ "web-time",
+]
+
[[package]]
name = "group"
version = "0.13.0"
@@ -1894,7 +1973,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
- "foldhash",
+ "foldhash 0.1.5",
]
[[package]]
@@ -1902,6 +1981,11 @@ name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
+]
[[package]]
name = "hashlink"
@@ -2168,6 +2252,19 @@ dependencies = [
"webpki-roots 1.0.5",
]
+[[package]]
+name = "hyper-timeout"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
+dependencies = [
+ "hyper",
+ "hyper-util",
+ "pin-project-lite",
+ "tokio",
+ "tower-service",
+]
+
[[package]]
name = "hyper-util"
version = "0.1.19"
@@ -2504,6 +2601,21 @@ dependencies = [
"serde",
]
+[[package]]
+name = "jsonwebtoken"
+version = "9.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
+dependencies = [
+ "base64 0.22.1",
+ "js-sys",
+ "pem",
+ "ring",
+ "serde",
+ "serde_json",
+ "simple_asn1",
+]
+
[[package]]
name = "k256"
version = "0.13.4"
@@ -2968,6 +3080,18 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "nonempty"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
+
+[[package]]
+name = "nonzero_ext"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
+
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@@ -3006,9 +3130,9 @@ dependencies = [
[[package]]
name = "num-conv"
-version = "0.1.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
[[package]]
name = "num-integer"
@@ -3382,6 +3506,16 @@ dependencies = [
"hmac",
]
+[[package]]
+name = "pem"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
+dependencies = [
+ "base64 0.22.1",
+ "serde_core",
+]
+
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@@ -3518,6 +3652,26 @@ dependencies = [
"serde",
]
+[[package]]
+name = "pin-project"
+version = "1.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.114",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.16"
@@ -3725,6 +3879,21 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "quanta"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
+dependencies = [
+ "crossbeam-utils",
+ "libc",
+ "once_cell",
+ "raw-cpuid",
+ "wasi",
+ "web-sys",
+ "winapi",
+]
+
[[package]]
name = "quick-xml"
version = "0.32.0"
@@ -3892,6 +4061,15 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
+[[package]]
+name = "raw-cpuid"
+version = "11.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
+dependencies = [
+ "bitflags",
+]
+
[[package]]
name = "redis-protocol"
version = "6.0.0"
@@ -4718,6 +4896,7 @@ dependencies = [
"async-trait",
"auth_core",
"axum",
+ "axum-client-ip",
"axum-valid",
"base32",
"base64 0.22.1",
@@ -4730,6 +4909,7 @@ dependencies = [
"hex",
"http",
"http-serde-ext",
+ "jsonwebtoken",
"macros",
"mail",
"mongodb",
@@ -4741,6 +4921,7 @@ dependencies = [
"rand_core 0.10.0",
"regex",
"reqwest 0.13.2",
+ "rsa",
"sea-orm",
"serde",
"serde_json",
@@ -4754,10 +4935,12 @@ dependencies = [
"tower-http",
"tower-sessions",
"tower-sessions-redis-store",
+ "tower_governor",
"tracing",
"tracing-error",
"tracing-subscriber",
"url",
+ "urlencoding",
"utoipa",
"utoipa-axum",
"utoipa-rapidoc",
@@ -4860,6 +5043,18 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+[[package]]
+name = "simple_asn1"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "thiserror 2.0.17",
+ "time",
+]
+
[[package]]
name = "slab"
version = "0.4.11"
@@ -4931,6 +5126,15 @@ dependencies = [
"lock_api",
]
+[[package]]
+name = "spinning_top"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
+dependencies = [
+ "lock_api",
+]
+
[[package]]
name = "spki"
version = "0.7.3"
@@ -5319,9 +5523,9 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.44"
+version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
@@ -5329,22 +5533,22 @@ dependencies = [
"num-conv",
"num_threads",
"powerfmt",
- "serde",
+ "serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
-version = "0.1.6"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
-version = "0.2.24"
+version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
+checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
@@ -5517,6 +5721,35 @@ version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
+[[package]]
+name = "tonic"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec"
+dependencies = [
+ "async-trait",
+ "axum",
+ "base64 0.22.1",
+ "bytes",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-timeout",
+ "hyper-util",
+ "percent-encoding",
+ "pin-project",
+ "socket2 0.6.1",
+ "sync_wrapper",
+ "tokio",
+ "tokio-stream",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
[[package]]
name = "totp-rs"
version = "5.7.1"
@@ -5665,6 +5898,23 @@ dependencies = [
"tower-sessions-core",
]
+[[package]]
+name = "tower_governor"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44de9b94d849d3c46e06a883d72d408c2de6403367b39df2b1c9d9e7b6736fe6"
+dependencies = [
+ "axum",
+ "forwarded-header-value",
+ "governor",
+ "http",
+ "pin-project",
+ "thiserror 2.0.17",
+ "tonic",
+ "tower",
+ "tracing",
+]
+
[[package]]
name = "tracing"
version = "0.1.44"
@@ -6289,6 +6539,28 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
[[package]]
name = "windows-core"
version = "0.62.2"
diff --git a/apps/frontend/app/(loginform)/confirm-email/page.tsx b/apps/frontend/app/(loginform)/confirm-email/page.tsx
new file mode 100644
index 0000000..ccb697e
--- /dev/null
+++ b/apps/frontend/app/(loginform)/confirm-email/page.tsx
@@ -0,0 +1,70 @@
+'use client';
+
+import { LoginIcon } from '@components/ui/login-icon';
+import { Button } from '@components/ui/button';
+import { IconCheck, IconMail, IconX } from '@tabler/icons-react';
+import Link from 'next/link';
+import { useSearchParams } from 'next/navigation';
+import { Suspense } from 'react';
+
+function ConfirmEmail() {
+ const searchParams = useSearchParams();
+ const status = searchParams.get('status');
+ const reason = searchParams.get('reason');
+
+ if (status === 'success') {
+ return (
+
+
+
+
Email confirmed!
+
Your email address has been verified.
+
+
+
+ Go to dashboard
+
+
+
+ );
+ }
+
+ if (status === 'error') {
+ const message = reason === 'expired'
+ ? 'This confirmation link has expired.'
+ : 'This confirmation link is invalid or has already been used.';
+
+ return (
+
+
+
+
{reason === 'expired' ? 'Link expired' : 'Invalid link'}
+
{message}
+
+
+
+ Back to dashboard
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
Invalid link
+
This confirmation link is missing required parameters.
+
+
+ );
+}
+
+export default function Page() {
+ return (
+
+
+
+ );
+}
diff --git a/apps/frontend/app/(loginform)/forgot-password/page.tsx b/apps/frontend/app/(loginform)/forgot-password/page.tsx
index 4dc4a4f..35ccc3e 100644
--- a/apps/frontend/app/(loginform)/forgot-password/page.tsx
+++ b/apps/frontend/app/(loginform)/forgot-password/page.tsx
@@ -6,12 +6,13 @@ import { Input } from '@components/ui/input';
import { LoginIcon } from '@components/ui/login-icon';
import { LinkComponent } from '@components/ui/link';
import { $api } from '@lib/providers/api';
-import { IconArrowRight, IconLock } from '@tabler/icons-react';
+import { IconArrowRight, IconLock, IconLoader2 } from '@tabler/icons-react';
import { AnimatePresence, motion } from 'motion/react';
import Link from 'next/link';
-import { useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
+import { useSearchParams } from 'next/navigation';
import z from 'zod';
const schema = z.object({
@@ -19,23 +20,35 @@ const schema = z.object({
});
type Schema = z.infer;
-type Step = 'form' | 'sent';
+type Step = 'form' | 'sending' | 'sent';
export default function Page() {
- const [step, setStep] = useState('form');
+ const searchParams = useSearchParams();
+ const prefillEmail = searchParams.get('email') ?? '';
+ const isValidEmail = schema.safeParse({ email: prefillEmail }).success;
+ const [step, setStep] = useState(isValidEmail ? 'sending' : 'form');
+ const autoSubmitted = useRef(false);
const form = useForm({
resolver: zodResolver(schema),
- defaultValues: { email: '' },
+ defaultValues: { email: prefillEmail },
});
const requestReset = $api.useMutation('post', '/api/password-reset', {
onSuccess: () => setStep('sent'),
onError: (e) => {
+ setStep('form');
form.setError('email', { message: (e as any)?.error || 'Something went wrong.' });
},
});
+ useEffect(() => {
+ if (isValidEmail && !autoSubmitted.current) {
+ autoSubmitted.current = true;
+ requestReset.mutate({ body: { email: prefillEmail } });
+ }
+ }, []);
+
return (
@@ -94,6 +107,25 @@ export default function Page() {
+ ) : step === 'sending' ? (
+
+
+
+
+
+
Sending reset link...
+
+ Please wait while we send you a password reset email.
+
+
+
) : (
= {
export function LoginOptions() {
const setScreen = useSetAtom(screenAtom);
const options = useAtomValue(optionsAtom);
+ const form = useFormContext();
+ const username = form.watch('username');
return (
@@ -64,7 +67,7 @@ export function LoginOptions() {
{options?.includes('password') && (
- Forgot Password?
+ Forgot Password?
)}
diff --git a/apps/frontend/app/(loginform)/login/welcome.tsx b/apps/frontend/app/(loginform)/login/welcome.tsx
index 7c2c409..fc652ee 100644
--- a/apps/frontend/app/(loginform)/login/welcome.tsx
+++ b/apps/frontend/app/(loginform)/login/welcome.tsx
@@ -27,7 +27,7 @@ export function Welcome() {
},
onError: (e) => {
form.setError('username', {
- message: e?.error || 'Login failed.',
+ message: (e as { error?: string })?.error || 'Login failed.',
});
},
});
diff --git a/apps/frontend/app/(loginform)/register/page.tsx b/apps/frontend/app/(loginform)/register/page.tsx
index fd85093..cf8b569 100644
--- a/apps/frontend/app/(loginform)/register/page.tsx
+++ b/apps/frontend/app/(loginform)/register/page.tsx
@@ -6,6 +6,7 @@ import { LinkComponent } from '@components/ui/link';
import z from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { $api } from '@lib/providers/api';
+import { getApiErrorMessage } from '@lib/api-error';
import { useCallback, useState } from 'react';
import { AnimatePresence, motion } from 'motion/react';
import { useRouter } from 'next/navigation';
@@ -54,33 +55,36 @@ export default function Page() {
},
});
- const register = $api.useMutation('post', '/api/register', {
- onSuccess: () => {
- setStep('success');
- setError(null);
- },
- onError: (e) => {
- setError(e?.error || 'Registration failed.');
- },
- });
+ const register = $api.useMutation('post', '/api/register');
const handleNext = useCallback(async () => {
const valid = await form.trigger(['first_name', 'last_name', 'display_name']);
if (valid) setStep('credentials');
}, []);
- const handleSubmit = form.handleSubmit((data) => {
+ const handleSubmit = form.handleSubmit(async (data) => {
setError(null);
- register.mutate({
- body: {
- first_name: data.first_name,
- last_name: data.last_name,
- display_name: data.display_name,
- preferred_username: data.preferred_username,
- email: data.email,
- password: data.password,
- },
- });
+ try {
+ const response = await register.mutateAsync({
+ body: {
+ first_name: data.first_name,
+ last_name: data.last_name,
+ display_name: data.display_name,
+ preferred_username: data.preferred_username,
+ email: data.email,
+ password: data.password,
+ },
+ });
+
+ if (!response?.success) {
+ setError('Registration failed.');
+ return;
+ }
+
+ setStep('success');
+ } catch (error) {
+ setError(getApiErrorMessage(error, 'Registration failed.'));
+ }
});
return (
diff --git a/apps/frontend/app/dashboard/components/dashboard-header.tsx b/apps/frontend/app/dashboard/components/dashboard-header.tsx
new file mode 100644
index 0000000..89aa9f1
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/dashboard-header.tsx
@@ -0,0 +1,37 @@
+'use client';
+
+import { motion } from 'motion/react';
+import { Button } from '@components/ui/button';
+import { IconLogout, IconUser } from '@tabler/icons-react';
+
+export function DashboardHeader({ profile, onLogout, loggingOut }: {
+ profile?: { display_name: string; email: string; email_confirmed: boolean };
+ onLogout: () => void;
+ loggingOut: boolean;
+}) {
+ return (
+
+
+
Account Security
+ {profile ? (
+
+
+ {profile.display_name}
+ ·
+ {profile.email}
+ {!profile.email_confirmed && (
+ Unverified
+ )}
+
+ ) : (
+
Keep your account info up to date to ensure you always have access.
+ )}
+
+
+
+ {loggingOut ? 'Logging out…' : 'Log out'}
+
+
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/dashboard-status.tsx b/apps/frontend/app/dashboard/components/dashboard-status.tsx
new file mode 100644
index 0000000..a13a296
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/dashboard-status.tsx
@@ -0,0 +1,31 @@
+export function DashboardSkeleton() {
+ return (
+
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+
+ );
+}
+
+export function DashboardError() {
+ return (
+
+
Failed to load - make sure you are signed in.
+
+ );
+}
+
+export function DashboardWarning({ message }: { message: string }) {
+ return (
+
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/delete-account-section.tsx b/apps/frontend/app/dashboard/components/delete-account-section.tsx
new file mode 100644
index 0000000..dde00d5
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/delete-account-section.tsx
@@ -0,0 +1,73 @@
+'use client';
+
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import z from 'zod';
+import { $api } from '@lib/providers/api';
+import { useRouter } from 'next/navigation';
+import { Button } from '@components/ui/button';
+import { IconTrash } from '@tabler/icons-react';
+import { ExpandForm, PasswordInput } from './helpers';
+import { Form, FormControl, FormField, FormItem, FormMessage } from '@components/ui/form';
+import { useState } from 'react';
+
+const deleteSchema = z.object({
+ password: z.string().min(1, 'Required'),
+});
+type DeleteForm = z.infer
;
+
+export function DeleteAccountSection() {
+ const router = useRouter();
+ const [open, setOpen] = useState(false);
+
+ const form = useForm({
+ resolver: zodResolver(deleteSchema),
+ defaultValues: { password: '' },
+ });
+
+ const deleteAccount = $api.useMutation('delete', '/api/settings/account', {
+ onSuccess: () => router.push('/login'),
+ onError: () => {
+ form.setError('password', { message: 'Incorrect password or failed to delete.' });
+ },
+ });
+
+ return (
+
+
+
+
Delete account
+
Permanently remove your account and all associated data.
+
+ {!open && (
+
setOpen(true)}>
+ Delete
+
+ )}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/factor-key-item.tsx b/apps/frontend/app/dashboard/components/factor-key-item.tsx
new file mode 100644
index 0000000..ffb208f
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/factor-key-item.tsx
@@ -0,0 +1,26 @@
+import { Button } from '@components/ui/button';
+
+export function FactorKeyItem({ icon, name, subtitle, onRemove }: {
+ icon: React.ReactNode;
+ name: string;
+ subtitle: string;
+ onRemove: () => void;
+}) {
+ return (
+
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/factor-row.tsx b/apps/frontend/app/dashboard/components/factor-row.tsx
new file mode 100644
index 0000000..74b3216
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/factor-row.tsx
@@ -0,0 +1,42 @@
+'use client';
+
+import { motion } from 'motion/react';
+import { IconChevronRight } from '@tabler/icons-react';
+
+export function StatusTag({ label, enabled }: { label: string; enabled: boolean }) {
+ return (
+
+ {label}
+
+ );
+}
+
+export function FactorRow({
+ icon, name, description, tag, onToggle, children, last, open,
+}: {
+ icon: React.ReactNode; name: string; description: string; tag: { label: string; enabled: boolean }; onToggle?: () => void; children: React.ReactNode; last?: boolean; open?: boolean;
+}) {
+ return (
+
+
+ {icon}
+
+
+
{name}
+
+
+
{description}
+
+
+
+
+
+ {children}
+
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/helpers.tsx b/apps/frontend/app/dashboard/components/helpers.tsx
new file mode 100644
index 0000000..f19bf08
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/helpers.tsx
@@ -0,0 +1,99 @@
+'use client';
+
+import { useState, useRef, useEffect } from 'react';
+import { motion, AnimatePresence } from 'motion/react';
+import { IconCheck, IconCopy, IconEye, IconEyeOff } from '@tabler/icons-react';
+import { Input } from '@components/ui/input';
+import { Button } from '@components/ui/button';
+
+export function CopyButton({ text }: { text: string }) {
+ const [copied, setCopied] = useState(false);
+ return (
+ {
+ try {
+ await navigator.clipboard.writeText(text);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 1500);
+ } catch {
+ // Clipboard API unavailable (e.g. insecure context)
+ }
+ }}
+ >
+ {copied ? : }
+ {copied ? 'Copied' : 'Copy'}
+
+ );
+}
+
+export function ErrorMsg({ msg }: { msg: string }) {
+ if (!msg) return null;
+ return {msg}
;
+}
+
+export function ExpandForm({ open, children }: { open: boolean; children: React.ReactNode }) {
+ const [overflow, setOverflow] = useState<'hidden' | 'visible'>('hidden');
+
+ useEffect(() => {
+ if (open) {
+ const t = setTimeout(() => setOverflow('visible'), 400);
+ return () => clearTimeout(t);
+ } else {
+ setOverflow('hidden');
+ }
+ }, [open]);
+
+ return (
+
+ {open && (
+
+ {children}
+
+ )}
+
+ );
+}
+
+export function SmoothResize({ children }: { children: React.ReactNode }) {
+ const ref = useRef(null);
+ const [height, setHeight] = useState('auto');
+
+ useEffect(() => {
+ if (!ref.current) return;
+ const ro = new ResizeObserver(([entry]) => setHeight(entry.contentRect.height));
+ ro.observe(ref.current);
+ return () => ro.disconnect();
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function PasswordInput({ value, onChange, placeholder, required, minLength, id }: {
+ value: string; onChange: (v: string) => void; placeholder?: string; required?: boolean; minLength?: number; id?: string;
+}) {
+ const [show, setShow] = useState(false);
+ return (
+
+ onChange(e.target.value)}
+ placeholder={placeholder} className="h-9 pr-9 text-sm" required={required} minLength={minLength} id={id} />
+ setShow(v => !v)}
+ aria-label={show ? 'Hide password' : 'Show password'}
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground transition-colors">
+ {show ? : }
+
+
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/index.ts b/apps/frontend/app/dashboard/components/index.ts
new file mode 100644
index 0000000..a0a0c5e
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/index.ts
@@ -0,0 +1,9 @@
+'use client';
+
+export { PasswordRow } from './password-row';
+export { TotpRow } from './totp-row';
+export { WebAuthnRow } from './webauthn-row';
+export { PgpRow } from './pgp-row';
+export { RecoveryCodesRow } from './recovery-codes-row';
+export { ProfileRow } from './profile-row';
+export { DeleteAccountSection } from './delete-account-section';
diff --git a/apps/frontend/app/dashboard/components/password-row.tsx b/apps/frontend/app/dashboard/components/password-row.tsx
new file mode 100644
index 0000000..776a7ac
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/password-row.tsx
@@ -0,0 +1,130 @@
+'use client';
+
+import { useState } from 'react';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import z from 'zod';
+import { $api } from '@lib/providers/api';
+import { getApiErrorMessage } from '@lib/api-error';
+import { Button } from '@components/ui/button';
+import { Label } from '@components/ui/label';
+import { Form, FormControl, FormField, FormItem, FormMessage } from '@components/ui/form';
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@components/ui/dialog';
+import { IconLock } from '@tabler/icons-react';
+import { FactorRow } from './factor-row';
+import { ErrorMsg, PasswordInput } from './helpers';
+
+const passwordSchema = z.object({
+ current_password: z.string().optional(),
+ new_password: z.string().min(8, 'Min. 8 characters'),
+ confirm_password: z.string(),
+}).refine(data => data.new_password === data.confirm_password, {
+ message: 'Passwords do not match',
+ path: ['confirm_password'],
+});
+type PasswordForm = z.infer;
+
+export function PasswordRow({ isSet, onRefetch }: { isSet: boolean; onRefetch: () => void }) {
+ const [open, setOpen] = useState(false);
+ const [error, setError] = useState('');
+
+ const form = useForm({
+ resolver: zodResolver(passwordSchema),
+ defaultValues: { current_password: '', new_password: '', confirm_password: '' },
+ });
+
+ const change = $api.useMutation('post', '/api/settings/password/change', {
+ onSuccess: () => {
+ form.reset();
+ setError('');
+ setOpen(false);
+ onRefetch();
+ },
+ onError: (error) => {
+ setError(getApiErrorMessage(error, 'Could not update the password.'));
+ },
+ });
+
+ const onSubmit = (data: PasswordForm) => {
+ setError('');
+ change.mutate({ body: { current_password: data.current_password ?? '', new_password: data.new_password } });
+ };
+
+ const handleClose = (v: boolean) => {
+ if (!v) { form.reset(); setError(''); }
+ setOpen(v);
+ };
+
+ return (
+ <>
+ } name="Password" description="Authenticate using a password." tag={{ label: isSet ? 'Enabled' : 'Disabled', enabled: isSet }} onToggle={() => setOpen(true)} open={false}>
+ <>>
+
+
+
+
+
+ {isSet ? 'Change password' : 'Set password'}
+
+ {isSet ? 'Enter your current password and choose a new one.' : 'Set a password for your account.'}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/pgp-row.tsx b/apps/frontend/app/dashboard/components/pgp-row.tsx
new file mode 100644
index 0000000..263cf43
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/pgp-row.tsx
@@ -0,0 +1,163 @@
+'use client';
+
+import { useState, useCallback } from 'react';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import z from 'zod';
+import { $api } from '@lib/providers/api';
+import { Button } from '@components/ui/button';
+import { Label } from '@components/ui/label';
+import { Form, FormControl, FormField, FormItem, FormMessage } from '@components/ui/form';
+import { Input } from '@components/ui/input';
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@components/ui/dialog';
+import { IconKey, IconPlus } from '@tabler/icons-react';
+import { FactorRow } from './factor-row';
+import { ExpandForm } from './helpers';
+import { FactorKeyItem } from './factor-key-item';
+
+const pgpSchema = z.object({
+ display_name: z.string().min(1, 'Required').max(32),
+ public_key: z.string().min(1, 'Required'),
+});
+type PgpForm = z.infer;
+
+export function PgpRow({ pgp, onRefetch }: { pgp: { fingerprint: string; display_name: string }[]; onRefetch: () => void }) {
+ const [open, setOpen] = useState(false);
+ const [addOpen, setAddOpen] = useState(false);
+ const [deleteTarget, setDeleteTarget] = useState<{ fingerprint: string; display_name: string } | null>(null);
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+
+ const form = useForm({
+ resolver: zodResolver(pgpSchema),
+ defaultValues: { display_name: '', public_key: '' },
+ });
+
+ const enable = $api.useMutation('post', '/api/settings/factors/pgp/enable', {
+ onSuccess: () => {
+ form.reset();
+ setAddOpen(false);
+ onRefetch();
+ },
+ onError: () => {
+ form.setError('public_key', { message: 'Invalid key or failed to add.' });
+ },
+ });
+
+ const deleteKey = $api.useMutation('delete', '/api/settings/factors/pgp/delete/{fingerprint}', {
+ onSuccess: () => { setDeleteDialogOpen(false); onRefetch(); },
+ });
+
+ const isEnabled = pgp.length > 0;
+
+ const onSubmit = useCallback((data: PgpForm) => {
+ enable.mutate({ body: data });
+ }, [enable]);
+
+ const handleToggle = useCallback(() => {
+ setOpen(v => !v);
+ }, []);
+
+ const handleDelete = () => {
+ if (!deleteTarget) return;
+ deleteKey.mutate({ params: { path: { fingerprint: deleteTarget.fingerprint } } });
+ };
+
+ return (
+ <>
+ }
+ name="PGP Key"
+ description="Authenticate by signing a server challenge with your PGP private key."
+ tag={{ label: isEnabled ? `${pgp.length} key${pgp.length > 1 ? 's' : ''}` : 'Disabled', enabled: isEnabled }}
+ onToggle={handleToggle}
+ open={open}
+ >
+
+
+ {isEnabled && (
+
+ {pgp.map(key => (
+ }
+ name={key.display_name}
+ subtitle={key.fingerprint}
+ onRemove={() => { setDeleteTarget(key); setDeleteDialogOpen(true); }}
+ />
+ ))}
+
+ )}
+
{ setAddOpen(true); form.reset(); }}>
+ Add PGP key
+
+
+
+
+
+ {/* Add PGP key modal */}
+ { if (!v) form.reset(); setAddOpen(v); }}>
+
+
+ Add PGP key
+
+ Provide a name and your ASCII-armored public key.
+
+
+
+
+
+
+
+ {/* Delete PGP key confirmation */}
+
+
+
+ Remove PGP key
+
+ Are you sure you want to delete{' '}{deleteTarget?.display_name} ? This action cannot be undone.
+
+
+ {deleteKey.isError && (
+ Failed to remove PGP key.
+ )}
+
+ setDeleteDialogOpen(false)} disabled={deleteKey.isPending}>
+ Cancel
+
+
+ {deleteKey.isPending ? 'Removing…' : 'Remove'}
+
+
+
+
+ >
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/profile-row.tsx b/apps/frontend/app/dashboard/components/profile-row.tsx
new file mode 100644
index 0000000..c2d6642
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/profile-row.tsx
@@ -0,0 +1,132 @@
+'use client';
+
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import z from 'zod';
+import { $api } from '@lib/providers/api';
+import { getApiErrorMessage } from '@lib/api-error';
+import { useQueryClient } from '@tanstack/react-query';
+import { Button } from '@components/ui/button';
+import { Input } from '@components/ui/input';
+import { IconUser, IconCheck, IconX } from '@tabler/icons-react';
+import { FactorRow } from './factor-row';
+import { ErrorMsg, ExpandForm } from './helpers';
+import { Form, FormControl, FormField, FormItem, FormMessage } from '@components/ui/form';
+import { Label } from '@components/ui/label';
+import { useState } from 'react';
+
+const profileSchema = z.object({
+ display_name: z.string().min(1, 'Required').max(64),
+ first_name: z.string().max(64).optional(),
+ last_name: z.string().max(64).optional(),
+});
+type ProfileForm = z.infer;
+
+interface Profile {
+ preferred_username: string;
+ display_name: string;
+ first_name: string;
+ last_name: string;
+}
+
+export function ProfileRow({ profile }: { profile: Profile }) {
+ const queryClient = useQueryClient();
+ const [open, setOpen] = useState(false);
+ const [error, setError] = useState('');
+
+ const form = useForm({
+ resolver: zodResolver(profileSchema),
+ values: {
+ display_name: profile.display_name,
+ first_name: profile.first_name,
+ last_name: profile.last_name,
+ },
+ });
+
+ const save = $api.useMutation('patch', '/api/settings/profile', {
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['get', '/api/settings/profile'] });
+ setError('');
+ setOpen(false);
+ },
+ onError: (error) => {
+ setError(getApiErrorMessage(error, 'Failed to save profile changes.'));
+ },
+ });
+
+ return (
+ }
+ name={profile.display_name}
+ description={[profile.first_name, profile.last_name].filter(Boolean).join(' ') || `@${profile.preferred_username}`}
+ tag={{ label: `@${profile.preferred_username}`, enabled: true }}
+ onToggle={() => {
+ setOpen((v) => !v);
+ form.reset();
+ setError('');
+ }}
+ open={open}
+ last
+ >
+
+
+ {
+ setError('');
+ save.mutate({ body: data });
+ })}
+ className="ml-9 px-5 pb-4 space-y-3 max-w-sm"
+ >
+
+ (
+
+ First name
+
+
+
+
+
+ )} />
+ (
+
+ Last name
+
+
+
+
+
+ )} />
+
+ (
+
+ Display name
+
+
+
+
+
+ )} />
+
+
+
+ {save.isPending ? 'Saving…' : 'Save'}
+
+ {
+ setOpen(false);
+ form.reset();
+ setError('');
+ }}
+ >
+ Cancel
+
+
+
+
+
+
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/recovery-codes-row.tsx b/apps/frontend/app/dashboard/components/recovery-codes-row.tsx
new file mode 100644
index 0000000..8596ca2
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/recovery-codes-row.tsx
@@ -0,0 +1,95 @@
+'use client';
+
+import { useState } from 'react';
+import { $api } from '@lib/providers/api';
+import { Button } from '@components/ui/button';
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@components/ui/dialog';
+import { IconLifebuoy, IconCheck } from '@tabler/icons-react';
+import { FactorRow } from './factor-row';
+import { CopyButton, ErrorMsg, ExpandForm } from './helpers';
+
+export function RecoveryCodesRow({ remaining, onRefetch }: { remaining: number; onRefetch: () => void }) {
+ const [open, setOpen] = useState(false);
+ const [codesModalOpen, setCodesModalOpen] = useState(false);
+ const [codes, setCodes] = useState(null);
+ const [error, setError] = useState('');
+
+ const enable = $api.useMutation('post', '/api/settings/factors/recovery-codes/enable', {
+ onSuccess: (data) => { setCodes(data.codes); setCodesModalOpen(true); onRefetch(); },
+ onError: () => { setError('Failed to generate codes.'); },
+ });
+ const reset = $api.useMutation('post', '/api/settings/factors/recovery-codes/reset', {
+ onSuccess: (data) => { setCodes(data.codes); setCodesModalOpen(true); onRefetch(); },
+ onError: () => { setError('Failed to regenerate.'); },
+ });
+
+ const isEnabled = remaining > 0;
+
+ const handleEnable = () => { setError(''); enable.mutate({}); };
+ const handleReset = () => { setError(''); reset.mutate({}); };
+
+ const handleCloseModal = () => {
+ setCodesModalOpen(false);
+ setCodes(null);
+ };
+
+ return (
+ <>
+ } name="Recovery Codes" description="One-time emergency codes to regain access if you lose your other factors." tag={{ label: isEnabled ? `${remaining} remaining` : 'Disabled', enabled: isEnabled }} onToggle={() => { setOpen(v => !v); setError(''); }} open={open} last>
+
+
+
+
+ {isEnabled
+ ? `${remaining} code${remaining !== 1 ? 's' : ''} remaining. Regenerating will replace all current codes.`
+ : 'Generate one-time codes to access your account if you lose your other sign-in methods.'}
+
+
+
+
+ {enable.isPending || reset.isPending ? 'Generating…' : isEnabled ? 'Regenerate' : 'Generate'}
+
+ { setOpen(false); setError(''); }}>Cancel
+
+
+
+
+
+
+ {/* Recovery codes modal */}
+ { if (!v) handleCloseModal(); }}>
+
+
+ Your recovery codes
+
+ Store these codes in a safe place. Each code can only be used once.
+
+
+ {codes && (
+
+
+
+
+ {codes.map((c, i) => (
+
+ {i + 1}
+ {c}
+
+ ))}
+
+
+
+
+ Done, I saved them
+
+
+
+ )}
+
+
+ >
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/totp-row.tsx b/apps/frontend/app/dashboard/components/totp-row.tsx
new file mode 100644
index 0000000..f42a82c
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/totp-row.tsx
@@ -0,0 +1,242 @@
+'use client';
+
+import { useState, useEffect, useRef } from 'react';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import z from 'zod';
+import { $api } from '@lib/providers/api';
+import { Button } from '@components/ui/button';
+import { Input } from '@components/ui/input';
+import { Label } from '@components/ui/label';
+import { Form, FormControl, FormField, FormItem, FormMessage } from '@components/ui/form';
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@components/ui/dialog';
+import { InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot } from '@components/ui/input-otp';
+import { REGEXP_ONLY_DIGITS } from 'input-otp';
+import { IconDeviceMobile } from '@tabler/icons-react';
+import QRCode from 'react-qr-code';
+import { FactorRow } from './factor-row';
+import { CopyButton, ExpandForm } from './helpers';
+import { FactorKeyItem } from './factor-key-item';
+import { Dialog as ConfirmDialog, DialogContent as ConfirmDialogContent, DialogDescription as ConfirmDialogDescription, DialogFooter as ConfirmDialogFooter, DialogHeader as ConfirmDialogHeader, DialogTitle as ConfirmDialogTitle } from '@components/ui/dialog';
+
+const nameSchema = z.object({
+ display_name: z.string().min(1, 'Required').max(32),
+});
+const codeSchema = z.object({
+ code: z.string().length(6, 'Must be 6 digits'),
+});
+
+type NameForm = z.infer;
+type CodeForm = z.infer;
+
+export function TotpRow({ totp, onRefetch }: { totp: { display_name: string; fully_enabled: boolean } | null | undefined; onRefetch: () => void }) {
+ const [step, setStep] = useState<'idle' | 'name' | 'confirm'>('idle');
+ const [setupData, setSetupData] = useState<{ secret: string; qr: string } | null>(null);
+ const [confirmDisable, setConfirmDisable] = useState(false);
+ const [detailsOpen, setDetailsOpen] = useState(false);
+ const lastSubmittedCode = useRef(null);
+
+ const isEnabled = totp?.fully_enabled ?? false;
+
+ const nameForm = useForm({
+ resolver: zodResolver(nameSchema),
+ defaultValues: { display_name: '' },
+ });
+
+ const codeForm = useForm({
+ resolver: zodResolver(codeSchema),
+ defaultValues: { code: '' },
+ });
+
+ const enable = $api.useMutation('post', '/api/settings/factors/totp/enable', {
+ onSuccess: (data) => {
+ setSetupData(data);
+ setStep('confirm');
+ },
+ onError: () => {
+ nameForm.setError('display_name', { message: 'Failed to start setup.' });
+ },
+ });
+
+ const confirmMutation = $api.useMutation('post', '/api/settings/factors/totp/enable/confirm', {
+ onSuccess: () => {
+ setStep('idle');
+ setSetupData(null);
+ nameForm.reset();
+ codeForm.reset();
+ onRefetch();
+ },
+ onError: () => {
+ codeForm.setError('code', { message: 'Invalid code, try again.' });
+ },
+ });
+
+ const disable = $api.useMutation('delete', '/api/settings/factors/totp/disable', {
+ onSuccess: () => { setConfirmDisable(false); onRefetch(); },
+ });
+
+ const handleToggle = () => {
+ if (step !== 'idle') {
+ setStep('idle');
+ setSetupData(null);
+ nameForm.reset();
+ codeForm.reset();
+ lastSubmittedCode.current = null;
+ } else if (isEnabled) {
+ setDetailsOpen(v => !v);
+ } else {
+ setStep('name');
+ }
+ };
+
+ const code = codeForm.watch('code');
+
+ useEffect(() => {
+ if (code?.length !== 6) {
+ lastSubmittedCode.current = null;
+ return;
+ }
+
+ if (confirmMutation.isPending || lastSubmittedCode.current === code) {
+ return;
+ }
+
+ lastSubmittedCode.current = code;
+ confirmMutation.mutate({ body: { code } });
+ }, [code, confirmMutation]);
+
+ const closeSetupDialog = () => {
+ setStep('idle');
+ setSetupData(null);
+ codeForm.reset();
+ lastSubmittedCode.current = null;
+ };
+
+ return (
+ <>
+ }
+ name="Authenticator App"
+ description="Time-based one-time passwords from your authenticator app."
+ tag={{ label: isEnabled ? totp?.display_name ?? 'Enabled' : 'Disabled', enabled: isEnabled }}
+ onToggle={handleToggle}
+ open={step !== 'idle' || detailsOpen}
+ >
+
+ {isEnabled && (
+
+
+ }
+ name={totp?.display_name ?? 'Authenticator'}
+ subtitle="Active"
+ onRemove={() => setConfirmDisable(true)}
+ />
+
+
+ )}
+
+
+
+ enable.mutate({ body: data }))} className="space-y-3 max-w-sm pb-4">
+ (
+
+ Authenticator name
+
+
+
+
+
+ )} />
+
+
+ {enable.isPending ? 'Generating…' : 'Continue'}
+
+ { setStep('idle'); nameForm.reset(); }}>Cancel
+
+
+
+
+
+
+
+ { if (!open) closeSetupDialog(); }}>
+
+
+ Set up authenticator
+
+ Scan the QR code with your authenticator app, then enter the verification code.
+
+
+ {setupData && (
+
+
+
+
+ Secret
+
+
+
{setupData.secret}
+
+
+
+ confirmMutation.mutate({ body: data }))}>
+ (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )} />
+
+
+
+ {confirmMutation.isPending && (
+
Verifying…
+ )}
+
+ )}
+
+
+
+
+
+
+ Remove authenticator
+
+ This will remove{' '}{totp?.display_name} {' '}from your account. You won't be able to use it for two-factor authentication until you set up a new one.
+
+
+ {disable.isError && (
+ Failed to remove authenticator.
+ )}
+
+ setConfirmDisable(false)} disabled={disable.isPending}>
+ Cancel
+
+ disable.mutate({})} disabled={disable.isPending}>
+ {disable.isPending ? 'Removing…' : 'Remove'}
+
+
+
+
+ >
+ );
+}
diff --git a/apps/frontend/app/dashboard/components/webauthn-row.tsx b/apps/frontend/app/dashboard/components/webauthn-row.tsx
new file mode 100644
index 0000000..519ddf7
--- /dev/null
+++ b/apps/frontend/app/dashboard/components/webauthn-row.tsx
@@ -0,0 +1,117 @@
+'use client';
+
+import { useState } from 'react';
+import { $api } from '@lib/providers/api';
+import { useWebAuthnRegistration } from '@lib/hooks';
+import { Button } from '@components/ui/button';
+import { Input } from '@components/ui/input';
+import { Label } from '@components/ui/label';
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@components/ui/dialog';
+import { IconFingerprint, IconPlus } from '@tabler/icons-react';
+import { FactorRow } from './factor-row';
+import { ErrorMsg, ExpandForm } from './helpers';
+import { FactorKeyItem } from './factor-key-item';
+
+export function WebAuthnRow({ keys, onRefetch }: { keys: { credential_id: string; display_name: string }[]; onRefetch: () => void }) {
+ const [open, setOpen] = useState(false);
+ const [addOpen, setAddOpen] = useState(false);
+ const [newName, setNewName] = useState('');
+ const [error, setError] = useState('');
+ const [deleteTarget, setDeleteTarget] = useState<{ credential_id: string; display_name: string } | null>(null);
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+
+ const webAuthn = useWebAuthnRegistration();
+ const deleteKey = $api.useMutation('delete', '/api/settings/factors/webauthn/delete/{credential_id}', {
+ onSuccess: () => { setDeleteDialogOpen(false); onRefetch(); },
+ onError: () => setError('Failed to delete passkey.'),
+ });
+
+ const handleAdd = async (e: React.FormEvent) => {
+ e.preventDefault(); setError('');
+ try {
+ await webAuthn.registerAsync(newName);
+ setNewName('');
+ setAddOpen(false);
+ onRefetch();
+ } catch {
+ setError('Failed to register passkey.');
+ }
+ };
+
+ const handleDelete = () => {
+ if (!deleteTarget) return;
+ deleteKey.mutate({ params: { path: { credential_id: deleteTarget.credential_id } } });
+ };
+
+ return (
+ <>
+ } name="Passkeys" description="Phishing-resistant authentication using your device or a hardware security key." tag={{ label: keys.length > 0 ? `${keys.length} key${keys.length > 1 ? 's' : ''}` : 'Disabled', enabled: keys.length > 0 }} onToggle={() => setOpen(v => !v)} open={open}>
+
+
+ {keys.length > 0 && (
+
+ {keys.map(key => (
+ }
+ name={key.display_name}
+ subtitle="Passkey"
+ onRemove={() => { setDeleteTarget(key); setDeleteDialogOpen(true); }}
+ />
+ ))}
+
+ )}
+
+
{ setAddOpen(true); setError(''); }}>
+ Add passkey
+
+
+
+
+
+ {/* Add passkey modal */}
+ { if (!v) { setNewName(''); setError(''); } setAddOpen(v); }}>
+
+
+ Add passkey
+
+ Give your passkey a name, then follow your browser's prompt to register it.
+
+
+
+
+ Key name
+ setNewName(e.target.value)}
+ placeholder="YubiKey 5, iPhone Face ID…" className="h-9 text-sm" required maxLength={32} />
+
+
+
+ { setAddOpen(false); setNewName(''); setError(''); }}>Cancel
+ Register
+
+
+
+
+
+ {/* Delete passkey confirmation */}
+
+
+
+ Delete passkey
+
+ Are you sure you want to delete{' '}{deleteTarget?.display_name} ? This action cannot be undone.
+
+
+
+ setDeleteDialogOpen(false)} disabled={deleteKey.isPending}>
+ Cancel
+
+
+ {deleteKey.isPending ? 'Deleting…' : 'Delete'}
+
+
+
+
+ >
+ );
+}
diff --git a/apps/frontend/app/dashboard/page.tsx b/apps/frontend/app/dashboard/page.tsx
index 9bfd936..f399121 100644
--- a/apps/frontend/app/dashboard/page.tsx
+++ b/apps/frontend/app/dashboard/page.tsx
@@ -1,14 +1,149 @@
'use client';
-import { Button } from '@components/ui/button';
-import { useWebAuthnRegistration } from '@lib/hooks';
-export default function Page() {
- const webAuthn = useWebAuthnRegistration();
+import { useCallback, useEffect, useState } from 'react';
+import { motion } from 'motion/react';
+import { useQueryClient } from '@tanstack/react-query';
+import { useRouter } from 'next/navigation';
+import { $api } from '@lib/providers/api';
+import {
+ PasswordRow,
+ TotpRow,
+ WebAuthnRow,
+ PgpRow,
+ RecoveryCodesRow,
+ ProfileRow,
+ DeleteAccountSection,
+} from './components';
+import { DashboardHeader } from './components/dashboard-header';
+import {
+ DashboardSkeleton,
+ DashboardError,
+ DashboardWarning,
+} from './components/dashboard-status';
+
+export default function DashboardPage() {
+ const queryClient = useQueryClient();
+ const router = useRouter();
+
+ const profileQuery = $api.useQuery('get', '/api/settings/profile');
+ const factorsQuery = $api.useQuery('get', '/api/settings/factors');
+
+ const [lastProfile, setLastProfile] = useState(profileQuery.data);
+ const [lastFactors, setLastFactors] = useState(factorsQuery.data);
+
+ useEffect(() => {
+ if (profileQuery.data) {
+ setLastProfile(profileQuery.data);
+ }
+ }, [profileQuery.data]);
+
+ useEffect(() => {
+ if (factorsQuery.data) {
+ setLastFactors(factorsQuery.data);
+ }
+ }, [factorsQuery.data]);
+
+ const profile = profileQuery.data ?? lastProfile;
+ const factors = factorsQuery.data ?? lastFactors;
+
+ const logout = $api.useMutation('post', '/api/logout', {
+ onSuccess: () => router.push('/login'),
+ });
+
+ const refetchFactors = useCallback(() => {
+ queryClient.invalidateQueries({ queryKey: ['get', '/api/settings/factors'] });
+ }, [queryClient]);
+
+ const handleLogout = useCallback(() => {
+ logout.mutate({});
+ }, [logout]);
return (
-
- Welcome!
-
webAuthn.registerAsync('test')}>Register
+
+
+
+
+ {profileQuery.isError && !profile && (
+
+
+
+ )}
+
+ {profileQuery.isError && profile && (
+
+
+
+ )}
+
+ {profile && (
+
+ Profile
+
+
+ )}
+
+ {!factors && factorsQuery.isLoading &&
}
+ {!factors && factorsQuery.isError &&
}
+
+ {factorsQuery.isError && factors && (
+
+
+
+ )}
+
+ {factors && (
+
+ Security
+
+
+ )}
+
+ {factors && (
+
+
+ Danger Zone
+
+
+
+
+
+ )}
+
);
}
diff --git a/apps/frontend/app/globals.css b/apps/frontend/app/globals.css
index dc98be7..9207552 100644
--- a/apps/frontend/app/globals.css
+++ b/apps/frontend/app/globals.css
@@ -1,6 +1,15 @@
@import "tailwindcss";
@import "tw-animate-css";
+html {
+ scrollbar-gutter: stable;
+}
+
+:root body[data-scroll-locked] {
+ margin-right: 0 !important;
+ padding-right: 0 !important;
+}
+
@custom-variant dark (&:is(.dark *));
@theme inline {
diff --git a/apps/frontend/components/ui/dialog.tsx b/apps/frontend/components/ui/dialog.tsx
new file mode 100644
index 0000000..84bdef4
--- /dev/null
+++ b/apps/frontend/components/ui/dialog.tsx
@@ -0,0 +1,158 @@
+"use client"
+
+import * as React from "react"
+import { XIcon } from "lucide-react"
+import { Dialog as DialogPrimitive } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+function Dialog({
+ ...props
+}: React.ComponentProps
) {
+ return
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({
+ className,
+ showCloseButton = false,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+ {children}
+ {showCloseButton && (
+
+ Close
+
+ )}
+
+ )
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/apps/frontend/lib/api-error.ts b/apps/frontend/lib/api-error.ts
new file mode 100644
index 0000000..62f76c7
--- /dev/null
+++ b/apps/frontend/lib/api-error.ts
@@ -0,0 +1,59 @@
+const NETWORK_ERROR_CODES = new Set([
+ 'ECONNREFUSED',
+ 'ECONNRESET',
+ 'ENETUNREACH',
+ 'ENOTFOUND',
+ 'ETIMEDOUT',
+ 'UND_ERR_CONNECT_TIMEOUT',
+]);
+
+function normalizeMessage(message: string, fallback: string) {
+ const trimmed = message.trim();
+ if (!trimmed) return fallback;
+
+ return trimmed;
+}
+
+function getErrorCode(error: unknown): string | undefined {
+ if (!error || typeof error !== 'object') return undefined;
+
+ if ('code' in error && typeof (error as { code?: unknown }).code === 'string') {
+ return (error as { code: string }).code;
+ }
+
+ if (
+ 'cause' in error &&
+ error.cause &&
+ typeof error.cause === 'object' &&
+ 'code' in error.cause &&
+ typeof (error.cause as { code?: unknown }).code === 'string'
+ ) {
+ return (error.cause as { code: string }).code;
+ }
+
+ return undefined;
+}
+
+export function getApiErrorMessage(error: unknown, fallback: string) {
+ const errorCode = getErrorCode(error);
+ if (errorCode && NETWORK_ERROR_CODES.has(errorCode)) {
+ return 'Could not reach the server. Try again.';
+ }
+
+ if (typeof error === 'string') {
+ return normalizeMessage(error, fallback);
+ }
+
+ if (error instanceof Error) {
+ return normalizeMessage(error.message, fallback);
+ }
+
+ if (error && typeof error === 'object' && 'error' in error) {
+ const message = (error as { error?: unknown }).error;
+ if (typeof message === 'string') {
+ return normalizeMessage(message, fallback);
+ }
+ }
+
+ return fallback;
+}
diff --git a/apps/frontend/lib/hooks/use-webauthn-core.ts b/apps/frontend/lib/hooks/use-webauthn-core.ts
index fb14894..9f31a34 100644
--- a/apps/frontend/lib/hooks/use-webauthn-core.ts
+++ b/apps/frontend/lib/hooks/use-webauthn-core.ts
@@ -23,7 +23,6 @@ export function useWebAuthnAssertion(
});
const assertion = await navigator.credentials.get({
- // @ts-expect-error Someone fucked up Credential type definitions
publicKey: credentialRequestOptions.publicKey,
});
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index 8e90bda..1c123b4 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -27,9 +27,11 @@
"next-themes": "^0.4.6",
"openapi-fetch": "^0.14.1",
"openapi-react-query": "^0.5.4",
+ "radix-ui": "^1.4.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-hook-form": "^7.72.0",
+ "react-qr-code": "^2.0.18",
"tailwind-merge": "^3.5.0",
"zod": "^3.25.76"
},
diff --git a/mail/src/lib.rs b/mail/src/lib.rs
index 2bac664..0c4bbaa 100644
--- a/mail/src/lib.rs
+++ b/mail/src/lib.rs
@@ -36,7 +36,7 @@ impl MailService {
Self {
config,
app_name,
- public_url,
+ public_url: public_url.trim_end_matches('/').to_string(),
}
}
@@ -68,9 +68,8 @@ impl MailService {
.await
}
- /// Not yet implemented — will be called once email confirmation flow is ready.
pub async fn send_email_confirmation(&self, to: &str, token: &str) -> Result<()> {
- let confirm_url = format!("{}/confirm-email?token={token}", self.public_url);
+ let confirm_url = format!("{}/api/confirm-email?token={token}", self.public_url);
let html = templates::email_confirmation(&confirm_url);
self.send(
to,
@@ -80,7 +79,6 @@ impl MailService {
.await
}
- /// Not yet implemented — will be called once login notification flow is ready (after DB migration).
pub async fn send_login_notification(&self, to: &str, ip: &str) -> Result<()> {
let html = templates::login_notification(ip);
self.send(
diff --git a/packages/api-schema/api.d.ts b/packages/api-schema/api.d.ts
index 3cf37d0..c2a256c 100644
--- a/packages/api-schema/api.d.ts
+++ b/packages/api-schema/api.d.ts
@@ -4,6 +4,22 @@
*/
export interface paths {
+ "/.well-known/openid-configuration": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get: operations["openid_configuration"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/api/admin/applications": {
parameters: {
query?: never;
@@ -14,6 +30,44 @@ export interface paths {
/** Get applications */
get: operations["get_applications"];
put?: never;
+ /** Create application */
+ post: operations["create_application"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/auth/factors/password/authenticate": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /** Authenticate Docs here */
+ post: operations["password_authenticate"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/confirm-email": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Confirm email address
+ * @description Validates the confirmation token, marks the email as confirmed, and redirects to the frontend result page.
+ */
+ get: operations["confirm_email"];
+ put?: never;
post?: never;
delete?: never;
options?: never;
@@ -173,9 +227,11 @@ export interface paths {
put?: never;
/**
* Finish discoverable (passwordless) WebAuthn authentication
- * @description Completes the discoverable credential authentication flow. Requires a previous call to `/api/login/webauthn/passwordless/start`. The server identifies the user from the credential's embedded user handle (UUID).
+ * @description Completes the discoverable credential authentication flow. Requires a previous call to
+ * `/api/login/webauthn/passwordless/start`. The server identifies the user from the credential's
+ * embedded user handle (UUID).
*/
- post: operations["discoverable_finish"];
+ post: operations["passwordless_finish"];
delete?: never;
options?: never;
head?: never;
@@ -193,9 +249,11 @@ export interface paths {
put?: never;
/**
* Start discoverable (passwordless) WebAuthn authentication
- * @description Initiates a discoverable credential authentication flow. No username is required — the authenticator will present the user with a list of available passkeys.
+ * @description Initiates a discoverable credential authentication flow. No username is required — the authenticator
+ * will present the user with a list of available passkeys. After receiving the browser response,
+ * call `/api/login/webauthn/passwordless/finish` to complete authentication.
*/
- post: operations["discoverable_start"];
+ post: operations["passwordless_start"];
delete?: never;
options?: never;
head?: never;
@@ -222,7 +280,7 @@ export interface paths {
patch?: never;
trace?: never;
};
- "/api/password-reset": {
+ "/api/logout": {
parameters: {
query?: never;
header?: never;
@@ -231,28 +289,53 @@ export interface paths {
};
get?: never;
put?: never;
- /** Request password reset */
- post: {
- requestBody: {
- content: {
- "application/json": components["schemas"]["RequestResetBody"];
- };
- };
- responses: {
- 200: {
- content: {
- "application/json": components["schemas"]["RequestResetResponse"];
- };
- };
- };
+ /**
+ * Log out
+ * @description Destroys the current session, effectively logging the user out.
+ */
+ post: operations["logout"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/oidc/authorize": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
+ /** Get authorization info (requires session) */
+ get: operations["authorize_get"];
+ put?: never;
+ /** Approve authorization (user consent) */
+ post: operations["authorize_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
- "/api/password-reset/confirm": {
+ "/api/oidc/jwks": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** JSON Web Key Set */
+ get: operations["jwks"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/oidc/token": {
parameters: {
query?: never;
header?: never;
@@ -261,26 +344,62 @@ export interface paths {
};
get?: never;
put?: never;
- /** Confirm password reset */
- post: {
- requestBody: {
- content: {
- "application/json": components["schemas"]["ConfirmResetBody"];
- };
- };
- responses: {
- 200: {
- content: {
- "application/json": components["schemas"]["ConfirmResetResponse"];
- };
- };
- 400: {
- content: {
- "application/json": { error: string };
- };
- };
- };
+ /** OAuth2 Token endpoint */
+ post: operations["token"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/oidc/userinfo": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** OpenID Connect UserInfo endpoint */
+ get: operations["userinfo"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/password-reset": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["request_reset"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/password-reset/confirm": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
};
+ get?: never;
+ put?: never;
+ /**
+ * Confirm password reset
+ * @description Validates the token and sets the new password. Tokens expire after 1 hour
+ * and are deleted on use.
+ */
+ post: operations["confirm_reset"];
delete?: never;
options?: never;
head?: never;
@@ -304,6 +423,26 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/api/settings/account": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ /**
+ * Delete account
+ * @description Permanently deletes the user's account. This action is irreversible.
+ */
+ delete: operations["delete_account"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/api/settings/factors": {
parameters: {
query?: never;
@@ -324,6 +463,78 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/api/settings/factors/password/disable": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["password_disable"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/settings/factors/password/enable": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post: operations["password_enable"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/settings/factors/pgp/delete/{fingerprint}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ /**
+ * Delete PGP key
+ * @description Removes a single PGP authentication key from the user's account.
+ */
+ delete: operations["delete_pgp"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/settings/factors/pgp/disable": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ /**
+ * Disable PGP
+ * @description Removes the PGP authentication factor from the user's account.
+ */
+ delete: operations["disable_pgp"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/api/settings/factors/pgp/enable": {
parameters: {
query?: never;
@@ -364,6 +575,46 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/api/settings/factors/recovery-codes/reset": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /**
+ * Reset recovery codes
+ * @description Invalidates all existing recovery codes and generates a fresh set of 10.
+ */
+ post: operations["reset_recovery_codes"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/settings/factors/totp/disable": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ /**
+ * Disable TOTP
+ * @description Removes the TOTP authentication factor from the user's account.
+ */
+ delete: operations["disable_totp"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/api/settings/factors/totp/enable": {
parameters: {
query?: never;
@@ -404,6 +655,26 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/api/settings/factors/webauthn/delete/{credential_id}": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ post?: never;
+ /**
+ * Delete WebAuthn key
+ * @description Removes a WebAuthn passkey by its credential ID.
+ */
+ delete: operations["delete_webauthn"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/api/settings/factors/webauthn/finish": {
parameters: {
query?: never;
@@ -444,9 +715,53 @@ export interface paths {
patch?: never;
trace?: never;
};
-}
-export type webhooks = Record;
-export interface components {
+ "/api/settings/password/change": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /**
+ * Change password
+ * @description Changes the current user's password.
+ */
+ post: operations["change_password"];
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+ "/api/settings/profile": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Get user profile
+ * @description Returns the current user's profile information.
+ */
+ get: operations["get_profile"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ /**
+ * Update user profile
+ * @description Updates the current user's profile. Only provided fields will be updated.
+ */
+ patch: operations["update_profile"];
+ trace?: never;
+ };
+}
+export type webhooks = Record;
+export interface components {
schemas: {
/** @description A descriptor of a credential that can be used. */
AllowCredentials: {
@@ -464,19 +779,6 @@ export interface components {
AlreadyEnabledError: {
error: string;
};
- RequestResetBody: {
- email: string;
- };
- RequestResetResponse: {
- success: boolean;
- };
- ConfirmResetBody: {
- token: string;
- new_password: string;
- };
- ConfirmResetResponse: {
- success: boolean;
- };
/**
* @description
* @enum {string}
@@ -489,6 +791,12 @@ export interface components {
* @enum {string}
*/
AttestationFormat: "packed" | "tpm" | "android-key" | "android-safetynet" | "fido-u2f" | "apple" | "none";
+ AuthenticateResponse: components["schemas"]["NoData"] & {
+ /** @description Indicates whether additional factors are required to complete authentication. */
+ fully_authenticated: boolean;
+ /** @description A list of factors to choose from for the next authentication step. */
+ next: string[];
+ };
/** @description
* The default option here for Options are None, so it can be derived */
AuthenticationExtensionsClientOutputs: {
@@ -542,14 +850,47 @@ export interface components {
* @enum {string}
*/
AuthenticatorTransport: "usb" | "nfc" | "ble" | "internal" | "hybrid" | "test" | "unknown";
+ AuthorizeConsent: {
+ client_id: string;
+ nonce?: string | null;
+ redirect_uri: string;
+ scope: string;
+ state?: string | null;
+ };
+ AuthorizeInfo: {
+ app_icon?: string | null;
+ app_name: string;
+ client_id: string;
+ nonce?: string | null;
+ redirect_uri: string;
+ scopes: string[];
+ state?: string | null;
+ };
+ AuthorizeResponse: {
+ redirect_url: string;
+ };
/** @example {
- * "error": "User with this username or email already existsd"
+ * "error": "User with this username or email already exists"
* } */
BadRequestError: {
error: string;
};
+ ChangePasswordBody: {
+ current_password: string;
+ new_password: string;
+ };
+ ChangePasswordResponse: {
+ success: boolean;
+ };
/** @enum {string} */
ClientType: "public" | "confidential";
+ ConfirmResetBody: {
+ new_password: string;
+ token: string;
+ };
+ ConfirmResetResponse: {
+ success: boolean;
+ };
/** @example {
* "success": true
* } */
@@ -597,6 +938,39 @@ export interface components {
* @enum {string}
*/
CredentialProtectionPolicy: "userVerificationOptional" | "userVerificationOptionalWithCredentialIDList" | "userVerificationRequired";
+ /** @example {
+ * "success": true
+ * } */
+ DeletePgpResponse: {
+ success: boolean;
+ };
+ /** @example {
+ * "success": true
+ * } */
+ DeleteWebAuthnResponse: {
+ success: boolean;
+ };
+ /** @example {
+ * "success": true
+ * } */
+ DisablePgpResponse: {
+ success: boolean;
+ };
+ /** @example {
+ * "success": true
+ * } */
+ DisableTotpResponse: {
+ success: boolean;
+ };
+ /** @description A partial version of `PublicApplication` omitting the field(s): id, client_id. Field attributes are copied. */
+ EditApplicationBody: {
+ allowed_groups: string[];
+ client_type: components["schemas"]["ClientType"];
+ icon?: string | null;
+ name: string;
+ redirect_uris: string[];
+ slug: string;
+ };
EnablePgpBody: {
/** @description The display name for the TOTP factor (for example authenticator app name). */
display_name: string;
@@ -612,6 +986,12 @@ export interface components {
/** @description Generated security codes. Save them securely as they won't be shown again. */
codes: string[];
};
+ EnableResponse: components["schemas"]["NoData"] & {
+ /** @description Indicates whether the factor is now enabled after this call. */
+ enabled: boolean;
+ /** @description Indicates whether fully enabling the factor requires a call to `confirm_enable`. */
+ requires_confirmation: boolean;
+ };
EnableTotpBody: {
/** @description The display name for the TOTP factor (for example authenticator app name). */
display_name: string;
@@ -622,8 +1002,20 @@ export interface components {
/** @description The secret won't be shown again, so save it securely. */
secret: string;
};
+ FactorDisableError: "NotEnabled" | "CannotDisableOnlyPrimary" | {
+ Other: components["schemas"]["String"];
+ };
+ FactorEnableError: "AlreadyEnabled" | {
+ Other: components["schemas"]["String"];
+ };
/** @enum {string} */
FirstFactor: "password" | "webauthnpasswordless" | "pgp";
+ /** @example {
+ * "error": "Forbidden"
+ * } */
+ ForbiddenError: {
+ error: string;
+ };
/** @description The inputs to the hmac secret if it was created during registration.
*
* */
@@ -669,12 +1061,17 @@ export interface components {
/** @description Username or email address */
username: string;
};
+ LogoutResponse: {
+ success: boolean;
+ };
/**
* @description Request in residentkey workflows that conditional mediation should be used
* in the UI, or not.
* @enum {string}
*/
Mediation: "conditional";
+ /** @default null */
+ NoData: unknown;
OptionsRepsonse: {
options: components["schemas"]["FirstFactor"][];
recent_factor?: null | components["schemas"]["FirstFactor"];
@@ -688,6 +1085,14 @@ export interface components {
PgpChallengeResponse: {
challenge: string;
};
+ ProfileResponse: {
+ display_name: string;
+ email: string;
+ email_confirmed: boolean;
+ first_name: string;
+ last_name: string;
+ preferred_username: string;
+ };
/** @description Public key cryptographic parameters */
PubKeyCredParams: {
/**
@@ -703,7 +1108,7 @@ export interface components {
allowed_groups: string[];
client_id: string;
client_type: components["schemas"]["ClientType"];
- icon: string;
+ icon?: string | null;
name: string;
redirect_uris: string[];
slug: string;
@@ -811,6 +1216,7 @@ export interface components {
fully_enabled: boolean;
};
PublicWebAuthnFactor: {
+ credential_id: string;
display_name: string;
};
RecentFactors: {
@@ -914,6 +1320,16 @@ export interface components {
* Uvm */
uvm?: boolean | null;
};
+ RequestResetBody: {
+ email: string;
+ };
+ RequestResetResponse: {
+ success: boolean;
+ };
+ ResetRecoveryCodesResponse: {
+ /** @description New recovery codes. Save them securely as they won't be shown again. */
+ codes: string[];
+ };
/**
* @description The Relying Party's requirements for client-side discoverable credentials.
*
@@ -923,11 +1339,44 @@ export interface components {
ResidentKeyRequirement: "discouraged" | "preferred" | "required";
/** @enum {string} */
SecondFactor: "totp" | "webauthn" | "recoverycode";
+ String: "NotEnabled" | {
+ Unauthorized: string;
+ } | {
+ BadRequest: string;
+ } | {
+ Other: string;
+ };
SuccessfulLoginResponse: {
recent_factor?: null | components["schemas"]["SecondFactor"];
second_factors?: components["schemas"]["SecondFactor"][] | null;
two_factor_required: boolean;
};
+ SudoBody: {
+ /** @description Current password for identity verification. */
+ password?: string | null;
+ /** @description TOTP code for identity verification (required if TOTP is enabled). */
+ totp_code?: string | null;
+ };
+ SudoResponse: {
+ success: boolean;
+ };
+ TokenRequest: {
+ client_id?: string | null;
+ client_secret?: string | null;
+ code?: string | null;
+ grant_type: string;
+ redirect_uri?: string | null;
+ refresh_token?: string | null;
+ };
+ TokenResponse: {
+ access_token: string;
+ /** Format: int64 */
+ expires_in: number;
+ id_token?: string | null;
+ refresh_token?: string | null;
+ scope: string;
+ token_type: string;
+ };
TotpCodeBody: {
/** @description TOTP code to confirm enabling the factor. */
code: string;
@@ -938,6 +1387,14 @@ export interface components {
UnauthorizedError: {
error: string;
};
+ UpdateProfileBody: {
+ display_name?: string | null;
+ first_name?: string | null;
+ last_name?: string | null;
+ };
+ UpdateProfileResponse: {
+ success: boolean;
+ };
/** @description User Entity */
User: {
/** @description The user's preferred name for display. This value **can** change, so
@@ -951,6 +1408,15 @@ export interface components {
* **can** change, so **must not** be used as a primary key. */
name: string;
};
+ UserInfoResponse: {
+ email?: string | null;
+ email_verified?: boolean | null;
+ family_name?: string | null;
+ given_name?: string | null;
+ name?: string | null;
+ preferred_username?: string | null;
+ sub: string;
+ };
/**
* @description Defines the User Authenticator Verification policy. This is documented
* , and each
@@ -1004,6 +1470,31 @@ export interface components {
}
export type $defs = Record;
export interface operations {
+ openid_configuration: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description OpenID Connect Discovery document */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ /** @description Failed to build discovery document */
+ 500: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
get_applications: {
parameters: {
query?: never;
@@ -1022,6 +1513,120 @@ export interface operations {
"application/json": components["schemas"]["PublicApplication"][];
};
};
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UnauthorizedError"];
+ };
+ };
+ /** @description Forbidden */
+ 403: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ForbiddenError"];
+ };
+ };
+ };
+ };
+ create_application: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["EditApplicationBody"];
+ };
+ };
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["PublicApplication"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UnauthorizedError"];
+ };
+ };
+ /** @description Forbidden */
+ 403: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ForbiddenError"];
+ };
+ };
+ };
+ };
+ password_authenticate: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["NoData"];
+ };
+ };
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AuthenticateResponse"];
+ };
+ };
+ /** @description Error */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["String"];
+ };
+ };
+ };
+ };
+ confirm_email: {
+ parameters: {
+ query: {
+ /** @description Confirmation token from the email link */
+ token: string;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Redirects to frontend with status */
+ 302: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
};
};
get_health: {
@@ -1067,7 +1672,442 @@ export interface operations {
};
};
};
- login_with_password: {
+ login_with_password: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["LoginBody"];
+ };
+ };
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["InvalidUserOrPass"];
+ };
+ };
+ };
+ };
+ get_pgp_challenge: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["PgpChallengeResponse"];
+ };
+ };
+ };
+ };
+ respond_to_pgp_challenge: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["PgpChallengeBody"];
+ };
+ };
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["InvalidSignature"];
+ };
+ };
+ };
+ };
+ login_with_recovery_code: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["RecoveryCodeLoginBody"];
+ };
+ };
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["InvalidRecoveryCode"];
+ };
+ };
+ };
+ };
+ login_with_totp: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["TotpCodeBody"];
+ };
+ };
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Invalid2faCode"];
+ };
+ };
+ };
+ };
+ webauthn_finish_login: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["PublicKeyCredential"];
+ };
+ };
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UnauthorizedError"];
+ };
+ };
+ };
+ };
+ passwordless_finish: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["PublicKeyCredential"];
+ };
+ };
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ };
+ };
+ /** @description Invalid credential or user not found */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UnauthorizedError"];
+ };
+ };
+ };
+ };
+ passwordless_start: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Challenge created */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["RequestChallengeResponse"];
+ };
+ };
+ };
+ };
+ webauthn_start_login: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["RequestChallengeResponse"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UnauthorizedError"];
+ };
+ };
+ };
+ };
+ logout: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Logged out */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["LogoutResponse"];
+ };
+ };
+ };
+ };
+ authorize_get: {
+ parameters: {
+ query: {
+ client_id: string;
+ redirect_uri: string;
+ response_type: string;
+ scope?: string;
+ state?: string;
+ nonce?: string;
+ };
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Authorization info */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AuthorizeInfo"];
+ };
+ };
+ /** @description Invalid request */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ authorize_post: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["AuthorizeConsent"];
+ };
+ };
+ responses: {
+ /** @description Authorization code issued */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["AuthorizeResponse"];
+ };
+ };
+ /** @description Invalid request */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ /** @description Not authenticated */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ jwks: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description JWKS Document */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ token: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/x-www-form-urlencoded": components["schemas"]["TokenRequest"];
+ };
+ };
+ responses: {
+ /** @description Token response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["TokenResponse"];
+ };
+ };
+ /** @description Token error */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ userinfo: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description User info */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UserInfoResponse"];
+ };
+ };
+ /** @description Invalid or missing access token */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ request_reset: {
parameters: {
query?: never;
header?: never;
@@ -1076,51 +2116,64 @@ export interface operations {
};
requestBody: {
content: {
- "application/json": components["schemas"]["LoginBody"];
+ "application/json": components["schemas"]["RequestResetBody"];
};
};
responses: {
- /** @description Success */
+ /** @description Email sent (or address not found — always succeeds) */
200: {
headers: {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ "application/json": components["schemas"]["RequestResetResponse"];
};
};
- /** @description Unauthorized */
- 401: {
+ /** @description Mail not configured */
+ 503: {
headers: {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["InvalidUserOrPass"];
+ "application/json": string;
};
};
};
};
- get_pgp_challenge: {
+ confirm_reset: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
- requestBody?: never;
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["ConfirmResetBody"];
+ };
+ };
responses: {
- /** @description Success */
+ /** @description Password updated */
200: {
headers: {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["PgpChallengeResponse"];
+ "application/json": components["schemas"]["ConfirmResetResponse"];
+ };
+ };
+ /** @description Invalid or expired token */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": string;
};
};
};
};
- respond_to_pgp_challenge: {
+ register: {
parameters: {
query?: never;
header?: never;
@@ -1129,7 +2182,7 @@ export interface operations {
};
requestBody: {
content: {
- "application/json": components["schemas"]["PgpChallengeBody"];
+ "application/json": components["schemas"]["RegisterBody"];
};
};
responses: {
@@ -1139,21 +2192,21 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["PgpChallengeResponse"];
+ "application/json": components["schemas"]["CreateSuccess"];
};
};
- /** @description Unauthorized */
- 401: {
+ /** @description BadRequest */
+ 400: {
headers: {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["InvalidSignature"];
+ "application/json": components["schemas"]["BadRequestError"];
};
};
};
};
- login_with_recovery_code: {
+ delete_account: {
parameters: {
query?: never;
header?: never;
@@ -1162,42 +2215,30 @@ export interface operations {
};
requestBody: {
content: {
- "application/json": components["schemas"]["RecoveryCodeLoginBody"];
+ "application/json": {
+ /** @description Current password for confirmation */
+ password: string;
+ };
};
};
responses: {
- /** @description Success */
- 200: {
- headers: {
- [name: string]: unknown;
- };
- content: {
- "application/json": components["schemas"]["SuccessfulLoginResponse"];
- };
- };
- /** @description Unauthorized */
- 401: {
+ /** @description Account deleted */
+ 204: {
headers: {
[name: string]: unknown;
};
- content: {
- "application/json": components["schemas"]["InvalidRecoveryCode"];
- };
+ content?: never;
};
};
};
- login_with_totp: {
+ get_factors: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
- requestBody: {
- content: {
- "application/json": components["schemas"]["TotpCodeBody"];
- };
- };
+ requestBody?: never;
responses: {
/** @description Success */
200: {
@@ -1205,7 +2246,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ "application/json": components["schemas"]["PublicAuthFactors"];
};
};
/** @description Unauthorized */
@@ -1214,32 +2255,45 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["Invalid2faCode"];
+ "application/json": components["schemas"]["UnauthorizedError"];
};
};
};
};
- discoverable_start: {
+ password_disable: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
- requestBody?: never;
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["NoData"];
+ };
+ };
responses: {
- /** @description Challenge created */
+ /** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["RequestChallengeResponse"];
+ "application/json": components["schemas"]["NoData"];
+ };
+ };
+ /** @description Error */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["FactorDisableError"];
};
};
};
};
- discoverable_finish: {
+ password_enable: {
parameters: {
query?: never;
header?: never;
@@ -1248,7 +2302,7 @@ export interface operations {
};
requestBody: {
content: {
- "application/json": components["schemas"]["PublicKeyCredential"];
+ "application/json": components["schemas"]["NoData"];
};
};
responses: {
@@ -1258,32 +2312,31 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ "application/json": components["schemas"]["EnableResponse"];
};
};
- /** @description Invalid credential or user not found */
- 401: {
+ /** @description Error */
+ 400: {
headers: {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["UnauthorizedError"];
+ "application/json": components["schemas"]["FactorEnableError"];
};
};
};
};
- webauthn_finish_login: {
+ delete_pgp: {
parameters: {
query?: never;
header?: never;
- path?: never;
- cookie?: never;
- };
- requestBody: {
- content: {
- "application/json": components["schemas"]["PublicKeyCredential"];
+ path: {
+ /** @description Fingerprint of the PGP key to remove */
+ fingerprint: string;
};
+ cookie?: never;
};
+ requestBody?: never;
responses: {
/** @description Success */
200: {
@@ -1291,7 +2344,16 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["SuccessfulLoginResponse"];
+ "application/json": components["schemas"]["DeletePgpResponse"];
+ };
+ };
+ /** @description PGP key not found */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": string;
};
};
/** @description Unauthorized */
@@ -1305,7 +2367,7 @@ export interface operations {
};
};
};
- webauthn_start_login: {
+ disable_pgp: {
parameters: {
query?: never;
header?: never;
@@ -1320,7 +2382,16 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["RequestChallengeResponse"];
+ "application/json": components["schemas"]["DisablePgpResponse"];
+ };
+ };
+ /** @description PGP not enabled */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": string;
};
};
/** @description Unauthorized */
@@ -1334,7 +2405,7 @@ export interface operations {
};
};
};
- register: {
+ enable_pgp: {
parameters: {
query?: never;
header?: never;
@@ -1343,7 +2414,7 @@ export interface operations {
};
requestBody: {
content: {
- "application/json": components["schemas"]["RegisterBody"];
+ "application/json": components["schemas"]["EnablePgpBody"];
};
};
responses: {
@@ -1353,21 +2424,21 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["CreateSuccess"];
+ "application/json": components["schemas"]["EnablePgpResponse"];
};
};
- /** @description BadRequest */
- 400: {
+ /** @description Unauthorized */
+ 401: {
headers: {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["BadRequestError"];
+ "application/json": components["schemas"]["UnauthorizedError"];
};
};
};
};
- get_factors: {
+ enable_recovery_codes: {
parameters: {
query?: never;
header?: never;
@@ -1382,7 +2453,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["PublicAuthFactors"];
+ "application/json": components["schemas"]["EnableRecoveryCodesResponse"];
};
};
/** @description Unauthorized */
@@ -1396,18 +2467,14 @@ export interface operations {
};
};
};
- enable_pgp: {
+ reset_recovery_codes: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
- requestBody: {
- content: {
- "application/json": components["schemas"]["EnablePgpBody"];
- };
- };
+ requestBody?: never;
responses: {
/** @description Success */
200: {
@@ -1415,7 +2482,16 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["EnablePgpResponse"];
+ "application/json": components["schemas"]["ResetRecoveryCodesResponse"];
+ };
+ };
+ /** @description Recovery codes not enabled */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": string;
};
};
/** @description Unauthorized */
@@ -1429,7 +2505,7 @@ export interface operations {
};
};
};
- enable_recovery_codes: {
+ disable_totp: {
parameters: {
query?: never;
header?: never;
@@ -1444,7 +2520,16 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["EnableRecoveryCodesResponse"];
+ "application/json": components["schemas"]["DisableTotpResponse"];
+ };
+ };
+ /** @description TOTP not enabled */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": string;
};
};
/** @description Unauthorized */
@@ -1542,6 +2627,47 @@ export interface operations {
};
};
};
+ delete_webauthn: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path: {
+ /** @description Credential ID of the WebAuthn key to delete */
+ credential_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Success */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["DeleteWebAuthnResponse"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UnauthorizedError"];
+ };
+ };
+ /** @description Key not found */
+ 404: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": string;
+ };
+ };
+ };
+ };
webauthn_finish_setup: {
parameters: {
query?: never;
@@ -1608,4 +2734,88 @@ export interface operations {
};
};
};
+ change_password: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["ChangePasswordBody"];
+ };
+ };
+ responses: {
+ /** @description Password changed */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ChangePasswordResponse"];
+ };
+ };
+ /** @description Unauthorized */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UnauthorizedError"];
+ };
+ };
+ };
+ };
+ get_profile: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Profile data */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ProfileResponse"];
+ };
+ };
+ };
+ };
+ update_profile: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["UpdateProfileBody"];
+ };
+ };
+ responses: {
+ /** @description Profile updated */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UpdateProfileResponse"];
+ };
+ };
+ /** @description No fields to update */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 0cbca55..dae636d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -62,6 +62,9 @@ importers:
openapi-react-query:
specifier: ^0.5.4
version: 0.5.4(@tanstack/react-query@5.96.1(react@19.2.4))(openapi-fetch@0.14.1)
+ radix-ui:
+ specifier: ^1.4.3
+ version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react:
specifier: ^19.2.4
version: 19.2.4
@@ -71,6 +74,9 @@ importers:
react-hook-form:
specifier: ^7.72.0
version: 7.72.0(react@19.2.4)
+ react-qr-code:
+ specifier: ^2.0.18
+ version: 2.0.18(react@19.2.4)
tailwind-merge:
specifier: ^3.5.0
version: 3.5.0
@@ -179,6 +185,21 @@ packages:
resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@floating-ui/core@1.7.5':
+ resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
+
+ '@floating-ui/dom@1.7.6':
+ resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==}
+
+ '@floating-ui/react-dom@2.1.8':
+ resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.11':
+ resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==}
+
'@hookform/resolvers@5.2.2':
resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==}
peerDependencies:
@@ -451,17 +472,27 @@ packages:
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
engines: {node: '>=12.4.0'}
- '@radix-ui/react-compose-refs@1.1.2':
- resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ '@radix-ui/number@1.1.1':
+ resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
+
+ '@radix-ui/primitive@1.1.3':
+ resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
+
+ '@radix-ui/react-accessible-icon@1.1.7':
+ resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==}
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-label@2.1.8':
- resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==}
+ '@radix-ui/react-accordion@1.2.12':
+ resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -473,8 +504,8 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-primitive@2.1.4':
- resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
+ '@radix-ui/react-alert-dialog@1.1.15':
+ resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -486,8 +517,8 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-separator@1.1.8':
- resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==}
+ '@radix-ui/react-arrow@1.1.7':
+ resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
@@ -499,1996 +530,3512 @@ packages:
'@types/react-dom':
optional: true
- '@radix-ui/react-slot@1.2.4':
- resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
+ '@radix-ui/react-aspect-ratio@1.1.7':
+ resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==}
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
- '@redocly/ajv@8.11.2':
- resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==}
+ '@radix-ui/react-avatar@1.1.10':
+ resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==}
+ 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
- '@redocly/config@0.22.2':
- resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==}
+ '@radix-ui/react-checkbox@1.3.3':
+ resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
+ 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
- '@redocly/openapi-core@1.34.3':
- resolution: {integrity: sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==}
- engines: {node: '>=18.17.0', npm: '>=9.5.0'}
+ '@radix-ui/react-collapsible@1.1.12':
+ resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==}
+ 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
- '@rtsao/scc@1.1.0':
- resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+ '@radix-ui/react-collection@1.1.7':
+ resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
+ 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
- '@rushstack/eslint-patch@1.16.1':
- resolution: {integrity: sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==}
+ '@radix-ui/react-compose-refs@1.1.2':
+ resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- '@standard-schema/utils@0.3.0':
- resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
+ '@radix-ui/react-context-menu@2.2.16':
+ resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==}
+ 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
- '@swc/helpers@0.5.15':
- resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+ '@radix-ui/react-context@1.1.2':
+ resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- '@tabler/icons-react@3.41.1':
- resolution: {integrity: sha512-kUgweE+DJtAlMZVIns1FTDdcbpRVnkK7ZpUOXmoxy3JAF0rSHj0TcP4VHF14+gMJGnF+psH2Zt26BLT6owetBA==}
+ '@radix-ui/react-dialog@1.1.15':
+ resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==}
peerDependencies:
- react: '>= 16'
+ '@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
- '@tabler/icons@3.41.1':
- resolution: {integrity: sha512-OaRnVbRmH2nHtFeg+RmMJ/7m2oBIF9XCJAUD5gQnMrpK9f05ydj8MZrAf3NZQqOXyxGN1UBL0D5IKLLEUfr74Q==}
+ '@radix-ui/react-direction@1.1.1':
+ resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- '@tailwindcss/node@4.2.2':
- resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==}
+ '@radix-ui/react-dismissable-layer@1.1.11':
+ resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
+ 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
- '@tailwindcss/oxide-android-arm64@4.2.2':
- resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==}
- engines: {node: '>= 20'}
- cpu: [arm64]
- os: [android]
+ '@radix-ui/react-dropdown-menu@2.1.16':
+ resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==}
+ 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
- '@tailwindcss/oxide-darwin-arm64@4.2.2':
- resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==}
- engines: {node: '>= 20'}
- cpu: [arm64]
- os: [darwin]
+ '@radix-ui/react-focus-guards@1.1.3':
+ resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- '@tailwindcss/oxide-darwin-x64@4.2.2':
- resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==}
- engines: {node: '>= 20'}
- cpu: [x64]
- os: [darwin]
+ '@radix-ui/react-focus-scope@1.1.7':
+ resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
+ 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
- '@tailwindcss/oxide-freebsd-x64@4.2.2':
- resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==}
- engines: {node: '>= 20'}
- cpu: [x64]
- os: [freebsd]
+ '@radix-ui/react-form@0.1.8':
+ resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==}
+ 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
- '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
- resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==}
- engines: {node: '>= 20'}
- cpu: [arm]
- os: [linux]
+ '@radix-ui/react-hover-card@1.1.15':
+ resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==}
+ 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
- '@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
- resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==}
- engines: {node: '>= 20'}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
+ '@radix-ui/react-id@1.1.1':
+ resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- '@tailwindcss/oxide-linux-arm64-musl@4.2.2':
- resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
- engines: {node: '>= 20'}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
+ '@radix-ui/react-label@2.1.7':
+ resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==}
+ 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
- '@tailwindcss/oxide-linux-x64-gnu@4.2.2':
- resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
- engines: {node: '>= 20'}
- cpu: [x64]
- os: [linux]
- libc: [glibc]
+ '@radix-ui/react-label@2.1.8':
+ resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==}
+ 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
- '@tailwindcss/oxide-linux-x64-musl@4.2.2':
- resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
- engines: {node: '>= 20'}
+ '@radix-ui/react-menu@2.1.16':
+ resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==}
+ 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-menubar@1.1.16':
+ resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==}
+ 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-navigation-menu@1.2.14':
+ resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==}
+ 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-one-time-password-field@0.1.8':
+ resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==}
+ 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-password-toggle-field@0.1.3':
+ resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==}
+ 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-popover@1.1.15':
+ resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==}
+ 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-popper@1.2.8':
+ resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==}
+ 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-portal@1.1.9':
+ resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
+ 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-presence@1.1.5':
+ resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==}
+ 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-primitive@2.1.3':
+ resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
+ 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-primitive@2.1.4':
+ resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==}
+ 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-progress@1.1.7':
+ resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==}
+ 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-radio-group@1.3.8':
+ resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==}
+ 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-roving-focus@1.1.11':
+ resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
+ 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-scroll-area@1.2.10':
+ resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==}
+ 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-select@2.2.6':
+ resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==}
+ 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-separator@1.1.7':
+ resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==}
+ 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-separator@1.1.8':
+ resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==}
+ 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-slider@1.3.6':
+ resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==}
+ 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-slot@1.2.3':
+ resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-slot@1.2.4':
+ resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-switch@1.2.6':
+ resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==}
+ 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-tabs@1.1.13':
+ resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==}
+ 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-toast@1.2.15':
+ resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==}
+ 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-toggle-group@1.1.11':
+ resolution: {integrity: sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==}
+ 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-toggle@1.1.10':
+ resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==}
+ 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-toolbar@1.1.11':
+ resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==}
+ 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-tooltip@1.2.8':
+ resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
+ 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.1':
+ resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-controllable-state@1.2.2':
+ resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-effect-event@0.0.2':
+ resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-escape-keydown@1.1.1':
+ resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-is-hydrated@0.1.0':
+ resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.1':
+ resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-previous@1.1.1':
+ resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-rect@1.1.1':
+ resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-size@1.1.1':
+ resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ 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.1':
+ resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
+
+ '@redocly/ajv@8.11.2':
+ resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==}
+
+ '@redocly/config@0.22.2':
+ resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==}
+
+ '@redocly/openapi-core@1.34.3':
+ resolution: {integrity: sha512-3arRdUp1fNx55itnjKiUhO6t4Mf91TsrTIYINDNLAZPS0TPd5YpiXRctwjel0qqWoOOhjA34cZ3m4dksLDFUYg==}
+ engines: {node: '>=18.17.0', npm: '>=9.5.0'}
+
+ '@rtsao/scc@1.1.0':
+ resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+
+ '@rushstack/eslint-patch@1.16.1':
+ resolution: {integrity: sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==}
+
+ '@standard-schema/utils@0.3.0':
+ resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@tabler/icons-react@3.41.1':
+ resolution: {integrity: sha512-kUgweE+DJtAlMZVIns1FTDdcbpRVnkK7ZpUOXmoxy3JAF0rSHj0TcP4VHF14+gMJGnF+psH2Zt26BLT6owetBA==}
+ peerDependencies:
+ react: '>= 16'
+
+ '@tabler/icons@3.41.1':
+ resolution: {integrity: sha512-OaRnVbRmH2nHtFeg+RmMJ/7m2oBIF9XCJAUD5gQnMrpK9f05ydj8MZrAf3NZQqOXyxGN1UBL0D5IKLLEUfr74Q==}
+
+ '@tailwindcss/node@4.2.2':
+ resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==}
+
+ '@tailwindcss/oxide-android-arm64@4.2.2':
+ resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/oxide-darwin-arm64@4.2.2':
+ resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-darwin-x64@4.2.2':
+ resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/oxide-freebsd-x64@4.2.2':
+ resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2':
+ resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==}
+ engines: {node: '>= 20'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/oxide-linux-arm64-gnu@4.2.2':
+ resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/oxide-linux-arm64-musl@4.2.2':
+ resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/oxide-linux-x64-gnu@4.2.2':
+ resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/oxide-linux-x64-musl@4.2.2':
+ resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/oxide-wasm32-wasi@4.2.2':
+ resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
+ resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/oxide-win32-x64-msvc@4.2.2':
+ resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/oxide@4.2.2':
+ resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==}
+ engines: {node: '>= 20'}
+
+ '@tailwindcss/postcss@4.2.2':
+ resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==}
+
+ '@tanstack/query-core@5.96.1':
+ resolution: {integrity: sha512-u1yBgtavSy+N8wgtW3PiER6UpxcplMje65yXnnVgiHTqiMwLlxiw4WvQDrXyn+UD6lnn8kHaxmerJUzQcV/MMg==}
+
+ '@tanstack/react-query@5.96.1':
+ resolution: {integrity: sha512-2X7KYK5KKWUKGeWCVcqxXAkYefJtrKB7tSKWgeG++b0H6BRHxQaLSSi8AxcgjmUnnosHuh9WsFZqvE16P1WCzA==}
+ peerDependencies:
+ react: ^18 || ^19
+
+ '@tybys/wasm-util@0.10.1':
+ resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/json5@0.0.29':
+ resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+
+ '@types/node@20.19.37':
+ resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==}
+
+ '@types/react-dom@19.2.3':
+ resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
+ peerDependencies:
+ '@types/react': ^19.2.0
+
+ '@types/react@19.2.14':
+ resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
+
+ '@typescript-eslint/eslint-plugin@8.58.0':
+ resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.58.0
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/parser@8.58.0':
+ resolution: {integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/project-service@8.58.0':
+ resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/scope-manager@8.58.0':
+ resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.58.0':
+ resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/type-utils@8.58.0':
+ resolution: {integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/types@8.58.0':
+ resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.58.0':
+ resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/utils@8.58.0':
+ resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
+ typescript: '>=4.8.4 <6.1.0'
+
+ '@typescript-eslint/visitor-keys@8.58.0':
+ resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@unrs/resolver-binding-android-arm-eabi@1.11.1':
+ resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
+ cpu: [arm]
+ os: [android]
+
+ '@unrs/resolver-binding-android-arm64@1.11.1':
+ resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==}
+ cpu: [arm64]
+ os: [android]
+
+ '@unrs/resolver-binding-darwin-arm64@1.11.1':
+ resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-darwin-x64@1.11.1':
+ resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@unrs/resolver-binding-freebsd-x64@1.11.1':
+ resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
+ resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
+ resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
+ resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
+ resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
+ resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
+ resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
+ resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
+ resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
+ resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@unrs/resolver-binding-linux-x64-musl@1.11.1':
+ resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
cpu: [x64]
os: [linux]
libc: [musl]
- '@tailwindcss/oxide-wasm32-wasi@4.2.2':
- resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==}
+ '@unrs/resolver-binding-wasm32-wasi@1.11.1':
+ resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
- bundledDependencies:
- - '@napi-rs/wasm-runtime'
- - '@emnapi/core'
- - '@emnapi/runtime'
- - '@tybys/wasm-util'
- - '@emnapi/wasi-threads'
- - tslib
- '@tailwindcss/oxide-win32-arm64-msvc@4.2.2':
- resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==}
- engines: {node: '>= 20'}
+ '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
+ resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==}
cpu: [arm64]
os: [win32]
- '@tailwindcss/oxide-win32-x64-msvc@4.2.2':
- resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==}
- engines: {node: '>= 20'}
- cpu: [x64]
- os: [win32]
+ '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
+ resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
+ resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==}
+ cpu: [x64]
+ os: [win32]
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.16.0:
+ resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ ajv@6.14.0:
+ resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
+
+ ansi-colors@4.1.3:
+ resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
+ engines: {node: '>=6'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-hidden@1.2.6:
+ resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
+ engines: {node: '>=10'}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
+ array-buffer-byte-length@1.0.2:
+ resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+ engines: {node: '>= 0.4'}
+
+ array-includes@3.1.9:
+ resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlast@1.2.5:
+ resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.findlastindex@1.2.6:
+ resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flat@1.3.3:
+ resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flatmap@1.3.3:
+ resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.tosorted@1.1.4:
+ resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+ engines: {node: '>= 0.4'}
+
+ arraybuffer.prototype.slice@1.0.4:
+ resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ engines: {node: '>= 0.4'}
+
+ ast-types-flow@0.0.8:
+ resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
+
+ async-function@1.0.0:
+ resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ engines: {node: '>= 0.4'}
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ axe-core@4.11.2:
+ resolution: {integrity: sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw==}
+ engines: {node: '>=4'}
+
+ axobject-query@4.1.0:
+ resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+ engines: {node: '>= 0.4'}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
+ baseline-browser-mapping@2.10.13:
+ resolution: {integrity: sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ brace-expansion@1.1.13:
+ resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==}
+
+ brace-expansion@2.0.2:
+ resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+
+ brace-expansion@5.0.5:
+ resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
+ engines: {node: 18 || 20 || >=22}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.8:
+ resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ caniuse-lite@1.0.30001784:
+ resolution: {integrity: sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ change-case@5.4.4:
+ resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
+
+ class-variance-authority@0.7.1:
+ resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ colorette@1.4.0:
+ resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ damerau-levenshtein@1.0.8:
+ resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
+
+ data-view-buffer@1.0.2:
+ resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-length@1.0.2:
+ resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-offset@1.0.1:
+ resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ engines: {node: '>= 0.4'}
+
+ debug@3.2.7:
+ resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.1:
+ resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
- '@tailwindcss/oxide@4.2.2':
- resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==}
- engines: {node: '>= 20'}
+ doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
- '@tailwindcss/postcss@4.2.2':
- resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==}
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
- '@tanstack/query-core@5.96.1':
- resolution: {integrity: sha512-u1yBgtavSy+N8wgtW3PiER6UpxcplMje65yXnnVgiHTqiMwLlxiw4WvQDrXyn+UD6lnn8kHaxmerJUzQcV/MMg==}
+ emoji-regex@9.2.2:
+ resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
- '@tanstack/react-query@5.96.1':
- resolution: {integrity: sha512-2X7KYK5KKWUKGeWCVcqxXAkYefJtrKB7tSKWgeG++b0H6BRHxQaLSSi8AxcgjmUnnosHuh9WsFZqvE16P1WCzA==}
- peerDependencies:
- react: ^18 || ^19
+ enhanced-resolve@5.20.1:
+ resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
+ engines: {node: '>=10.13.0'}
- '@tybys/wasm-util@0.10.1':
- resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
+ es-abstract@1.24.1:
+ resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
+ engines: {node: '>= 0.4'}
- '@types/estree@1.0.8':
- resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
- '@types/json-schema@7.0.15':
- resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
- '@types/json5@0.0.29':
- resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
+ es-iterator-helpers@1.3.1:
+ resolution: {integrity: sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==}
+ engines: {node: '>= 0.4'}
- '@types/node@20.19.37':
- resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==}
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
- '@types/react-dom@19.2.3':
- resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
- peerDependencies:
- '@types/react': ^19.2.0
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
- '@types/react@19.2.14':
- resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
+ es-shim-unscopables@1.1.0:
+ resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+ engines: {node: '>= 0.4'}
- '@typescript-eslint/eslint-plugin@8.58.0':
- resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- '@typescript-eslint/parser': ^8.58.0
- eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.1.0'
+ es-to-primitive@1.3.0:
+ resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+ engines: {node: '>= 0.4'}
- '@typescript-eslint/parser@8.58.0':
- resolution: {integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- peerDependencies:
- eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.1.0'
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
- '@typescript-eslint/project-service@8.58.0':
- resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ eslint-config-next@15.3.5:
+ resolution: {integrity: sha512-oQdvnIgP68wh2RlR3MdQpvaJ94R6qEFl+lnu8ZKxPj5fsAHrSF/HlAOZcsimLw3DT6bnEQIUdbZC2Ab6sWyptg==}
peerDependencies:
- typescript: '>=4.8.4 <6.1.0'
+ eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
+ typescript: '>=3.3.1'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
- '@typescript-eslint/scope-manager@8.58.0':
- resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ eslint-import-resolver-node@0.3.10:
+ resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==}
- '@typescript-eslint/tsconfig-utils@8.58.0':
- resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ eslint-import-resolver-typescript@3.10.1:
+ resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
- typescript: '>=4.8.4 <6.1.0'
+ eslint: '*'
+ eslint-plugin-import: '*'
+ eslint-plugin-import-x: '*'
+ peerDependenciesMeta:
+ eslint-plugin-import:
+ optional: true
+ eslint-plugin-import-x:
+ optional: true
- '@typescript-eslint/type-utils@8.58.0':
- resolution: {integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ eslint-module-utils@2.12.1:
+ resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==}
+ engines: {node: '>=4'}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.1.0'
-
- '@typescript-eslint/types@8.58.0':
- resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
- '@typescript-eslint/typescript-estree@8.58.0':
- resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ eslint-plugin-import@2.32.0:
+ resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==}
+ engines: {node: '>=4'}
peerDependencies:
- typescript: '>=4.8.4 <6.1.0'
+ '@typescript-eslint/parser': '*'
+ eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
- '@typescript-eslint/utils@8.58.0':
- resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ eslint-plugin-jsx-a11y@6.10.2:
+ resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
+ engines: {node: '>=4.0'}
peerDependencies:
- eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.1.0'
-
- '@typescript-eslint/visitor-keys@8.58.0':
- resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@unrs/resolver-binding-android-arm-eabi@1.11.1':
- resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==}
- cpu: [arm]
- os: [android]
-
- '@unrs/resolver-binding-android-arm64@1.11.1':
- resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==}
- cpu: [arm64]
- os: [android]
-
- '@unrs/resolver-binding-darwin-arm64@1.11.1':
- resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==}
- cpu: [arm64]
- os: [darwin]
-
- '@unrs/resolver-binding-darwin-x64@1.11.1':
- resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==}
- cpu: [x64]
- os: [darwin]
-
- '@unrs/resolver-binding-freebsd-x64@1.11.1':
- resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==}
- cpu: [x64]
- os: [freebsd]
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
- '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1':
- resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==}
- cpu: [arm]
- os: [linux]
+ eslint-plugin-react-hooks@5.2.0:
+ resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
- '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1':
- resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==}
- cpu: [arm]
- os: [linux]
+ eslint-plugin-react@7.37.5:
+ resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
- '@unrs/resolver-binding-linux-arm64-gnu@1.11.1':
- resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@unrs/resolver-binding-linux-arm64-musl@1.11.1':
- resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
- resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
- cpu: [ppc64]
- os: [linux]
- libc: [glibc]
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
- resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
- cpu: [riscv64]
- os: [linux]
- libc: [glibc]
+ eslint-visitor-keys@5.0.1:
+ resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
- resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
- cpu: [riscv64]
- os: [linux]
- libc: [musl]
+ eslint@9.39.4:
+ resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
- '@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
- resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
- cpu: [s390x]
- os: [linux]
- libc: [glibc]
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@unrs/resolver-binding-linux-x64-gnu@1.11.1':
- resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
- cpu: [x64]
- os: [linux]
- libc: [glibc]
+ esquery@1.7.0:
+ resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
+ engines: {node: '>=0.10'}
- '@unrs/resolver-binding-linux-x64-musl@1.11.1':
- resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
- cpu: [x64]
- os: [linux]
- libc: [musl]
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
- '@unrs/resolver-binding-wasm32-wasi@1.11.1':
- resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
- engines: {node: '>=14.0.0'}
- cpu: [wasm32]
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
- '@unrs/resolver-binding-win32-arm64-msvc@1.11.1':
- resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==}
- cpu: [arm64]
- os: [win32]
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
- '@unrs/resolver-binding-win32-ia32-msvc@1.11.1':
- resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==}
- cpu: [ia32]
- os: [win32]
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
- '@unrs/resolver-binding-win32-x64-msvc@1.11.1':
- resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==}
- cpu: [x64]
- os: [win32]
+ fast-glob@3.3.1:
+ resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
+ engines: {node: '>=8.6.0'}
- acorn-jsx@5.3.2:
- resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
- peerDependencies:
- acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
- acorn@8.16.0:
- resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
- engines: {node: '>=0.4.0'}
- hasBin: true
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
- agent-base@7.1.4:
- resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
- engines: {node: '>= 14'}
+ fastq@1.20.1:
+ resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
- ajv@6.14.0:
- resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
- ansi-colors@4.1.3:
- resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
- engines: {node: '>=6'}
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
- ansi-styles@4.3.0:
- resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
- argparse@2.0.1:
- resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
- aria-query@5.3.2:
- resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
- engines: {node: '>= 0.4'}
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
- array-buffer-byte-length@1.0.2:
- resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
- engines: {node: '>= 0.4'}
+ flatted@3.4.2:
+ resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
- array-includes@3.1.9:
- resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
+ for-each@0.3.5:
+ resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
- array.prototype.findlast@1.2.5:
- resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
- engines: {node: '>= 0.4'}
+ framer-motion@12.38.0:
+ resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==}
+ peerDependencies:
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
- array.prototype.findlastindex@1.2.6:
- resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==}
- engines: {node: '>= 0.4'}
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
- array.prototype.flat@1.3.3:
- resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+ function.prototype.name@1.1.8:
+ resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
engines: {node: '>= 0.4'}
- array.prototype.flatmap@1.3.3:
- resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
- engines: {node: '>= 0.4'}
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
- array.prototype.tosorted@1.1.4:
- resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+ generator-function@2.0.1:
+ resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
engines: {node: '>= 0.4'}
- arraybuffer.prototype.slice@1.0.4:
- resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
- ast-types-flow@0.0.8:
- resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
- async-function@1.0.0:
- resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
- available-typed-arrays@1.0.7:
- resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ get-symbol-description@1.1.0:
+ resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
engines: {node: '>= 0.4'}
- axe-core@4.11.2:
- resolution: {integrity: sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw==}
- engines: {node: '>=4'}
+ get-tsconfig@4.13.7:
+ resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
- axobject-query@4.1.0:
- resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
- engines: {node: '>= 0.4'}
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
- balanced-match@1.0.2:
- resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
- balanced-match@4.0.4:
- resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
- engines: {node: 18 || 20 || >=22}
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
- baseline-browser-mapping@2.10.13:
- resolution: {integrity: sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==}
- engines: {node: '>=6.0.0'}
- hasBin: true
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
- brace-expansion@1.1.13:
- resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==}
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
- brace-expansion@2.0.2:
- resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
- brace-expansion@5.0.5:
- resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
- engines: {node: 18 || 20 || >=22}
+ has-bigints@1.1.0:
+ resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ engines: {node: '>= 0.4'}
- braces@3.0.3:
- resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
- call-bind-apply-helpers@1.0.2:
- resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
- engines: {node: '>= 0.4'}
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
- call-bind@1.0.8:
- resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+ has-proto@1.2.0:
+ resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
engines: {node: '>= 0.4'}
- call-bound@1.0.4:
- resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
- callsites@3.1.0:
- resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
- engines: {node: '>=6'}
-
- caniuse-lite@1.0.30001784:
- resolution: {integrity: sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==}
-
- chalk@4.1.2:
- resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
- engines: {node: '>=10'}
-
- change-case@5.4.4:
- resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==}
-
- class-variance-authority@0.7.1:
- resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
-
- client-only@0.0.1:
- resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
- clsx@2.1.1:
- resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
- engines: {node: '>=6'}
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
- color-convert@2.0.1:
- resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
- engines: {node: '>=7.0.0'}
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
- color-name@1.1.4:
- resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
- colorette@1.4.0:
- resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
+ ignore@7.0.5:
+ resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
+ engines: {node: '>= 4'}
- concat-map@0.0.1:
- resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
- cross-spawn@7.0.6:
- resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
- engines: {node: '>= 8'}
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
- csstype@3.2.3:
- resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+ index-to-position@1.1.0:
+ resolution: {integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==}
+ engines: {node: '>=18'}
- damerau-levenshtein@1.0.8:
- resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
+ input-otp@1.4.2:
+ resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
- data-view-buffer@1.0.2:
- resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ internal-slot@1.1.0:
+ resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
- data-view-byte-length@1.0.2:
- resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ is-array-buffer@3.0.5:
+ resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
- data-view-byte-offset@1.0.1:
- resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ is-async-function@2.1.1:
+ resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
engines: {node: '>= 0.4'}
- debug@3.2.7:
- resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
+ is-bigint@1.1.0:
+ resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ engines: {node: '>= 0.4'}
- debug@4.4.1:
- resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
- engines: {node: '>=6.0'}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
+ is-boolean-object@1.2.2:
+ resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ engines: {node: '>= 0.4'}
- debug@4.4.3:
- resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
- engines: {node: '>=6.0'}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
+ is-bun-module@2.0.0:
+ resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
- deep-is@0.1.4:
- resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
- define-data-property@1.1.4:
- resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
- define-properties@1.2.1:
- resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ is-data-view@1.0.2:
+ resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
engines: {node: '>= 0.4'}
- detect-libc@2.1.2:
- resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
- engines: {node: '>=8'}
+ is-date-object@1.1.0:
+ resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ engines: {node: '>= 0.4'}
- doctrine@2.1.0:
- resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
- dunder-proto@1.0.1:
- resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ is-finalizationregistry@1.1.1:
+ resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
engines: {node: '>= 0.4'}
- emoji-regex@9.2.2:
- resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+ is-generator-function@1.1.2:
+ resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
+ engines: {node: '>= 0.4'}
- enhanced-resolve@5.20.1:
- resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
- engines: {node: '>=10.13.0'}
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
- es-abstract@1.24.1:
- resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
+ is-map@2.0.3:
+ resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
engines: {node: '>= 0.4'}
- es-define-property@1.0.1:
- resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
engines: {node: '>= 0.4'}
- es-errors@1.3.0:
- resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ is-number-object@1.1.1:
+ resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
engines: {node: '>= 0.4'}
- es-iterator-helpers@1.3.1:
- resolution: {integrity: sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==}
- engines: {node: '>= 0.4'}
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
- es-object-atoms@1.1.1:
- resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ is-regex@1.2.1:
+ resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
- es-set-tostringtag@2.1.0:
- resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ is-set@2.0.3:
+ resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
engines: {node: '>= 0.4'}
- es-shim-unscopables@1.1.0:
- resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+ is-shared-array-buffer@1.0.4:
+ resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
engines: {node: '>= 0.4'}
- es-to-primitive@1.3.0:
- resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+ is-string@1.1.1:
+ resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
engines: {node: '>= 0.4'}
- escape-string-regexp@4.0.0:
- resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
- engines: {node: '>=10'}
-
- eslint-config-next@15.3.5:
- resolution: {integrity: sha512-oQdvnIgP68wh2RlR3MdQpvaJ94R6qEFl+lnu8ZKxPj5fsAHrSF/HlAOZcsimLw3DT6bnEQIUdbZC2Ab6sWyptg==}
- peerDependencies:
- eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
- typescript: '>=3.3.1'
- peerDependenciesMeta:
- typescript:
- optional: true
-
- eslint-import-resolver-node@0.3.10:
- resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==}
-
- eslint-import-resolver-typescript@3.10.1:
- resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==}
- engines: {node: ^14.18.0 || >=16.0.0}
- peerDependencies:
- eslint: '*'
- eslint-plugin-import: '*'
- eslint-plugin-import-x: '*'
- peerDependenciesMeta:
- eslint-plugin-import:
- optional: true
- eslint-plugin-import-x:
- optional: true
-
- eslint-module-utils@2.12.1:
- resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==}
- engines: {node: '>=4'}
- peerDependencies:
- '@typescript-eslint/parser': '*'
- eslint: '*'
- eslint-import-resolver-node: '*'
- eslint-import-resolver-typescript: '*'
- eslint-import-resolver-webpack: '*'
- peerDependenciesMeta:
- '@typescript-eslint/parser':
- optional: true
- eslint:
- optional: true
- eslint-import-resolver-node:
- optional: true
- eslint-import-resolver-typescript:
- optional: true
- eslint-import-resolver-webpack:
- optional: true
-
- eslint-plugin-import@2.32.0:
- resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==}
- engines: {node: '>=4'}
- peerDependencies:
- '@typescript-eslint/parser': '*'
- eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
- peerDependenciesMeta:
- '@typescript-eslint/parser':
- optional: true
+ is-symbol@1.1.1:
+ resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+ engines: {node: '>= 0.4'}
- eslint-plugin-jsx-a11y@6.10.2:
- resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
- engines: {node: '>=4.0'}
- peerDependencies:
- eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
+ is-typed-array@1.1.15:
+ resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+ engines: {node: '>= 0.4'}
- eslint-plugin-react-hooks@5.2.0:
- resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==}
- engines: {node: '>=10'}
- peerDependencies:
- eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+ is-weakmap@2.0.2:
+ resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+ engines: {node: '>= 0.4'}
- eslint-plugin-react@7.37.5:
- resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
- engines: {node: '>=4'}
- peerDependencies:
- eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+ is-weakref@1.1.1:
+ resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+ engines: {node: '>= 0.4'}
- eslint-scope@8.4.0:
- resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ is-weakset@2.0.4:
+ resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ engines: {node: '>= 0.4'}
- eslint-visitor-keys@3.4.3:
- resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
- eslint-visitor-keys@4.2.1:
- resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
- eslint-visitor-keys@5.0.1:
- resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
- engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+ iterator.prototype@1.1.5:
+ resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+ engines: {node: '>= 0.4'}
- eslint@9.39.4:
- resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ jiti@2.6.1:
+ resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
+
+ jotai@2.19.0:
+ resolution: {integrity: sha512-r2wwxEXP1F2JteDLZEOPoIpAHhV89paKsN5GWVYndPNMMP/uVZDcC+fNj0A8NjKgaPWzdyO8Vp8YcYKe0uCEqQ==}
+ engines: {node: '>=12.20.0'}
peerDependencies:
- jiti: '*'
+ '@babel/core': '>=7.0.0'
+ '@babel/template': '>=7.0.0'
+ '@types/react': '>=17.0.0'
+ react: '>=17.0.0'
peerDependenciesMeta:
- jiti:
+ '@babel/core':
+ optional: true
+ '@babel/template':
+ optional: true
+ '@types/react':
+ optional: true
+ react:
optional: true
- espree@10.4.0:
- resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ js-base64@3.7.8:
+ resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
- esquery@1.7.0:
- resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
- engines: {node: '>=0.10'}
+ js-levenshtein@1.1.6:
+ resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
+ engines: {node: '>=0.10.0'}
- esrecurse@4.3.0:
- resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
- engines: {node: '>=4.0'}
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
- estraverse@5.3.0:
- resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
- engines: {node: '>=4.0'}
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
- esutils@2.0.3:
- resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
- engines: {node: '>=0.10.0'}
+ js-yaml@4.1.1:
+ resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+ hasBin: true
- fast-deep-equal@3.1.3:
- resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
- fast-glob@3.3.1:
- resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
- engines: {node: '>=8.6.0'}
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
- fast-json-stable-stringify@2.1.0:
- resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
- fast-levenshtein@2.0.6:
- resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
- fastq@1.20.1:
- resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+ json5@1.0.2:
+ resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
+ hasBin: true
- fdir@6.5.0:
- resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
- engines: {node: '>=12.0.0'}
- peerDependencies:
- picomatch: ^3 || ^4
- peerDependenciesMeta:
- picomatch:
- optional: true
+ jsx-ast-utils@3.3.5:
+ resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+ engines: {node: '>=4.0'}
- file-entry-cache@8.0.0:
- resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
- engines: {node: '>=16.0.0'}
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
- fill-range@7.1.1:
- resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
- engines: {node: '>=8'}
+ language-subtag-registry@0.3.23:
+ resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
- find-up@5.0.0:
- resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
- engines: {node: '>=10'}
+ language-tags@1.0.9:
+ resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
+ engines: {node: '>=0.10'}
- flat-cache@4.0.1:
- resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
- engines: {node: '>=16'}
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
- flatted@3.4.2:
- resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
+ lightningcss-android-arm64@1.32.0:
+ resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
- for-each@0.3.5:
- resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
- engines: {node: '>= 0.4'}
+ lightningcss-darwin-arm64@1.32.0:
+ resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
- framer-motion@12.38.0:
- resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==}
- peerDependencies:
- '@emotion/is-prop-valid': '*'
- react: ^18.0.0 || ^19.0.0
- react-dom: ^18.0.0 || ^19.0.0
- peerDependenciesMeta:
- '@emotion/is-prop-valid':
- optional: true
- react:
- optional: true
- react-dom:
- optional: true
+ lightningcss-darwin-x64@1.32.0:
+ resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
- function-bind@1.1.2:
- resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+ lightningcss-freebsd-x64@1.32.0:
+ resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
- function.prototype.name@1.1.8:
- resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
- engines: {node: '>= 0.4'}
+ lightningcss-linux-arm-gnueabihf@1.32.0:
+ resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
- functions-have-names@1.2.3:
- resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+ lightningcss-linux-arm64-gnu@1.32.0:
+ resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
- generator-function@2.0.1:
- resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
- engines: {node: '>= 0.4'}
+ lightningcss-linux-arm64-musl@1.32.0:
+ resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
- get-intrinsic@1.3.0:
- resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
- engines: {node: '>= 0.4'}
+ lightningcss-linux-x64-gnu@1.32.0:
+ resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
- get-proto@1.0.1:
- resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
- engines: {node: '>= 0.4'}
+ lightningcss-linux-x64-musl@1.32.0:
+ resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
- get-symbol-description@1.1.0:
- resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
- engines: {node: '>= 0.4'}
+ lightningcss-win32-arm64-msvc@1.32.0:
+ resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
- get-tsconfig@4.13.7:
- resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
+ lightningcss-win32-x64-msvc@1.32.0:
+ resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
- glob-parent@5.1.2:
- resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
- engines: {node: '>= 6'}
+ lightningcss@1.32.0:
+ resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
+ engines: {node: '>= 12.0.0'}
- glob-parent@6.0.2:
- resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
- engines: {node: '>=10.13.0'}
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
- globals@14.0.0:
- resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
- engines: {node: '>=18'}
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
- globalthis@1.0.4:
- resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
- engines: {node: '>= 0.4'}
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
- gopd@1.2.0:
- resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
- engines: {node: '>= 0.4'}
+ lucide-react@0.525.0:
+ resolution: {integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
- graceful-fs@4.2.11:
- resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
- has-bigints@1.1.0:
- resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
- has-flag@4.0.0:
- resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
- engines: {node: '>=8'}
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
- has-property-descriptors@1.0.2:
- resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ minimatch@10.2.5:
+ resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
+ engines: {node: 18 || 20 || >=22}
+
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
- has-proto@1.2.0:
- resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
- engines: {node: '>= 0.4'}
+ minimatch@5.1.6:
+ resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+ engines: {node: '>=10'}
- has-symbols@1.1.0:
- resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
- engines: {node: '>= 0.4'}
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
- has-tostringtag@1.0.2:
- resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
- engines: {node: '>= 0.4'}
+ motion-dom@12.38.0:
+ resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
- hasown@2.0.2:
- resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
- engines: {node: '>= 0.4'}
+ motion-utils@12.36.0:
+ resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==}
- https-proxy-agent@7.0.6:
- resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
- engines: {node: '>= 14'}
+ motion@12.38.0:
+ resolution: {integrity: sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==}
+ peerDependencies:
+ '@emotion/is-prop-valid': '*'
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@emotion/is-prop-valid':
+ optional: true
+ react:
+ optional: true
+ react-dom:
+ optional: true
- ignore@5.3.2:
- resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
- engines: {node: '>= 4'}
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
- ignore@7.0.5:
- resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
- engines: {node: '>= 4'}
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
- import-fresh@3.3.1:
- resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
- engines: {node: '>=6'}
+ napi-postinstall@0.3.4:
+ resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ hasBin: true
- imurmurhash@0.1.4:
- resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
- engines: {node: '>=0.8.19'}
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
- index-to-position@1.1.0:
- resolution: {integrity: sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==}
- engines: {node: '>=18'}
+ next-themes@0.4.6:
+ resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
+ peerDependencies:
+ react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
- input-otp@1.4.2:
- resolution: {integrity: sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==}
+ next@16.2.2:
+ resolution: {integrity: sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==}
+ engines: {node: '>=20.9.0'}
+ hasBin: true
peerDependencies:
- react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
- react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.51.1
+ babel-plugin-react-compiler: '*'
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
- internal-slot@1.1.0:
- resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+ node-exports-info@1.6.0:
+ resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==}
engines: {node: '>= 0.4'}
- is-array-buffer@3.0.5:
- resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
- engines: {node: '>= 0.4'}
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
- is-async-function@2.1.1:
- resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
- is-bigint@1.1.0:
- resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
engines: {node: '>= 0.4'}
- is-boolean-object@1.2.2:
- resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ object.assign@4.1.7:
+ resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
engines: {node: '>= 0.4'}
- is-bun-module@2.0.0:
- resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
-
- is-callable@1.2.7:
- resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ object.entries@1.1.9:
+ resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
engines: {node: '>= 0.4'}
- is-core-module@2.16.1:
- resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ object.fromentries@2.0.8:
+ resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
engines: {node: '>= 0.4'}
- is-data-view@1.0.2:
- resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+ object.groupby@1.0.3:
+ resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
engines: {node: '>= 0.4'}
- is-date-object@1.1.0:
- resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ object.values@1.2.1:
+ resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
- is-extglob@2.1.1:
- resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
- engines: {node: '>=0.10.0'}
+ openapi-fetch@0.14.1:
+ resolution: {integrity: sha512-l7RarRHxlEZYjMLd/PR0slfMVse2/vvIAGm75/F7J6MlQ8/b9uUQmUF2kCPrQhJqMXSxmYWObVgeYXbFYzZR+A==}
- is-finalizationregistry@1.1.1:
- resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
- engines: {node: '>= 0.4'}
+ openapi-react-query@0.5.4:
+ resolution: {integrity: sha512-V9lRiozjHot19/BYSgXYoyznDxDJQhEBSdi26+SJ0UqjMANLQhkni4XG+Z7e3Ag7X46ZLMrL9VxYkghU3QvbWg==}
+ peerDependencies:
+ '@tanstack/react-query': ^5.80.0
+ openapi-fetch: ^0.17.0
- is-generator-function@1.1.2:
- resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
- engines: {node: '>= 0.4'}
+ openapi-typescript-helpers@0.0.15:
+ resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==}
- is-glob@4.0.3:
- resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
- engines: {node: '>=0.10.0'}
+ openapi-typescript-helpers@0.1.0:
+ resolution: {integrity: sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==}
- is-map@2.0.3:
- resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
- engines: {node: '>= 0.4'}
+ openapi-typescript@7.8.0:
+ resolution: {integrity: sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==}
+ hasBin: true
+ peerDependencies:
+ typescript: ^5.x
- is-negative-zero@2.0.3:
- resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
- engines: {node: '>= 0.4'}
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
- is-number-object@1.1.1:
- resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+ own-keys@1.0.1:
+ resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'}
- is-number@7.0.0:
- resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
- engines: {node: '>=0.12.0'}
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
- is-regex@1.2.1:
- resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
- engines: {node: '>= 0.4'}
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
- is-set@2.0.3:
- resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
- engines: {node: '>= 0.4'}
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
- is-shared-array-buffer@1.0.4:
- resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
- engines: {node: '>= 0.4'}
+ parse-json@8.3.0:
+ resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==}
+ engines: {node: '>=18'}
- is-string@1.1.1:
- resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
- engines: {node: '>= 0.4'}
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
- is-symbol@1.1.1:
- resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
- engines: {node: '>= 0.4'}
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
- is-typed-array@1.1.15:
- resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
- engines: {node: '>= 0.4'}
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
- is-weakmap@2.0.2:
- resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
- engines: {node: '>= 0.4'}
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
- is-weakref@1.1.1:
- resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
- engines: {node: '>= 0.4'}
+ picomatch@2.3.2:
+ resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
+ engines: {node: '>=8.6'}
- is-weakset@2.0.4:
- resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+ engines: {node: '>=12'}
+
+ pluralize@8.0.0:
+ resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
+ engines: {node: '>=4'}
+
+ possible-typed-array-names@1.1.0:
+ resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'}
- isarray@2.0.5:
- resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
- isexe@2.0.0:
- resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ postcss@8.5.8:
+ resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
- iterator.prototype@1.1.5:
- resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
- engines: {node: '>= 0.4'}
+ qr.js@0.0.0:
+ resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==}
- jiti@2.6.1:
- resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
- hasBin: true
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- jotai@2.19.0:
- resolution: {integrity: sha512-r2wwxEXP1F2JteDLZEOPoIpAHhV89paKsN5GWVYndPNMMP/uVZDcC+fNj0A8NjKgaPWzdyO8Vp8YcYKe0uCEqQ==}
- engines: {node: '>=12.20.0'}
+ radix-ui@1.4.3:
+ resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==}
peerDependencies:
- '@babel/core': '>=7.0.0'
- '@babel/template': '>=7.0.0'
- '@types/react': '>=17.0.0'
- react: '>=17.0.0'
+ '@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:
- '@babel/core':
- optional: true
- '@babel/template':
- optional: true
'@types/react':
optional: true
- react:
+ '@types/react-dom':
optional: true
- js-base64@3.7.8:
- resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
+ react-dom@19.2.4:
+ resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
+ peerDependencies:
+ react: ^19.2.4
- js-levenshtein@1.1.6:
- resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
- engines: {node: '>=0.10.0'}
+ react-hook-form@7.72.0:
+ resolution: {integrity: sha512-V4v6jubaf6JAurEaVnT9aUPKFbNtDgohj5CIgVGyPHvT9wRx5OZHVjz31GsxnPNI278XMu+ruFz+wGOscHaLKw==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
- js-tokens@4.0.0:
- resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
- js-yaml@4.1.0:
- resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
- hasBin: true
+ react-qr-code@2.0.18:
+ resolution: {integrity: sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg==}
+ peerDependencies:
+ react: '*'
- js-yaml@4.1.1:
- resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
- hasBin: true
+ react-remove-scroll-bar@2.3.8:
+ resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- json-buffer@3.0.1:
- resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+ react-remove-scroll@2.7.2:
+ resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- json-schema-traverse@0.4.1:
- resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+ react-style-singleton@2.2.3:
+ resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- json-schema-traverse@1.0.0:
- resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+ react@19.2.4:
+ resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
+ engines: {node: '>=0.10.0'}
- json-stable-stringify-without-jsonify@1.0.1:
- resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+ reflect.getprototypeof@1.0.10:
+ resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+ engines: {node: '>= 0.4'}
- json5@1.0.2:
- resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
- hasBin: true
+ regexp.prototype.flags@1.5.4:
+ resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+ engines: {node: '>= 0.4'}
- jsx-ast-utils@3.3.5:
- resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
- engines: {node: '>=4.0'}
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
- keyv@4.5.4:
- resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
- language-subtag-registry@0.3.23:
- resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
+ resolve-pkg-maps@1.0.0:
+ resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
- language-tags@1.0.9:
- resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
- engines: {node: '>=0.10'}
+ resolve@2.0.0-next.6:
+ resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
- levn@0.4.1:
- resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
- engines: {node: '>= 0.8.0'}
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
- lightningcss-android-arm64@1.32.0:
- resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [android]
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
- lightningcss-darwin-arm64@1.32.0:
- resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [darwin]
+ safe-array-concat@1.1.3:
+ resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
+ engines: {node: '>=0.4'}
- lightningcss-darwin-x64@1.32.0:
- resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [darwin]
+ safe-push-apply@1.0.0:
+ resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+ engines: {node: '>= 0.4'}
- lightningcss-freebsd-x64@1.32.0:
- resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [freebsd]
+ safe-regex-test@1.1.0:
+ resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+ engines: {node: '>= 0.4'}
- lightningcss-linux-arm-gnueabihf@1.32.0:
- resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm]
- os: [linux]
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
- lightningcss-linux-arm64-gnu@1.32.0:
- resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
- lightningcss-linux-arm64-musl@1.32.0:
- resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
+ semver@7.7.4:
+ resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
+ engines: {node: '>=10'}
+ hasBin: true
- lightningcss-linux-x64-gnu@1.32.0:
- resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [linux]
- libc: [glibc]
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
- lightningcss-linux-x64-musl@1.32.0:
- resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [linux]
- libc: [musl]
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
- lightningcss-win32-arm64-msvc@1.32.0:
- resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
- engines: {node: '>= 12.0.0'}
- cpu: [arm64]
- os: [win32]
+ set-proto@1.0.0:
+ resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+ engines: {node: '>= 0.4'}
- lightningcss-win32-x64-msvc@1.32.0:
- resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
- engines: {node: '>= 12.0.0'}
- cpu: [x64]
- os: [win32]
+ sharp@0.34.5:
+ resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
- lightningcss@1.32.0:
- resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
- engines: {node: '>= 12.0.0'}
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
- locate-path@6.0.0:
- resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
- engines: {node: '>=10'}
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
- lodash.merge@4.6.2:
- resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ side-channel-list@1.0.0:
+ resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+ engines: {node: '>= 0.4'}
- loose-envify@1.4.0:
- resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
- hasBin: true
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
- lucide-react@0.525.0:
- resolution: {integrity: sha512-Tm1txJ2OkymCGkvwoHt33Y2JpN5xucVq1slHcgE6Lk0WjDfjgKWor5CdVER8U6DvcfMwh4M8XxmpTiyzfmfDYQ==}
- peerDependencies:
- react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
- magic-string@0.30.21:
- resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+ side-channel@1.1.0:
+ resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+ engines: {node: '>= 0.4'}
- math-intrinsics@1.1.0:
- resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ stable-hash@0.0.5:
+ resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
+
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
- merge2@1.4.1:
- resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
- engines: {node: '>= 8'}
+ string.prototype.includes@2.0.1:
+ resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
+ engines: {node: '>= 0.4'}
- micromatch@4.0.8:
- resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
- engines: {node: '>=8.6'}
+ string.prototype.matchall@4.0.12:
+ resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+ engines: {node: '>= 0.4'}
- minimatch@10.2.5:
- resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
- engines: {node: 18 || 20 || >=22}
+ string.prototype.repeat@1.0.0:
+ resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
- minimatch@3.1.5:
- resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
+ string.prototype.trim@1.2.10:
+ resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
+ engines: {node: '>= 0.4'}
- minimatch@5.1.6:
- resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
- engines: {node: '>=10'}
+ string.prototype.trimend@1.0.9:
+ resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+ engines: {node: '>= 0.4'}
- minimist@1.2.8:
- resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ string.prototype.trimstart@1.0.8:
+ resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+ engines: {node: '>= 0.4'}
- motion-dom@12.38.0:
- resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==}
+ strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
- motion-utils@12.36.0:
- resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==}
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
- motion@12.38.0:
- resolution: {integrity: sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==}
+ styled-jsx@5.1.6:
+ resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
+ engines: {node: '>= 12.0.0'}
peerDependencies:
- '@emotion/is-prop-valid': '*'
- react: ^18.0.0 || ^19.0.0
- react-dom: ^18.0.0 || ^19.0.0
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
peerDependenciesMeta:
- '@emotion/is-prop-valid':
- optional: true
- react:
+ '@babel/core':
optional: true
- react-dom:
+ babel-plugin-macros:
optional: true
- ms@2.1.3:
- resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ supports-color@10.0.0:
+ resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==}
+ engines: {node: '>=18'}
- nanoid@3.3.11:
- resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
- napi-postinstall@0.3.4:
- resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
- engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
- hasBin: true
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
- natural-compare@1.4.0:
- resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ tailwind-merge@3.5.0:
+ resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
- next-themes@0.4.6:
- resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
- peerDependencies:
- react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
- react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ tailwindcss@4.2.2:
+ resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
- next@16.2.2:
- resolution: {integrity: sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==}
- engines: {node: '>=20.9.0'}
- hasBin: true
+ tapable@2.3.2:
+ resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==}
+ engines: {node: '>=6'}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ ts-api-utils@2.5.0:
+ resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
+ engines: {node: '>=18.12'}
peerDependencies:
- '@opentelemetry/api': ^1.1.0
- '@playwright/test': ^1.51.1
- babel-plugin-react-compiler: '*'
- react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
- react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
- sass: ^1.3.0
- peerDependenciesMeta:
- '@opentelemetry/api':
- optional: true
- '@playwright/test':
- optional: true
- babel-plugin-react-compiler:
- optional: true
- sass:
- optional: true
+ typescript: '>=4.8.4'
- node-exports-info@1.6.0:
- resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==}
- engines: {node: '>= 0.4'}
+ tsconfig-paths@3.15.0:
+ resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
- object-assign@4.1.1:
- resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
- engines: {node: '>=0.10.0'}
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
- object-inspect@1.13.4:
- resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
- engines: {node: '>= 0.4'}
+ tw-animate-css@1.4.0:
+ resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
- object-keys@1.1.1:
- resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
- engines: {node: '>= 0.4'}
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
- object.assign@4.1.7:
- resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
- engines: {node: '>= 0.4'}
+ type-fest@4.41.0:
+ resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
+ engines: {node: '>=16'}
- object.entries@1.1.9:
- resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
+ typed-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
- object.fromentries@2.0.8:
- resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+ typed-array-byte-length@1.0.3:
+ resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
engines: {node: '>= 0.4'}
- object.groupby@1.0.3:
- resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
+ typed-array-byte-offset@1.0.4:
+ resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
engines: {node: '>= 0.4'}
- object.values@1.2.1:
- resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
+ typed-array-length@1.0.7:
+ resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
- openapi-fetch@0.14.1:
- resolution: {integrity: sha512-l7RarRHxlEZYjMLd/PR0slfMVse2/vvIAGm75/F7J6MlQ8/b9uUQmUF2kCPrQhJqMXSxmYWObVgeYXbFYzZR+A==}
-
- openapi-react-query@0.5.4:
- resolution: {integrity: sha512-V9lRiozjHot19/BYSgXYoyznDxDJQhEBSdi26+SJ0UqjMANLQhkni4XG+Z7e3Ag7X46ZLMrL9VxYkghU3QvbWg==}
- peerDependencies:
- '@tanstack/react-query': ^5.80.0
- openapi-fetch: ^0.17.0
-
- openapi-typescript-helpers@0.0.15:
- resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==}
-
- openapi-typescript-helpers@0.1.0:
- resolution: {integrity: sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==}
-
- openapi-typescript@7.8.0:
- resolution: {integrity: sha512-1EeVWmDzi16A+siQlo/SwSGIT7HwaFAVjvMA7/jG5HMLSnrUOzPL7uSTRZZa4v/LCRxHTApHKtNY6glApEoiUQ==}
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
hasBin: true
- peerDependencies:
- typescript: ^5.x
-
- optionator@0.9.4:
- resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
- engines: {node: '>= 0.8.0'}
- own-keys@1.0.1:
- resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+ unbox-primitive@1.1.0:
+ resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
- p-limit@3.1.0:
- resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
- engines: {node: '>=10'}
-
- p-locate@5.0.0:
- resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
- engines: {node: '>=10'}
-
- parent-module@1.0.1:
- resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
- engines: {node: '>=6'}
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
- parse-json@8.3.0:
- resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==}
- engines: {node: '>=18'}
+ unrs-resolver@1.11.1:
+ resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
- path-exists@4.0.0:
- resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
- engines: {node: '>=8'}
+ uri-js-replace@1.0.1:
+ resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==}
- path-key@3.1.1:
- resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
- engines: {node: '>=8'}
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
- path-parse@1.0.7:
- resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+ use-callback-ref@1.3.3:
+ resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- picocolors@1.1.1:
- resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+ use-sidecar@1.1.3:
+ resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
- picomatch@2.3.2:
- resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
- engines: {node: '>=8.6'}
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- picomatch@4.0.4:
- resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
- engines: {node: '>=12'}
+ which-boxed-primitive@1.1.1:
+ resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+ engines: {node: '>= 0.4'}
- pluralize@8.0.0:
- resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
- engines: {node: '>=4'}
+ which-builtin-type@1.2.1:
+ resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+ engines: {node: '>= 0.4'}
- possible-typed-array-names@1.1.0:
- resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+ which-collection@1.0.2:
+ resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
engines: {node: '>= 0.4'}
- postcss@8.4.31:
- resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
- engines: {node: ^10 || ^12 || >=14}
+ which-typed-array@1.1.20:
+ resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
+ engines: {node: '>= 0.4'}
- postcss@8.5.8:
- resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
- engines: {node: ^10 || ^12 || >=14}
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
- prelude-ls@1.2.1:
- resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
- engines: {node: '>= 0.8.0'}
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
- prop-types@15.8.1:
- resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ yaml-ast-parser@0.0.43:
+ resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==}
- punycode@2.3.1:
- resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
- engines: {node: '>=6'}
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
- queue-microtask@1.2.3:
- resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
- react-dom@19.2.4:
- resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
- peerDependencies:
- react: ^19.2.4
+ zod@3.25.76:
+ resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
- react-hook-form@7.72.0:
- resolution: {integrity: sha512-V4v6jubaf6JAurEaVnT9aUPKFbNtDgohj5CIgVGyPHvT9wRx5OZHVjz31GsxnPNI278XMu+ruFz+wGOscHaLKw==}
- engines: {node: '>=18.0.0'}
- peerDependencies:
- react: ^16.8.0 || ^17 || ^18 || ^19
+snapshots:
- react-is@16.13.1:
- resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+ '@alloc/quick-lru@5.2.0': {}
- react@19.2.4:
- resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
- engines: {node: '>=0.10.0'}
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.27.1
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
- reflect.getprototypeof@1.0.10:
- resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
- engines: {node: '>= 0.4'}
+ '@babel/helper-validator-identifier@7.27.1': {}
- regexp.prototype.flags@1.5.4:
- resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
- engines: {node: '>= 0.4'}
+ '@emnapi/core@1.9.2':
+ dependencies:
+ '@emnapi/wasi-threads': 1.2.1
+ tslib: 2.8.1
+ optional: true
- require-from-string@2.0.2:
- resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
- engines: {node: '>=0.10.0'}
+ '@emnapi/runtime@1.9.2':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
- resolve-from@4.0.0:
- resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
- engines: {node: '>=4'}
+ '@emnapi/wasi-threads@1.2.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
- resolve-pkg-maps@1.0.0:
- resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+ '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))':
+ dependencies:
+ eslint: 9.39.4(jiti@2.6.1)
+ eslint-visitor-keys: 3.4.3
- resolve@2.0.0-next.6:
- resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==}
- engines: {node: '>= 0.4'}
- hasBin: true
+ '@eslint-community/regexpp@4.12.2': {}
- reusify@1.1.0:
- resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
- engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ '@eslint/config-array@0.21.2':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.3
+ minimatch: 3.1.5
+ transitivePeerDependencies:
+ - supports-color
- run-parallel@1.2.0:
- resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ '@eslint/config-helpers@0.4.2':
+ dependencies:
+ '@eslint/core': 0.17.0
- safe-array-concat@1.1.3:
- resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
- engines: {node: '>=0.4'}
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
- safe-push-apply@1.0.0:
- resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
- engines: {node: '>= 0.4'}
+ '@eslint/eslintrc@3.3.5':
+ dependencies:
+ ajv: 6.14.0
+ debug: 4.4.3
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.1
+ minimatch: 3.1.5
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
- safe-regex-test@1.1.0:
- resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
- engines: {node: '>= 0.4'}
+ '@eslint/js@9.39.4': {}
- scheduler@0.27.0:
- resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+ '@eslint/object-schema@2.1.7': {}
- semver@6.3.1:
- resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
- hasBin: true
+ '@eslint/plugin-kit@0.4.1':
+ dependencies:
+ '@eslint/core': 0.17.0
+ levn: 0.4.1
- semver@7.7.4:
- resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
- engines: {node: '>=10'}
- hasBin: true
+ '@floating-ui/core@1.7.5':
+ dependencies:
+ '@floating-ui/utils': 0.2.11
- set-function-length@1.2.2:
- resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
- engines: {node: '>= 0.4'}
+ '@floating-ui/dom@1.7.6':
+ dependencies:
+ '@floating-ui/core': 1.7.5
+ '@floating-ui/utils': 0.2.11
- set-function-name@2.0.2:
- resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
- engines: {node: '>= 0.4'}
+ '@floating-ui/react-dom@2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@floating-ui/dom': 1.7.6
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
- set-proto@1.0.0:
- resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
- engines: {node: '>= 0.4'}
+ '@floating-ui/utils@0.2.11': {}
- sharp@0.34.5:
- resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
- engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ '@hookform/resolvers@5.2.2(react-hook-form@7.72.0(react@19.2.4))':
+ dependencies:
+ '@standard-schema/utils': 0.3.0
+ react-hook-form: 7.72.0(react@19.2.4)
- shebang-command@2.0.0:
- resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
- engines: {node: '>=8'}
+ '@humanfs/core@0.19.1': {}
- shebang-regex@3.0.0:
- resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
- engines: {node: '>=8'}
+ '@humanfs/node@0.16.7':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.4.3
- side-channel-list@1.0.0:
- resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
- engines: {node: '>= 0.4'}
+ '@humanwhocodes/module-importer@1.0.1': {}
- side-channel-map@1.0.1:
- resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
- engines: {node: '>= 0.4'}
+ '@humanwhocodes/retry@0.4.3': {}
- side-channel-weakmap@1.0.2:
- resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
- engines: {node: '>= 0.4'}
+ '@img/colour@1.1.0':
+ optional: true
- side-channel@1.1.0:
- resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
- engines: {node: '>= 0.4'}
+ '@img/sharp-darwin-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ optional: true
- source-map-js@1.2.1:
- resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
- engines: {node: '>=0.10.0'}
+ '@img/sharp-darwin-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ optional: true
- stable-hash@0.0.5:
- resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ optional: true
- stop-iteration-iterator@1.1.0:
- resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
- engines: {node: '>= 0.4'}
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ optional: true
- string.prototype.includes@2.0.1:
- resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
- engines: {node: '>= 0.4'}
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ optional: true
- string.prototype.matchall@4.0.12:
- resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
- engines: {node: '>= 0.4'}
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ optional: true
- string.prototype.repeat@1.0.0:
- resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ optional: true
- string.prototype.trim@1.2.10:
- resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
- engines: {node: '>= 0.4'}
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ optional: true
- string.prototype.trimend@1.0.9:
- resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
- engines: {node: '>= 0.4'}
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ optional: true
- string.prototype.trimstart@1.0.8:
- resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
- engines: {node: '>= 0.4'}
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ optional: true
- strip-bom@3.0.0:
- resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
- engines: {node: '>=4'}
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ optional: true
- strip-json-comments@3.1.1:
- resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
- engines: {node: '>=8'}
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ optional: true
- styled-jsx@5.1.6:
- resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
- engines: {node: '>= 12.0.0'}
- peerDependencies:
- '@babel/core': '*'
- babel-plugin-macros: '*'
- react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
- peerDependenciesMeta:
- '@babel/core':
- optional: true
- babel-plugin-macros:
- optional: true
+ '@img/sharp-linux-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ optional: true
- supports-color@10.0.0:
- resolution: {integrity: sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==}
- engines: {node: '>=18'}
+ '@img/sharp-linux-arm@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ optional: true
- supports-color@7.2.0:
- resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
- engines: {node: '>=8'}
+ '@img/sharp-linux-ppc64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ optional: true
- supports-preserve-symlinks-flag@1.0.0:
- resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
- engines: {node: '>= 0.4'}
+ '@img/sharp-linux-riscv64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ optional: true
- tailwind-merge@3.5.0:
- resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==}
+ '@img/sharp-linux-s390x@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ optional: true
- tailwindcss@4.2.2:
- resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==}
+ '@img/sharp-linux-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ optional: true
- tapable@2.3.2:
- resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==}
- engines: {node: '>=6'}
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ optional: true
- tinyglobby@0.2.15:
- resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
- engines: {node: '>=12.0.0'}
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ optional: true
- to-regex-range@5.0.1:
- resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
- engines: {node: '>=8.0'}
+ '@img/sharp-wasm32@0.34.5':
+ dependencies:
+ '@emnapi/runtime': 1.9.2
+ optional: true
- ts-api-utils@2.5.0:
- resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
- engines: {node: '>=18.12'}
- peerDependencies:
- typescript: '>=4.8.4'
+ '@img/sharp-win32-arm64@0.34.5':
+ optional: true
- tsconfig-paths@3.15.0:
- resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
+ '@img/sharp-win32-ia32@0.34.5':
+ optional: true
- tslib@2.8.1:
- resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ '@img/sharp-win32-x64@0.34.5':
+ optional: true
- tw-animate-css@1.4.0:
- resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
- type-check@0.4.0:
- resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
- engines: {node: '>= 0.8.0'}
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
- type-fest@4.41.0:
- resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
- engines: {node: '>=16'}
+ '@jridgewell/resolve-uri@3.1.2': {}
- typed-array-buffer@1.0.3:
- resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
- engines: {node: '>= 0.4'}
+ '@jridgewell/sourcemap-codec@1.5.5': {}
- typed-array-byte-length@1.0.3:
- resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
- engines: {node: '>= 0.4'}
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
- typed-array-byte-offset@1.0.4:
- resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
- engines: {node: '>= 0.4'}
+ '@mantine/hooks@8.3.18(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
- typed-array-length@1.0.7:
- resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
- engines: {node: '>= 0.4'}
+ '@napi-rs/wasm-runtime@0.2.12':
+ dependencies:
+ '@emnapi/core': 1.9.2
+ '@emnapi/runtime': 1.9.2
+ '@tybys/wasm-util': 0.10.1
+ optional: true
- typescript@5.9.3:
- resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
- engines: {node: '>=14.17'}
- hasBin: true
+ '@next/env@16.2.2': {}
- unbox-primitive@1.1.0:
- resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
- engines: {node: '>= 0.4'}
+ '@next/eslint-plugin-next@15.3.5':
+ dependencies:
+ fast-glob: 3.3.1
- undici-types@6.21.0:
- resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ '@next/swc-darwin-arm64@16.2.2':
+ optional: true
- unrs-resolver@1.11.1:
- resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}
+ '@next/swc-darwin-x64@16.2.2':
+ optional: true
- uri-js-replace@1.0.1:
- resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==}
+ '@next/swc-linux-arm64-gnu@16.2.2':
+ optional: true
- uri-js@4.4.1:
- resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ '@next/swc-linux-arm64-musl@16.2.2':
+ optional: true
- which-boxed-primitive@1.1.1:
- resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
- engines: {node: '>= 0.4'}
+ '@next/swc-linux-x64-gnu@16.2.2':
+ optional: true
- which-builtin-type@1.2.1:
- resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
- engines: {node: '>= 0.4'}
+ '@next/swc-linux-x64-musl@16.2.2':
+ optional: true
- which-collection@1.0.2:
- resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
- engines: {node: '>= 0.4'}
+ '@next/swc-win32-arm64-msvc@16.2.2':
+ optional: true
- which-typed-array@1.1.20:
- resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
- engines: {node: '>= 0.4'}
+ '@next/swc-win32-x64-msvc@16.2.2':
+ optional: true
- which@2.0.2:
- resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
- engines: {node: '>= 8'}
- hasBin: true
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
- word-wrap@1.2.5:
- resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
- engines: {node: '>=0.10.0'}
+ '@nodelib/fs.stat@2.0.5': {}
- yaml-ast-parser@0.0.43:
- resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==}
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.1
- yargs-parser@21.1.1:
- resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
- engines: {node: '>=12'}
+ '@nolyfill/is-core-module@1.0.39': {}
- yocto-queue@0.1.0:
- resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
- engines: {node: '>=10'}
+ '@radix-ui/number@1.1.1': {}
- zod@3.25.76:
- resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
+ '@radix-ui/primitive@1.1.3': {}
-snapshots:
+ '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@alloc/quick-lru@5.2.0': {}
+ '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@babel/code-frame@7.27.1':
+ '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@babel/helper-validator-identifier': 7.27.1
- js-tokens: 4.0.0
- picocolors: 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@babel/helper-validator-identifier@7.27.1': {}
+ '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@emnapi/core@1.9.2':
+ '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@emnapi/wasi-threads': 1.2.1
- tslib: 2.8.1
- optional: true
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
+
+ '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@emnapi/runtime@1.9.2':
+ '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- tslib: 2.8.1
- optional: true
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@emnapi/wasi-threads@1.2.1':
+ '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- tslib: 2.8.1
- optional: true
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4(jiti@2.6.1))':
+ '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- eslint: 9.39.4(jiti@2.6.1)
- eslint-visitor-keys: 3.4.3
-
- '@eslint-community/regexpp@4.12.2': {}
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@eslint/config-array@0.21.2':
+ '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@eslint/object-schema': 2.1.7
- debug: 4.4.3
- minimatch: 3.1.5
- transitivePeerDependencies:
- - supports-color
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@eslint/config-helpers@0.4.2':
+ '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@eslint/core': 0.17.0
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@eslint/core@0.17.0':
+ '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@types/json-schema': 7.0.15
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@eslint/eslintrc@3.3.5':
+ '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- ajv: 6.14.0
- debug: 4.4.3
- espree: 10.4.0
- globals: 14.0.0
- ignore: 5.3.2
- import-fresh: 3.3.1
- js-yaml: 4.1.1
- minimatch: 3.1.5
- strip-json-comments: 3.1.1
- transitivePeerDependencies:
- - supports-color
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ aria-hidden: 1.2.6
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@eslint/js@9.39.4': {}
+ '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@eslint/object-schema@2.1.7': {}
+ '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@eslint/plugin-kit@0.4.1':
+ '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@eslint/core': 0.17.0
- levn: 0.4.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@hookform/resolvers@5.2.2(react-hook-form@7.72.0(react@19.2.4))':
+ '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@standard-schema/utils': 0.3.0
- react-hook-form: 7.72.0(react@19.2.4)
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@humanfs/core@0.19.1': {}
+ '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@humanfs/node@0.16.7':
+ '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@humanfs/core': 0.19.1
- '@humanwhocodes/retry': 0.4.3
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@humanwhocodes/module-importer@1.0.1': {}
+ '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@humanwhocodes/retry@0.4.3': {}
+ '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@img/colour@1.1.0':
- optional: true
+ '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-darwin-arm64@0.34.5':
+ '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-darwin-arm64': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-darwin-x64@0.34.5':
+ '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ aria-hidden: 1.2.6
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-darwin-x64': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-darwin-arm64@1.2.4':
- optional: true
+ '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-darwin-x64@1.2.4':
- optional: true
+ '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-linux-arm64@1.2.4':
- optional: true
+ '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-linux-arm@1.2.4':
- optional: true
+ '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-linux-ppc64@1.2.4':
- optional: true
+ '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ aria-hidden: 1.2.6
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-linux-riscv64@1.2.4':
- optional: true
+ '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/rect': 1.1.1
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-linux-s390x@1.2.4':
- optional: true
+ '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-linux-x64@1.2.4':
- optional: true
+ '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
- optional: true
+ '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-libvips-linuxmusl-x64@1.2.4':
- optional: true
+ '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-linux-arm64@0.34.5':
+ '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-linux-arm64': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-linux-arm@0.34.5':
+ '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-linux-arm': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-linux-ppc64@0.34.5':
+ '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-linux-ppc64': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-linux-riscv64@0.34.5':
+ '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-linux-riscv64': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-linux-s390x@0.34.5':
+ '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ aria-hidden: 1.2.6
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-linux-s390x': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-linux-x64@0.34.5':
+ '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-linux-x64': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-linuxmusl-arm64@0.34.5':
+ '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-linuxmusl-x64@0.34.5':
+ '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/number': 1.1.1
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
- '@img/sharp-libvips-linuxmusl-x64': 1.2.4
- optional: true
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@img/sharp-wasm32@0.34.5':
+ '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@emnapi/runtime': 1.9.2
- optional: true
-
- '@img/sharp-win32-arm64@0.34.5':
- optional: true
-
- '@img/sharp-win32-ia32@0.34.5':
- optional: true
-
- '@img/sharp-win32-x64@0.34.5':
- optional: true
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@jridgewell/gen-mapping@0.3.13':
+ '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@jridgewell/sourcemap-codec': 1.5.5
- '@jridgewell/trace-mapping': 0.3.31
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@jridgewell/remapping@2.3.5':
+ '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@jridgewell/gen-mapping': 0.3.13
- '@jridgewell/trace-mapping': 0.3.31
-
- '@jridgewell/resolve-uri@3.1.2': {}
-
- '@jridgewell/sourcemap-codec@1.5.5': {}
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@jridgewell/trace-mapping@0.3.31':
+ '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.5.5
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@mantine/hooks@8.3.18(react@19.2.4)':
+ '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@napi-rs/wasm-runtime@0.2.12':
+ '@radix-ui/react-toggle-group@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@emnapi/core': 1.9.2
- '@emnapi/runtime': 1.9.2
- '@tybys/wasm-util': 0.10.1
- optional: true
-
- '@next/env@16.2.2': {}
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@next/eslint-plugin-next@15.3.5':
+ '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- fast-glob: 3.3.1
-
- '@next/swc-darwin-arm64@16.2.2':
- optional: true
-
- '@next/swc-darwin-x64@16.2.2':
- optional: true
-
- '@next/swc-linux-arm64-gnu@16.2.2':
- optional: true
-
- '@next/swc-linux-arm64-musl@16.2.2':
- optional: true
-
- '@next/swc-linux-x64-gnu@16.2.2':
- optional: true
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@next/swc-linux-x64-musl@16.2.2':
- optional: true
+ '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@next/swc-win32-arm64-msvc@16.2.2':
- optional: true
+ '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@next/swc-win32-x64-msvc@16.2.2':
- optional: true
+ '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@nodelib/fs.scandir@2.1.5':
+ '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@nodelib/fs.stat': 2.0.5
- run-parallel: 1.2.0
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@nodelib/fs.stat@2.0.5': {}
+ '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.4)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@nodelib/fs.walk@1.2.8':
+ '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@nodelib/fs.scandir': 2.1.5
- fastq: 1.20.1
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ react: 19.2.4
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@nolyfill/is-core-module@1.0.39': {}
+ '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.14)(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+ use-sync-external-store: 1.6.0(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
- '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)':
+ '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.4)':
dependencies:
react: 19.2.4
optionalDependencies:
'@types/react': 19.2.14
- '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
- react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
- '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/rect': 1.1.1
react: 19.2.4
- react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
- '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.4)':
dependencies:
- '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
react: 19.2.4
- react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
- '@types/react-dom': 19.2.3(@types/react@19.2.14)
- '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.4)':
+ '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
'@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
+
+ '@radix-ui/rect@1.1.1': {}
'@redocly/ajv@8.11.2':
dependencies:
@@ -2802,6 +4349,10 @@ snapshots:
argparse@2.0.1: {}
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
aria-query@5.3.2: {}
array-buffer-byte-length@1.0.2:
@@ -3010,6 +4561,8 @@ snapshots:
detect-libc@2.1.2: {}
+ detect-node-es@1.1.0: {}
+
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
@@ -3412,6 +4965,8 @@ snapshots:
hasown: 2.0.2
math-intrinsics: 1.1.0
+ get-nonce@1.0.1: {}
+
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
@@ -3964,8 +5519,73 @@ snapshots:
punycode@2.3.1: {}
+ qr.js@0.0.0: {}
+
queue-microtask@1.2.3: {}
+ radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+ dependencies:
+ '@radix-ui/primitive': 1.1.3
+ '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4)
+ '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+ '@types/react-dom': 19.2.3(@types/react@19.2.14)
+
react-dom@19.2.4(react@19.2.4):
dependencies:
react: 19.2.4
@@ -3977,6 +5597,39 @@ snapshots:
react-is@16.13.1: {}
+ react-qr-code@2.0.18(react@19.2.4):
+ dependencies:
+ prop-types: 15.8.1
+ qr.js: 0.0.0
+ react: 19.2.4
+
+ react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+ react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.2.14
+
+ react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+ react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.4)
+ react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.4)
+ use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.4)
+ optionalDependencies:
+ '@types/react': 19.2.14
+
+ react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.4):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 19.2.4
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.2.14
+
react@19.2.4: {}
reflect.getprototypeof@1.0.10:
@@ -4319,6 +5972,25 @@ snapshots:
dependencies:
punycode: 2.3.1
+ use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.2.14
+
+ use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 19.2.4
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.2.14
+
+ use-sync-external-store@1.6.0(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 278bd13..46dbb8f 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -21,6 +21,7 @@ futures = "0.3.32"
hex = "0.4.3"
http = "1.4.0"
http-serde-ext = "1.0.2"
+jsonwebtoken = "9"
macros = { version = "0.1.0", path = "../macros" }
mongodb = "3.5.2"
openidconnect = { version = "4.0.1", default-features = false, features = [
@@ -32,6 +33,7 @@ partial_struct = "0.4.5"
paste = "1.0.15"
pgp = "0.19.0"
rand = { version = "0.10.0", features = ["thread_rng"] }
+rsa = { version = "0.9", features = ["pem"] }
sha2 = "0.10"
rand_core = { version = "0.10.0" }
regex = "1.12.3"
@@ -56,6 +58,7 @@ tokio = { version = "1.50.0", features = ["full"] }
toml = "1.1.2"
totp-rs = { version = "5.7.1", features = ["gen_secret", "otpauth"] }
tower = { version = "0.5.3", features = ["full", "tokio", "log"] }
+tower_governor = "0.8.0"
tower-http = { version = "0.6.8", features = ["trace"] }
tower-sessions = "0.14.0"
tower-sessions-redis-store = "0.16.0"
@@ -71,6 +74,7 @@ tracing-subscriber = { version = "0.3.23", features = [
"time",
] }
url = "2.5.8"
+urlencoding = "2"
utoipa = { version = "5.4.0", features = ["axum_extras", "url", "uuid"] }
utoipa-axum = "0.2.0"
utoipa-rapidoc = { version = "6.0.0", features = ["axum"] }
@@ -85,3 +89,4 @@ webauthn-rs = { version = "0.5.4", features = [
"conditional-ui",
] }
webauthn-rs-proto = "0.5.4"
+axum-client-ip = "1.3.1"
diff --git a/server/src/axum_error.rs b/server/src/axum_error.rs
index 0d00dbe..2040859 100644
--- a/server/src/axum_error.rs
+++ b/server/src/axum_error.rs
@@ -44,6 +44,7 @@ impl AxumError {
Self::with_status(report, StatusCode::NOT_FOUND)
}
+ #[allow(dead_code)]
pub fn conflict(report: Report) -> Self {
Self::with_status(report, StatusCode::CONFLICT)
}
diff --git a/server/src/database.rs b/server/src/database.rs
index c6aade4..bd09de1 100644
--- a/server/src/database.rs
+++ b/server/src/database.rs
@@ -4,6 +4,7 @@ use mongodb::{
bson::{self, Bson, doc, oid::ObjectId},
};
use partial_struct::Partial;
+use serde::de::Deserializer;
use serde::{Deserialize, Serialize};
use tower_sessions::{
Expiry, SessionManagerLayer,
@@ -80,6 +81,7 @@ impl TOTPFactor {
#[derive(Debug, Serialize, Deserialize, ToSchema, Clone)]
pub struct WebAuthnFactor {
+ pub credential_id: String,
pub serialized_key: String,
pub display_name: String,
}
@@ -93,6 +95,7 @@ impl From for Bson {
impl WebAuthnFactor {
pub fn to_public(&self) -> PublicWebAuthnFactor {
PublicWebAuthnFactor {
+ credential_id: self.credential_id.clone(),
display_name: self.display_name.clone(),
}
}
@@ -122,6 +125,12 @@ pub struct PasswordFactor {
pub password_hash: Option,
}
+impl From for Bson {
+ fn from(value: PGPFactor) -> Self {
+ bson::to_bson(&value).unwrap()
+ }
+}
+
impl PGPFactor {
pub fn to_public(&self) -> PublicPGPFactor {
PublicPGPFactor {
@@ -137,12 +146,31 @@ pub struct RecentFactors {
pub second_factor: Option,
}
+/// Accepts both a single PGPFactor object (legacy) and a Vec (new format).
+fn deserialize_pgp_factors<'de, D>(deserializer: D) -> Result, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ #[derive(Deserialize)]
+ #[serde(untagged)]
+ enum PgpField {
+ Vec(Vec),
+ Single(PGPFactor),
+ }
+
+ match PgpField::deserialize(deserializer)? {
+ PgpField::Vec(v) => Ok(v),
+ PgpField::Single(f) => Ok(vec![f]),
+ }
+}
+
#[derive(Debug, Serialize, Deserialize, ToSchema, Clone, Default)]
pub struct AuthFactors {
pub totp: Option,
pub webauthn: Vec,
pub recovery_codes: Vec,
- pub pgp: Option,
+ #[serde(deserialize_with = "deserialize_pgp_factors", default)]
+ pub pgp: Vec,
pub password: PasswordFactor,
pub recent: RecentFactors,
}
@@ -162,7 +190,7 @@ impl AuthFactors {
recovery_codes: PublicRecoveryCodeFactor {
remaining_codes: remaining_recovery_codes,
},
- pgp: self.pgp.iter().map(|factor| factor.to_public()).collect(),
+ pgp: self.pgp.iter().map(|f| f.to_public()).collect(),
password: PublicPasswordFactor {
is_set: self.password.password_hash.is_some(),
},
@@ -184,6 +212,7 @@ pub struct PublicPasswordFactor {
#[derive(Debug, Serialize, Deserialize, ToSchema, Clone)]
pub struct PublicWebAuthnFactor {
+ pub credential_id: String,
pub display_name: String,
}
@@ -259,6 +288,8 @@ database_object!(User {
display_name: String,
preferred_username: String,
email: String,
+ #[serde(default)]
+ email_confirmed: bool,
auth_factors: AuthFactors,
#[serde(with = "vec_oid_to_vec_string")]
@@ -336,14 +367,6 @@ impl Application {
}
}
-database_object!(Group {
- #[serde(rename = "_id", with = "object_id_as_string_required")]
- #[schema(value_type = String)]
- id: ObjectId,
- name: String,
- is_admin: bool,
-});
-
pub async fn get_user(
database: &Database,
username_or_email: &str,
diff --git a/server/src/factors/password.rs b/server/src/factors/password.rs
index 4b00ea0..dc2478a 100644
--- a/server/src/factors/password.rs
+++ b/server/src/factors/password.rs
@@ -29,9 +29,11 @@ impl Factor for PasswordFactor {
// Enable Docs here
async fn enable(
&self,
- args: Self::EnableRequest,
+ _args: Self::EnableRequest,
) -> Result, FactorEnableError> {
- Err(FactorEnableError::Other(FactorError::Other(color_eyre::eyre::eyre!("Not implemented"))))
+ Err(FactorEnableError::Other(FactorError::Other(
+ color_eyre::eyre::eyre!("Not implemented"),
+ )))
}
type DisableRequest = NoData;
@@ -40,9 +42,11 @@ impl Factor for PasswordFactor {
// Disable Docs here
async fn disable(
&self,
- args: Self::DisableRequest,
+ _args: Self::DisableRequest,
) -> Result {
- Err(FactorDisableError::Other(FactorError::Other(color_eyre::eyre::eyre!("Not implemented"))))
+ Err(FactorDisableError::Other(FactorError::Other(
+ color_eyre::eyre::eyre!("Not implemented"),
+ )))
}
type AuthenticateRequest = NoData;
@@ -51,8 +55,10 @@ impl Factor for PasswordFactor {
/// Authenticate Docs here
async fn authenticate(
&self,
- args: Self::AuthenticateRequest,
+ _args: Self::AuthenticateRequest,
) -> Result, FactorError> {
- Err(FactorError::Other(color_eyre::eyre::eyre!("Not implemented")))
+ Err(FactorError::Other(color_eyre::eyre::eyre!(
+ "Not implemented"
+ )))
}
}
diff --git a/server/src/init.rs b/server/src/init.rs
index b463c67..0546645 100644
--- a/server/src/init.rs
+++ b/server/src/init.rs
@@ -1,6 +1,7 @@
use std::net::SocketAddr;
use axum::{Extension, Json, Router, http::StatusCode, response::IntoResponse, routing::get};
+use axum_client_ip::ClientIpSource;
use color_eyre::Result;
use tokio::net::TcpListener;
use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer};
@@ -50,10 +51,17 @@ pub async fn init_axum(
.merge(Scalar::with_url("/scalar", api.clone()))
.route(spec_name, get(|| async move { Json(api) }));
+ let ip_source = if state.settings.general.trust_proxy {
+ ClientIpSource::RightmostXForwardedFor
+ } else {
+ ClientIpSource::ConnectInfo
+ };
+
let router = router
.nest(openapi_prefix, docs)
.layer(Extension(state))
.layer(session_layer)
+ .layer(ip_source.into_extension())
.fallback(|| async { (StatusCode::NOT_FOUND, "Not found").into_response() })
.layer(
TraceLayer::new_for_http()
diff --git a/server/src/main.rs b/server/src/main.rs
index 9bde9f7..072e0ed 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -6,6 +6,7 @@ mod factors;
mod init;
mod middlewares;
mod mongo_id;
+mod oidc;
mod routes;
mod settings;
mod state;
@@ -23,6 +24,7 @@ use utoipa::OpenApi;
use crate::{
database::{init_database, init_session_store},
init::{init_axum, init_listener, init_tracing},
+ oidc::init_oidc_keys,
settings::Settings,
state::AppState,
webauthn::init_webauthn,
@@ -59,11 +61,14 @@ async fn main() -> Result<()> {
))
});
+ let oidc_keys = init_oidc_keys(&settings.oidc.signing_key_file)?;
+
let app_state = AppState {
database,
settings: settings.clone(),
webauthn,
mail_service,
+ oidc_keys,
};
let session_layer = init_session_store(&settings).await?;
@@ -78,9 +83,12 @@ async fn main() -> Result<()> {
settings.general.public_url
);
- axum::serve(listener, app.into_make_service())
- .await
- .wrap_err("failed to run server")?;
+ axum::serve(
+ listener,
+ app.into_make_service_with_connect_info::(),
+ )
+ .await
+ .wrap_err("failed to run server")?;
Ok(())
}
diff --git a/server/src/mongo_id.rs b/server/src/mongo_id.rs
index 8a08e82..c55f778 100644
--- a/server/src/mongo_id.rs
+++ b/server/src/mongo_id.rs
@@ -1,45 +1,3 @@
-pub mod object_id_as_string {
- use bson::oid::ObjectId;
- use mongodb::bson;
- use serde::{self, Deserialize, Deserializer, Serialize, Serializer};
-
- pub fn serialize(id: &Option, serializer: S) -> Result
- where
- S: Serializer,
- {
- if serializer.is_human_readable() {
- match id {
- Some(id) => serializer.serialize_some(&id.to_string()),
- None => serializer.serialize_none(),
- }
- } else {
- // Serialize as native ObjectId for BSON and others
- id.serialize(serializer)
- }
- }
-
- pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error>
- where
- D: Deserializer<'de>,
- {
- #[derive(Deserialize)]
- #[serde(untagged)]
- enum StringOrObjectId {
- String(String),
- ObjectId(ObjectId),
- }
-
- let opt = Option::::deserialize(deserializer)?;
- match opt {
- Some(StringOrObjectId::ObjectId(id)) => Ok(Some(id)),
- Some(StringOrObjectId::String(s)) => ObjectId::parse_str(&s)
- .map(Some)
- .map_err(serde::de::Error::custom),
- None => Ok(None),
- }
- }
-}
-
pub mod object_id_as_string_required {
use bson::oid::ObjectId;
use mongodb::bson;
diff --git a/server/src/oidc.rs b/server/src/oidc.rs
new file mode 100644
index 0000000..faec1e3
--- /dev/null
+++ b/server/src/oidc.rs
@@ -0,0 +1,221 @@
+use std::sync::Arc;
+
+use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
+use color_eyre::eyre::{Context, Result};
+use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header};
+use openidconnect::{
+ AuthUrl, EmptyAdditionalProviderMetadata, IssuerUrl, JsonWebKeyId, JsonWebKeySetUrl,
+ PrivateSigningKey, ResponseTypes, Scope, TokenUrl, UserInfoUrl,
+ core::{
+ CoreClaimName, CoreJsonWebKey, CoreJsonWebKeySet, CoreJwsSigningAlgorithm,
+ CoreProviderMetadata, CoreResponseType, CoreRsaPrivateSigningKey,
+ CoreSubjectIdentifierType,
+ },
+};
+use rsa::pkcs1::{DecodeRsaPrivateKey, EncodeRsaPrivateKey, EncodeRsaPublicKey};
+use rsa::pkcs8::LineEnding;
+use rsa::{RsaPrivateKey, RsaPublicKey};
+use serde::{Deserialize, Serialize};
+use sha2::{Digest, Sha256};
+use tracing::info;
+
+/// RSA key size for OIDC signing
+const RSA_KEY_BITS: usize = 2048;
+
+/// OIDC signing key data stored in AppState
+pub struct OidcKeys {
+ /// RSA private signing key (from `openidconnect` crate) — used for ID token signing.
+ pub signing_key: CoreRsaPrivateSigningKey,
+ /// JWKS for the `/jwks` endpoint.
+ pub jwks: CoreJsonWebKeySet,
+ /// jsonwebtoken encoding key — used for access token JWTs.
+ pub encoding_key: EncodingKey,
+ /// jsonwebtoken decoding key — used for access token verification (e.g. userinfo).
+ pub decoding_key: DecodingKey,
+ /// Key ID used for JWT headers.
+ pub kid: String,
+}
+
+/// Access token claims (not covered by the openidconnect crate — access tokens are opaque there).
+#[derive(Debug, Serialize, Deserialize)]
+pub struct AccessTokenClaims {
+ pub iss: String,
+ pub sub: String,
+ pub aud: String,
+ pub exp: usize,
+ pub iat: usize,
+ pub scope: String,
+ pub client_id: String,
+}
+
+/// Authorization code stored in MongoDB.
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct AuthorizationCode {
+ pub code_hash: String,
+ pub client_id: String,
+ pub user_id: String,
+ pub redirect_uri: String,
+ pub scope: String,
+ pub nonce: Option,
+ pub created_at: chrono::DateTime,
+ pub used: bool,
+}
+
+/// Refresh token stored in MongoDB.
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct RefreshToken {
+ pub token_hash: String,
+ pub client_id: String,
+ pub user_id: String,
+ pub scope: String,
+ pub created_at: chrono::DateTime,
+ pub revoked: bool,
+}
+
+/// Generate an OIDC signing key file at the default path.
+/// Called during first-run config generation.
+pub fn generate_oidc_key_file(key_file: &str) -> Result<()> {
+ if std::path::Path::new(key_file).exists() {
+ return Ok(());
+ }
+
+ info!("Generating new OIDC signing key ({RSA_KEY_BITS} bits)...");
+ let mut rng = rsa::rand_core::OsRng;
+ let private_key =
+ RsaPrivateKey::new(&mut rng, RSA_KEY_BITS).wrap_err("Failed to generate RSA key")?;
+
+ let pem = private_key
+ .to_pkcs1_pem(LineEnding::LF)
+ .wrap_err("Failed to encode RSA key to PEM")?;
+ std::fs::write(key_file, pem.as_str()).wrap_err("Failed to write OIDC signing key")?;
+ info!("OIDC signing key saved to {key_file}");
+
+ Ok(())
+}
+
+/// Initialize OIDC keys — load from file specified in config, or generate new if missing.
+pub fn init_oidc_keys(key_file: &str) -> Result> {
+ let (private_key, public_key) = if std::path::Path::new(key_file).exists() {
+ info!("Loading OIDC signing key from {key_file}");
+ let pem = std::fs::read_to_string(key_file).wrap_err("Failed to read OIDC signing key")?;
+ let private_key =
+ RsaPrivateKey::from_pkcs1_pem(&pem).wrap_err("Failed to parse OIDC signing key")?;
+ let public_key = RsaPublicKey::from(&private_key);
+ (private_key, public_key)
+ } else {
+ info!("Generating new OIDC signing key ({RSA_KEY_BITS} bits)...");
+ let mut rng = rsa::rand_core::OsRng;
+ let private_key =
+ RsaPrivateKey::new(&mut rng, RSA_KEY_BITS).wrap_err("Failed to generate RSA key")?;
+ let public_key = RsaPublicKey::from(&private_key);
+
+ // Save for persistence across restarts
+ let pem = private_key
+ .to_pkcs1_pem(LineEnding::LF)
+ .wrap_err("Failed to encode RSA key to PEM")?;
+ std::fs::write(key_file, pem.as_str()).wrap_err("Failed to write OIDC signing key")?;
+ info!("OIDC signing key saved to {key_file}");
+
+ (private_key, public_key)
+ };
+
+ // Derive a stable kid from the public key hash
+ let kid = {
+ let der = public_key
+ .to_pkcs1_der()
+ .wrap_err("Failed to encode public key DER")?;
+ let hash = Sha256::digest(der.as_bytes());
+ URL_SAFE_NO_PAD.encode(&hash[..8])
+ };
+
+ // Build the openidconnect signing key + JWKS
+ let private_pem = private_key
+ .to_pkcs1_pem(LineEnding::LF)
+ .wrap_err("Failed to encode private key")?;
+
+ let signing_key = CoreRsaPrivateSigningKey::from_pem(
+ private_pem.as_str(),
+ Some(JsonWebKeyId::new(kid.clone())),
+ )
+ .map_err(|e| color_eyre::eyre::eyre!("Failed to create CoreRsaPrivateSigningKey: {e}"))?;
+
+ let verification_key: CoreJsonWebKey = signing_key.as_verification_key();
+ let jwks = CoreJsonWebKeySet::new(vec![verification_key]);
+
+ // Build jsonwebtoken keys for access token signing/verification
+ let encoding_key = EncodingKey::from_rsa_pem(private_pem.as_bytes())
+ .wrap_err("Failed to create encoding key")?;
+
+ let public_pem = public_key
+ .to_pkcs1_pem(LineEnding::LF)
+ .wrap_err("Failed to encode public key")?;
+ let decoding_key = DecodingKey::from_rsa_pem(public_pem.as_bytes())
+ .wrap_err("Failed to create decoding key")?;
+
+ info!("OIDC provider initialized with kid={kid}");
+
+ Ok(Arc::new(OidcKeys {
+ signing_key,
+ jwks,
+ encoding_key,
+ decoding_key,
+ kid,
+ }))
+}
+
+impl OidcKeys {
+ /// Sign an access token JWT (not covered by the openidconnect crate).
+ pub fn sign_access_token(&self, claims: &AccessTokenClaims) -> Result {
+ let mut header = Header::new(Algorithm::RS256);
+ header.kid = Some(self.kid.clone());
+
+ jsonwebtoken::encode(&header, claims, &self.encoding_key)
+ .wrap_err("Failed to sign access token")
+ }
+}
+
+/// Build the OIDC discovery document using `CoreProviderMetadata`.
+pub fn build_provider_metadata(issuer: &str) -> Result {
+ let issuer_url = IssuerUrl::new(issuer.to_string()).wrap_err("Invalid issuer URL")?;
+ let auth_url = AuthUrl::new(format!("{issuer}/api/oidc/authorize"))
+ .wrap_err("Invalid authorization URL")?;
+ let jwks_url =
+ JsonWebKeySetUrl::new(format!("{issuer}/api/oidc/jwks")).wrap_err("Invalid JWKS URL")?;
+
+ let provider_metadata = CoreProviderMetadata::new(
+ issuer_url,
+ auth_url,
+ jwks_url,
+ vec![ResponseTypes::new(vec![CoreResponseType::Code])],
+ vec![CoreSubjectIdentifierType::Public],
+ vec![CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256],
+ EmptyAdditionalProviderMetadata {},
+ )
+ .set_token_endpoint(Some(
+ TokenUrl::new(format!("{issuer}/api/oidc/token")).wrap_err("Invalid token URL")?,
+ ))
+ .set_userinfo_endpoint(Some(
+ UserInfoUrl::new(format!("{issuer}/api/oidc/userinfo")).wrap_err("Invalid userinfo URL")?,
+ ))
+ .set_scopes_supported(Some(vec![
+ Scope::new("openid".to_string()),
+ Scope::new("profile".to_string()),
+ Scope::new("email".to_string()),
+ Scope::new("offline_access".to_string()),
+ ]))
+ .set_claims_supported(Some(vec![
+ CoreClaimName::new("sub".to_string()),
+ CoreClaimName::new("iss".to_string()),
+ CoreClaimName::new("aud".to_string()),
+ CoreClaimName::new("exp".to_string()),
+ CoreClaimName::new("iat".to_string()),
+ CoreClaimName::new("name".to_string()),
+ CoreClaimName::new("given_name".to_string()),
+ CoreClaimName::new("family_name".to_string()),
+ CoreClaimName::new("preferred_username".to_string()),
+ CoreClaimName::new("email".to_string()),
+ CoreClaimName::new("email_verified".to_string()),
+ ]));
+
+ Ok(provider_metadata)
+}
diff --git a/server/src/routes.rs b/server/src/routes.rs
index 7607bc9..0c5db6d 100644
--- a/server/src/routes.rs
+++ b/server/src/routes.rs
@@ -1,4 +1,6 @@
pub mod api;
+pub mod oidc_routes;
+pub mod well_known;
use utoipa::OpenApi;
use utoipa_axum::router::OpenApiRouter;
@@ -6,5 +8,8 @@ use utoipa_axum::router::OpenApiRouter;
use crate::{ApiDoc, state::AppState};
pub fn routes() -> OpenApiRouter {
- OpenApiRouter::with_openapi(ApiDoc::openapi()).nest("/api", api::routes())
+ OpenApiRouter::with_openapi(ApiDoc::openapi())
+ .nest("/api", api::routes())
+ .nest("/api/oidc", oidc_routes::routes())
+ .nest("/.well-known", well_known::routes())
}
diff --git a/server/src/routes/api.rs b/server/src/routes/api.rs
index 3a31bab..3162963 100644
--- a/server/src/routes/api.rs
+++ b/server/src/routes/api.rs
@@ -1,6 +1,8 @@
mod admin;
+mod confirm_email;
mod health;
mod login;
+mod logout;
mod password_reset;
mod register;
mod settings;
@@ -8,7 +10,8 @@ mod settings;
use axum::middleware;
use serde::{Deserialize, Serialize};
use strum::Display;
-use utoipa::{ToSchema, schema};
+use tower_governor::{GovernorLayer, governor::GovernorConfigBuilder};
+use utoipa::ToSchema;
use utoipa_axum::router::OpenApiRouter;
use crate::{middlewares::require_auth::require_auth, state::AppState};
@@ -16,16 +19,26 @@ use crate::{middlewares::require_auth::require_auth, state::AppState};
pub fn routes() -> OpenApiRouter {
let auth = OpenApiRouter::new()
.nest("/admin", admin::routes())
+ .nest("/logout", logout::routes())
.nest("/settings", settings::routes())
.layer(middleware::from_fn(require_auth));
+ // Global rate limit: 5 burst, 1 replenish per 2s per IP
+ let rate_limit_conf = GovernorConfigBuilder::default()
+ .per_second(2)
+ .burst_size(5)
+ .finish()
+ .unwrap();
+
let public = OpenApiRouter::new()
+ .nest("/confirm-email", confirm_email::routes())
.nest("/health", health::routes())
.nest("/login", login::routes())
.nest("/password-reset", password_reset::routes())
.nest("/register", register::routes());
auth.merge(public)
+ .layer(GovernorLayer::new(rate_limit_conf))
}
#[derive(Clone, Deserialize, Serialize, Eq, PartialEq, Debug, Display)]
diff --git a/server/src/routes/api/confirm_email.rs b/server/src/routes/api/confirm_email.rs
new file mode 100644
index 0000000..9a3042f
--- /dev/null
+++ b/server/src/routes/api/confirm_email.rs
@@ -0,0 +1,139 @@
+use axum::{
+ Extension,
+ extract::Query,
+ response::{IntoResponse, Redirect},
+};
+use chrono::{DateTime, Duration, Utc};
+use mongodb::bson::{DateTime as BsonDateTime, doc, oid::ObjectId};
+use serde::{Deserialize, Serialize};
+use utoipa::IntoParams;
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+use crate::{
+ axum_error::{AxumError, AxumResult},
+ database::User,
+ state::AppState,
+ utils::{generate_reset_token, hash_token},
+};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new().routes(routes!(confirm_email))
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct EmailConfirmationToken {
+ pub token_hash: String,
+ pub user_id: ObjectId,
+ pub expires_at: DateTime,
+}
+
+pub async fn send_confirmation_email(
+ state: &AppState,
+ user_id: ObjectId,
+ email: &str,
+) -> AxumResult<()> {
+ let Some(mail) = &state.mail_service else {
+ return Err(AxumError::service_unavailable(color_eyre::eyre::eyre!(
+ "Confirmation email service is unavailable"
+ )));
+ };
+
+ let token = generate_reset_token();
+ let token_hash = hash_token(&token);
+ let expires_at = Utc::now() + Duration::hours(24);
+
+ state
+ .database
+ .collection::("email_confirmation_tokens")
+ .insert_one(EmailConfirmationToken {
+ token_hash,
+ user_id,
+ expires_at,
+ })
+ .await?;
+
+ if let Err(error) = mail.send_email_confirmation(email, &token).await {
+ tracing::warn!(error = ?error, %user_id, "Failed to send confirmation email");
+ return Err(AxumError::service_unavailable(color_eyre::eyre::eyre!(
+ "Confirmation email service is unavailable"
+ )));
+ }
+
+ Ok(())
+}
+
+#[derive(Deserialize, IntoParams)]
+struct ConfirmEmailQuery {
+ /// Confirmation token from the email link
+ token: String,
+}
+
+/// Confirm email address
+///
+/// Validates the confirmation token, marks the email as confirmed, and redirects to the frontend result page.
+#[utoipa::path(
+ method(get),
+ path = "/",
+ params(ConfirmEmailQuery),
+ responses(
+ (status = 302, description = "Redirects to frontend with status"),
+ ),
+ tag = "Email Confirmation"
+)]
+async fn confirm_email(
+ Extension(state): Extension,
+ Query(query): Query,
+) -> impl IntoResponse {
+ let redirect_error =
+ |reason: &str| Redirect::temporary(&format!("/confirm-email?status=error&reason={reason}"));
+
+ let token_hash = hash_token(&query.token);
+
+ let token_doc = match state
+ .database
+ .collection::("email_confirmation_tokens")
+ .find_one_and_delete(doc! { "token_hash": &token_hash })
+ .await
+ {
+ Ok(Some(doc)) => doc,
+ Ok(None) => return redirect_error("invalid"),
+ Err(e) => {
+ tracing::warn!(error = ?e, "Failed to look up email confirmation token");
+ return redirect_error("invalid");
+ }
+ };
+
+ if Utc::now() > token_doc.expires_at {
+ // Clean up other expired tokens in the background
+ let db = state.database.clone();
+ tokio::spawn(async move {
+ let now = BsonDateTime::now();
+ if let Err(e) = db
+ .collection::("email_confirmation_tokens")
+ .delete_many(doc! { "expires_at": { "$lt": now } })
+ .await
+ {
+ tracing::warn!(error = ?e, "Failed to clean up expired email confirmation tokens");
+ }
+ });
+ return redirect_error("expired");
+ }
+
+ let result = state
+ .database
+ .collection::("users")
+ .update_one(
+ doc! { "_id": token_doc.user_id },
+ doc! { "$set": { "email_confirmed": true } },
+ )
+ .await;
+
+ match result {
+ Ok(r) if r.matched_count > 0 => Redirect::temporary("/confirm-email?status=success"),
+ Ok(_) => redirect_error("not_found"),
+ Err(e) => {
+ tracing::warn!(error = ?e, "Failed to update user email confirmation status");
+ redirect_error("invalid")
+ }
+ }
+}
diff --git a/server/src/routes/api/login.rs b/server/src/routes/api/login.rs
index b26d8a5..368ffd7 100644
--- a/server/src/routes/api/login.rs
+++ b/server/src/routes/api/login.rs
@@ -11,9 +11,7 @@ use utoipa::ToSchema;
use utoipa_axum::router::OpenApiRouter;
use crate::{
- database::SecondFactor,
- middlewares::require_auth::require_first_factor,
- state::AppState,
+ database::SecondFactor, middlewares::require_auth::require_first_factor, state::AppState,
};
pub fn routes() -> OpenApiRouter {
diff --git a/server/src/routes/api/login/options.rs b/server/src/routes/api/login/options.rs
index 9bdff2b..a599490 100644
--- a/server/src/routes/api/login/options.rs
+++ b/server/src/routes/api/login/options.rs
@@ -62,7 +62,7 @@ async fn get_login_options(
options.push(FirstFactor::WebAuthnPasswordless);
}
- if user.auth_factors.pgp.is_some() {
+ if !user.auth_factors.pgp.is_empty() {
options.push(FirstFactor::Pgp);
}
diff --git a/server/src/routes/api/login/password.rs b/server/src/routes/api/login/password.rs
index 8514d2d..09b5122 100644
--- a/server/src/routes/api/login/password.rs
+++ b/server/src/routes/api/login/password.rs
@@ -1,8 +1,5 @@
-use argon2::{
- Argon2, PasswordHash, PasswordVerifier,
- password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
-};
use axum::{Extension, Json};
+use axum_client_ip::ClientIp;
use color_eyre::eyre;
use mongodb::bson::doc;
use serde::{Deserialize, Serialize};
@@ -15,6 +12,7 @@ use crate::{
database::{FirstFactor, get_second_factors, get_user, set_recent_factor},
routes::api::AuthState,
state::AppState,
+ utils::{hash_password, verify_password},
};
use super::SuccessfulLoginResponse;
@@ -36,19 +34,6 @@ pub struct InvalidUserOrPass {
error: String,
}
-trait A {
- type T;
-}
-
-#[derive(ToSchema)]
-struct B {}
-
-impl A for B {
- type T = InvalidUserOrPass;
-}
-
-type C = ::T;
-
/// Log in with password
///
/// If user is not found or the password isn't enabled for the user returns the same response as if the password was incorrect.
@@ -64,6 +49,7 @@ type C = ::T;
async fn login_with_password(
Extension(state): Extension,
session: Session,
+ ClientIp(client_ip): ClientIp,
Json(body): Json,
) -> AxumResult> {
let user = get_user(&state.database, &body.username).await?;
@@ -78,12 +64,7 @@ async fn login_with_password(
.password_hash
.is_none()
{
- let salt = SaltString::generate(&mut OsRng);
- let argon2 = Argon2::default();
-
- argon2
- .hash_password(body.password.as_bytes(), &salt)
- .map_err(|_| eyre::eyre!("Failed to compute hash"))?;
+ let _ = hash_password(&body.password);
return Err(AxumError::unauthorized(eyre::eyre!(
"Invalid username or password"
@@ -94,12 +75,7 @@ async fn login_with_password(
let password_hash = &user.clone().auth_factors.password.password_hash.unwrap();
- let parsed_hash =
- PasswordHash::new(password_hash).map_err(|_| eyre::eyre!("Failed to compute hash"))?;
- let argon2 = Argon2::default();
-
- argon2
- .verify_password(body.password.as_bytes(), &parsed_hash)
+ verify_password(&body.password, password_hash)
.map_err(|_| AxumError::unauthorized(eyre::eyre!("Invalid username or password")))?;
session.insert("user_id", user.id).await?;
@@ -111,6 +87,17 @@ async fn login_with_password(
.insert("auth_state", AuthState::Authenticated)
.await?;
+ if let Some(mail) = &state.mail_service {
+ let ip = client_ip.to_string();
+ let email = user.email.clone();
+ let mail = mail.clone();
+ tokio::spawn(async move {
+ if let Err(e) = mail.send_login_notification(&email, &ip).await {
+ tracing::warn!(error = ?e, "Failed to send login notification");
+ }
+ });
+ }
+
return Ok(Json(SuccessfulLoginResponse {
two_factor_required: false,
second_factors: None,
diff --git a/server/src/routes/api/login/pgp/challenge.rs b/server/src/routes/api/login/pgp/challenge.rs
index 6a45aa7..ad59678 100644
--- a/server/src/routes/api/login/pgp/challenge.rs
+++ b/server/src/routes/api/login/pgp/challenge.rs
@@ -114,11 +114,9 @@ async fn respond_to_pgp_challenge(
let user = user.ok_or_else(|| AxumError::unauthorized(eyre::eyre!("No user")))?;
- let pgp_factor = user
- .auth_factors
- .pgp
- .as_ref()
- .ok_or_else(|| AxumError::unauthorized(eyre::eyre!("Invalid signature")))?;
+ if user.auth_factors.pgp.is_empty() {
+ return Err(AxumError::unauthorized(eyre::eyre!("Invalid signature")));
+ }
let (parsed, _) = Any::from_string(&body.signature)
.map_err(|_| AxumError::bad_request(eyre::eyre!("Invalid signature format")))?;
@@ -134,14 +132,24 @@ async fn respond_to_pgp_challenge(
return Err(AxumError::unauthorized(eyre::eyre!("Invalid signature")));
}
- let (public_key, _) =
- SignedPublicKey::from_string(&pgp_factor.public_key)
- .map_err(|_| AxumError::unauthorized(eyre::eyre!("Invalid signature")))?;
+ // Try verifying against all registered PGP keys
+ let mut verified = false;
+ for pgp_factor in &user.auth_factors.pgp {
+ if let Ok((public_key, _)) = SignedPublicKey::from_string(&pgp_factor.public_key)
+ && msg.verify(&public_key).is_ok()
+ {
+ verified = true;
+ break;
+ }
+ }
- msg.verify(&public_key)
- .map_err(|_| AxumError::unauthorized(eyre::eyre!("Invalid signature")))?;
+ if !verified {
+ return Err(AxumError::unauthorized(eyre::eyre!("Invalid signature")));
+ }
- session.remove::("login::pgp_challenge").await?;
+ session
+ .remove::("login::pgp_challenge")
+ .await?;
session.insert("user_id", user.id).await?;
diff --git a/server/src/routes/api/login/recovery_codes.rs b/server/src/routes/api/login/recovery_codes.rs
index 2363c2f..5c92da9 100644
--- a/server/src/routes/api/login/recovery_codes.rs
+++ b/server/src/routes/api/login/recovery_codes.rs
@@ -51,14 +51,7 @@ async fn login_with_recovery_code(
) -> AxumResult> {
let user = get_user_by_id(&state.database, &user_id).await?;
- if user.is_none()
- || user
- .clone()
- .unwrap()
- .auth_factors
- .recovery_codes
- .is_empty()
- {
+ if user.is_none() || user.clone().unwrap().auth_factors.recovery_codes.is_empty() {
return Err(AxumError::unauthorized(eyre::eyre!("Invalid 2FA code")));
}
diff --git a/server/src/routes/api/login/webauthn/finish.rs b/server/src/routes/api/login/webauthn/finish.rs
index bb97613..c02c5d3 100644
--- a/server/src/routes/api/login/webauthn/finish.rs
+++ b/server/src/routes/api/login/webauthn/finish.rs
@@ -54,7 +54,9 @@ async fn webauthn_finish_login(
let auth_result = state
.webauthn
.finish_passkey_authentication(&auth, &auth_state)
- .map_err(|e| AxumError::bad_request(eyre::eyre!("WebAuthn registration failed: {}", e)))?;
+ .map_err(|e| {
+ AxumError::bad_request(eyre::eyre!("WebAuthn authentication failed: {}", e))
+ })?;
update_webauthn_credentials(&state, &user, &auth_result).await?;
diff --git a/server/src/routes/api/login/webauthn/helpers.rs b/server/src/routes/api/login/webauthn/helpers.rs
index 5f5dae1..704622c 100644
--- a/server/src/routes/api/login/webauthn/helpers.rs
+++ b/server/src/routes/api/login/webauthn/helpers.rs
@@ -1,51 +1,42 @@
-use color_eyre::eyre::{Context, Result};
+use base64::Engine;
+use color_eyre::eyre::Context;
use mongodb::bson::doc;
use webauthn_rs::prelude::{AuthenticationResult, Passkey};
-use crate::{
- axum_error::AxumResult,
- database::{User, WebAuthnFactor},
- state::AppState,
-};
+use crate::{axum_error::AxumResult, database::User, state::AppState};
pub async fn update_webauthn_credentials(
state: &AppState,
user: &User,
auth_result: &AuthenticationResult,
) -> AxumResult<()> {
- let mut user_keys = user
+ let cred_id =
+ base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(auth_result.cred_id().as_ref());
+
+ let factor = user
.auth_factors
.webauthn
.iter()
- .map(|f| -> Result<(WebAuthnFactor, Passkey)> {
- let passkey: Passkey = serde_json::from_str(&f.serialized_key)?;
- Ok((f.clone(), passkey))
- })
- .collect::, _>>()?;
+ .find(|f| f.credential_id == cred_id);
- user_keys.iter_mut().for_each(|(_, sk)| {
- sk.update_credential(auth_result);
- });
+ let Some(factor) = factor else {
+ return Ok(());
+ };
- let serialized_keys = user_keys
- .iter()
- .map(|(factor, sk)| {
- Ok(WebAuthnFactor {
- serialized_key: serde_json::to_string(&sk)
- .wrap_err("Failed to serialize passkey")?,
- ..factor.clone()
- })
- })
- .collect::>>()?;
+ let mut passkey: Passkey =
+ serde_json::from_str(&factor.serialized_key).wrap_err("Failed to deserialize passkey")?;
+ passkey.update_credential(auth_result);
+
+ let updated_key = serde_json::to_string(&passkey).wrap_err("Failed to serialize passkey")?;
state
.database
.collection::("users")
- .find_one_and_update(
- doc! { "_id": user.id },
+ .update_one(
+ doc! { "_id": user.id, "auth_factors.webauthn.credential_id": &cred_id },
doc! {
"$set": {
- "auth_factors.webauthn": serialized_keys,
+ "auth_factors.webauthn.$.serialized_key": updated_key,
}
},
)
diff --git a/server/src/routes/api/login/webauthn/passwordless/finish.rs b/server/src/routes/api/login/webauthn/passwordless/finish.rs
index 746dfc4..d23fca8 100644
--- a/server/src/routes/api/login/webauthn/passwordless/finish.rs
+++ b/server/src/routes/api/login/webauthn/passwordless/finish.rs
@@ -54,11 +54,12 @@ async fn passwordless_finish(
AxumError::unauthorized(eyre::eyre!("Failed to identify credential: {}", e))
})?;
- let user = get_user_by_uuid(&state.database, &user_uuid)
- .await?
- .ok_or(AxumError::unauthorized(eyre::eyre!(
- "User not found for this credential"
- )))?;
+ let user =
+ get_user_by_uuid(&state.database, &user_uuid)
+ .await?
+ .ok_or(AxumError::unauthorized(eyre::eyre!(
+ "User not found for this credential"
+ )))?;
let discoverable_keys = user
.auth_factors
diff --git a/server/src/routes/api/login/webauthn/passwordless/start.rs b/server/src/routes/api/login/webauthn/passwordless/start.rs
index f78c6d2..6de8d96 100644
--- a/server/src/routes/api/login/webauthn/passwordless/start.rs
+++ b/server/src/routes/api/login/webauthn/passwordless/start.rs
@@ -4,10 +4,7 @@ use tower_sessions::Session;
use utoipa_axum::{router::OpenApiRouter, routes};
use webauthn_rs::prelude::RequestChallengeResponse;
-use crate::{
- axum_error::AxumResult,
- state::AppState,
-};
+use crate::{axum_error::AxumResult, state::AppState};
pub fn routes() -> OpenApiRouter {
OpenApiRouter::new().routes(routes!(passwordless_start))
diff --git a/server/src/routes/api/logout.rs b/server/src/routes/api/logout.rs
new file mode 100644
index 0000000..01a85a9
--- /dev/null
+++ b/server/src/routes/api/logout.rs
@@ -0,0 +1,32 @@
+use axum::Json;
+use serde::Serialize;
+use tower_sessions::Session;
+use utoipa::ToSchema;
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+use crate::{axum_error::AxumResult, state::AppState};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new().routes(routes!(logout))
+}
+
+#[derive(Serialize, ToSchema)]
+struct LogoutResponse {
+ success: bool,
+}
+
+/// Log out
+///
+/// Destroys the current session, effectively logging the user out.
+#[utoipa::path(
+ method(post),
+ path = "/",
+ responses(
+ (status = OK, description = "Logged out", body = LogoutResponse, content_type = "application/json"),
+ ),
+ tag = "Auth"
+)]
+async fn logout(session: Session) -> AxumResult> {
+ session.flush().await?;
+ Ok(Json(LogoutResponse { success: true }))
+}
diff --git a/server/src/routes/api/password_reset.rs b/server/src/routes/api/password_reset.rs
index fac18ff..222f5c7 100644
--- a/server/src/routes/api/password_reset.rs
+++ b/server/src/routes/api/password_reset.rs
@@ -130,7 +130,9 @@ async fn confirm_reset(
.await?;
let Some(token_doc) = token_doc else {
- return Err(AxumError::bad_request(eyre::eyre!("Invalid or expired token")));
+ return Err(AxumError::bad_request(eyre::eyre!(
+ "Invalid or expired token"
+ )));
};
if Utc::now() > token_doc.expires_at {
@@ -139,7 +141,9 @@ async fn confirm_reset(
.collection::("password_reset_tokens")
.delete_one(doc! { "token_hash": &token_hash })
.await?;
- return Err(AxumError::bad_request(eyre::eyre!("Invalid or expired token")));
+ return Err(AxumError::bad_request(eyre::eyre!(
+ "Invalid or expired token"
+ )));
}
let new_hash = hash_password(&body.new_password)?;
diff --git a/server/src/routes/api/register.rs b/server/src/routes/api/register.rs
index 5b2a2db..ed23333 100644
--- a/server/src/routes/api/register.rs
+++ b/server/src/routes/api/register.rs
@@ -1,11 +1,7 @@
-use argon2::{
- Argon2,
- password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
-};
use axum::{Extension, Json};
use axum_valid::Valid;
-use color_eyre::eyre::{self, ContextCompat};
-use mongodb::bson::doc;
+use color_eyre::eyre;
+use mongodb::bson::{doc, oid::ObjectId};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use utoipa_axum::{router::OpenApiRouter, routes};
@@ -14,9 +10,13 @@ use validator::Validate;
use crate::{
axum_error::{AxumError, AxumResult},
- database::{AuthFactors, PartialUser, PasswordFactor, User},
- routes::api::CreateSuccess,
+ database::{AuthFactors, PasswordFactor, User},
+ routes::api::{
+ CreateSuccess,
+ confirm_email::{EmailConfirmationToken, send_confirmation_email},
+ },
state::AppState,
+ utils::hash_password,
validators::username_validator,
};
@@ -51,12 +51,6 @@ pub struct BadRequestError {
error: String,
}
-#[derive(Serialize, ToSchema)]
-#[schema(example = json!({"success": true}))]
-pub struct RegisterSuccess {
- success: bool,
-}
-
/// Register
#[utoipa::path(
method(post),
@@ -88,42 +82,66 @@ async fn register(
)));
}
- let salt = SaltString::generate(&mut OsRng);
- let argon2 = Argon2::default();
-
- let hashed_password = argon2
- .hash_password(body.password.as_bytes(), &salt)
- .map_err(|_| eyre::eyre!("Failed to compute hash"))?;
-
+ let hashed_password = hash_password(&body.password)?;
+ let user_id = ObjectId::new();
let uuid = Uuid::new_v4();
- let user = PartialUser {
+ let user = User {
+ id: user_id,
uuid,
first_name: body.first_name,
last_name: body.last_name,
display_name: body.display_name,
preferred_username: body.preferred_username,
- email: body.email,
+ email: body.email.clone(),
+ email_confirmed: false,
auth_factors: AuthFactors {
password: PasswordFactor {
- password_hash: Some(hashed_password.to_string()),
+ password_hash: Some(hashed_password),
},
..Default::default()
},
groups: vec![],
};
- let inserted = state
+ state
.database
- .collection::("users")
+ .collection::("users")
.insert_one(user)
.await?;
- let id = inserted
- .inserted_id
- .as_object_id()
- .wrap_err("Failed to fetch project ID")?
- .to_string();
+ if let Err(error) = send_confirmation_email(&state, user_id, &body.email).await {
+ if let Err(cleanup_error) = state
+ .database
+ .collection::("email_confirmation_tokens")
+ .delete_many(doc! { "user_id": user_id })
+ .await
+ {
+ tracing::warn!(
+ error = ?cleanup_error,
+ %user_id,
+ "Failed to clean up confirmation tokens after registration error"
+ );
+ }
+
+ if let Err(cleanup_error) = state
+ .database
+ .collection::("users")
+ .delete_one(doc! { "_id": user_id })
+ .await
+ {
+ tracing::error!(
+ error = ?cleanup_error,
+ %user_id,
+ "Failed to roll back user after registration error"
+ );
+ }
+
+ return Err(error);
+ }
- Ok(Json(CreateSuccess { success: true, id }))
+ Ok(Json(CreateSuccess {
+ success: true,
+ id: user_id.to_string(),
+ }))
}
diff --git a/server/src/routes/api/settings.rs b/server/src/routes/api/settings.rs
index f91aca8..40eb63e 100644
--- a/server/src/routes/api/settings.rs
+++ b/server/src/routes/api/settings.rs
@@ -2,11 +2,15 @@ use utoipa_axum::router::OpenApiRouter;
use crate::state::AppState;
+pub mod account;
pub mod factors;
pub mod password;
+pub mod profile;
pub fn routes() -> OpenApiRouter {
OpenApiRouter::new()
+ .nest("/account", account::routes())
.nest("/factors", factors::routes())
.nest("/password", password::routes())
+ .nest("/profile", profile::routes())
}
diff --git a/server/src/routes/api/settings/account.rs b/server/src/routes/api/settings/account.rs
new file mode 100644
index 0000000..cc621a6
--- /dev/null
+++ b/server/src/routes/api/settings/account.rs
@@ -0,0 +1,88 @@
+use axum::{Extension, Json, http::StatusCode};
+use color_eyre::eyre::{self, OptionExt};
+use mongodb::bson::doc;
+use serde::Deserialize;
+use tower_sessions::Session;
+use utoipa::ToSchema;
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+use crate::{
+ axum_error::{AxumError, AxumResult},
+ database::User,
+ middlewares::require_auth::UserId,
+ state::AppState,
+ utils::verify_password,
+};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new().routes(routes!(delete_account))
+}
+
+#[derive(Deserialize, ToSchema)]
+struct DeleteAccountBody {
+ /// Current password for confirmation
+ password: String,
+}
+
+/// Delete account
+///
+/// Permanently deletes the user's account after verifying their password. This action is irreversible.
+#[utoipa::path(
+ method(delete),
+ path = "/",
+ request_body = DeleteAccountBody,
+ responses(
+ (status = NO_CONTENT, description = "Account deleted"),
+ (status = UNAUTHORIZED, description = "Invalid password"),
+ ),
+ tag = "Settings"
+)]
+async fn delete_account(
+ Extension(state): Extension,
+ Extension(user_id): Extension,
+ session: Session,
+ Json(body): Json,
+) -> AxumResult {
+ let user = state
+ .database
+ .collection::("users")
+ .find_one(doc! { "_id": *user_id })
+ .await?
+ .ok_or_eyre("User not found")?;
+
+ // Verify password
+ let password_hash = user
+ .auth_factors
+ .password
+ .password_hash
+ .ok_or_else(|| AxumError::unauthorized(eyre::eyre!("Password not set")))?;
+
+ verify_password(&body.password, &password_hash)
+ .map_err(|_| AxumError::unauthorized(eyre::eyre!("Invalid password")))?;
+
+ // Delete user
+ state
+ .database
+ .collection::("users")
+ .delete_one(doc! { "_id": *user_id })
+ .await?;
+
+ // Clean up related data
+ let db = state.database.clone();
+ let uid = *user_id;
+ tokio::spawn(async move {
+ let _ = db
+ .collection::("email_confirmation_tokens")
+ .delete_many(doc! { "user_id": uid })
+ .await;
+ let _ = db
+ .collection::("password_reset_tokens")
+ .delete_many(doc! { "user_id": uid })
+ .await;
+ });
+
+ // Destroy session
+ session.flush().await?;
+
+ Ok(StatusCode::NO_CONTENT)
+}
diff --git a/server/src/routes/api/settings/factors/pgp.rs b/server/src/routes/api/settings/factors/pgp.rs
index 75c0189..749eb4c 100644
--- a/server/src/routes/api/settings/factors/pgp.rs
+++ b/server/src/routes/api/settings/factors/pgp.rs
@@ -2,8 +2,13 @@ use utoipa_axum::router::OpenApiRouter;
use crate::state::AppState;
+mod delete;
+mod disable;
mod enable;
pub fn routes() -> OpenApiRouter {
- OpenApiRouter::new().nest("/enable", enable::routes())
+ OpenApiRouter::new()
+ .nest("/enable", enable::routes())
+ .nest("/disable", disable::routes())
+ .nest("/delete", delete::routes())
}
diff --git a/server/src/routes/api/settings/factors/pgp/delete.rs b/server/src/routes/api/settings/factors/pgp/delete.rs
new file mode 100644
index 0000000..fd0fdba
--- /dev/null
+++ b/server/src/routes/api/settings/factors/pgp/delete.rs
@@ -0,0 +1,73 @@
+use axum::{Extension, Json, extract::Path};
+use color_eyre::eyre;
+use mongodb::bson::doc;
+use serde::Serialize;
+use utoipa::ToSchema;
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+use crate::{
+ axum_error::{AxumError, AxumResult},
+ database::{User, get_user_by_id},
+ middlewares::require_auth::{UnauthorizedError, UserId},
+ state::AppState,
+};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new().routes(routes!(delete_pgp_key))
+}
+
+#[derive(Serialize, ToSchema)]
+#[schema(example = json!({ "success": true }))]
+struct DeletePgpResponse {
+ success: bool,
+}
+
+/// Delete PGP key
+///
+/// Removes a PGP key by its fingerprint.
+#[utoipa::path(
+ method(delete),
+ path = "/{fingerprint}",
+ params(
+ ("fingerprint" = String, Path, description = "Fingerprint of the PGP key to delete")
+ ),
+ responses(
+ (status = OK, description = "Success", body = DeletePgpResponse, content_type = "application/json"),
+ (status = UNAUTHORIZED, description = "Unauthorized", body = UnauthorizedError, content_type = "application/json"),
+ (status = NOT_FOUND, description = "Key not found", body = String, content_type = "application/json"),
+ ),
+ tag = "Settings"
+)]
+async fn delete_pgp_key(
+ Extension(state): Extension,
+ Extension(user_id): Extension,
+ Path(fingerprint): Path,
+) -> AxumResult> {
+ let result = state
+ .database
+ .collection::("users")
+ .update_one(
+ doc! { "_id": *user_id, "auth_factors.pgp.fingerprint": &fingerprint },
+ doc! { "$pull": { "auth_factors.pgp": { "fingerprint": &fingerprint } } },
+ )
+ .await?;
+
+ if result.matched_count == 0 {
+ return Err(AxumError::not_found(eyre::eyre!("PGP key not found")));
+ }
+
+ if let Some(mail) = &state.mail_service {
+ let user = get_user_by_id(&state.database, &user_id).await?;
+ if let Some(user) = user {
+ let email = user.email;
+ let mail = mail.clone();
+ tokio::spawn(async move {
+ if let Err(e) = mail.send_factor_removed(&email, "PGP key").await {
+ tracing::warn!(error = ?e, "Failed to send factor removed notification");
+ }
+ });
+ }
+ }
+
+ Ok(Json(DeletePgpResponse { success: true }))
+}
diff --git a/server/src/routes/api/settings/factors/pgp/disable.rs b/server/src/routes/api/settings/factors/pgp/disable.rs
new file mode 100644
index 0000000..cc8fd77
--- /dev/null
+++ b/server/src/routes/api/settings/factors/pgp/disable.rs
@@ -0,0 +1,70 @@
+use axum::{Extension, Json};
+use color_eyre::eyre::{self, ContextCompat};
+use mongodb::bson::doc;
+use serde::Serialize;
+use utoipa::ToSchema;
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+use crate::{
+ axum_error::{AxumError, AxumResult},
+ database::{User, get_user_by_id},
+ middlewares::require_auth::{UnauthorizedError, UserId},
+ state::AppState,
+};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new().routes(routes!(disable_pgp))
+}
+
+#[derive(Serialize, ToSchema)]
+#[schema(example = json!({ "success": true }))]
+struct DisablePgpResponse {
+ success: bool,
+}
+
+/// Disable PGP
+///
+/// Removes the PGP authentication factor from the user's account.
+#[utoipa::path(
+ method(delete),
+ path = "/",
+ responses(
+ (status = OK, description = "Success", body = DisablePgpResponse, content_type = "application/json"),
+ (status = UNAUTHORIZED, description = "Unauthorized", body = UnauthorizedError, content_type = "application/json"),
+ (status = BAD_REQUEST, description = "PGP not enabled", body = String, content_type = "application/json"),
+ ),
+ tag = "Settings"
+)]
+async fn disable_pgp(
+ Extension(state): Extension,
+ Extension(user_id): Extension,
+) -> AxumResult> {
+ let user = get_user_by_id(&state.database, &user_id)
+ .await?
+ .wrap_err("User not found")?;
+
+ if user.auth_factors.pgp.is_empty() {
+ return Err(AxumError::bad_request(eyre::eyre!("PGP is not enabled")));
+ }
+
+ state
+ .database
+ .collection::("users")
+ .update_one(
+ doc! { "_id": *user_id },
+ doc! { "$set": { "auth_factors.pgp": [] } },
+ )
+ .await?;
+
+ if let Some(mail) = &state.mail_service {
+ let email = user.email.clone();
+ let mail = mail.clone();
+ tokio::spawn(async move {
+ if let Err(e) = mail.send_factor_removed(&email, "PGP key").await {
+ tracing::warn!(error = ?e, "Failed to send factor removed notification");
+ }
+ });
+ }
+
+ Ok(Json(DisablePgpResponse { success: true }))
+}
diff --git a/server/src/routes/api/settings/factors/pgp/enable.rs b/server/src/routes/api/settings/factors/pgp/enable.rs
index 9fc93fe..b5546f6 100644
--- a/server/src/routes/api/settings/factors/pgp/enable.rs
+++ b/server/src/routes/api/settings/factors/pgp/enable.rs
@@ -13,7 +13,7 @@ use validator::Validate;
use crate::{
axum_error::{AxumError, AxumResult},
- database::User,
+ database::{PGPFactor, User, get_user_by_id},
middlewares::require_auth::{UnauthorizedError, UserId},
state::AppState,
};
@@ -60,18 +60,38 @@ async fn enable_pgp(
let (public_key, _) = SignedPublicKey::from_string(&body.public_key)
.map_err(|_| AxumError::bad_request(eyre::eyre!("Invalid public key")))?;
+ let fingerprint = public_key.fingerprint().to_string();
+
+ // Check for duplicate fingerprint
+ let user = get_user_by_id(&state.database, &user_id)
+ .await?
+ .ok_or_else(|| AxumError::unauthorized(eyre::eyre!("User not found")))?;
+
+ if user
+ .auth_factors
+ .pgp
+ .iter()
+ .any(|k| k.fingerprint == fingerprint)
+ {
+ return Err(AxumError::bad_request(eyre::eyre!(
+ "This PGP key is already added"
+ )));
+ }
+
+ let factor = PGPFactor {
+ public_key: public_key.to_armored_string(ArmorOptions::default())?,
+ fingerprint,
+ display_name: body.display_name,
+ };
+
state
.database
.collection::("users")
- .find_one_and_update(
+ .update_one(
doc! { "_id": *user_id },
doc! {
- "$set": {
- "auth_factors.pgp": {
- "public_key": public_key.to_armored_string(ArmorOptions::default())?,
- "fingerprint": public_key.fingerprint().to_string(),
- "display_name": body.display_name,
- }
+ "$push": {
+ "auth_factors.pgp": mongodb::bson::to_bson(&factor)?
}
},
)
diff --git a/server/src/routes/api/settings/factors/recovery_codes.rs b/server/src/routes/api/settings/factors/recovery_codes.rs
index a905f2c..2653a1b 100644
--- a/server/src/routes/api/settings/factors/recovery_codes.rs
+++ b/server/src/routes/api/settings/factors/recovery_codes.rs
@@ -1,4 +1,5 @@
mod enable;
+mod reset;
use argon2::{
Argon2, PasswordHash, PasswordVerifier,
@@ -15,7 +16,9 @@ use crate::{
};
pub fn routes() -> OpenApiRouter {
- OpenApiRouter::new().nest("/enable", enable::routes())
+ OpenApiRouter::new()
+ .nest("/enable", enable::routes())
+ .nest("/reset", reset::routes())
}
pub fn generate_recovery_code(len: usize) -> String {
diff --git a/server/src/routes/api/settings/factors/recovery_codes/reset.rs b/server/src/routes/api/settings/factors/recovery_codes/reset.rs
new file mode 100644
index 0000000..a8d1383
--- /dev/null
+++ b/server/src/routes/api/settings/factors/recovery_codes/reset.rs
@@ -0,0 +1,89 @@
+use axum::{Extension, Json};
+use color_eyre::eyre::{self, ContextCompat};
+use mongodb::bson::doc;
+use serde::Serialize;
+use utoipa::ToSchema;
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+use crate::{
+ axum_error::{AxumError, AxumResult},
+ database::{RecoveryCodeFactor, User, get_user_by_id},
+ middlewares::require_auth::{UnauthorizedError, UserId},
+ routes::api::settings::factors::recovery_codes::{
+ generate_recovery_codes, hash_recovery_codes,
+ },
+ state::AppState,
+};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new().routes(routes!(reset_recovery_codes))
+}
+
+#[derive(Serialize, ToSchema)]
+pub struct ResetRecoveryCodesResponse {
+ /// New recovery codes. Save them securely as they won't be shown again.
+ pub codes: Vec,
+}
+
+/// Reset recovery codes
+///
+/// Invalidates all existing recovery codes and generates a fresh set of 10.
+#[utoipa::path(
+ method(post),
+ path = "/",
+ responses(
+ (status = OK, description = "Success", body = ResetRecoveryCodesResponse, content_type = "application/json"),
+ (status = UNAUTHORIZED, description = "Unauthorized", body = UnauthorizedError, content_type = "application/json"),
+ (status = BAD_REQUEST, description = "Recovery codes not enabled", body = String, content_type = "application/json"),
+ ),
+ tag = "Settings"
+)]
+async fn reset_recovery_codes(
+ Extension(state): Extension,
+ Extension(user_id): Extension,
+) -> AxumResult> {
+ let user = get_user_by_id(&state.database, &user_id)
+ .await?
+ .wrap_err("User not found")?;
+
+ if user.auth_factors.recovery_codes.is_empty() {
+ return Err(AxumError::bad_request(eyre::eyre!(
+ "Recovery codes are not enabled"
+ )));
+ }
+
+ let codes = generate_recovery_codes(10, 12);
+ let hashed_codes = hash_recovery_codes(codes.clone())?;
+
+ let db_codes = hashed_codes
+ .iter()
+ .map(|code| RecoveryCodeFactor {
+ code_hash: code.clone(),
+ used: false,
+ })
+ .collect::>();
+
+ state
+ .database
+ .collection::("users")
+ .update_one(
+ doc! { "_id": *user_id },
+ doc! { "$set": { "auth_factors.recovery_codes": db_codes } },
+ )
+ .await?;
+
+ if let Some(mail) = &state.mail_service {
+ let email = user.email.clone();
+ let mail = mail.clone();
+ tokio::spawn(async move {
+ if let Err(e) = mail
+ .send_factor_added(&email, "recovery codes (regenerated)")
+ .await
+ {
+ tracing::warn!(error = ?e, "Failed to send factor notification");
+ }
+ });
+ }
+
+ Ok(Json(ResetRecoveryCodesResponse { codes }))
+}
diff --git a/server/src/routes/api/settings/factors/totp.rs b/server/src/routes/api/settings/factors/totp.rs
index 7fee81b..6306f24 100644
--- a/server/src/routes/api/settings/factors/totp.rs
+++ b/server/src/routes/api/settings/factors/totp.rs
@@ -1,4 +1,4 @@
-// mod disable;
+mod disable;
mod enable;
use base32::{Alphabet, decode};
@@ -15,8 +15,9 @@ use crate::{
};
pub fn routes() -> OpenApiRouter {
- OpenApiRouter::new().nest("/enable", enable::routes())
- // .nest("/disable", disable::routes())
+ OpenApiRouter::new()
+ .nest("/enable", enable::routes())
+ .nest("/disable", disable::routes())
}
pub fn create_totp_instance(
diff --git a/server/src/routes/api/settings/factors/totp/disable.rs b/server/src/routes/api/settings/factors/totp/disable.rs
index 8b13789..2c2fb63 100644
--- a/server/src/routes/api/settings/factors/totp/disable.rs
+++ b/server/src/routes/api/settings/factors/totp/disable.rs
@@ -1 +1,74 @@
+use axum::{Extension, Json};
+use color_eyre::eyre::{self, ContextCompat};
+use mongodb::bson::doc;
+use serde::Serialize;
+use utoipa::ToSchema;
+use utoipa_axum::{router::OpenApiRouter, routes};
+use crate::{
+ axum_error::{AxumError, AxumResult},
+ database::{User, get_user_by_id},
+ middlewares::require_auth::{UnauthorizedError, UserId},
+ state::AppState,
+};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new().routes(routes!(disable_totp))
+}
+
+#[derive(Serialize, ToSchema)]
+#[schema(example = json!({ "success": true }))]
+struct DisableTotpResponse {
+ success: bool,
+}
+
+/// Disable TOTP
+///
+/// Removes the TOTP authentication factor from the user's account.
+#[utoipa::path(
+ method(delete),
+ path = "/",
+ responses(
+ (status = OK, description = "Success", body = DisableTotpResponse, content_type = "application/json"),
+ (status = UNAUTHORIZED, description = "Unauthorized", body = UnauthorizedError, content_type = "application/json"),
+ (status = BAD_REQUEST, description = "TOTP not enabled", body = String, content_type = "application/json"),
+ ),
+ tag = "Settings"
+)]
+async fn disable_totp(
+ Extension(state): Extension,
+ Extension(user_id): Extension,
+) -> AxumResult> {
+ let user = get_user_by_id(&state.database, &user_id)
+ .await?
+ .wrap_err("User not found")?;
+
+ if !user
+ .auth_factors
+ .totp
+ .is_some_and(|totp| totp.fully_enabled)
+ {
+ return Err(AxumError::bad_request(eyre::eyre!("TOTP is not enabled")));
+ }
+
+ state
+ .database
+ .collection::("users")
+ .update_one(
+ doc! { "_id": *user_id },
+ doc! { "$unset": { "auth_factors.totp": "" } },
+ )
+ .await?;
+
+ if let Some(mail) = &state.mail_service {
+ let email = user.email.clone();
+ let mail = mail.clone();
+ tokio::spawn(async move {
+ if let Err(e) = mail.send_factor_removed(&email, "TOTP authenticator").await {
+ tracing::warn!(error = ?e, "Failed to send factor removed notification");
+ }
+ });
+ }
+
+ Ok(Json(DisableTotpResponse { success: true }))
+}
diff --git a/server/src/routes/api/settings/factors/totp/enable.rs b/server/src/routes/api/settings/factors/totp/enable.rs
index 4e6a990..9ce186f 100644
--- a/server/src/routes/api/settings/factors/totp/enable.rs
+++ b/server/src/routes/api/settings/factors/totp/enable.rs
@@ -20,7 +20,9 @@ use crate::{
};
pub fn routes() -> OpenApiRouter {
- OpenApiRouter::new().routes(routes!(enable_totp))
+ OpenApiRouter::new()
+ .routes(routes!(enable_totp))
+ .nest("/confirm", confirm::routes())
}
#[derive(Deserialize, ToSchema, Validate)]
diff --git a/server/src/routes/api/settings/factors/webauthn.rs b/server/src/routes/api/settings/factors/webauthn.rs
index b5b9b3e..f212441 100644
--- a/server/src/routes/api/settings/factors/webauthn.rs
+++ b/server/src/routes/api/settings/factors/webauthn.rs
@@ -2,6 +2,7 @@ use utoipa_axum::router::OpenApiRouter;
use crate::state::AppState;
+mod delete;
mod finish;
mod start;
@@ -9,4 +10,5 @@ pub fn routes() -> OpenApiRouter {
OpenApiRouter::new()
.nest("/start", start::routes())
.nest("/finish", finish::routes())
+ .nest("/delete", delete::routes())
}
diff --git a/server/src/routes/api/settings/factors/webauthn/delete.rs b/server/src/routes/api/settings/factors/webauthn/delete.rs
new file mode 100644
index 0000000..06784b3
--- /dev/null
+++ b/server/src/routes/api/settings/factors/webauthn/delete.rs
@@ -0,0 +1,73 @@
+use axum::{Extension, Json, extract::Path};
+use color_eyre::eyre;
+use mongodb::bson::doc;
+use serde::Serialize;
+use utoipa::ToSchema;
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+use crate::{
+ axum_error::{AxumError, AxumResult},
+ database::{User, get_user_by_id},
+ middlewares::require_auth::{UnauthorizedError, UserId},
+ state::AppState,
+};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new().routes(routes!(delete_webauthn))
+}
+
+#[derive(Serialize, ToSchema)]
+#[schema(example = json!({ "success": true }))]
+struct DeleteWebAuthnResponse {
+ success: bool,
+}
+
+/// Delete WebAuthn key
+///
+/// Removes a WebAuthn passkey by its credential ID.
+#[utoipa::path(
+ method(delete),
+ path = "/{credential_id}",
+ params(
+ ("credential_id" = String, Path, description = "Credential ID of the WebAuthn key to delete")
+ ),
+ responses(
+ (status = OK, description = "Success", body = DeleteWebAuthnResponse, content_type = "application/json"),
+ (status = UNAUTHORIZED, description = "Unauthorized", body = UnauthorizedError, content_type = "application/json"),
+ (status = NOT_FOUND, description = "Key not found", body = String, content_type = "application/json"),
+ ),
+ tag = "Settings"
+)]
+async fn delete_webauthn(
+ Extension(state): Extension,
+ Extension(user_id): Extension,
+ Path(credential_id): Path,
+) -> AxumResult> {
+ let result = state
+ .database
+ .collection::("users")
+ .update_one(
+ doc! { "_id": *user_id, "auth_factors.webauthn.credential_id": &credential_id },
+ doc! { "$pull": { "auth_factors.webauthn": { "credential_id": &credential_id } } },
+ )
+ .await?;
+
+ if result.matched_count == 0 {
+ return Err(AxumError::not_found(eyre::eyre!("WebAuthn key not found")));
+ }
+
+ if let Some(mail) = &state.mail_service {
+ let user = get_user_by_id(&state.database, &user_id).await?;
+ if let Some(user) = user {
+ let email = user.email;
+ let mail = mail.clone();
+ tokio::spawn(async move {
+ if let Err(e) = mail.send_factor_removed(&email, "WebAuthn passkey").await {
+ tracing::warn!(error = ?e, "Failed to send factor removed notification");
+ }
+ });
+ }
+ }
+
+ Ok(Json(DeleteWebAuthnResponse { success: true }))
+}
diff --git a/server/src/routes/api/settings/factors/webauthn/finish.rs b/server/src/routes/api/settings/factors/webauthn/finish.rs
index 77dd82d..80218c6 100644
--- a/server/src/routes/api/settings/factors/webauthn/finish.rs
+++ b/server/src/routes/api/settings/factors/webauthn/finish.rs
@@ -1,4 +1,5 @@
use axum::{Extension, Json};
+use base64::Engine;
use color_eyre::eyre::{self, Context, ContextCompat};
use mongodb::bson::doc;
use serde::Serialize;
@@ -58,6 +59,8 @@ async fn webauthn_finish_setup(
.wrap_err("Missing display name")?;
let serialized_key = serde_json::to_string(&sk).wrap_err("Failed to serialize passkey")?;
+ let credential_id =
+ base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(sk.cred_id().as_ref());
state
.database
@@ -67,6 +70,7 @@ async fn webauthn_finish_setup(
doc! {
"$push": {
"auth_factors.webauthn": {
+ "credential_id": credential_id,
"serialized_key": serialized_key,
"display_name": display_name,
}
diff --git a/server/src/routes/api/settings/password.rs b/server/src/routes/api/settings/password.rs
index f0f4bed..c80cc0d 100644
--- a/server/src/routes/api/settings/password.rs
+++ b/server/src/routes/api/settings/password.rs
@@ -1,4 +1,3 @@
-use argon2::{Argon2, PasswordHash, PasswordVerifier};
use axum::{Extension, Json};
use axum_valid::Valid;
use color_eyre::eyre::{self, ContextCompat};
@@ -13,7 +12,7 @@ use crate::{
database::{User, get_user_by_id},
middlewares::require_auth::{UnauthorizedError, UserId},
state::AppState,
- utils::hash_password,
+ utils::{hash_password, verify_password},
};
pub fn routes() -> OpenApiRouter {
@@ -61,13 +60,11 @@ async fn change_password(
.password
.password_hash
.as_deref()
- .ok_or_else(|| AxumError::bad_request(eyre::eyre!("Password is not set for this account")))?;
+ .ok_or_else(|| {
+ AxumError::bad_request(eyre::eyre!("Password is not set for this account"))
+ })?;
- let parsed_hash = PasswordHash::new(password_hash)
- .map_err(|_| eyre::eyre!("Failed to parse password hash"))?;
-
- Argon2::default()
- .verify_password(body.current_password.as_bytes(), &parsed_hash)
+ verify_password(&body.current_password, password_hash)
.map_err(|_| AxumError::bad_request(eyre::eyre!("Current password is incorrect")))?;
let new_hash = hash_password(&body.new_password)?;
diff --git a/server/src/routes/api/settings/profile.rs b/server/src/routes/api/settings/profile.rs
new file mode 100644
index 0000000..0a07b55
--- /dev/null
+++ b/server/src/routes/api/settings/profile.rs
@@ -0,0 +1,121 @@
+use axum::{Extension, Json};
+use axum_valid::Valid;
+use color_eyre::eyre::OptionExt;
+use mongodb::bson::doc;
+use serde::{Deserialize, Serialize};
+use utoipa::ToSchema;
+use utoipa_axum::{router::OpenApiRouter, routes};
+use validator::Validate;
+
+use crate::{
+ axum_error::AxumResult, database::User, middlewares::require_auth::UserId, state::AppState,
+};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new()
+ .routes(routes!(get_profile))
+ .routes(routes!(update_profile))
+}
+
+#[derive(Serialize, ToSchema)]
+struct ProfileResponse {
+ preferred_username: String,
+ display_name: String,
+ email: String,
+ email_confirmed: bool,
+ first_name: String,
+ last_name: String,
+}
+
+/// Get user profile
+///
+/// Returns the current user's profile information.
+#[utoipa::path(
+ method(get),
+ path = "/",
+ responses(
+ (status = OK, description = "Profile data", body = ProfileResponse, content_type = "application/json"),
+ ),
+ tag = "Settings"
+)]
+async fn get_profile(
+ Extension(state): Extension,
+ Extension(user_id): Extension,
+) -> AxumResult> {
+ let user = state
+ .database
+ .collection::("users")
+ .find_one(doc! { "_id": *user_id })
+ .await?
+ .ok_or_eyre("User not found")?;
+
+ Ok(Json(ProfileResponse {
+ preferred_username: user.preferred_username,
+ display_name: user.display_name,
+ email: user.email,
+ email_confirmed: user.email_confirmed,
+ first_name: user.first_name,
+ last_name: user.last_name,
+ }))
+}
+
+#[derive(Deserialize, ToSchema, Validate)]
+struct UpdateProfileBody {
+ #[validate(length(min = 1, max = 64))]
+ display_name: Option,
+ #[validate(length(max = 64))]
+ first_name: Option,
+ #[validate(length(max = 64))]
+ last_name: Option,
+}
+
+#[derive(Serialize, ToSchema)]
+struct UpdateProfileResponse {
+ success: bool,
+}
+
+/// Update user profile
+///
+/// Updates the current user's profile. Only provided fields will be updated.
+#[utoipa::path(
+ method(patch),
+ path = "/",
+ request_body = UpdateProfileBody,
+ responses(
+ (status = OK, description = "Profile updated", body = UpdateProfileResponse, content_type = "application/json"),
+ (status = BAD_REQUEST, description = "No fields to update"),
+ ),
+ tag = "Settings"
+)]
+async fn update_profile(
+ Extension(state): Extension,
+ Extension(user_id): Extension,
+ Valid(Json(body)): Valid>,
+) -> AxumResult> {
+ let mut update = doc! {};
+
+ if let Some(display_name) = &body.display_name {
+ let trimmed = display_name.trim();
+ if !trimmed.is_empty() {
+ update.insert("display_name", trimmed);
+ }
+ }
+ if let Some(first_name) = &body.first_name {
+ update.insert("first_name", first_name.trim());
+ }
+ if let Some(last_name) = &body.last_name {
+ update.insert("last_name", last_name.trim());
+ }
+
+ if update.is_empty() {
+ return Ok(Json(UpdateProfileResponse { success: true }));
+ }
+
+ state
+ .database
+ .collection::("users")
+ .update_one(doc! { "_id": *user_id }, doc! { "$set": update })
+ .await?;
+
+ Ok(Json(UpdateProfileResponse { success: true }))
+}
diff --git a/server/src/routes/oidc_routes.rs b/server/src/routes/oidc_routes.rs
new file mode 100644
index 0000000..9228ec9
--- /dev/null
+++ b/server/src/routes/oidc_routes.rs
@@ -0,0 +1,987 @@
+use axum::{
+ Extension, Json,
+ extract::Query,
+ http::{HeaderMap, StatusCode},
+ response::IntoResponse,
+};
+use chrono::Utc;
+use color_eyre::eyre::{self, Context as _};
+use mongodb::bson::doc;
+use openidconnect::{
+ AccessToken, Audience, EmptyAdditionalClaims, EndUserEmail, EndUserFamilyName,
+ EndUserGivenName, EndUserName, EndUserUsername, IssuerUrl, LocalizedClaim, Nonce,
+ SubjectIdentifier,
+ core::{CoreIdToken, CoreIdTokenClaims, CoreJwsSigningAlgorithm},
+};
+use serde::{Deserialize, Serialize};
+use tower_sessions::Session;
+use utoipa::ToSchema;
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+use crate::{
+ axum_error::{AxumError, AxumResult},
+ database::{Application, User, get_user_by_uuid},
+ oidc::{AccessTokenClaims, AuthorizationCode, RefreshToken},
+ state::AppState,
+ utils::{generate_reset_token, hash_token},
+};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new()
+ .routes(routes!(jwks))
+ .routes(routes!(authorize_get, authorize_post))
+ .routes(routes!(token))
+ .routes(routes!(userinfo))
+}
+
+// ── Discovery ────────────────────────────────────────────────────
+
+// ── JWKS ─────────────────────────────────────────────────────────
+
+/// JSON Web Key Set
+#[utoipa::path(
+ method(get),
+ path = "/jwks",
+ responses(
+ (status = OK, description = "JWKS Document"),
+ ),
+ tag = "OIDC"
+)]
+async fn jwks(Extension(state): Extension) -> impl IntoResponse {
+ Json(serde_json::to_value(&state.oidc_keys.jwks).unwrap_or_default())
+}
+
+// ── Authorize (GET) ──────────────────────────────────────────────
+
+#[derive(Debug, Deserialize, ToSchema)]
+pub struct AuthorizeQuery {
+ pub client_id: String,
+ pub redirect_uri: String,
+ pub response_type: String,
+ #[serde(default)]
+ pub scope: Option,
+ #[serde(default)]
+ pub state: Option,
+ #[serde(default)]
+ pub nonce: Option,
+}
+
+#[derive(Debug, Serialize, ToSchema)]
+pub struct AuthorizeInfo {
+ pub app_name: String,
+ pub app_icon: Option,
+ pub scopes: Vec,
+ pub client_id: String,
+ pub redirect_uri: String,
+ pub state: Option,
+ pub nonce: Option,
+}
+
+/// Get authorization info (requires session)
+#[utoipa::path(
+ method(get),
+ path = "/authorize",
+ params(
+ ("client_id" = String, Query,),
+ ("redirect_uri" = String, Query,),
+ ("response_type" = String, Query,),
+ ("scope" = Option, Query,),
+ ("state" = Option, Query,),
+ ("nonce" = Option, Query,),
+ ),
+ responses(
+ (status = OK, description = "Authorization info", body = AuthorizeInfo),
+ (status = BAD_REQUEST, description = "Invalid request"),
+ ),
+ tag = "OIDC"
+)]
+async fn authorize_get(
+ Extension(state): Extension,
+ session: Session,
+ Query(params): Query,
+) -> AxumResult> {
+ // Check user is authenticated
+ let user_id = session
+ .get::("user_id")
+ .await?;
+ let auth_state = session
+ .get::("auth_state")
+ .await?;
+
+ if user_id.is_none()
+ || !matches!(
+ auth_state,
+ Some(crate::routes::api::AuthState::Authenticated)
+ )
+ {
+ return Err(AxumError::unauthorized(eyre::eyre!(
+ "Login required. Redirect to login page first."
+ )));
+ }
+
+ // Validate response_type
+ if params.response_type != "code" {
+ return Err(AxumError::bad_request(eyre::eyre!(
+ "Unsupported response_type. Only 'code' is supported."
+ )));
+ }
+
+ // Look up application by client_id
+ let app = state
+ .database
+ .collection::("applications")
+ .find_one(doc! { "client_id": ¶ms.client_id })
+ .await
+ .wrap_err("Database error")?
+ .ok_or_else(|| AxumError::bad_request(eyre::eyre!("Unknown client_id")))?;
+
+ // Validate redirect_uri
+ if !app.redirect_uris.contains(¶ms.redirect_uri) {
+ return Err(AxumError::bad_request(eyre::eyre!(
+ "Invalid redirect_uri for this application"
+ )));
+ }
+
+ // Parse scopes
+ let scopes: Vec = params
+ .scope
+ .unwrap_or_default()
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect();
+
+ let valid_scopes = ["openid", "profile", "email", "offline_access"];
+ let filtered_scopes: Vec = scopes
+ .into_iter()
+ .filter(|s| valid_scopes.contains(&s.as_str()))
+ .collect();
+
+ Ok(Json(AuthorizeInfo {
+ app_name: app.name,
+ app_icon: app.icon,
+ scopes: filtered_scopes,
+ client_id: params.client_id,
+ redirect_uri: params.redirect_uri,
+ state: params.state,
+ nonce: params.nonce,
+ }))
+}
+
+// ── Authorize (POST) ─────────────────────────────────────────────
+
+#[derive(Debug, Deserialize, ToSchema)]
+pub struct AuthorizeConsent {
+ pub client_id: String,
+ pub redirect_uri: String,
+ pub scope: String,
+ pub state: Option,
+ pub nonce: Option,
+}
+
+#[derive(Debug, Serialize, ToSchema)]
+pub struct AuthorizeResponse {
+ pub redirect_url: String,
+}
+
+/// Approve authorization (user consent)
+#[utoipa::path(
+ method(post),
+ path = "/authorize",
+ request_body = AuthorizeConsent,
+ responses(
+ (status = OK, description = "Authorization code issued", body = AuthorizeResponse),
+ (status = BAD_REQUEST, description = "Invalid request"),
+ (status = UNAUTHORIZED, description = "Not authenticated"),
+ ),
+ tag = "OIDC"
+)]
+async fn authorize_post(
+ Extension(state): Extension,
+ session: Session,
+ Json(body): Json,
+) -> AxumResult> {
+ // Check user is authenticated
+ let user_id = session
+ .get::("user_id")
+ .await?
+ .ok_or_else(|| AxumError::unauthorized(eyre::eyre!("Not authenticated")))?;
+
+ let auth_state = session
+ .get::("auth_state")
+ .await?;
+ if !matches!(
+ auth_state,
+ Some(crate::routes::api::AuthState::Authenticated)
+ ) {
+ return Err(AxumError::unauthorized(eyre::eyre!("Not authenticated")));
+ }
+
+ // Validate application
+ let app = state
+ .database
+ .collection::("applications")
+ .find_one(doc! { "client_id": &body.client_id })
+ .await
+ .wrap_err("Database error")?
+ .ok_or_else(|| AxumError::bad_request(eyre::eyre!("Unknown client_id")))?;
+
+ if !app.redirect_uris.contains(&body.redirect_uri) {
+ return Err(AxumError::bad_request(eyre::eyre!("Invalid redirect_uri")));
+ }
+
+ // Check user group access
+ let user = state
+ .database
+ .collection::("users")
+ .find_one(doc! { "_id": &user_id })
+ .await
+ .wrap_err("Database error")?
+ .ok_or_else(|| AxumError::bad_request(eyre::eyre!("User not found")))?;
+
+ if !app.allowed_groups.is_empty() {
+ let has_access = user.groups.iter().any(|g| app.allowed_groups.contains(g));
+ if !has_access {
+ return Err(AxumError::forbidden(eyre::eyre!(
+ "You don't have access to this application"
+ )));
+ }
+ }
+
+ // Generate authorization code
+ let code = generate_reset_token(); // 64 char random string
+ let code_hash = hash_token(&code);
+
+ let auth_code = AuthorizationCode {
+ code_hash,
+ client_id: body.client_id.clone(),
+ user_id: user_id.to_hex(),
+ redirect_uri: body.redirect_uri.clone(),
+ scope: body.scope.clone(),
+ nonce: body.nonce.clone(),
+ created_at: Utc::now(),
+ used: false,
+ };
+
+ state
+ .database
+ .collection::("authorization_codes")
+ .insert_one(auth_code)
+ .await
+ .wrap_err("Failed to store authorization code")?;
+
+ // Build redirect URL with code and state
+ let mut redirect_url = body.redirect_uri.clone();
+ redirect_url.push_str(if redirect_url.contains('?') { "&" } else { "?" });
+ redirect_url.push_str(&format!("code={code}"));
+ if let Some(ref st) = body.state {
+ redirect_url.push_str(&format!("&state={}", urlencoding::encode(st)));
+ }
+
+ Ok(Json(AuthorizeResponse { redirect_url }))
+}
+
+// ── Token ────────────────────────────────────────────────────────
+
+#[derive(Debug, Deserialize, ToSchema)]
+pub struct TokenRequest {
+ pub grant_type: String,
+ pub code: Option,
+ pub redirect_uri: Option,
+ pub client_id: Option,
+ pub client_secret: Option,
+ pub refresh_token: Option,
+}
+
+#[derive(Debug, Serialize, ToSchema)]
+pub struct TokenResponse {
+ pub access_token: String,
+ pub token_type: String,
+ pub expires_in: u64,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub refresh_token: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub id_token: Option,
+ pub scope: String,
+}
+
+#[derive(Debug, Serialize)]
+pub struct TokenError {
+ pub error: String,
+ pub error_description: String,
+}
+
+/// OAuth2 Token endpoint
+#[utoipa::path(
+ method(post),
+ path = "/token",
+ responses(
+ (status = OK, description = "Token response", body = TokenResponse),
+ (status = BAD_REQUEST, description = "Token error"),
+ ),
+ tag = "OIDC"
+)]
+async fn token(
+ Extension(state): Extension,
+ headers: HeaderMap,
+ axum::Form(body): axum::Form,
+) -> Result, axum::response::Response> {
+ // Extract client credentials from Basic auth header or body
+ let (client_id, client_secret) = extract_client_credentials(&headers, &body);
+
+ match body.grant_type.as_str() {
+ "authorization_code" => {
+ handle_authorization_code_grant(&state, &body, &client_id, &client_secret).await
+ }
+ "refresh_token" => {
+ handle_refresh_token_grant(&state, &body, &client_id, &client_secret).await
+ }
+ _ => Err(token_error(
+ StatusCode::BAD_REQUEST,
+ "unsupported_grant_type",
+ "Only authorization_code and refresh_token are supported",
+ )),
+ }
+}
+
+fn extract_client_credentials(
+ headers: &HeaderMap,
+ body: &TokenRequest,
+) -> (Option, Option) {
+ // Try Basic auth first
+ if let Some(auth) = headers.get("authorization")
+ && let Ok(auth_str) = auth.to_str()
+ && let Some(basic) = auth_str.strip_prefix("Basic ")
+ && let Ok(decoded) =
+ base64::Engine::decode(&base64::engine::general_purpose::STANDARD, basic.trim())
+ && let Ok(creds) = String::from_utf8(decoded)
+ && let Some((id, secret)) = creds.split_once(':')
+ {
+ return (Some(id.to_string()), Some(secret.to_string()));
+ }
+
+ // Fall back to body parameters
+ (body.client_id.clone(), body.client_secret.clone())
+}
+
+async fn handle_authorization_code_grant(
+ state: &AppState,
+ body: &TokenRequest,
+ client_id: &Option,
+ client_secret: &Option,
+) -> Result, axum::response::Response> {
+ let code = body
+ .code
+ .as_ref()
+ .ok_or_else(|| token_error(StatusCode::BAD_REQUEST, "invalid_request", "Missing code"))?;
+
+ let redirect_uri = body.redirect_uri.as_ref().ok_or_else(|| {
+ token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_request",
+ "Missing redirect_uri",
+ )
+ })?;
+
+ let client_id = client_id.as_ref().ok_or_else(|| {
+ token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_request",
+ "Missing client_id",
+ )
+ })?;
+
+ // Find the authorization code
+ let code_hash = hash_token(code);
+ let auth_code = state
+ .database
+ .collection::("authorization_codes")
+ .find_one(doc! { "code_hash": &code_hash, "used": false })
+ .await
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Database error",
+ )
+ })?
+ .ok_or_else(|| {
+ token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_grant",
+ "Invalid or expired authorization code",
+ )
+ })?;
+
+ // Validate code hasn't expired (10 minute lifetime)
+ let age = Utc::now() - auth_code.created_at;
+ if age.num_minutes() > 10 {
+ // Mark as used to prevent replay
+ let _ = state
+ .database
+ .collection::("authorization_codes")
+ .update_one(
+ doc! { "code_hash": &code_hash },
+ doc! { "$set": { "used": true } },
+ )
+ .await;
+
+ return Err(token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_grant",
+ "Authorization code expired",
+ ));
+ }
+
+ // Validate client_id and redirect_uri match
+ if auth_code.client_id != *client_id {
+ return Err(token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_grant",
+ "client_id mismatch",
+ ));
+ }
+ if auth_code.redirect_uri != *redirect_uri {
+ return Err(token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_grant",
+ "redirect_uri mismatch",
+ ));
+ }
+
+ // Validate client_secret for confidential clients
+ let app = state
+ .database
+ .collection::("applications")
+ .find_one(doc! { "client_id": client_id })
+ .await
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Database error",
+ )
+ })?
+ .ok_or_else(|| token_error(StatusCode::BAD_REQUEST, "invalid_client", "Unknown client"))?;
+
+ if matches!(app.client_type, crate::database::ClientType::Confidential) {
+ let expected_secret = app.client_secret.as_ref().ok_or_else(|| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Confidential client has no secret",
+ )
+ })?;
+ let provided_secret = client_secret.as_ref().ok_or_else(|| {
+ token_error(
+ StatusCode::UNAUTHORIZED,
+ "invalid_client",
+ "Client secret required",
+ )
+ })?;
+ if provided_secret != expected_secret {
+ return Err(token_error(
+ StatusCode::UNAUTHORIZED,
+ "invalid_client",
+ "Invalid client secret",
+ ));
+ }
+ }
+
+ // Mark code as used
+ state
+ .database
+ .collection::("authorization_codes")
+ .update_one(
+ doc! { "code_hash": &code_hash },
+ doc! { "$set": { "used": true } },
+ )
+ .await
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Failed to invalidate code",
+ )
+ })?;
+
+ // Get user for claims
+ let user_oid = mongodb::bson::oid::ObjectId::parse_str(&auth_code.user_id).map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Invalid user_id",
+ )
+ })?;
+
+ let user = state
+ .database
+ .collection::("users")
+ .find_one(doc! { "_id": &user_oid })
+ .await
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Database error",
+ )
+ })?
+ .ok_or_else(|| token_error(StatusCode::BAD_REQUEST, "invalid_grant", "User not found"))?;
+
+ let issuer = state
+ .settings
+ .general
+ .public_url
+ .to_string()
+ .trim_end_matches('/')
+ .to_string();
+
+ let now = Utc::now().timestamp() as usize;
+ let scopes: Vec<&str> = auth_code.scope.split_whitespace().collect();
+
+ // Build access token (1 hour)
+ let access_claims = AccessTokenClaims {
+ iss: issuer.clone(),
+ sub: user.uuid.to_string(),
+ aud: client_id.clone(),
+ exp: now + 3600,
+ iat: now,
+ scope: auth_code.scope.clone(),
+ client_id: client_id.clone(),
+ };
+ let access_token = state
+ .oidc_keys
+ .sign_access_token(&access_claims)
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Failed to sign access token",
+ )
+ })?;
+
+ // Build ID token if openid scope requested
+ let id_token = if scopes.contains(&"openid") {
+ let issuer_url = IssuerUrl::new(issuer.clone()).map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Invalid issuer URL",
+ )
+ })?;
+
+ let mut standard_claims =
+ openidconnect::StandardClaims::new(SubjectIdentifier::new(user.uuid.to_string()));
+
+ if scopes.contains(&"profile") {
+ standard_claims = standard_claims
+ .set_name(Some(LocalizedClaim::from(EndUserName::new(
+ user.display_name.clone(),
+ ))))
+ .set_preferred_username(Some(EndUserUsername::new(user.preferred_username.clone())))
+ .set_given_name(Some(LocalizedClaim::from(EndUserGivenName::new(
+ user.first_name.clone(),
+ ))))
+ .set_family_name(Some(LocalizedClaim::from(EndUserFamilyName::new(
+ user.last_name.clone(),
+ ))));
+ }
+ if scopes.contains(&"email") {
+ standard_claims = standard_claims
+ .set_email(Some(EndUserEmail::new(user.email.clone())))
+ .set_email_verified(Some(user.email_confirmed));
+ }
+
+ let id_claims = CoreIdTokenClaims::new(
+ issuer_url,
+ vec![Audience::new(client_id.clone())],
+ Utc::now() + chrono::Duration::hours(1),
+ Utc::now(),
+ standard_claims,
+ EmptyAdditionalClaims {},
+ )
+ .set_nonce(auth_code.nonce.as_ref().map(|n| Nonce::new(n.clone())));
+
+ let access_token_obj = AccessToken::new(access_token.clone());
+
+ let signed_id_token = CoreIdToken::new(
+ id_claims,
+ &state.oidc_keys.signing_key,
+ CoreJwsSigningAlgorithm::RsaSsaPkcs1V15Sha256,
+ Some(&access_token_obj),
+ None,
+ )
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Failed to sign ID token",
+ )
+ })?;
+
+ Some(signed_id_token.to_string())
+ } else {
+ None
+ };
+
+ // Generate refresh token if offline_access scope requested
+ let refresh_token = if scopes.contains(&"offline_access") {
+ let raw_token = generate_reset_token();
+ let token_hash = hash_token(&raw_token);
+
+ let rt = RefreshToken {
+ token_hash,
+ client_id: client_id.clone(),
+ user_id: auth_code.user_id.clone(),
+ scope: auth_code.scope.clone(),
+ created_at: Utc::now(),
+ revoked: false,
+ };
+
+ state
+ .database
+ .collection::("refresh_tokens")
+ .insert_one(rt)
+ .await
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Failed to store refresh token",
+ )
+ })?;
+
+ Some(raw_token)
+ } else {
+ None
+ };
+
+ Ok(Json(TokenResponse {
+ access_token,
+ token_type: "Bearer".to_string(),
+ expires_in: 3600,
+ refresh_token,
+ id_token,
+ scope: auth_code.scope,
+ }))
+}
+
+async fn handle_refresh_token_grant(
+ state: &AppState,
+ body: &TokenRequest,
+ client_id: &Option,
+ client_secret: &Option,
+) -> Result, axum::response::Response> {
+ let raw_token = body.refresh_token.as_ref().ok_or_else(|| {
+ token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_request",
+ "Missing refresh_token",
+ )
+ })?;
+
+ let token_hash = hash_token(raw_token);
+ let stored = state
+ .database
+ .collection::("refresh_tokens")
+ .find_one(doc! { "token_hash": &token_hash, "revoked": false })
+ .await
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Database error",
+ )
+ })?
+ .ok_or_else(|| {
+ token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_grant",
+ "Invalid or revoked refresh token",
+ )
+ })?;
+
+ // Validate refresh token age (30 days max)
+ let age = Utc::now() - stored.created_at;
+ if age.num_days() > 30 {
+ let _ = state
+ .database
+ .collection::("refresh_tokens")
+ .update_one(
+ doc! { "token_hash": &token_hash },
+ doc! { "$set": { "revoked": true } },
+ )
+ .await;
+ return Err(token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_grant",
+ "Refresh token expired",
+ ));
+ }
+
+ // Validate client
+ let req_client_id = client_id.as_ref().ok_or_else(|| {
+ token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_request",
+ "Missing client_id",
+ )
+ })?;
+
+ if stored.client_id != *req_client_id {
+ return Err(token_error(
+ StatusCode::BAD_REQUEST,
+ "invalid_grant",
+ "client_id mismatch",
+ ));
+ }
+
+ // Validate client_secret for confidential clients
+ let app = state
+ .database
+ .collection::("applications")
+ .find_one(doc! { "client_id": req_client_id })
+ .await
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Database error",
+ )
+ })?
+ .ok_or_else(|| token_error(StatusCode::BAD_REQUEST, "invalid_client", "Unknown client"))?;
+
+ if matches!(app.client_type, crate::database::ClientType::Confidential) {
+ let expected = app.client_secret.as_ref().ok_or_else(|| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Confidential client has no secret",
+ )
+ })?;
+ let provided = client_secret.as_ref().ok_or_else(|| {
+ token_error(
+ StatusCode::UNAUTHORIZED,
+ "invalid_client",
+ "Client secret required",
+ )
+ })?;
+ if provided != expected {
+ return Err(token_error(
+ StatusCode::UNAUTHORIZED,
+ "invalid_client",
+ "Invalid client secret",
+ ));
+ }
+ }
+
+ // Get user
+ let user_oid = mongodb::bson::oid::ObjectId::parse_str(&stored.user_id).map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Invalid user_id",
+ )
+ })?;
+
+ let user = state
+ .database
+ .collection::("users")
+ .find_one(doc! { "_id": &user_oid })
+ .await
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Database error",
+ )
+ })?
+ .ok_or_else(|| token_error(StatusCode::BAD_REQUEST, "invalid_grant", "User not found"))?;
+
+ let issuer = state
+ .settings
+ .general
+ .public_url
+ .to_string()
+ .trim_end_matches('/')
+ .to_string();
+ let now = Utc::now().timestamp() as usize;
+
+ let access_claims = AccessTokenClaims {
+ iss: issuer,
+ sub: user.uuid.to_string(),
+ aud: req_client_id.clone(),
+ exp: now + 3600,
+ iat: now,
+ scope: stored.scope.clone(),
+ client_id: req_client_id.clone(),
+ };
+
+ let access_token = state
+ .oidc_keys
+ .sign_access_token(&access_claims)
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Failed to sign access token",
+ )
+ })?;
+
+ // Revoke the used refresh token and issue a new one (rotation)
+ let _ = state
+ .database
+ .collection::("refresh_tokens")
+ .update_one(
+ doc! { "token_hash": &token_hash },
+ doc! { "$set": { "revoked": true } },
+ )
+ .await;
+
+ let raw_new_token = generate_reset_token();
+ let new_token_hash = hash_token(&raw_new_token);
+ let new_refresh_token = RefreshToken {
+ token_hash: new_token_hash,
+ user_id: stored.user_id,
+ client_id: req_client_id.clone(),
+ scope: stored.scope.clone(),
+ created_at: Utc::now(),
+ revoked: false,
+ };
+ let _ = state
+ .database
+ .collection::("refresh_tokens")
+ .insert_one(new_refresh_token)
+ .await;
+
+ Ok(Json(TokenResponse {
+ access_token,
+ token_type: "Bearer".to_string(),
+ expires_in: 3600,
+ refresh_token: Some(raw_new_token),
+ id_token: None,
+ scope: stored.scope,
+ }))
+}
+
+// ── UserInfo ─────────────────────────────────────────────────────
+
+#[derive(Debug, Serialize, ToSchema)]
+pub struct UserInfoResponse {
+ pub sub: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub name: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub preferred_username: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub given_name: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub family_name: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub email: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub email_verified: Option,
+}
+
+/// OpenID Connect UserInfo endpoint
+#[utoipa::path(
+ method(get),
+ path = "/userinfo",
+ responses(
+ (status = OK, description = "User info", body = UserInfoResponse),
+ (status = UNAUTHORIZED, description = "Invalid or missing access token"),
+ ),
+ tag = "OIDC"
+)]
+async fn userinfo(
+ Extension(state): Extension,
+ headers: HeaderMap,
+) -> Result, axum::response::Response> {
+ // Extract Bearer token
+ let auth_header = headers
+ .get("authorization")
+ .and_then(|v| v.to_str().ok())
+ .and_then(|v| v.strip_prefix("Bearer "))
+ .ok_or_else(|| {
+ token_error(
+ StatusCode::UNAUTHORIZED,
+ "invalid_token",
+ "Missing or invalid Bearer token",
+ )
+ })?;
+
+ // Verify access token
+ let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::RS256);
+ validation.set_audience(&[""]); // We'll skip audience validation for userinfo
+ validation.validate_aud = false;
+
+ let token_data = jsonwebtoken::decode::(
+ auth_header,
+ &state.oidc_keys.decoding_key,
+ &validation,
+ )
+ .map_err(|e| {
+ token_error(
+ StatusCode::UNAUTHORIZED,
+ "invalid_token",
+ &format!("Token verification failed: {e}"),
+ )
+ })?;
+
+ let claims = token_data.claims;
+
+ // Get user by UUID
+ let user_uuid = uuid::Uuid::parse_str(&claims.sub).map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Invalid sub claim",
+ )
+ })?;
+
+ let user = get_user_by_uuid(&state.database, &user_uuid)
+ .await
+ .map_err(|_| {
+ token_error(
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "server_error",
+ "Database error",
+ )
+ })?
+ .ok_or_else(|| token_error(StatusCode::UNAUTHORIZED, "invalid_token", "User not found"))?;
+
+ let scopes: Vec<&str> = claims.scope.split_whitespace().collect();
+
+ let mut response = UserInfoResponse {
+ sub: user.uuid.to_string(),
+ name: None,
+ preferred_username: None,
+ given_name: None,
+ family_name: None,
+ email: None,
+ email_verified: None,
+ };
+
+ if scopes.contains(&"profile") {
+ response.name = Some(user.display_name);
+ response.preferred_username = Some(user.preferred_username);
+ response.given_name = Some(user.first_name);
+ response.family_name = Some(user.last_name);
+ }
+
+ if scopes.contains(&"email") {
+ response.email = Some(user.email);
+ response.email_verified = Some(user.email_confirmed);
+ }
+
+ Ok(Json(response))
+}
+
+// ── Helpers ──────────────────────────────────────────────────────
+
+fn token_error(status: StatusCode, error: &str, description: &str) -> axum::response::Response {
+ let body = TokenError {
+ error: error.to_string(),
+ error_description: description.to_string(),
+ };
+ (status, Json(body)).into_response()
+}
diff --git a/server/src/routes/well_known.rs b/server/src/routes/well_known.rs
new file mode 100644
index 0000000..29df981
--- /dev/null
+++ b/server/src/routes/well_known.rs
@@ -0,0 +1,30 @@
+use axum::{Extension, Json, http::StatusCode, response::IntoResponse};
+use utoipa_axum::{router::OpenApiRouter, routes};
+
+use crate::{oidc::build_provider_metadata, state::AppState};
+
+pub fn routes() -> OpenApiRouter {
+ OpenApiRouter::new().routes(routes!(openid_configuration))
+}
+
+#[utoipa::path(
+ method(get),
+ path = "/openid-configuration",
+ responses(
+ (status = OK, description = "OpenID Connect Discovery document"),
+ (status = INTERNAL_SERVER_ERROR, description = "Failed to build discovery document")
+ ),
+ tag = "OpenID Connect"
+)]
+async fn openid_configuration(Extension(state): Extension) -> impl IntoResponse {
+ let issuer = state.settings.general.public_url.to_string();
+ let issuer = issuer.trim_end_matches('/');
+ match build_provider_metadata(issuer) {
+ Ok(metadata) => Json(metadata).into_response(),
+ Err(_) => (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "Failed to build discovery document",
+ )
+ .into_response(),
+ }
+}
diff --git a/server/src/settings.rs b/server/src/settings.rs
index c640d2e..ac1bd5d 100644
--- a/server/src/settings.rs
+++ b/server/src/settings.rs
@@ -45,6 +45,9 @@ pub struct General {
pub public_url: Uri,
pub app_name: String,
+
+ #[serde(default)]
+ pub trust_proxy: bool,
}
#[derive(Debug, Deserialize, Serialize)]
@@ -72,6 +75,19 @@ pub struct WebAuthn {
pub allow_subdomains: Option,
}
+#[derive(Debug, Deserialize, Serialize)]
+pub struct Oidc {
+ pub signing_key_file: String,
+}
+
+impl Oidc {
+ pub fn default() -> Self {
+ Self {
+ signing_key_file: "oidc-signing-key.pem".to_string(),
+ }
+ }
+}
+
#[derive(Debug, Deserialize, Serialize)]
pub struct Settings {
pub general: General,
@@ -80,6 +96,8 @@ pub struct Settings {
pub webauthn: WebAuthn,
#[serde(default)]
pub mail: Option,
+ #[serde(default = "Oidc::default")]
+ pub oidc: Oidc,
}
impl Settings {
@@ -128,11 +146,14 @@ impl Settings {
if add_suggestion && !std::path::Path::new("config.toml").exists() {
let example_settings = Settings::example();
- let example_settings = toml::to_string_pretty(&example_settings)?;
+ let example_toml = toml::to_string_pretty(&example_settings)?;
+
+ std::fs::write("config.toml", &example_toml)?;
- std::fs::write("config.toml", example_settings)?;
+ // Generate OIDC signing key alongside the example config
+ crate::oidc::generate_oidc_key_file(&example_settings.oidc.signing_key_file)?;
- res = res.suggestion("An example configuration file has been created at `config.toml` in the current directory.");
+ res = res.suggestion("An example configuration file has been created at `config.toml` in the current directory. An OIDC signing key has also been generated.");
}
res
@@ -146,6 +167,7 @@ impl Settings {
.parse()
.expect("hardcoded uri should parse"),
app_name: "Agin Auth".to_string(),
+ trust_proxy: false,
},
db: Db {
connection_string: "mongodb://localhost:27017".to_string(),
@@ -164,6 +186,7 @@ impl Settings {
allow_subdomains: Some(false),
},
mail: None,
+ oidc: Oidc::default(),
}
}
}
diff --git a/server/src/state.rs b/server/src/state.rs
index f264158..79b168d 100644
--- a/server/src/state.rs
+++ b/server/src/state.rs
@@ -4,7 +4,7 @@ use mail::MailService;
use mongodb::Database;
use webauthn_rs::Webauthn;
-use crate::settings::Settings;
+use crate::{oidc::OidcKeys, settings::Settings};
#[derive(Clone)]
pub struct AppState {
@@ -12,4 +12,5 @@ pub struct AppState {
pub settings: Arc,
pub webauthn: Arc,
pub mail_service: Option>,
+ pub oidc_keys: Arc,
}
diff --git a/server/src/utils.rs b/server/src/utils.rs
index 368c862..9dcf5c7 100644
--- a/server/src/utils.rs
+++ b/server/src/utils.rs
@@ -1,4 +1,3 @@
-use hex;
use rand::{RngExt, distr::Alphanumeric, rngs::ThreadRng};
use sha2::{Digest, Sha256};
@@ -25,7 +24,10 @@ pub fn hash_token(token: &str) -> String {
}
pub fn hash_password(password: &str) -> color_eyre::eyre::Result {
- use argon2::{Argon2, password_hash::{PasswordHasher, SaltString, rand_core::OsRng}};
+ use argon2::{
+ Argon2,
+ password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
+ };
let salt = SaltString::generate(&mut OsRng);
let hash = Argon2::default()
@@ -35,3 +37,16 @@ pub fn hash_password(password: &str) -> color_eyre::eyre::Result {
Ok(hash)
}
+
+pub fn verify_password(password: &str, hash: &str) -> color_eyre::eyre::Result<()> {
+ use argon2::{Argon2, PasswordHash, PasswordVerifier};
+
+ let parsed_hash =
+ PasswordHash::new(hash).map_err(|_| color_eyre::eyre::eyre!("Failed to parse hash"))?;
+
+ Argon2::default()
+ .verify_password(password.as_bytes(), &parsed_hash)
+ .map_err(|_| color_eyre::eyre::eyre!("Invalid password"))?;
+
+ Ok(())
+}
diff --git a/server/src/webauthn/types.rs b/server/src/webauthn/types.rs
index 28bf0a2..8602547 100644
--- a/server/src/webauthn/types.rs
+++ b/server/src/webauthn/types.rs
@@ -1,4 +1,7 @@
//! Types related to attestation (Registration)
+//!
+//! This file contains types copied from the `webauthn-rs` crate.
+#![allow(dead_code, unused_imports, clippy::enum_variant_names)]
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
@@ -114,6 +117,7 @@ pub struct RegisterPublicKeyCredential {
#[derive(Debug, Serialize, Clone, Copy, Deserialize, PartialEq, Eq, ToSchema)]
#[serde(rename_all = "camelCase")]
#[repr(u8)]
+#[allow(clippy::enum_variant_names)]
pub enum CredentialProtectionPolicy {
/// This reflects "FIDO_2_0" semantics. In this configuration, performing
/// some form of user verification is optional with or without credentialID
@@ -276,6 +280,7 @@ pub struct RegistrationExtensionsClientOutputs {
/// The result state of an extension as returned from the authenticator.
#[derive(Clone, Debug, Default, Serialize, Deserialize, ToSchema)]
+#[allow(dead_code)]
pub enum ExtnState
where
T: Clone + std::fmt::Debug + ToSchema,
@@ -296,6 +301,7 @@ where
/// The set of extensions that were registered by this credential.
#[derive(Clone, Debug, Default, Serialize, Deserialize, ToSchema)]
+#[allow(dead_code)]
pub struct RegisteredExtensions {
// ⚠️ It's critical we place serde default here so that we
// can deserialise in the future as we add new types!
@@ -315,6 +321,7 @@ pub struct RegisteredExtensions {
/// The set of extensions that were provided by the client during authentication
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
+#[allow(dead_code)]
pub struct AuthenticationExtensions {}
// Types that define options as to how an authenticator may interact with
@@ -599,6 +606,7 @@ pub struct AllowCredentials {
/// The data collected and hashed in the operation.
///
#[derive(Debug, Serialize, Clone, Deserialize, ToSchema)]
+#[allow(dead_code)]
pub struct CollectedClientData {
/// The credential type
#[serde(rename = "type")]
@@ -623,6 +631,7 @@ pub struct CollectedClientData {
/// Token binding
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
+#[allow(dead_code)]
pub struct TokenBinding {
/// status
pub status: String,