diff --git a/packages/ui/src/components/custom/cover-picker/unsplash.tsx b/packages/ui/src/components/custom/cover-picker/unsplash.tsx index b3670910..31c4ccff 100644 --- a/packages/ui/src/components/custom/cover-picker/unsplash.tsx +++ b/packages/ui/src/components/custom/cover-picker/unsplash.tsx @@ -1,3 +1,4 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ "use client"; diff --git a/packages/ui/src/components/custom/crud-item/index.tsx b/packages/ui/src/components/custom/crud-item/index.tsx index daf3870b..44359664 100644 --- a/packages/ui/src/components/custom/crud-item/index.tsx +++ b/packages/ui/src/components/custom/crud-item/index.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/interactive-supports-focus */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ "use client"; import type { MouseEvent } from "react"; diff --git a/packages/ui/src/components/custom/kanban/kanban-container.tsx b/packages/ui/src/components/custom/kanban/kanban-container.tsx index f9cec18c..47247262 100644 --- a/packages/ui/src/components/custom/kanban/kanban-container.tsx +++ b/packages/ui/src/components/custom/kanban/kanban-container.tsx @@ -1,7 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-argument */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ - "use client"; import type { OnDragEndResponder } from "@hello-pangea/dnd"; diff --git a/packages/ui/src/components/custom/kanban/kanban-item-form.tsx b/packages/ui/src/components/custom/kanban/kanban-item-form.tsx index afa6bf5f..4aff5201 100644 --- a/packages/ui/src/components/custom/kanban/kanban-item-form.tsx +++ b/packages/ui/src/components/custom/kanban/kanban-item-form.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ "use client"; import type { KeyboardEventHandler } from "react"; diff --git a/packages/ui/src/components/custom/kanban/kanban-item.tsx b/packages/ui/src/components/custom/kanban/kanban-item.tsx index 841a9a92..6d8d1125 100644 --- a/packages/ui/src/components/custom/kanban/kanban-item.tsx +++ b/packages/ui/src/components/custom/kanban/kanban-item.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ "use client"; import { Draggable } from "@hello-pangea/dnd"; diff --git a/packages/ui/src/components/custom/kanban/kanban-list-form.tsx b/packages/ui/src/components/custom/kanban/kanban-list-form.tsx index 1431304c..fb62754d 100644 --- a/packages/ui/src/components/custom/kanban/kanban-list-form.tsx +++ b/packages/ui/src/components/custom/kanban/kanban-list-form.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ "use client"; import { useImperativeHandle, useRef, useState, useTransition } from "react"; diff --git a/packages/ui/src/components/custom/kanban/kanban-list-header.tsx b/packages/ui/src/components/custom/kanban/kanban-list-header.tsx index a3ed1d83..5346a229 100644 --- a/packages/ui/src/components/custom/kanban/kanban-list-header.tsx +++ b/packages/ui/src/components/custom/kanban/kanban-list-header.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-call */ "use client"; import { useImperativeHandle, useRef, useState, useTransition } from "react"; diff --git a/packages/ui/src/components/custom/kanban/kanban-list.tsx b/packages/ui/src/components/custom/kanban/kanban-list.tsx index b685a599..32ec6f3e 100644 --- a/packages/ui/src/components/custom/kanban/kanban-list.tsx +++ b/packages/ui/src/components/custom/kanban/kanban-list.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ "use client"; import { useRef, useState } from "react"; diff --git a/packages/ui/src/components/dnd/single-image-dropzone/index.tsx b/packages/ui/src/components/dnd/single-image-dropzone/index.tsx index f5fd7a8e..a5fc579c 100644 --- a/packages/ui/src/components/dnd/single-image-dropzone/index.tsx +++ b/packages/ui/src/components/dnd/single-image-dropzone/index.tsx @@ -1,3 +1,5 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ "use client"; import * as React from "react"; diff --git a/packages/ui/src/hooks/index.ts b/packages/ui/src/hooks/index.ts index acb1f473..fa87f7ac 100644 --- a/packages/ui/src/hooks/index.ts +++ b/packages/ui/src/hooks/index.ts @@ -1,2 +1,3 @@ +export * from "./use-action"; export * from "./use-fetch"; export * from "./use-scroll-top"; diff --git a/packages/ui/src/hooks/use-action.ts b/packages/ui/src/hooks/use-action.ts new file mode 100644 index 00000000..ffb6ad84 --- /dev/null +++ b/packages/ui/src/hooks/use-action.ts @@ -0,0 +1,58 @@ +"use client"; + +import { useCallback, useState } from "react"; + +import { ActionHandler, FieldErrors } from "@/lib"; + +interface UseActionOptions { + onSuccess?: (data: TOutput) => void; + onError?: (error: string) => void; + onComplete?: () => void; +} + +interface UseActionResult { + execute: (data: TInput) => Promise; + fieldErrors?: FieldErrors; + error?: string; + data?: TOutput; + isLoading: boolean; +} + +export const useAction = ( + action: ActionHandler, + options: UseActionOptions, +): UseActionResult => { + const [fieldErrors, setFieldErrors] = useState< + FieldErrors | undefined + >(undefined); + const [error, setError] = useState(undefined); + const [data, setData] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + + const execute = useCallback( + async (input: TInput) => { + setIsLoading(true); + try { + const result = await action(input); + if (!result) return; + + const { fieldErrors, error, data } = result; + setFieldErrors(fieldErrors); + if (error) { + setError(error); + options.onError?.(error); + } + if (data) { + setData(data); + options.onSuccess?.(data); + } + } finally { + setIsLoading(false); + options.onComplete?.(); + } + }, + [action, options], + ); + + return { execute, fieldErrors, error, data, isLoading }; +}; diff --git a/packages/ui/src/lib/action.ts b/packages/ui/src/lib/action.ts new file mode 100644 index 00000000..30575527 --- /dev/null +++ b/packages/ui/src/lib/action.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; + +import type { ActionHandler, FieldErrors } from "./types"; + +export const createSafeAction = + ( + schema: z.Schema, + handler: ActionHandler, + ): ActionHandler => + async (data) => { + const result = schema.safeParse(data); + return result.success + ? await handler(result.data) + : { + fieldErrors: result.error.flatten() + .fieldErrors as FieldErrors, + }; + }; diff --git a/packages/ui/src/lib/index.ts b/packages/ui/src/lib/index.ts index cf179548..033b6cce 100644 --- a/packages/ui/src/lib/index.ts +++ b/packages/ui/src/lib/index.ts @@ -1,2 +1,3 @@ -export * from "./utils"; +export * from "./action"; export * from "./types"; +export * from "./utils";