mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-31 06:05:43 +00:00 
			
		
		
		
	 318f00993e
			
		
	
	318f00993e
	
	
	
		
			
			* wip * feat: make forms functional * feat: finalize pages * chore: remove unused translations * feat: app context * feat: user context * feat: finalize username login * fix: use correct tab order in login form * feat: add oauth logic * chore: update readme and assets * chore: rename docs back to assets * feat: favicons * feat: custom background image config option * chore: add acknowledgements for background image * feat: sanitize redirect URL * feat: sanitize redirect URL on check * chore: fix dependabot config * refactor: bot suggestions * fix: correctly redirect to app and check for untrusted redirects * fix: run oauth auto redirect only when there is a redirect URI * refactor: change select color * fix: fix dockerfiles * fix: fix hook rendering * chore: remove translations cdn * chore: formatting * feat: validate api response against zod schema * fix: use axios error instead of generic error in login page
		
			
				
	
	
		
			167 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			167 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import * as React from "react";
 | |
| import * as LabelPrimitive from "@radix-ui/react-label";
 | |
| import { Slot } from "@radix-ui/react-slot";
 | |
| import {
 | |
|   Controller,
 | |
|   FormProvider,
 | |
|   useFormContext,
 | |
|   useFormState,
 | |
|   type ControllerProps,
 | |
|   type FieldPath,
 | |
|   type FieldValues,
 | |
| } from "react-hook-form";
 | |
| 
 | |
| import { cn } from "@/lib/utils";
 | |
| import { Label } from "@/components/ui/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 } = useFormContext();
 | |
|   const formState = useFormState({ name: fieldContext.name });
 | |
|   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,
 | |
| );
 | |
| 
 | |
| function FormItem({ className, ...props }: React.ComponentProps<"div">) {
 | |
|   const id = React.useId();
 | |
| 
 | |
|   return (
 | |
|     <FormItemContext.Provider value={{ id }}>
 | |
|       <div
 | |
|         data-slot="form-item"
 | |
|         className={cn("grid gap-2", className)}
 | |
|         {...props}
 | |
|       />
 | |
|     </FormItemContext.Provider>
 | |
|   );
 | |
| }
 | |
| 
 | |
| function FormLabel({
 | |
|   className,
 | |
|   ...props
 | |
| }: React.ComponentProps<typeof LabelPrimitive.Root>) {
 | |
|   const { error, formItemId } = useFormField();
 | |
| 
 | |
|   return (
 | |
|     <Label
 | |
|       data-slot="form-label"
 | |
|       data-error={!!error}
 | |
|       className={cn("data-[error=true]:text-destructive", className)}
 | |
|       htmlFor={formItemId}
 | |
|       {...props}
 | |
|     />
 | |
|   );
 | |
| }
 | |
| 
 | |
| function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
 | |
|   const { error, formItemId, formDescriptionId, formMessageId } =
 | |
|     useFormField();
 | |
| 
 | |
|   return (
 | |
|     <Slot
 | |
|       data-slot="form-control"
 | |
|       id={formItemId}
 | |
|       aria-describedby={
 | |
|         !error
 | |
|           ? `${formDescriptionId}`
 | |
|           : `${formDescriptionId} ${formMessageId}`
 | |
|       }
 | |
|       aria-invalid={!!error}
 | |
|       {...props}
 | |
|     />
 | |
|   );
 | |
| }
 | |
| 
 | |
| function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
 | |
|   const { formDescriptionId } = useFormField();
 | |
| 
 | |
|   return (
 | |
|     <p
 | |
|       data-slot="form-description"
 | |
|       id={formDescriptionId}
 | |
|       className={cn("text-muted-foreground text-sm", className)}
 | |
|       {...props}
 | |
|     />
 | |
|   );
 | |
| }
 | |
| 
 | |
| function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
 | |
|   const { error, formMessageId } = useFormField();
 | |
|   const body = error ? String(error?.message ?? "") : props.children;
 | |
| 
 | |
|   if (!body) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     <p
 | |
|       data-slot="form-message"
 | |
|       id={formMessageId}
 | |
|       className={cn("text-destructive text-sm", className)}
 | |
|       {...props}
 | |
|     >
 | |
|       {body}
 | |
|     </p>
 | |
|   );
 | |
| }
 | |
| 
 | |
| export {
 | |
|   useFormField,
 | |
|   Form,
 | |
|   FormItem,
 | |
|   FormLabel,
 | |
|   FormControl,
 | |
|   FormDescription,
 | |
|   FormMessage,
 | |
|   FormField,
 | |
| };
 |