diff --git a/api/src/modules/users/users.controller.ts b/api/src/modules/users/users.controller.ts
index 25903819..50177fdc 100644
--- a/api/src/modules/users/users.controller.ts
+++ b/api/src/modules/users/users.controller.ts
@@ -53,7 +53,7 @@ export class UsersController {
async update(@GetUser() user: User): ControllerResponse {
return tsRestHandler(usersContract.updateMe, async ({ body }) => {
const updatedUser = await this.usersService.update(user.id, body);
- return { body: { data: updatedUser }, status: HttpStatus.CREATED };
+ return { body: { data: updatedUser }, status: HttpStatus.OK };
});
}
diff --git a/client/package.json b/client/package.json
index 47d69352..b8eecb63 100644
--- a/client/package.json
+++ b/client/package.json
@@ -11,6 +11,8 @@
"dependencies": {
"@hookform/resolvers": "3.9.0",
"@lukemorales/query-key-factory": "1.3.4",
+ "@radix-ui/react-alert-dialog": "1.1.2",
+ "@radix-ui/react-dialog": "1.1.2",
"@radix-ui/react-icons": "1.3.0",
"@radix-ui/react-label": "2.1.0",
"@radix-ui/react-separator": "1.1.0",
diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx
index ef90bd63..26d871d8 100644
--- a/client/src/app/layout.tsx
+++ b/client/src/app/layout.tsx
@@ -6,6 +6,8 @@ import { getServerSession } from "next-auth";
import { config } from "@/app/auth/api/[...nextauth]/config";
+import Toaster from "@/components/ui/toast/toaster";
+
import LayoutProviders from "./providers";
const inter = Inter({ subsets: ["latin"] });
@@ -27,6 +29,7 @@ export default async function RootLayout({
{children}
+
diff --git a/client/src/app/profile/page.tsx b/client/src/app/profile/page.tsx
index 58d524c3..fc9672f5 100644
--- a/client/src/app/profile/page.tsx
+++ b/client/src/app/profile/page.tsx
@@ -1,10 +1,26 @@
import { QueryClient, dehydrate } from "@tanstack/react-query";
import { HydrationBoundary } from "@tanstack/react-query";
+import { client } from "@/lib/query-client";
+import { queryKeys } from "@/lib/query-keys";
+
+import { auth } from "@/app/auth/api/[...nextauth]/config";
+
import Profile from "@/containers/profile";
export default async function ProfilePage() {
const queryClient = new QueryClient();
+ const session = await auth();
+
+ await queryClient.prefetchQuery({
+ queryKey: queryKeys.user.me(session?.user?.id as string).queryKey,
+ queryFn: () =>
+ client.user.findMe.query({
+ extraHeaders: {
+ authorization: `Bearer ${session?.accessToken as string}`,
+ },
+ }),
+ });
return (
diff --git a/client/src/components/ui/alert-dialog.tsx b/client/src/components/ui/alert-dialog.tsx
new file mode 100644
index 00000000..cfc54c49
--- /dev/null
+++ b/client/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,143 @@
+"use client";
+
+import * as React from "react";
+
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
+
+import { cn } from "@/lib/utils";
+
+import { buttonVariants } from "@/components/ui/button";
+
+const AlertDialog = AlertDialogPrimitive.Root;
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal;
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
+
+const AlertDialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+));
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
+
+const AlertDialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+AlertDialogHeader.displayName = "AlertDialogHeader";
+
+const AlertDialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+AlertDialogFooter.displayName = "AlertDialogFooter";
+
+const AlertDialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
+
+const AlertDialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogDescription.displayName =
+ AlertDialogPrimitive.Description.displayName;
+
+const AlertDialogAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
+
+const AlertDialogCancel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+};
diff --git a/client/src/components/ui/dialog.tsx b/client/src/components/ui/dialog.tsx
new file mode 100644
index 00000000..d9d425a7
--- /dev/null
+++ b/client/src/components/ui/dialog.tsx
@@ -0,0 +1,123 @@
+"use client";
+
+import * as React from "react";
+
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { Cross2Icon } from "@radix-ui/react-icons";
+
+import { cn } from "@/lib/utils";
+
+const Dialog = DialogPrimitive.Root;
+
+const DialogTrigger = DialogPrimitive.Trigger;
+
+const DialogPortal = DialogPrimitive.Portal;
+
+const DialogClose = DialogPrimitive.Close;
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+));
+DialogContent.displayName = DialogPrimitive.Content.displayName;
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+DialogHeader.displayName = "DialogHeader";
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+DialogFooter.displayName = "DialogFooter";
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DialogTitle.displayName = DialogPrimitive.Title.displayName;
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DialogDescription.displayName = DialogPrimitive.Description.displayName;
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogTrigger,
+ DialogClose,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+};
diff --git a/client/src/components/ui/toast/toaster.tsx b/client/src/components/ui/toast/toaster.tsx
index fd10fa6b..95056ad0 100644
--- a/client/src/components/ui/toast/toaster.tsx
+++ b/client/src/components/ui/toast/toaster.tsx
@@ -10,7 +10,7 @@ import {
} from "@/components/ui/toast";
import { useToast } from "@/components/ui/toast/use-toast";
-export function Toaster() {
+export default function Toaster() {
const { toasts } = useToast();
return (
diff --git a/client/src/containers/auth/signup/form/action.ts b/client/src/containers/auth/signup/form/action.ts
index 0f7f160c..4e613bdb 100644
--- a/client/src/containers/auth/signup/form/action.ts
+++ b/client/src/containers/auth/signup/form/action.ts
@@ -19,7 +19,7 @@ export async function signUpAction(
if (!parsed.success) {
return {
ok: false,
- message: "Invalid form data",
+ message: "Invalid update-email data",
};
}
diff --git a/client/src/containers/profile/account-details/index.tsx b/client/src/containers/profile/account-details/index.tsx
new file mode 100644
index 00000000..f8a421ca
--- /dev/null
+++ b/client/src/containers/profile/account-details/index.tsx
@@ -0,0 +1,171 @@
+"use client";
+
+import { FC, KeyboardEvent, useCallback, useRef } from "react";
+
+import { useForm } from "react-hook-form";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useQueryClient } from "@tanstack/react-query";
+import { useSession } from "next-auth/react";
+import { z } from "zod";
+
+import { client } from "@/lib/query-client";
+import { queryKeys } from "@/lib/query-keys";
+
+import { Button } from "@/components/ui/button";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { useToast } from "@/components/ui/toast/use-toast";
+
+import { accountDetailsSchema } from "./schema";
+
+const UpdateEmailForm: FC = () => {
+ const queryClient = useQueryClient();
+ const { data: session, update: updateSession } = useSession();
+ const formRef = useRef(null);
+ const { toast } = useToast();
+
+ const { data: user } = client.user.findMe.useQuery(
+ queryKeys.user.me(session?.user?.id as string).queryKey,
+ {
+ extraHeaders: {
+ authorization: `Bearer ${session?.accessToken as string}`,
+ },
+ },
+ {
+ select: (data) => data.body.data,
+ },
+ );
+
+ const form = useForm>({
+ resolver: zodResolver(accountDetailsSchema),
+ defaultValues: {
+ name: user?.name,
+ role: user?.role,
+ },
+ mode: "onSubmit",
+ });
+
+ const onSubmit = useCallback(
+ async (data: FormData) => {
+ const formData = Object.fromEntries(data);
+ const parsed = accountDetailsSchema.safeParse(formData);
+
+ if (parsed.success) {
+ // todo: update method
+ const response = await client.user.updateMe.mutation({
+ params: {
+ id: session?.user?.id as string,
+ },
+ body: {
+ name: parsed.data.name,
+ },
+ extraHeaders: {
+ authorization: `Bearer ${session?.accessToken as string}`,
+ },
+ });
+
+ if (response.status === 200) {
+ updateSession(response.body);
+
+ queryClient.invalidateQueries({
+ queryKey: queryKeys.user.me(session?.user?.id as string).queryKey,
+ });
+
+ toast({
+ description: "Your account details have been updated successfully.",
+ });
+ }
+ }
+ },
+ [queryClient, session, updateSession, toast],
+ );
+
+ const handleEnterKey = useCallback(
+ (evt: KeyboardEvent) => {
+ if (evt.code === "Enter" && form.formState.isValid) {
+ form.handleSubmit(() => {
+ onSubmit(new FormData(formRef.current!));
+ })();
+ }
+ },
+ [form, onSubmit],
+ );
+
+ return (
+
+
+ );
+};
+
+export default UpdateEmailForm;
diff --git a/client/src/containers/profile/account-details/schema.ts b/client/src/containers/profile/account-details/schema.ts
new file mode 100644
index 00000000..996e9296
--- /dev/null
+++ b/client/src/containers/profile/account-details/schema.ts
@@ -0,0 +1,9 @@
+import { ROLES } from "@shared/entities/users/roles.enum";
+import { UpdateUserSchema } from "@shared/schemas/users/update-user.schema";
+import { z } from "zod";
+
+export const accountDetailsSchema = UpdateUserSchema.and(
+ z.object({
+ role: z.enum([ROLES.ADMIN, ROLES.PARTNER, ROLES.GENERAL_USER]).optional(),
+ }),
+);
diff --git a/client/src/containers/profile/delete-account/index.tsx b/client/src/containers/profile/delete-account/index.tsx
new file mode 100644
index 00000000..7539303b
--- /dev/null
+++ b/client/src/containers/profile/delete-account/index.tsx
@@ -0,0 +1,96 @@
+"use client";
+
+import { FC, useCallback } from "react";
+
+import { Trash2Icon } from "lucide-react";
+import { signOut, useSession } from "next-auth/react";
+
+import { client } from "@/lib/query-client";
+
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog";
+import { Button } from "@/components/ui/button";
+import { useToast } from "@/components/ui/toast/use-toast";
+
+const DeleteAccount: FC = () => {
+ const { data: session } = useSession();
+ const { toast } = useToast();
+
+ const onDeleteAccount = useCallback(async () => {
+ try {
+ const { status, body } = await client.user.deleteMe.mutation({
+ extraHeaders: {
+ authorization: `Bearer ${session?.accessToken as string}`,
+ },
+ });
+
+ if (status === 200) {
+ signOut({ callbackUrl: "/auth/signin" });
+ }
+
+ if (status === 400 || status === 401) {
+ toast({
+ variant: "destructive",
+ description: body.errors?.[0].title,
+ });
+ }
+ } catch (e) {
+ toast({
+ variant: "destructive",
+ description: "Something went wrong deleting the account.",
+ });
+ }
+ }, [session?.accessToken, toast]);
+
+ return (
+
+
+
+
+ Delete account
+
+
+
+
+
+ Account deletion
+
+ This action cannot be undone. This will permanently delete your
+ account and remove your data from our servers.
+
+
+
+
+ Cancel
+
+
+
+ Delete account
+
+
+
+
+
+
+ );
+};
+
+export default DeleteAccount;
diff --git a/client/src/containers/profile/edit-password/form/index.tsx b/client/src/containers/profile/edit-password/form/index.tsx
new file mode 100644
index 00000000..db4fb397
--- /dev/null
+++ b/client/src/containers/profile/edit-password/form/index.tsx
@@ -0,0 +1,182 @@
+"use client";
+
+import { FC, useCallback, useRef } from "react";
+
+import { useForm } from "react-hook-form";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useSession } from "next-auth/react";
+import { z } from "zod";
+
+import { client } from "@/lib/query-client";
+
+import { Button } from "@/components/ui/button";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+ FormDescription,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { useToast } from "@/components/ui/toast/use-toast";
+
+import { changePasswordSchema } from "./schema";
+
+const SignUpForm: FC = () => {
+ const formRef = useRef(null);
+ const { data: session } = useSession();
+ const { toast } = useToast();
+
+ const form = useForm>({
+ resolver: zodResolver(changePasswordSchema),
+ defaultValues: {
+ password: "",
+ newPassword: "",
+ confirmPassword: "",
+ },
+ mode: "onSubmit",
+ });
+
+ const onSubmit = useCallback(
+ async (data: FormData) => {
+ const formData = Object.fromEntries(data);
+ const parsed = changePasswordSchema.safeParse(formData);
+ if (parsed.success) {
+ try {
+ const response = await client.user.updatePassword.mutation({
+ body: {
+ password: parsed.data.password,
+ newPassword: parsed.data.newPassword,
+ },
+ extraHeaders: {
+ authorization: `Bearer ${session?.accessToken as string}`,
+ },
+ });
+
+ if (response.status === 200) {
+ toast({
+ description: "Your password has been updated successfully.",
+ });
+ }
+
+ if (response.status === 400 || response.status === 401) {
+ toast({
+ variant: "destructive",
+ description: response.body.errors?.[0].title,
+ });
+ }
+ } catch (e) {
+ toast({
+ variant: "destructive",
+ description: "Something went wrong updating the password",
+ });
+ }
+ }
+ },
+ [session, toast],
+ );
+
+ return (
+
+
+ );
+};
+
+export default SignUpForm;
diff --git a/client/src/containers/profile/edit-password/form/schema.ts b/client/src/containers/profile/edit-password/form/schema.ts
new file mode 100644
index 00000000..ab47a3a8
--- /dev/null
+++ b/client/src/containers/profile/edit-password/form/schema.ts
@@ -0,0 +1,11 @@
+import { UpdateUserPasswordSchema } from "@shared/schemas/users/update-password.schema";
+import { z } from "zod";
+
+export const changePasswordSchema = UpdateUserPasswordSchema.and(
+ z.object({
+ confirmPassword: UpdateUserPasswordSchema.shape.newPassword,
+ }),
+).refine((data) => data.newPassword === data.confirmPassword, {
+ message: "Passwords must match",
+ path: ["confirmPassword"],
+});
diff --git a/client/src/containers/profile/edit-password/index.tsx b/client/src/containers/profile/edit-password/index.tsx
new file mode 100644
index 00000000..4141b657
--- /dev/null
+++ b/client/src/containers/profile/edit-password/index.tsx
@@ -0,0 +1,9 @@
+import { FC } from "react";
+
+import EditPasswordForm from "./form";
+
+const EditPassword: FC = () => {
+ return ;
+};
+
+export default EditPassword;
diff --git a/client/src/containers/profile/index.tsx b/client/src/containers/profile/index.tsx
index 88f4229b..f70c1f0c 100644
--- a/client/src/containers/profile/index.tsx
+++ b/client/src/containers/profile/index.tsx
@@ -1,17 +1,27 @@
"use client";
-import { signOut, useSession } from "next-auth/react";
+import { signOut } from "next-auth/react";
+
+import AccountDetails from "@/containers/profile/account-details";
+import EditPassword from "@/containers/profile/edit-password";
+import UpdateEmail from "@/containers/profile/update-email";
import { Button } from "@/components/ui/button";
+import DeleteAccount from "src/containers/profile/delete-account";
export default function Profile() {
- const { data: session } = useSession();
-
return (
-
-
Welcome {session?.user?.name}
-
Email: {session?.user?.email}
-
role: {session?.user?.role}
+
+
{
+ const queryClient = useQueryClient();
+ const { data: session, update: updateSession } = useSession();
+ const formRef = useRef(null);
+ const { toast } = useToast();
+
+ const { data: user } = client.user.findMe.useQuery(
+ queryKeys.user.me(session?.user?.id as string).queryKey,
+ {
+ extraHeaders: {
+ authorization: `Bearer ${session?.accessToken as string}`,
+ },
+ },
+ {
+ select: (data) => data.body.data,
+ },
+ );
+
+ const form = useForm>({
+ resolver: zodResolver(accountDetailsSchema),
+ defaultValues: {
+ email: user?.email,
+ },
+ mode: "onSubmit",
+ });
+
+ const onSubmit = useCallback(
+ async (data: FormData) => {
+ const formData = Object.fromEntries(data);
+ const parsed = accountDetailsSchema.safeParse(formData);
+
+ if (parsed.success) {
+ // todo: update method
+ const response = await client.user.updateUser.mutation({
+ params: {
+ id: session?.user?.id as string,
+ },
+ body: {
+ email: parsed.data.email,
+ },
+ extraHeaders: {
+ authorization: `Bearer ${session?.accessToken as string}`,
+ },
+ });
+
+ if (response.status === 200) {
+ updateSession(response.body);
+
+ queryClient.invalidateQueries({
+ queryKey: queryKeys.user.me(session?.user?.id as string).queryKey,
+ });
+
+ toast({
+ description: "Your email has been updated successfully.",
+ });
+ }
+ }
+ },
+ [queryClient, session, updateSession, toast],
+ );
+
+ const handleEnterKey = useCallback(
+ (evt: KeyboardEvent) => {
+ if (evt.code === "Enter" && form.formState.isValid) {
+ form.handleSubmit(() => {
+ onSubmit(new FormData(formRef.current!));
+ })();
+ }
+ },
+ [form, onSubmit],
+ );
+
+ return (
+
+
+ );
+};
+
+export default UpdateEmailForm;
diff --git a/client/src/containers/profile/update-email/schema.ts b/client/src/containers/profile/update-email/schema.ts
new file mode 100644
index 00000000..9985ddb7
--- /dev/null
+++ b/client/src/containers/profile/update-email/schema.ts
@@ -0,0 +1,9 @@
+import { z } from "zod";
+
+// todo: use the one in shared/dtos/users/request-email-update
+export const accountDetailsSchema = z.object({
+ email: z
+ .string({ message: "Email is required" })
+ .min(1, "Email is required")
+ .email("Invalid email"),
+});
diff --git a/client/src/lib/query-keys.ts b/client/src/lib/query-keys.ts
index 3dc8a995..429a7981 100644
--- a/client/src/lib/query-keys.ts
+++ b/client/src/lib/query-keys.ts
@@ -6,4 +6,8 @@ import {
export const authKeys = createQueryKeys("auth", {
resetPasswordToken: (token: string) => ["reset-password-token", token],
});
-export const queryKeys = mergeQueryKeys(authKeys);
+
+export const userKeys = createQueryKeys("user", {
+ me: (token: string) => ["me", token],
+});
+export const queryKeys = mergeQueryKeys(authKeys, userKeys);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6ac66a86..ae532446 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -273,6 +273,12 @@ importers:
'@lukemorales/query-key-factory':
specifier: 1.3.4
version: 1.3.4(@tanstack/query-core@5.59.0)(@tanstack/react-query@5.59.0(react@18.3.1))
+ '@radix-ui/react-alert-dialog':
+ specifier: 1.1.2
+ version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-dialog':
+ specifier: 1.1.2
+ version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-icons':
specifier: 1.3.0
version: 1.3.0(react@18.3.1)
@@ -1936,6 +1942,19 @@ packages:
'@radix-ui/primitive@1.1.0':
resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==}
+ '@radix-ui/react-alert-dialog@1.1.2':
+ resolution: {integrity: sha512-eGSlLzPhKO+TErxkiGcCZGuvbVMnLA1MTnyBksGOeGRGkxHiiJUujsjmNTdWTm4iHVSRaUao9/4Ur671auMghQ==}
+ 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-collection@1.1.0':
resolution: {integrity: sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==}
peerDependencies:
@@ -1976,6 +1995,19 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-dialog@1.1.2':
+ resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==}
+ 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-dismissable-layer@1.1.1':
resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==}
peerDependencies:
@@ -1989,11 +2021,42 @@ packages:
'@types/react-dom':
optional: true
+ '@radix-ui/react-focus-guards@1.1.1':
+ resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-focus-scope@1.1.0':
+ resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==}
+ 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-icons@1.3.0':
resolution: {integrity: sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==}
peerDependencies:
react: ^16.x || ^17.x || ^18.x
+ '@radix-ui/react-id@1.1.0':
+ resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@radix-ui/react-label@2.1.0':
resolution: {integrity: sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==}
peerDependencies:
@@ -3234,6 +3297,10 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ aria-hidden@1.2.4:
+ resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
+ engines: {node: '>=10'}
+
aria-query@5.1.3:
resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
@@ -3846,6 +3913,9 @@ packages:
resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
engines: {node: '>=8'}
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
dezalgo@1.0.4:
resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==}
@@ -4363,6 +4433,10 @@ packages:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
get-package-type@0.1.0:
resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
engines: {node: '>=8.0.0'}
@@ -4566,6 +4640,9 @@ packages:
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
engines: {node: '>= 0.4'}
+ invariant@2.2.4:
+ resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
+
ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
@@ -6044,6 +6121,26 @@ packages:
redux:
optional: true
+ react-remove-scroll-bar@2.3.6:
+ resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.6.0:
+ resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
react-router-dom@6.26.2:
resolution: {integrity: sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==}
engines: {node: '>=14.0.0'}
@@ -6063,6 +6160,16 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+ react-style-singleton@2.2.1:
+ resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
react-text-mask@5.5.0:
resolution: {integrity: sha512-SLJlJQxa0uonMXsnXRpv5abIepGmHz77ylQcra0GNd7Jtk4Wj2Mtp85uGQHv1avba2uI8ZvRpIEQPpJKsqRGYw==}
peerDependencies:
@@ -6870,6 +6977,16 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ use-callback-ref@1.3.2:
+ resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
use-isomorphic-layout-effect@1.1.2:
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
peerDependencies:
@@ -6884,6 +7001,16 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ use-sidecar@1.1.2:
+ resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
use-sync-external-store@1.2.2:
resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==}
peerDependencies:
@@ -9205,6 +9332,20 @@ snapshots:
'@radix-ui/primitive@1.1.0': {}
+ '@radix-ui/react-alert-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.0
+ '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.5)(react@18.3.1)
+ '@radix-ui/react-dialog': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.5
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-collection@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
@@ -9235,6 +9376,28 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.5
+ '@radix-ui/react-dialog@1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.0
+ '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ '@radix-ui/react-context': 1.1.1(@types/react@18.3.5)(react@18.3.1)
+ '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.5)(react@18.3.1)
+ '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-id': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ '@radix-ui/react-portal': 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-slot': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ aria-hidden: 1.2.4
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ react-remove-scroll: 2.6.0(@types/react@18.3.5)(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.5
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.0
@@ -9248,10 +9411,34 @@ snapshots:
'@types/react': 18.3.5
'@types/react-dom': 18.3.0
+ '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.5)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.5
+
+ '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.5
+ '@types/react-dom': 18.3.0
+
'@radix-ui/react-icons@1.3.0(react@18.3.1)':
dependencies:
react: 18.3.1
+ '@radix-ui/react-id@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.5
+
'@radix-ui/react-label@2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -10683,6 +10870,10 @@ snapshots:
argparse@2.0.1: {}
+ aria-hidden@1.2.4:
+ dependencies:
+ tslib: 2.7.0
+
aria-query@5.1.3:
dependencies:
deep-equal: 2.2.3
@@ -11405,6 +11596,8 @@ snapshots:
detect-newline@3.1.0: {}
+ detect-node-es@1.1.0: {}
+
dezalgo@1.0.4:
dependencies:
asap: 2.0.6
@@ -12205,6 +12398,8 @@ snapshots:
has-symbols: 1.0.3
hasown: 2.0.2
+ get-nonce@1.0.1: {}
+
get-package-type@0.1.0: {}
get-stream@6.0.1: {}
@@ -12448,6 +12643,10 @@ snapshots:
hasown: 2.0.2
side-channel: 1.0.6
+ invariant@2.2.4:
+ dependencies:
+ loose-envify: 1.4.0
+
ipaddr.js@1.9.1: {}
is-arguments@1.1.1:
@@ -14057,6 +14256,25 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
redux: 4.2.1
+ react-remove-scroll-bar@2.3.6(@types/react@18.3.5)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-style-singleton: 2.2.1(@types/react@18.3.5)(react@18.3.1)
+ tslib: 2.7.0
+ optionalDependencies:
+ '@types/react': 18.3.5
+
+ react-remove-scroll@2.6.0(@types/react@18.3.5)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-remove-scroll-bar: 2.3.6(@types/react@18.3.5)(react@18.3.1)
+ react-style-singleton: 2.2.1(@types/react@18.3.5)(react@18.3.1)
+ tslib: 2.7.0
+ use-callback-ref: 1.3.2(@types/react@18.3.5)(react@18.3.1)
+ use-sidecar: 1.1.2(@types/react@18.3.5)(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.5
+
react-router-dom@6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@remix-run/router': 1.19.2
@@ -14086,6 +14304,15 @@ snapshots:
- '@types/react'
- supports-color
+ react-style-singleton@2.2.1(@types/react@18.3.5)(react@18.3.1):
+ dependencies:
+ get-nonce: 1.0.1
+ invariant: 2.2.4
+ react: 18.3.1
+ tslib: 2.7.0
+ optionalDependencies:
+ '@types/react': 18.3.5
+
react-text-mask@5.5.0(react@18.3.1):
dependencies:
prop-types: 15.8.1
@@ -15018,6 +15245,13 @@ snapshots:
dependencies:
punycode: 2.3.1
+ use-callback-ref@1.3.2(@types/react@18.3.5)(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ tslib: 2.7.0
+ optionalDependencies:
+ '@types/react': 18.3.5
+
use-isomorphic-layout-effect@1.1.2(@types/react@18.3.5)(react@18.3.1):
dependencies:
react: 18.3.1
@@ -15028,6 +15262,14 @@ snapshots:
dependencies:
react: 18.3.1
+ use-sidecar@1.1.2(@types/react@18.3.5)(react@18.3.1):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 18.3.1
+ tslib: 2.7.0
+ optionalDependencies:
+ '@types/react': 18.3.5
+
use-sync-external-store@1.2.2(react@18.3.1):
dependencies:
react: 18.3.1