diff --git a/client/.env.development b/client/.env.development
index d99a62f9..5acc95e2 100644
--- a/client/.env.development
+++ b/client/.env.development
@@ -1,3 +1,3 @@
NEXTAUTH_URL=http://localhost:$PORT
NEXTAUTH_SECRET=WAzjpS46vFxp17TsRDU3FXo+TF0vrfy6uhCXwGMBUE8=
-NEXT_PUBLIC_API_URL=http://localhost:4000
\ No newline at end of file
+NEXT_PUBLIC_API_URL=https://dev.blue-carbon-cost-tool.dev-vizzuality.com/api
\ No newline at end of file
diff --git a/client/components.json b/client/components.json
new file mode 100644
index 00000000..42f059b5
--- /dev/null
+++ b/client/components.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "src/app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ }
+}
\ No newline at end of file
diff --git a/client/package.json b/client/package.json
index 99b18a77..9182da03 100644
--- a/client/package.json
+++ b/client/package.json
@@ -9,12 +9,22 @@
"lint": "next lint"
},
"dependencies": {
+ "@hookform/resolvers": "3.9.0",
+ "@radix-ui/react-icons": "1.3.0",
+ "@radix-ui/react-label": "2.1.0",
+ "@radix-ui/react-separator": "1.1.0",
+ "@radix-ui/react-slot": "1.1.0",
"@tanstack/react-query": "5.59.0",
"@ts-rest/react-query": "3.51.0",
+ "class-variance-authority": "0.7.0",
+ "clsx": "2.1.1",
+ "lucide-react": "0.447.0",
"next": "14.2.10",
"next-auth": "4.24.8",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "tailwind-merge": "2.5.3",
+ "tailwindcss-animate": "1.0.7"
},
"devDependencies": {
"@types/node": "catalog:",
@@ -26,7 +36,9 @@
"postcss": "^8",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.8",
+ "react-hook-form": "7.53.0",
"tailwindcss": "^3.4.1",
- "typescript": "catalog:"
+ "typescript": "catalog:",
+ "zod": "catalog:"
}
}
diff --git a/client/src/app/auth/api/[...nextauth]/config.ts b/client/src/app/auth/api/[...nextauth]/config.ts
index 6e23ccef..eb319c15 100644
--- a/client/src/app/auth/api/[...nextauth]/config.ts
+++ b/client/src/app/auth/api/[...nextauth]/config.ts
@@ -83,6 +83,7 @@ export const config = {
},
pages: {
signIn: "/auth/signin",
+ signOut: "/",
},
} as NextAuthOptions;
diff --git a/client/src/app/auth/signin/page.tsx b/client/src/app/auth/signin/page.tsx
new file mode 100644
index 00000000..76c7db1e
--- /dev/null
+++ b/client/src/app/auth/signin/page.tsx
@@ -0,0 +1,15 @@
+import { redirect } from "next/navigation";
+
+import { auth } from "@/app/auth/api/[...nextauth]/config";
+
+import SignIn from "@/containers/auth/signin";
+
+export default async function SignInPage() {
+ const session = await auth();
+
+ if (session) {
+ redirect("/profile");
+ }
+
+ return ;
+}
diff --git a/client/src/app/globals.css b/client/src/app/globals.css
index 13d40b89..1dcb0fc6 100644
--- a/client/src/app/globals.css
+++ b/client/src/app/globals.css
@@ -2,21 +2,7 @@
@tailwind components;
@tailwind utilities;
-:root {
- --background: #ffffff;
- --foreground: #171717;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
-}
-
body {
- color: var(--foreground);
- background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
@@ -25,3 +11,68 @@ body {
text-wrap: balance;
}
}
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 0 0% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 0 0% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 0 0% 3.9%;
+ --primary: 0 0% 9%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 0 0% 96.1%;
+ --secondary-foreground: 0 0% 9%;
+ --muted: 0 0% 96.1%;
+ --muted-foreground: 0 0% 45.1%;
+ --accent: 0 0% 96.1%;
+ --accent-foreground: 0 0% 9%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 89.8%;
+ --input: 0 0% 89.8%;
+ --ring: 0 0% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ }
+ .dark {
+ --background: 0 0% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 0 0% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 0 0% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 0 0% 9%;
+ --secondary: 0 0% 14.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 0 0% 14.9%;
+ --muted-foreground: 0 0% 63.9%;
+ --accent: 0 0% 14.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 0 0% 14.9%;
+ --input: 0 0% 14.9%;
+ --ring: 0 0% 83.1%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx
index 688cafd1..bc30c89d 100644
--- a/client/src/app/page.tsx
+++ b/client/src/app/page.tsx
@@ -1,101 +1,23 @@
-import Image from "next/image";
+import Link from "next/link";
-export default function Home() {
- return (
-
-
-
+import { auth } from "@/app/auth/api/[...nextauth]/config";
+
+import { Button } from "@/components/ui/button";
-
- -
- Placeholder for{" "}
-
- Blue Carbon Cost Tool
-
- .
-
-
+export default async function Home() {
+ const session = await auth();
+
+ return (
+
+
Welcome to Blue Carbon Cost
-
-
-
+
);
}
diff --git a/client/src/app/profile/page.tsx b/client/src/app/profile/page.tsx
new file mode 100644
index 00000000..58d524c3
--- /dev/null
+++ b/client/src/app/profile/page.tsx
@@ -0,0 +1,14 @@
+import { QueryClient, dehydrate } from "@tanstack/react-query";
+import { HydrationBoundary } from "@tanstack/react-query";
+
+import Profile from "@/containers/profile";
+
+export default async function ProfilePage() {
+ const queryClient = new QueryClient();
+
+ return (
+
+
+
+ );
+}
diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx
new file mode 100644
index 00000000..0d7d377d
--- /dev/null
+++ b/client/src/components/ui/button.tsx
@@ -0,0 +1,58 @@
+import * as React from "react";
+
+import { Slot } from "@radix-ui/react-slot";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ },
+);
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean;
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button";
+ return (
+
+ );
+ },
+);
+Button.displayName = "Button";
+
+export { Button, buttonVariants };
diff --git a/client/src/components/ui/form.tsx b/client/src/components/ui/form.tsx
new file mode 100644
index 00000000..d5fcbe2e
--- /dev/null
+++ b/client/src/components/ui/form.tsx
@@ -0,0 +1,182 @@
+"use client";
+
+import * as React from "react";
+
+import {
+ Controller,
+ ControllerProps,
+ FieldPath,
+ FieldValues,
+ FormProvider,
+ useFormContext,
+} from "react-hook-form";
+
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { Slot } from "@radix-ui/react-slot";
+
+import { cn } from "@/lib/utils";
+
+import { Label } from "@/components/ui/label";
+
+const Form = FormProvider;
+
+type FormFieldContextValue<
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
+> = {
+ name: TName;
+};
+
+const FormFieldContext = React.createContext(
+ {} as FormFieldContextValue,
+);
+
+const FormField = <
+ TFieldValues extends FieldValues = FieldValues,
+ TName extends FieldPath = FieldPath,
+>({
+ ...props
+}: ControllerProps) => {
+ return (
+
+
+
+ );
+};
+
+const useFormField = () => {
+ const fieldContext = React.useContext(FormFieldContext);
+ const itemContext = React.useContext(FormItemContext);
+ const { getFieldState, formState } = useFormContext();
+
+ const fieldState = getFieldState(fieldContext.name, formState);
+
+ if (!fieldContext) {
+ throw new Error("useFormField should be used within ");
+ }
+
+ const { id } = itemContext;
+
+ return {
+ id,
+ name: fieldContext.name,
+ formItemId: `${id}-form-item`,
+ formDescriptionId: `${id}-form-item-description`,
+ formMessageId: `${id}-form-item-message`,
+ ...fieldState,
+ };
+};
+
+type FormItemContextValue = {
+ id: string;
+};
+
+const FormItemContext = React.createContext(
+ {} as FormItemContextValue,
+);
+
+const FormItem = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const id = React.useId();
+
+ return (
+
+
+
+ );
+});
+FormItem.displayName = "FormItem";
+
+const FormLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => {
+ const { error, formItemId } = useFormField();
+
+ return (
+
+ );
+});
+FormLabel.displayName = "FormLabel";
+
+const FormControl = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ ...props }, ref) => {
+ const { error, formItemId, formDescriptionId, formMessageId } =
+ useFormField();
+
+ return (
+
+ );
+});
+FormControl.displayName = "FormControl";
+
+const FormDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => {
+ const { formDescriptionId } = useFormField();
+
+ return (
+
+ );
+});
+FormDescription.displayName = "FormDescription";
+
+const FormMessage = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, children, ...props }, ref) => {
+ const { error, formMessageId } = useFormField();
+ const body = error ? String(error?.message) : children;
+
+ if (!body) {
+ return null;
+ }
+
+ return (
+
+ {body}
+
+ );
+});
+FormMessage.displayName = "FormMessage";
+
+export {
+ useFormField,
+ Form,
+ FormItem,
+ FormLabel,
+ FormControl,
+ FormDescription,
+ FormMessage,
+ FormField,
+};
diff --git a/client/src/components/ui/input.tsx b/client/src/components/ui/input.tsx
new file mode 100644
index 00000000..ef29a4ac
--- /dev/null
+++ b/client/src/components/ui/input.tsx
@@ -0,0 +1,25 @@
+import * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+export interface InputProps
+ extends React.InputHTMLAttributes {}
+
+const Input = React.forwardRef(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ );
+ },
+);
+Input.displayName = "Input";
+
+export { Input };
diff --git a/client/src/components/ui/label.tsx b/client/src/components/ui/label.tsx
new file mode 100644
index 00000000..4d869af7
--- /dev/null
+++ b/client/src/components/ui/label.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import * as React from "react";
+
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { cva, type VariantProps } from "class-variance-authority";
+
+import { cn } from "@/lib/utils";
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
+);
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+));
+Label.displayName = LabelPrimitive.Root.displayName;
+
+export { Label };
diff --git a/client/src/containers/auth/signin/form/index.tsx b/client/src/containers/auth/signin/form/index.tsx
new file mode 100644
index 00000000..78feaa11
--- /dev/null
+++ b/client/src/containers/auth/signin/form/index.tsx
@@ -0,0 +1,116 @@
+"use client";
+
+import { FC, FormEvent, useCallback, useRef, useState } from "react";
+
+import { useForm } from "react-hook-form";
+
+import { useSearchParams, useRouter } from "next/navigation";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { LogInSchema } from "@shared/schemas/auth/login.schema";
+import { signIn } from "next-auth/react";
+import { z } from "zod";
+
+import { Button } from "@/components/ui/button";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+
+const SignInForm: FC = () => {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const [errorMessage, setErrorMessage] = useState("");
+ const formRef = useRef(null);
+ const form = useForm>({
+ resolver: zodResolver(LogInSchema),
+ defaultValues: {
+ email: "",
+ password: "",
+ },
+ });
+
+ const handleSignIn = useCallback(
+ (evt: FormEvent) => {
+ evt.preventDefault();
+ setErrorMessage(undefined);
+
+ form.handleSubmit(async (formValues) => {
+ try {
+ const response = await signIn("credentials", {
+ ...formValues,
+ redirect: false,
+ });
+
+ if (response?.ok) {
+ router.push(searchParams.get("callbackUrl") ?? "/profile");
+ }
+
+ if (!response?.ok) {
+ setErrorMessage(response?.error ?? "unknown error");
+ }
+ } catch (err) {
+ if (err instanceof Error) {
+ setErrorMessage(err.message ?? "unknown error");
+ }
+ }
+ })(evt);
+ },
+ [form, router, searchParams],
+ );
+
+ return (
+
+
+ );
+};
+
+export default SignInForm;
diff --git a/client/src/containers/auth/signin/index.tsx b/client/src/containers/auth/signin/index.tsx
new file mode 100644
index 00000000..4038eb1d
--- /dev/null
+++ b/client/src/containers/auth/signin/index.tsx
@@ -0,0 +1,27 @@
+import { FC } from "react";
+
+import Link from "next/link";
+
+import { Button } from "@/components/ui/button";
+
+import SignInForm from "./form";
+
+const SignIn: FC = () => {
+ return (
+
+
+
+
+ Welcome to Blue Carbon Cost
+
+
+
+
+
+
+ );
+};
+
+export default SignIn;
diff --git a/client/src/containers/profile/index.tsx b/client/src/containers/profile/index.tsx
new file mode 100644
index 00000000..88f4229b
--- /dev/null
+++ b/client/src/containers/profile/index.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import { signOut, useSession } from "next-auth/react";
+
+import { Button } from "@/components/ui/button";
+
+export default function Profile() {
+ const { data: session } = useSession();
+
+ return (
+
+
Welcome {session?.user?.name}
+
Email: {session?.user?.email}
+
role: {session?.user?.role}
+
+
+
+ );
+}
diff --git a/client/src/lib/queryClient.ts b/client/src/lib/queryClient.ts
index 15124781..1a743a4a 100644
--- a/client/src/lib/queryClient.ts
+++ b/client/src/lib/queryClient.ts
@@ -21,7 +21,7 @@ function makeQueryClient() {
const client = initQueryClient(router, {
validateResponse: true,
- baseUrl: "http://localhost:4000",
+ baseUrl: process.env.NEXT_PUBLIC_API_URL as string,
});
export { client, makeQueryClient };
diff --git a/client/src/lib/utils.ts b/client/src/lib/utils.ts
new file mode 100644
index 00000000..a5ef1935
--- /dev/null
+++ b/client/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/client/middleware.ts b/client/src/middleware.ts
similarity index 51%
rename from client/middleware.ts
rename to client/src/middleware.ts
index 3c52c3d8..300e8d8d 100644
--- a/client/middleware.ts
+++ b/client/src/middleware.ts
@@ -15,17 +15,3 @@ export default function middleware(req: NextRequestWithAuth) {
return NextResponse.next();
}
-
-export const config = {
- matcher: [
- /*
- * Match all request paths except for the ones starting with:
- * - api (API routes)
- * - _next/static (static files)
- * - _next/image (image optimization files)
- * - favicon.ico (favicon file)
- * - health (health check endpoint)
- */
- "/((?!api|_next/static|_next/image|favicon.ico|health).*)",
- ],
-};
diff --git a/client/tailwind.config.ts b/client/tailwind.config.ts
index 3d6668de..c65c631f 100644
--- a/client/tailwind.config.ts
+++ b/client/tailwind.config.ts
@@ -1,15 +1,62 @@
import type { Config } from "tailwindcss";
const config: Config = {
+ darkMode: ["class"],
content: ["./src/**/*.{ts,tsx}"],
theme: {
+ container: {
+ center: true,
+ },
extend: {
colors: {
- background: "var(--background)",
- foreground: "var(--foreground)",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ chart: {
+ "1": "hsl(var(--chart-1))",
+ "2": "hsl(var(--chart-2))",
+ "3": "hsl(var(--chart-3))",
+ "4": "hsl(var(--chart-4))",
+ "5": "hsl(var(--chart-5))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
},
},
},
- plugins: [],
+ plugins: [require("tailwindcss-animate")],
};
export default config;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b6ece7bf..a7d625bc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -191,12 +191,36 @@ importers:
client:
dependencies:
+ '@hookform/resolvers':
+ specifier: 3.9.0
+ version: 3.9.0(react-hook-form@7.53.0(react@18.3.1))
+ '@radix-ui/react-icons':
+ specifier: 1.3.0
+ version: 1.3.0(react@18.3.1)
+ '@radix-ui/react-label':
+ specifier: 2.1.0
+ version: 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)
+ '@radix-ui/react-separator':
+ specifier: 1.1.0
+ version: 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-slot':
+ specifier: 1.1.0
+ version: 1.1.0(@types/react@18.3.5)(react@18.3.1)
'@tanstack/react-query':
specifier: 5.59.0
version: 5.59.0(react@18.3.1)
'@ts-rest/react-query':
specifier: 3.51.0
version: 3.51.0(@tanstack/react-query@5.59.0(react@18.3.1))(@ts-rest/core@3.51.0(@types/node@20.14.2)(zod@3.23.8))(react@18.3.1)(zod@3.23.8)
+ class-variance-authority:
+ specifier: 0.7.0
+ version: 0.7.0
+ clsx:
+ specifier: 2.1.1
+ version: 2.1.1
+ lucide-react:
+ specifier: 0.447.0
+ version: 0.447.0(react@18.3.1)
next:
specifier: 14.2.10
version: 14.2.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -209,6 +233,12 @@ importers:
react-dom:
specifier: ^18
version: 18.3.1(react@18.3.1)
+ tailwind-merge:
+ specifier: 2.5.3
+ version: 2.5.3
+ tailwindcss-animate:
+ specifier: 1.0.7
+ version: 1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5)))
devDependencies:
'@types/node':
specifier: 'catalog:'
@@ -237,12 +267,18 @@ importers:
prettier-plugin-tailwindcss:
specifier: 0.6.8
version: 0.6.8(prettier@3.3.3)
+ react-hook-form:
+ specifier: 7.53.0
+ version: 7.53.0(react@18.3.1)
tailwindcss:
specifier: ^3.4.1
version: 3.4.10(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5))
typescript:
specifier: 'catalog:'
version: 5.4.5
+ zod:
+ specifier: 'catalog:'
+ version: 3.23.8
shared:
dependencies:
@@ -622,6 +658,11 @@ packages:
resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ '@hookform/resolvers@3.9.0':
+ resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==}
+ peerDependencies:
+ react-hook-form: ^7.0.0
+
'@humanwhocodes/config-array@0.11.14':
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'}
@@ -955,6 +996,68 @@ packages:
resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+ '@radix-ui/react-compose-refs@1.1.0':
+ resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ 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-label@2.1.0':
+ resolution: {integrity: sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==}
+ 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.0.0':
+ resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
+ 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.0':
+ resolution: {integrity: sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==}
+ 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.1.0':
+ resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
@@ -1850,6 +1953,9 @@ packages:
class-transformer@0.5.1:
resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==}
+ class-variance-authority@0.7.0:
+ resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==}
+
cli-cursor@3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
@@ -1889,6 +1995,14 @@ packages:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
+ clsx@2.0.0:
+ resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==}
+ engines: {node: '>=6'}
+
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
co@4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
@@ -3223,6 +3337,11 @@ packages:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
+ lucide-react@0.447.0:
+ resolution: {integrity: sha512-SZ//hQmvi+kDKrNepArVkYK7/jfeZ5uFNEnYmd45RKZcbGD78KLnrcNXmgeg6m+xNHFvTG+CblszXCy4n6DN4w==}
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
+
magic-string@0.30.8:
resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==}
engines: {node: '>=12'}
@@ -3877,6 +3996,12 @@ packages:
peerDependencies:
react: ^18.3.1
+ react-hook-form@7.53.0:
+ resolution: {integrity: sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^16.8.0 || ^17 || ^18 || ^19
+
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -4234,6 +4359,14 @@ packages:
resolution: {integrity: sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==}
engines: {node: ^14.18.0 || >=16.0.0}
+ tailwind-merge@2.5.3:
+ resolution: {integrity: sha512-d9ZolCAIzom1nf/5p4LdD5zvjmgSxY0BGgdSvmXIoMYAiPdAW/dSpP7joCDYFY7r/HkEa2qmPtkgsu0xjQeQtw==}
+
+ tailwindcss-animate@1.0.7:
+ resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders'
+
tailwindcss@3.4.10:
resolution: {integrity: sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==}
engines: {node: '>=14.0.0'}
@@ -5352,6 +5485,10 @@ snapshots:
'@eslint/js@8.57.0': {}
+ '@hookform/resolvers@3.9.0(react-hook-form@7.53.0(react@18.3.1))':
+ dependencies:
+ react-hook-form: 7.53.0(react@18.3.1)
+
'@humanwhocodes/config-array@0.11.14':
dependencies:
'@humanwhocodes/object-schema': 2.0.3
@@ -5792,6 +5929,50 @@ snapshots:
'@pkgr/core@0.1.1': {}
+ '@radix-ui/react-compose-refs@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.5
+
+ '@radix-ui/react-icons@1.3.0(react@18.3.1)':
+ dependencies:
+ react: 18.3.1
+
+ '@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)
+ 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-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)':
+ dependencies:
+ '@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-separator@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-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)
+ 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-slot@1.1.0(@types/react@18.3.5)(react@18.3.1)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ react: 18.3.1
+ optionalDependencies:
+ '@types/react': 18.3.5
+
'@rtsao/scc@1.1.0': {}
'@rushstack/eslint-patch@1.10.4': {}
@@ -6939,6 +7120,10 @@ snapshots:
class-transformer@0.5.1: {}
+ class-variance-authority@0.7.0:
+ dependencies:
+ clsx: 2.0.0
+
cli-cursor@3.1.0:
dependencies:
restore-cursor: 3.1.0
@@ -6980,6 +7165,10 @@ snapshots:
clone@1.0.4: {}
+ clsx@2.0.0: {}
+
+ clsx@2.1.1: {}
+
co@4.6.0: {}
codepage@1.15.0: {}
@@ -7365,8 +7554,8 @@ snapshots:
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.5)
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0)
- eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
+ eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0)
+ eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.0)
eslint-plugin-react: 7.35.2(eslint@8.57.0)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
@@ -7389,37 +7578,37 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0):
+ eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.3.6
enhanced-resolve: 5.17.1
eslint: 8.57.0
- eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
+ eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.8.0
is-bun-module: 1.1.0
is-glob: 4.0.3
optionalDependencies:
- eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
+ eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
- eslint-import-resolver-webpack
- supports-color
- eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
+ eslint-module-utils@2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.4.5)
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0)
+ eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
+ eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -7430,7 +7619,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
+ eslint-module-utils: 2.9.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0)(eslint@8.57.0))(eslint@8.57.0)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -8692,6 +8881,10 @@ snapshots:
dependencies:
yallist: 4.0.0
+ lucide-react@0.447.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
magic-string@0.30.8:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
@@ -9248,6 +9441,10 @@ snapshots:
react: 18.3.1
scheduler: 0.23.2
+ react-hook-form@7.53.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
react-is@16.13.1: {}
react-is@18.3.1: {}
@@ -9646,6 +9843,12 @@ snapshots:
'@pkgr/core': 0.1.1
tslib: 2.7.0
+ tailwind-merge@2.5.3: {}
+
+ tailwindcss-animate@1.0.7(tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5))):
+ dependencies:
+ tailwindcss: 3.4.10(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5))
+
tailwindcss@3.4.10(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5)):
dependencies:
'@alloc/quick-lru': 5.2.0