feat: add homepage & auth

This commit is contained in:
2024-02-23 19:36:10 +07:00
parent d579c1b7c5
commit 735fa9354b
42 changed files with 981 additions and 70 deletions
+25 -9
View File
@@ -3,6 +3,7 @@ import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "~/lib/utils";
import { Loader2 } from "lucide-react";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300",
@@ -14,7 +15,7 @@ const buttonVariants = cva(
destructive:
"bg-red-500 text-slate-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-slate-50 dark:hover:bg-red-900/90",
outline:
"border border-slate-200 bg-white hover:bg-slate-100 hover:text-slate-900 dark:border-slate-800 dark:bg-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50",
"border border-slate-200 bg-white hover:bg-slate-100 hover:text-slate-900 dark:border-white/40 dark:bg-transparent dark:hover:bg-slate-800 dark:hover:text-slate-50",
secondary:
"bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80",
ghost:
@@ -39,21 +40,36 @@ export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
href?: string;
isLoading?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{ className, variant, size, type = "button", asChild = false, ...props },
ref
) => {
const Comp = asChild ? Slot : "button";
(props, ref) => {
const {
className,
variant,
size,
type = "button",
asChild = false,
disabled,
isLoading,
children,
...restProps
} = props;
const Comp = asChild ? Slot : props.href ? "a" : "button";
return (
<Comp
type={type}
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
ref={ref as any}
disabled={isLoading || disabled}
{...(restProps as any)}
>
{isLoading ? <Loader2 className="animate-spin mr-2" /> : null}
{children}
</Comp>
);
}
);
+25
View File
@@ -0,0 +1,25 @@
import React from "react";
import { cn } from "~/lib/utils";
type Props = React.ComponentProps<"div">;
const Card = ({ className, ...props }: Props) => {
return (
<div
className={cn(
"bg-background border border-white/20 rounded-lg p-4",
className
)}
{...props}
/>
);
};
export const CardTitle = ({
className,
...props
}: React.ComponentProps<"p">) => (
<p className={cn("text-2xl mb-8", className)} {...props} />
);
export default Card;
+10
View File
@@ -0,0 +1,10 @@
import React from "react";
import { cn } from "~/lib/utils";
type Props = React.ComponentProps<"hr">;
const Divider = ({ className, ...props }: Props) => {
return <hr className={cn("border-white/20", className)} {...props} />;
};
export default Divider;
+34
View File
@@ -0,0 +1,34 @@
import React from "react";
import { cn } from "~/lib/utils";
export type FormFieldProps = {
id?: string;
label?: string;
error?: string;
};
type Props = FormFieldProps & {
children?: React.ReactNode;
className?: string;
};
const FormField = ({ id, label, error, children, className }: Props) => {
return (
<div className={cn("form-field", className)}>
{label ? <FormLabel htmlFor={id}>{label}</FormLabel> : null}
{children}
{error ? <p className="text-sm mt-1 text-red-500">{error}</p> : null}
</div>
);
};
export const FormLabel = ({
className,
...props
}: React.ComponentProps<"label">) => (
<label className={cn("text-sm block mb-2", className)} {...props} />
);
export default FormField;
+63 -11
View File
@@ -1,25 +1,77 @@
import * as React from "react";
import { ForwardedRef, forwardRef, useId } from "react";
import { Controller, FieldValues, Path } from "react-hook-form";
import { useFormReturn } from "~/hooks/useForm";
import { cn } from "~/lib/utils";
import FormField, { FormFieldProps } from "./form-field";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
export type BaseInputProps = React.InputHTMLAttributes<HTMLInputElement> &
FormFieldProps & {
inputClassName?: string;
};
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
const BaseInput = forwardRef<HTMLInputElement, BaseInputProps>(
({ className, inputClassName, type, label, error, ...props }, ref) => {
const id = useId();
const input = (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300",
className
"flex h-10 w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/40 dark:bg-background dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300",
inputClassName
)}
ref={ref}
{...props}
/>
);
if (label || error) {
return (
<FormField
id={id}
label={label}
error={error}
className={className}
children={input}
/>
);
}
return input;
}
);
Input.displayName = "Input";
BaseInput.displayName = "BaseInput";
export { Input };
type InputProps<T extends FieldValues> = Omit<
BaseInputProps,
"form" | "name"
> & {
form?: useFormReturn<T>;
name?: Path<T>;
};
const Input = <T extends FieldValues>(props: InputProps<T>) => {
const { form, ...restProps } = props;
if (form && props.name) {
return (
<Controller
control={form.control}
name={props.name}
render={({ field, fieldState }) => (
<BaseInput
{...restProps}
{...field}
value={field.value || ""}
error={fieldState.error?.message}
/>
)}
/>
);
}
return <BaseInput {...restProps} />;
};
export { BaseInput };
export default Input;