init
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { useAuthStore } from "@/shared/stores/auth-store"
|
||||
|
||||
/**
|
||||
* Hydrates auth state from server cookies on mount and exposes auth helpers.
|
||||
*/
|
||||
export function useAuth() {
|
||||
const store = useAuthStore()
|
||||
|
||||
useEffect(() => {
|
||||
store.hydrate()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return {
|
||||
token: store.token,
|
||||
user: store.user,
|
||||
isAuthenticated: store.isAuthenticated,
|
||||
login: store.login,
|
||||
logout: store.logout,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
"use client"
|
||||
|
||||
import { useMutation, type UseMutationOptions } from "@tanstack/react-query"
|
||||
import type { FieldValues, UseFormReturn } from "react-hook-form"
|
||||
import { ApiError } from "@garage/api"
|
||||
|
||||
export function useFormMutation<TValues extends FieldValues, TResponse = unknown>(
|
||||
form: UseFormReturn<TValues>,
|
||||
options: UseMutationOptions<TResponse, Error, TValues>,
|
||||
) {
|
||||
return useMutation<TResponse, Error, TValues>({
|
||||
...options,
|
||||
onError: (err, vars,values, ctx) => {
|
||||
if (err instanceof ApiError && err.validationErrors) {
|
||||
Object.entries(err.validationErrors).forEach(([field, msgs]) => {
|
||||
form.setError(field as any, { message: msgs[0] })
|
||||
})
|
||||
}
|
||||
options.onError?.(err, vars, values, ctx,)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as React from "react"
|
||||
|
||||
const MOBILE_BREAKPOINT = 768
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
}
|
||||
mql.addEventListener("change", onChange)
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
||||
return () => mql.removeEventListener("change", onChange)
|
||||
}, [])
|
||||
|
||||
return !!isMobile
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { useForm, type DefaultValues, type FieldValues, type UseFormReturn } from "react-hook-form"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useQuery, type QueryKey } from "@tanstack/react-query"
|
||||
import type { ZodType } from "zod"
|
||||
|
||||
type UseResourceFormOptions<TFormValues extends FieldValues, TApiData = unknown> = {
|
||||
schema: ZodType<TFormValues, any, any>
|
||||
defaultValues: DefaultValues<TFormValues>
|
||||
resourceId?: string | null
|
||||
initialize?: (id: string) => Promise<TApiData>
|
||||
mapToFormValues: (data: TApiData) => TFormValues
|
||||
initialData?: TApiData | null
|
||||
queryKey?: QueryKey
|
||||
}
|
||||
|
||||
type UseResourceFormReturn<TFormValues extends FieldValues> = {
|
||||
form: UseFormReturn<TFormValues>
|
||||
isEditing: boolean
|
||||
isInitializing: boolean
|
||||
}
|
||||
|
||||
export function useResourceForm<TFormValues extends FieldValues, TApiData = unknown>({
|
||||
schema,
|
||||
defaultValues,
|
||||
resourceId,
|
||||
initialize,
|
||||
mapToFormValues,
|
||||
initialData,
|
||||
queryKey,
|
||||
}: UseResourceFormOptions<TFormValues, TApiData>): UseResourceFormReturn<TFormValues> {
|
||||
const isEditing = !!resourceId
|
||||
|
||||
const { data: queriedData, isLoading: isQueryLoading } = useQuery<TApiData>({
|
||||
queryKey: queryKey ?? ["resource", resourceId],
|
||||
queryFn: () => initialize!(resourceId!),
|
||||
enabled: isEditing && !!initialize,
|
||||
})
|
||||
|
||||
const resolvedData = queriedData ?? (isEditing ? initialData : undefined)
|
||||
|
||||
const form = useForm<TFormValues>({
|
||||
resolver: zodResolver(schema) as any,
|
||||
defaultValues,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEditing) {
|
||||
form.reset(defaultValues)
|
||||
return
|
||||
}
|
||||
|
||||
if (resolvedData) {
|
||||
form.reset(mapToFormValues(resolvedData) as any)
|
||||
}
|
||||
}, [isEditing, resolvedData]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return { form, isEditing, isInitializing: isEditing && !!initialize && isQueryLoading }
|
||||
}
|
||||
Reference in New Issue
Block a user