chore: WIP

This commit is contained in:
Pablo Lara committed 2024-08-22 18:04:30 +02:00
1 parent 063de00e45
commit ed0d975e43
15 files changed
+520 -53

No files matched your search

+109 -47
View File
@@ -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&nbsp;
<Link href="#" size="sm">
Terms
</Link>
&nbsp; and&nbsp;
<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?&nbsp;
+88
View File
@@ -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
View File
@@ -1 +1,2 @@
export * from "./CustomBox";
export * from "./CustomInput";
+186
View File
@@ -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,
};
+26
View File
@@ -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 };
+2
View File
@@ -0,0 +1,2 @@
export * from "./Form";
export * from "./Label";
+2
View File
@@ -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",
{