mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 04:21:52 +00:00
chore: WIP
This commit is contained in:
+5
-3
@@ -2,9 +2,7 @@
|
||||
|
||||
import { AuthError } from "next-auth";
|
||||
|
||||
import { signIn } from "@/auth.config";
|
||||
|
||||
// ...
|
||||
import { signIn, signOut } from "@/auth.config";
|
||||
|
||||
export async function authenticate(
|
||||
prevState: string | undefined,
|
||||
@@ -26,3 +24,7 @@ export async function authenticate(
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function logOut() {
|
||||
await signOut();
|
||||
}
|
||||
|
||||
+16
-3
@@ -1,3 +1,4 @@
|
||||
import bcryptjs from "bcryptjs";
|
||||
import NextAuth, { type NextAuthConfig } from "next-auth";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { z } from "zod";
|
||||
@@ -17,9 +18,21 @@ export const authConfig = {
|
||||
if (!parsedCredentials.success) return null;
|
||||
|
||||
const { email, password } = parsedCredentials.data;
|
||||
console.log("AuthConfig.ts");
|
||||
console.log({ email, password });
|
||||
return null;
|
||||
console.log({ email, password }, "from AuthConfig.ts");
|
||||
|
||||
// Check the user using the email
|
||||
|
||||
// const user = await getUser(email);
|
||||
// if (!user) return null;
|
||||
|
||||
// Compare passwords
|
||||
|
||||
// if (!bcryptjs.compareSync(password, user.password)) return null;
|
||||
|
||||
// Return the user object without the password field
|
||||
|
||||
// const { password: _, ...rest } = user;
|
||||
// return rest;
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
+109
-47
@@ -1,23 +1,47 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Button, Checkbox, Divider, Input, Link } from "@nextui-org/react";
|
||||
import { useState } from "react";
|
||||
import { useFormState } from "react-dom";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { authenticate } from "@/actions";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { authFormSchema } from "@/types";
|
||||
|
||||
import { ProwlerExtended } from "../icons";
|
||||
import { ThemeSwitch } from "../ThemeSwitch";
|
||||
import { CustomInput } from "../ui/custom";
|
||||
import { AuthButton } from "./AuthButton";
|
||||
|
||||
export const AuthForm = ({ type }: { type: string }) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [user, setUser] = useState(null);
|
||||
|
||||
const [state, dispath] = useFormState(authenticate, undefined);
|
||||
console.log(state);
|
||||
|
||||
const toggleVisibility = () => setIsVisible(!isVisible);
|
||||
const form = useForm<z.infer<typeof authFormSchema>>({
|
||||
resolver: zodResolver(authFormSchema),
|
||||
defaultValues: {
|
||||
username: "",
|
||||
password: "",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = (values: z.infer<typeof authFormSchema>) => {
|
||||
// Do something with the form values
|
||||
// this will be type-safe and validated
|
||||
console.log(values);
|
||||
};
|
||||
|
||||
console.log(state);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -39,61 +63,98 @@ export const AuthForm = ({ type }: { type: string }) => {
|
||||
|
||||
{/* Testimonial */}
|
||||
<div className="absolute bottom-10 right-10 hidden md:block">
|
||||
<p className="max-w-xl text-right text-white/60">
|
||||
<p className="max-w-xl text-right text-white/60 text-md">
|
||||
<span className="font-medium">“</span>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eget
|
||||
augue nec massa volutpat aliquet.
|
||||
Open Cloud Security
|
||||
<span className="font-medium">”</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Login Form */}
|
||||
<div className="flex w-full max-w-sm flex-col gap-4 rounded-large bg-content1 px-8 pb-10 pt-6 shadow-small">
|
||||
<p className="pb-2 text-xl font-medium">
|
||||
{type === "sign-in" ? "Sign In" : "Sign Up"}
|
||||
</p>
|
||||
<form className="flex flex-col gap-3" action={dispath}>
|
||||
<Input
|
||||
label="Email Address"
|
||||
name="email"
|
||||
placeholder="Enter your email"
|
||||
type="email"
|
||||
variant="bordered"
|
||||
/>
|
||||
<Input
|
||||
endContent={
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
{isVisible ? (
|
||||
<Icon
|
||||
className="pointer-events-none text-2xl text-default-400"
|
||||
icon="solar:eye-closed-linear"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
className="pointer-events-none text-2xl text-default-400"
|
||||
icon="solar:eye-bold"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
}
|
||||
label="Password"
|
||||
name="password"
|
||||
placeholder="Enter your password"
|
||||
type={isVisible ? "text" : "password"}
|
||||
variant="bordered"
|
||||
/>
|
||||
<div className="flex items-center justify-between px-1 py-2">
|
||||
<Checkbox name="remember" size="sm">
|
||||
Remember me
|
||||
</Checkbox>
|
||||
<Link className="text-default-500" href="#">
|
||||
Forgot password?
|
||||
</Link>
|
||||
</div>
|
||||
<AuthButton type={type} />
|
||||
</form>
|
||||
{/* Sign UP Form */}
|
||||
{type === "sign-up" && (
|
||||
<Form {...form}>
|
||||
<form
|
||||
className="flex flex-col gap-3"
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
>
|
||||
<CustomInput
|
||||
control={form.control}
|
||||
name="username"
|
||||
type="text"
|
||||
label="Username"
|
||||
placeholder="Enter your username"
|
||||
/>
|
||||
<CustomInput
|
||||
control={form.control}
|
||||
name="email"
|
||||
type="email"
|
||||
label="Email"
|
||||
placeholder="Enter your email"
|
||||
/>
|
||||
<CustomInput control={form.control} password />
|
||||
{/* <Input
|
||||
isRequired
|
||||
endContent={
|
||||
<button type="button" onClick={toggleConfirmVisibility}>
|
||||
{isConfirmVisible ? (
|
||||
<Icon
|
||||
className="pointer-events-none text-2xl text-default-400"
|
||||
icon="solar:eye-closed-linear"
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
className="pointer-events-none text-2xl text-default-400"
|
||||
icon="solar:eye-bold"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
}
|
||||
label="Confirm Password"
|
||||
name="confirmPassword"
|
||||
placeholder="Confirm your password"
|
||||
type={isConfirmVisible ? "text" : "password"}
|
||||
variant="bordered"
|
||||
/> */}
|
||||
<Checkbox isRequired className="py-4" size="sm">
|
||||
I agree with the
|
||||
<Link href="#" size="sm">
|
||||
Terms
|
||||
</Link>
|
||||
and
|
||||
<Link href="#" size="sm">
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</Checkbox>
|
||||
<AuthButton type={type} />
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
{/* Sign IN Form */}
|
||||
{type === "sign-in" && (
|
||||
<>
|
||||
<form className="flex flex-col gap-3" action={dispath}>
|
||||
<Input
|
||||
label="Email Address"
|
||||
name="email"
|
||||
placeholder="Enter your email"
|
||||
type="email"
|
||||
variant="bordered"
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between px-1 py-2">
|
||||
<Checkbox name="remember" size="sm">
|
||||
Remember me
|
||||
</Checkbox>
|
||||
<Link className="text-default-500" href="#">
|
||||
Forgot password?
|
||||
</Link>
|
||||
</div>
|
||||
<AuthButton type={type} />
|
||||
</form>
|
||||
<div className="flex items-center gap-4 py-2">
|
||||
<Divider className="flex-1" />
|
||||
<p className="shrink-0 text-tiny text-default-500">OR</p>
|
||||
@@ -123,6 +184,7 @@ export const AuthForm = ({ type }: { type: string }) => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === "sign-in" ? (
|
||||
<p className="text-center text-small">
|
||||
Need to create an account?
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
"use client";
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import { Input } from "@nextui-org/react";
|
||||
import React, { useState } from "react";
|
||||
import { Control, FieldPath } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { FormControl, FormField, FormMessage } from "@/components/ui/form";
|
||||
import { authFormSchema } from "@/types";
|
||||
|
||||
type CustomInputProps =
|
||||
| {
|
||||
control: Control<z.infer<typeof authFormSchema>>;
|
||||
name: FieldPath<z.infer<typeof authFormSchema>>;
|
||||
password?: false;
|
||||
label: string;
|
||||
type: "text" | "email";
|
||||
placeholder: string;
|
||||
isRequired?: boolean;
|
||||
}
|
||||
| {
|
||||
control: Control<z.infer<typeof authFormSchema>>;
|
||||
password: true;
|
||||
name?: never;
|
||||
label?: never;
|
||||
type?: never;
|
||||
placeholder?: never;
|
||||
isRequired?: never;
|
||||
};
|
||||
|
||||
export const CustomInput = ({
|
||||
control,
|
||||
name,
|
||||
type,
|
||||
label,
|
||||
placeholder,
|
||||
password = false,
|
||||
isRequired = true,
|
||||
}: CustomInputProps) => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const toggleVisibility = () => setIsVisible(!isVisible);
|
||||
|
||||
const inputProps = password
|
||||
? {
|
||||
name: "password",
|
||||
label: "Password",
|
||||
type: isVisible ? "text" : "password",
|
||||
placeholder: "Enter your password",
|
||||
isRequired: true,
|
||||
}
|
||||
: { name, label, type, placeholder, isRequired };
|
||||
|
||||
return (
|
||||
<FormField
|
||||
control={control}
|
||||
name={inputProps.name}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<FormControl>
|
||||
<Input
|
||||
isRequired={inputProps.isRequired}
|
||||
label={inputProps.label}
|
||||
placeholder={inputProps.placeholder}
|
||||
type={inputProps.type}
|
||||
variant="bordered"
|
||||
{...field}
|
||||
endContent={
|
||||
password && (
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
<Icon
|
||||
className="pointer-events-none text-2xl text-default-400"
|
||||
icon={
|
||||
isVisible ? "solar:eye-closed-linear" : "solar:eye-bold"
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./CustomBox";
|
||||
export * from "./CustomInput";
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
"use client";
|
||||
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import * as React from "react";
|
||||
import {
|
||||
Controller,
|
||||
ControllerProps,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from "react-hook-form";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Label } from "./label";
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue,
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
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 <FormField>");
|
||||
}
|
||||
|
||||
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<FormItemContextValue>(
|
||||
{} as FormItemContextValue,
|
||||
);
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
});
|
||||
FormItem.displayName = "FormItem";
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "text-red-500 dark:text-red-900", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormLabel.displayName = "FormLabel";
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||
useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormControl.displayName = "FormControl";
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn(
|
||||
"text-[0.8rem] text-slate-500 dark:text-slate-400",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
FormDescription.displayName = "FormDescription";
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message) : children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn(
|
||||
"text-[0.8rem] font-medium text-red-500 dark:text-red-900",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
FormMessage.displayName = "FormMessage";
|
||||
|
||||
export {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
useFormField,
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
|
||||
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<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Label.displayName = LabelPrimitive.Root.displayName;
|
||||
|
||||
export { Label };
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./Form";
|
||||
export * from "./Label";
|
||||
@@ -7,6 +7,7 @@ import { usePathname } from "next/navigation";
|
||||
import React, { useCallback } from "react";
|
||||
import { useMediaQuery } from "usehooks-ts";
|
||||
|
||||
import { logOut } from "@/actions";
|
||||
import { useLocalStorage } from "@/hooks/useLocalStorage";
|
||||
|
||||
import {
|
||||
@@ -126,6 +127,7 @@ export const SidebarWrap = () => {
|
||||
<Tooltip content="Log Out" isDisabled={!isCompact} placement="right">
|
||||
<Button
|
||||
aria-label="Log Out"
|
||||
onClick={() => logOut}
|
||||
className={clsx(
|
||||
"justify-start text-default-500 data-[hover=true]:text-foreground",
|
||||
{
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./seed";
|
||||
export * from "./utils";
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import bcryptjs from "bcryptjs";
|
||||
|
||||
export const initialData = {
|
||||
users: [
|
||||
{
|
||||
email: "admin@admin.com",
|
||||
name: "Admin User",
|
||||
password: bcryptjs.hashSync("123456", 10),
|
||||
role: "admin",
|
||||
},
|
||||
{
|
||||
email: "user@user.com",
|
||||
name: "Prowler User",
|
||||
password: bcryptjs.hashSync("123456", 10),
|
||||
role: "user",
|
||||
},
|
||||
],
|
||||
};
|
||||
Generated
+49
@@ -8,12 +8,15 @@
|
||||
"name": "prowler-next-app",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@nextui-org/react": "^2.4.2",
|
||||
"@nextui-org/system": "2.2.1",
|
||||
"@nextui-org/theme": "2.2.5",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@react-aria/ssr": "3.9.4",
|
||||
"@react-aria/visually-hidden": "3.8.12",
|
||||
@@ -32,6 +35,7 @@
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hook-form": "^7.52.2",
|
||||
"recharts": "^2.13.0-alpha.4",
|
||||
"server-only": "^0.0.1",
|
||||
"shadcn-ui": "^0.2.3",
|
||||
@@ -389,6 +393,14 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hookform/resolvers": {
|
||||
"version": "3.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.9.0.tgz",
|
||||
"integrity": "sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==",
|
||||
"peerDependencies": {
|
||||
"react-hook-form": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.14",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
|
||||
@@ -2543,6 +2555,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-label": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz",
|
||||
"integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.0.0"
|
||||
},
|
||||
"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
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
|
||||
@@ -9487,6 +9521,21 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.52.2",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.2.tgz",
|
||||
"integrity": "sha512-pqfPEbERnxxiNMPd0bzmt1tuaPcVccywFDpyk2uV5xCIBphHV5T8SVnX9/o3kplPE1zzKt77+YIoq+EMwJp56A==",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/react-hook-form"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@nextui-org/react": "^2.4.2",
|
||||
"@nextui-org/system": "2.2.1",
|
||||
"@nextui-org/theme": "2.2.5",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.1.0",
|
||||
"@radix-ui/react-select": "^2.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@react-aria/ssr": "3.9.4",
|
||||
"@react-aria/visually-hidden": "3.8.12",
|
||||
@@ -24,6 +27,7 @@
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-hook-form": "^7.52.2",
|
||||
"recharts": "^2.13.0-alpha.4",
|
||||
"server-only": "^0.0.1",
|
||||
"shadcn-ui": "^0.2.3",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const authFormSchema = z.object({
|
||||
username: z
|
||||
.string()
|
||||
.min(4, {
|
||||
message: "Username must be at least 4 characters.",
|
||||
})
|
||||
.max(20),
|
||||
password: z.string().min(6),
|
||||
email: z.string().email(),
|
||||
});
|
||||
@@ -1 +1,2 @@
|
||||
export * from "./auth";
|
||||
export * from "./components";
|
||||
|
||||
Reference in New Issue
Block a user