@famgia/omnify-react 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/index.cjs +26 -5
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +42 -3
- package/dist/hooks/index.d.ts +42 -3
- package/dist/hooks/index.js +23 -5
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +35 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +31 -5
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/hooks/index.cjs
CHANGED
|
@@ -20,6 +20,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/hooks/index.ts
|
|
21
21
|
var hooks_exports = {};
|
|
22
22
|
__export(hooks_exports, {
|
|
23
|
+
getFirstValidationError: () => getFirstValidationError,
|
|
24
|
+
getFormErrors: () => getFormErrors,
|
|
25
|
+
getValidationMessage: () => getValidationMessage,
|
|
23
26
|
useFormMutation: () => useFormMutation
|
|
24
27
|
});
|
|
25
28
|
module.exports = __toCommonJS(hooks_exports);
|
|
@@ -31,20 +34,31 @@ function getFormErrors(error) {
|
|
|
31
34
|
const data = error?.response?.data;
|
|
32
35
|
const errors = data?.errors;
|
|
33
36
|
if (!errors || typeof errors !== "object") return [];
|
|
34
|
-
return Object.entries(errors).map(([
|
|
35
|
-
name
|
|
37
|
+
return Object.entries(errors).map(([fieldName, messages]) => ({
|
|
38
|
+
// Convert "user.name" or "items.0.name" to array path for Ant Design
|
|
39
|
+
name: fieldName.includes(".") ? fieldName.split(".").map((part) => /^\d+$/.test(part) ? parseInt(part, 10) : part) : fieldName,
|
|
36
40
|
errors: Array.isArray(messages) ? messages : [String(messages)]
|
|
37
41
|
}));
|
|
38
42
|
}
|
|
39
43
|
function getValidationMessage(error) {
|
|
40
|
-
const
|
|
41
|
-
|
|
44
|
+
const axiosError = error;
|
|
45
|
+
if (axiosError?.response?.status !== 422) return null;
|
|
46
|
+
return axiosError?.response?.data?.message ?? null;
|
|
47
|
+
}
|
|
48
|
+
function getFirstValidationError(error) {
|
|
49
|
+
const errors = error?.response?.data?.errors;
|
|
50
|
+
if (!errors || typeof errors !== "object") return null;
|
|
51
|
+
const firstField = Object.keys(errors)[0];
|
|
52
|
+
return firstField ? errors[firstField][0] : null;
|
|
42
53
|
}
|
|
43
54
|
function useFormMutation({
|
|
44
55
|
form,
|
|
45
56
|
mutationFn,
|
|
46
57
|
invalidateKeys = [],
|
|
47
58
|
successMessage,
|
|
59
|
+
redirectTo,
|
|
60
|
+
router,
|
|
61
|
+
translateFn,
|
|
48
62
|
onSuccess,
|
|
49
63
|
onError
|
|
50
64
|
}) {
|
|
@@ -57,7 +71,11 @@ function useFormMutation({
|
|
|
57
71
|
queryClient.invalidateQueries({ queryKey: [...key] });
|
|
58
72
|
});
|
|
59
73
|
if (successMessage) {
|
|
60
|
-
|
|
74
|
+
const msg = translateFn ? translateFn(successMessage) : successMessage;
|
|
75
|
+
message.success(msg);
|
|
76
|
+
}
|
|
77
|
+
if (redirectTo && router) {
|
|
78
|
+
router.push(redirectTo);
|
|
61
79
|
}
|
|
62
80
|
onSuccess?.(data);
|
|
63
81
|
},
|
|
@@ -76,6 +94,9 @@ function useFormMutation({
|
|
|
76
94
|
}
|
|
77
95
|
// Annotate the CommonJS export names for ESM import in node:
|
|
78
96
|
0 && (module.exports = {
|
|
97
|
+
getFirstValidationError,
|
|
98
|
+
getFormErrors,
|
|
99
|
+
getValidationMessage,
|
|
79
100
|
useFormMutation
|
|
80
101
|
});
|
|
81
102
|
//# sourceMappingURL=index.cjs.map
|
package/dist/hooks/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-form-mutation.ts"],"sourcesContent":["/**\n * @famgia/omnify-react/hooks\n *\n * React hooks for form handling and data mutations\n */\n\nexport {
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/index.ts","../../src/hooks/use-form-mutation.ts"],"sourcesContent":["/**\n * @famgia/omnify-react/hooks\n *\n * React hooks for form handling and data mutations\n */\n\nexport {\n useFormMutation,\n type UseFormMutationOptions,\n type FormMutationRouter,\n type TranslateFn,\n type FormFieldError,\n // Helper functions (also exported for direct use)\n getFormErrors,\n getValidationMessage,\n getFirstValidationError,\n} from './use-form-mutation';\n","/**\n * useFormMutation - Form mutation with auto Laravel error handling\n *\n * Features:\n * - Auto parse Laravel validation errors to Ant Design form format\n * - Support nested field paths (e.g., \"items.0.name\" → [\"items\", 0, \"name\"])\n * - Auto invalidate queries on success\n * - Optional redirect after success\n * - Optional i18n translation for messages\n *\n * @example\n * ```typescript\n * import { useFormMutation } from '@famgia/omnify-react';\n * import { useRouter } from 'next/navigation';\n * import { useTranslations } from 'next-intl';\n *\n * function MyForm() {\n * const [form] = Form.useForm();\n * const router = useRouter();\n * const t = useTranslations();\n *\n * const mutation = useFormMutation({\n * form,\n * mutationFn: (data) => api.post('/api/customers', data),\n * invalidateKeys: [['customers']],\n * successMessage: 'messages.saved',\n * redirectTo: '/customers',\n * router, // Optional: for redirect\n * translateFn: t, // Optional: for i18n\n * });\n *\n * return (\n * <Form form={form} onFinish={mutation.mutate}>\n * ...\n * <Button loading={mutation.isPending}>保存</Button>\n * </Form>\n * );\n * }\n * ```\n */\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { App } from 'antd';\nimport type { FormInstance } from 'antd';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Router interface - compatible with Next.js useRouter */\nexport interface FormMutationRouter {\n push: (path: string) => void;\n}\n\n/** Translation function - compatible with next-intl useTranslations */\nexport type TranslateFn = (key: string) => string;\n\nexport interface UseFormMutationOptions<TData, TResult> {\n /** Ant Design form instance */\n form: FormInstance;\n /** API call function */\n mutationFn: (data: TData) => Promise<TResult>;\n /** Query keys to invalidate on success */\n invalidateKeys?: readonly (readonly unknown[])[];\n /** Success message to show (will be translated if translateFn provided) */\n successMessage?: string;\n /** Redirect path after success (requires router) */\n redirectTo?: string;\n /** Router instance for redirect (e.g., useRouter from next/navigation) */\n router?: FormMutationRouter;\n /** Translation function for messages (e.g., useTranslations from next-intl) */\n translateFn?: TranslateFn;\n /** Callback on success */\n onSuccess?: (data: TResult) => void;\n /** Callback on error */\n onError?: (error: unknown) => void;\n}\n\n// =============================================================================\n// Laravel Error Helpers (exported for reuse)\n// =============================================================================\n\n/** Form field error for Ant Design */\nexport interface FormFieldError {\n name: string | (string | number)[];\n errors: string[];\n}\n\n/**\n * Parse Laravel validation errors to Ant Design form format\n *\n * Supports:\n * - Simple fields: \"email\" → name: \"email\"\n * - Dot notation: \"user.name\" → name: [\"user\", \"name\"]\n * - Array notation: \"items.0.name\" → name: [\"items\", 0, \"name\"]\n *\n * @example\n * form.setFields(getFormErrors(error))\n */\nexport function getFormErrors(error: unknown): FormFieldError[] {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const data = (error as any)?.response?.data;\n const errors = data?.errors;\n\n if (!errors || typeof errors !== 'object') return [];\n\n return Object.entries(errors).map(([fieldName, messages]) => ({\n // Convert \"user.name\" or \"items.0.name\" to array path for Ant Design\n name: fieldName.includes('.')\n ? fieldName.split('.').map((part) => (/^\\d+$/.test(part) ? parseInt(part, 10) : part))\n : fieldName,\n errors: Array.isArray(messages) ? (messages as string[]) : [String(messages)],\n }));\n}\n\n/**\n * Get general validation message from Laravel 422 response\n * @example \"The name_lastname field is required. (and 3 more errors)\"\n */\nexport function getValidationMessage(error: unknown): string | null {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const axiosError = error as any;\n\n // Only for 422 validation errors\n if (axiosError?.response?.status !== 422) return null;\n\n return axiosError?.response?.data?.message ?? null;\n}\n\n/**\n * Get first error message from validation errors\n * Useful when field names don't match form fields\n */\nexport function getFirstValidationError(error: unknown): string | null {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors = (error as any)?.response?.data?.errors;\n\n if (!errors || typeof errors !== 'object') return null;\n\n const firstField = Object.keys(errors)[0];\n return firstField ? errors[firstField][0] : null;\n}\n\n// =============================================================================\n// Hook\n// =============================================================================\n\nexport function useFormMutation<TData, TResult = unknown>({\n form,\n mutationFn,\n invalidateKeys = [],\n successMessage,\n redirectTo,\n router,\n translateFn,\n onSuccess,\n onError,\n}: UseFormMutationOptions<TData, TResult>) {\n const queryClient = useQueryClient();\n const { message } = App.useApp();\n\n return useMutation({\n mutationFn,\n onSuccess: (data) => {\n // Invalidate queries\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: [...key] });\n });\n\n // Show success message (translate if translateFn provided)\n if (successMessage) {\n const msg = translateFn ? translateFn(successMessage) : successMessage;\n message.success(msg);\n }\n\n // Redirect if router and redirectTo provided\n if (redirectTo && router) {\n router.push(redirectTo);\n }\n\n // Custom callback\n onSuccess?.(data);\n },\n onError: (error) => {\n // Set form field errors from Laravel validation\n const formErrors = getFormErrors(error);\n if (formErrors.length > 0) {\n form.setFields(formErrors);\n }\n\n // Show general validation message (from Laravel)\n const validationMessage = getValidationMessage(error);\n if (validationMessage) {\n message.error(validationMessage);\n }\n\n // Custom callback\n onError?.(error);\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACyCA,yBAA4C;AAC5C,kBAAoB;AAyDb,SAAS,cAAc,OAAkC;AAE9D,QAAM,OAAQ,OAAe,UAAU;AACvC,QAAM,SAAS,MAAM;AAErB,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO,CAAC;AAEnD,SAAO,OAAO,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,WAAW,QAAQ,OAAO;AAAA;AAAA,IAE5D,MAAM,UAAU,SAAS,GAAG,IACxB,UAAU,MAAM,GAAG,EAAE,IAAI,CAAC,SAAU,QAAQ,KAAK,IAAI,IAAI,SAAS,MAAM,EAAE,IAAI,IAAK,IACnF;AAAA,IACJ,QAAQ,MAAM,QAAQ,QAAQ,IAAK,WAAwB,CAAC,OAAO,QAAQ,CAAC;AAAA,EAC9E,EAAE;AACJ;AAMO,SAAS,qBAAqB,OAA+B;AAElE,QAAM,aAAa;AAGnB,MAAI,YAAY,UAAU,WAAW,IAAK,QAAO;AAEjD,SAAO,YAAY,UAAU,MAAM,WAAW;AAChD;AAMO,SAAS,wBAAwB,OAA+B;AAErE,QAAM,SAAU,OAAe,UAAU,MAAM;AAE/C,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAElD,QAAM,aAAa,OAAO,KAAK,MAAM,EAAE,CAAC;AACxC,SAAO,aAAa,OAAO,UAAU,EAAE,CAAC,IAAI;AAC9C;AAMO,SAAS,gBAA0C;AAAA,EACxD;AAAA,EACA;AAAA,EACA,iBAAiB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2C;AACzC,QAAM,kBAAc,mCAAe;AACnC,QAAM,EAAE,QAAQ,IAAI,gBAAI,OAAO;AAE/B,aAAO,gCAAY;AAAA,IACjB;AAAA,IACA,WAAW,CAAC,SAAS;AAEnB,qBAAe,QAAQ,CAAC,QAAQ;AAC9B,oBAAY,kBAAkB,EAAE,UAAU,CAAC,GAAG,GAAG,EAAE,CAAC;AAAA,MACtD,CAAC;AAGD,UAAI,gBAAgB;AAClB,cAAM,MAAM,cAAc,YAAY,cAAc,IAAI;AACxD,gBAAQ,QAAQ,GAAG;AAAA,MACrB;AAGA,UAAI,cAAc,QAAQ;AACxB,eAAO,KAAK,UAAU;AAAA,MACxB;AAGA,kBAAY,IAAI;AAAA,IAClB;AAAA,IACA,SAAS,CAAC,UAAU;AAElB,YAAM,aAAa,cAAc,KAAK;AACtC,UAAI,WAAW,SAAS,GAAG;AACzB,aAAK,UAAU,UAAU;AAAA,MAC3B;AAGA,YAAM,oBAAoB,qBAAqB,KAAK;AACpD,UAAI,mBAAmB;AACrB,gBAAQ,MAAM,iBAAiB;AAAA,MACjC;AAGA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/hooks/index.d.cts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
2
|
import { FormInstance } from 'antd';
|
|
3
3
|
|
|
4
|
+
/** Router interface - compatible with Next.js useRouter */
|
|
5
|
+
interface FormMutationRouter {
|
|
6
|
+
push: (path: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/** Translation function - compatible with next-intl useTranslations */
|
|
9
|
+
type TranslateFn = (key: string) => string;
|
|
4
10
|
interface UseFormMutationOptions<TData, TResult> {
|
|
5
11
|
/** Ant Design form instance */
|
|
6
12
|
form: FormInstance;
|
|
@@ -8,13 +14,46 @@ interface UseFormMutationOptions<TData, TResult> {
|
|
|
8
14
|
mutationFn: (data: TData) => Promise<TResult>;
|
|
9
15
|
/** Query keys to invalidate on success */
|
|
10
16
|
invalidateKeys?: readonly (readonly unknown[])[];
|
|
11
|
-
/** Success message to show */
|
|
17
|
+
/** Success message to show (will be translated if translateFn provided) */
|
|
12
18
|
successMessage?: string;
|
|
19
|
+
/** Redirect path after success (requires router) */
|
|
20
|
+
redirectTo?: string;
|
|
21
|
+
/** Router instance for redirect (e.g., useRouter from next/navigation) */
|
|
22
|
+
router?: FormMutationRouter;
|
|
23
|
+
/** Translation function for messages (e.g., useTranslations from next-intl) */
|
|
24
|
+
translateFn?: TranslateFn;
|
|
13
25
|
/** Callback on success */
|
|
14
26
|
onSuccess?: (data: TResult) => void;
|
|
15
27
|
/** Callback on error */
|
|
16
28
|
onError?: (error: unknown) => void;
|
|
17
29
|
}
|
|
18
|
-
|
|
30
|
+
/** Form field error for Ant Design */
|
|
31
|
+
interface FormFieldError {
|
|
32
|
+
name: string | (string | number)[];
|
|
33
|
+
errors: string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse Laravel validation errors to Ant Design form format
|
|
37
|
+
*
|
|
38
|
+
* Supports:
|
|
39
|
+
* - Simple fields: "email" → name: "email"
|
|
40
|
+
* - Dot notation: "user.name" → name: ["user", "name"]
|
|
41
|
+
* - Array notation: "items.0.name" → name: ["items", 0, "name"]
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* form.setFields(getFormErrors(error))
|
|
45
|
+
*/
|
|
46
|
+
declare function getFormErrors(error: unknown): FormFieldError[];
|
|
47
|
+
/**
|
|
48
|
+
* Get general validation message from Laravel 422 response
|
|
49
|
+
* @example "The name_lastname field is required. (and 3 more errors)"
|
|
50
|
+
*/
|
|
51
|
+
declare function getValidationMessage(error: unknown): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Get first error message from validation errors
|
|
54
|
+
* Useful when field names don't match form fields
|
|
55
|
+
*/
|
|
56
|
+
declare function getFirstValidationError(error: unknown): string | null;
|
|
57
|
+
declare function useFormMutation<TData, TResult = unknown>({ form, mutationFn, invalidateKeys, successMessage, redirectTo, router, translateFn, onSuccess, onError, }: UseFormMutationOptions<TData, TResult>): _tanstack_react_query.UseMutationResult<TResult, Error, TData, unknown>;
|
|
19
58
|
|
|
20
|
-
export { type UseFormMutationOptions, useFormMutation };
|
|
59
|
+
export { type FormFieldError, type FormMutationRouter, type TranslateFn, type UseFormMutationOptions, getFirstValidationError, getFormErrors, getValidationMessage, useFormMutation };
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
2
|
import { FormInstance } from 'antd';
|
|
3
3
|
|
|
4
|
+
/** Router interface - compatible with Next.js useRouter */
|
|
5
|
+
interface FormMutationRouter {
|
|
6
|
+
push: (path: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/** Translation function - compatible with next-intl useTranslations */
|
|
9
|
+
type TranslateFn = (key: string) => string;
|
|
4
10
|
interface UseFormMutationOptions<TData, TResult> {
|
|
5
11
|
/** Ant Design form instance */
|
|
6
12
|
form: FormInstance;
|
|
@@ -8,13 +14,46 @@ interface UseFormMutationOptions<TData, TResult> {
|
|
|
8
14
|
mutationFn: (data: TData) => Promise<TResult>;
|
|
9
15
|
/** Query keys to invalidate on success */
|
|
10
16
|
invalidateKeys?: readonly (readonly unknown[])[];
|
|
11
|
-
/** Success message to show */
|
|
17
|
+
/** Success message to show (will be translated if translateFn provided) */
|
|
12
18
|
successMessage?: string;
|
|
19
|
+
/** Redirect path after success (requires router) */
|
|
20
|
+
redirectTo?: string;
|
|
21
|
+
/** Router instance for redirect (e.g., useRouter from next/navigation) */
|
|
22
|
+
router?: FormMutationRouter;
|
|
23
|
+
/** Translation function for messages (e.g., useTranslations from next-intl) */
|
|
24
|
+
translateFn?: TranslateFn;
|
|
13
25
|
/** Callback on success */
|
|
14
26
|
onSuccess?: (data: TResult) => void;
|
|
15
27
|
/** Callback on error */
|
|
16
28
|
onError?: (error: unknown) => void;
|
|
17
29
|
}
|
|
18
|
-
|
|
30
|
+
/** Form field error for Ant Design */
|
|
31
|
+
interface FormFieldError {
|
|
32
|
+
name: string | (string | number)[];
|
|
33
|
+
errors: string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse Laravel validation errors to Ant Design form format
|
|
37
|
+
*
|
|
38
|
+
* Supports:
|
|
39
|
+
* - Simple fields: "email" → name: "email"
|
|
40
|
+
* - Dot notation: "user.name" → name: ["user", "name"]
|
|
41
|
+
* - Array notation: "items.0.name" → name: ["items", 0, "name"]
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* form.setFields(getFormErrors(error))
|
|
45
|
+
*/
|
|
46
|
+
declare function getFormErrors(error: unknown): FormFieldError[];
|
|
47
|
+
/**
|
|
48
|
+
* Get general validation message from Laravel 422 response
|
|
49
|
+
* @example "The name_lastname field is required. (and 3 more errors)"
|
|
50
|
+
*/
|
|
51
|
+
declare function getValidationMessage(error: unknown): string | null;
|
|
52
|
+
/**
|
|
53
|
+
* Get first error message from validation errors
|
|
54
|
+
* Useful when field names don't match form fields
|
|
55
|
+
*/
|
|
56
|
+
declare function getFirstValidationError(error: unknown): string | null;
|
|
57
|
+
declare function useFormMutation<TData, TResult = unknown>({ form, mutationFn, invalidateKeys, successMessage, redirectTo, router, translateFn, onSuccess, onError, }: UseFormMutationOptions<TData, TResult>): _tanstack_react_query.UseMutationResult<TResult, Error, TData, unknown>;
|
|
19
58
|
|
|
20
|
-
export { type UseFormMutationOptions, useFormMutation };
|
|
59
|
+
export { type FormFieldError, type FormMutationRouter, type TranslateFn, type UseFormMutationOptions, getFirstValidationError, getFormErrors, getValidationMessage, useFormMutation };
|
package/dist/hooks/index.js
CHANGED
|
@@ -5,20 +5,31 @@ function getFormErrors(error) {
|
|
|
5
5
|
const data = error?.response?.data;
|
|
6
6
|
const errors = data?.errors;
|
|
7
7
|
if (!errors || typeof errors !== "object") return [];
|
|
8
|
-
return Object.entries(errors).map(([
|
|
9
|
-
name
|
|
8
|
+
return Object.entries(errors).map(([fieldName, messages]) => ({
|
|
9
|
+
// Convert "user.name" or "items.0.name" to array path for Ant Design
|
|
10
|
+
name: fieldName.includes(".") ? fieldName.split(".").map((part) => /^\d+$/.test(part) ? parseInt(part, 10) : part) : fieldName,
|
|
10
11
|
errors: Array.isArray(messages) ? messages : [String(messages)]
|
|
11
12
|
}));
|
|
12
13
|
}
|
|
13
14
|
function getValidationMessage(error) {
|
|
14
|
-
const
|
|
15
|
-
|
|
15
|
+
const axiosError = error;
|
|
16
|
+
if (axiosError?.response?.status !== 422) return null;
|
|
17
|
+
return axiosError?.response?.data?.message ?? null;
|
|
18
|
+
}
|
|
19
|
+
function getFirstValidationError(error) {
|
|
20
|
+
const errors = error?.response?.data?.errors;
|
|
21
|
+
if (!errors || typeof errors !== "object") return null;
|
|
22
|
+
const firstField = Object.keys(errors)[0];
|
|
23
|
+
return firstField ? errors[firstField][0] : null;
|
|
16
24
|
}
|
|
17
25
|
function useFormMutation({
|
|
18
26
|
form,
|
|
19
27
|
mutationFn,
|
|
20
28
|
invalidateKeys = [],
|
|
21
29
|
successMessage,
|
|
30
|
+
redirectTo,
|
|
31
|
+
router,
|
|
32
|
+
translateFn,
|
|
22
33
|
onSuccess,
|
|
23
34
|
onError
|
|
24
35
|
}) {
|
|
@@ -31,7 +42,11 @@ function useFormMutation({
|
|
|
31
42
|
queryClient.invalidateQueries({ queryKey: [...key] });
|
|
32
43
|
});
|
|
33
44
|
if (successMessage) {
|
|
34
|
-
|
|
45
|
+
const msg = translateFn ? translateFn(successMessage) : successMessage;
|
|
46
|
+
message.success(msg);
|
|
47
|
+
}
|
|
48
|
+
if (redirectTo && router) {
|
|
49
|
+
router.push(redirectTo);
|
|
35
50
|
}
|
|
36
51
|
onSuccess?.(data);
|
|
37
52
|
},
|
|
@@ -49,6 +64,9 @@ function useFormMutation({
|
|
|
49
64
|
});
|
|
50
65
|
}
|
|
51
66
|
export {
|
|
67
|
+
getFirstValidationError,
|
|
68
|
+
getFormErrors,
|
|
69
|
+
getValidationMessage,
|
|
52
70
|
useFormMutation
|
|
53
71
|
};
|
|
54
72
|
//# sourceMappingURL=index.js.map
|
package/dist/hooks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/use-form-mutation.ts"],"sourcesContent":["/**\n * useFormMutation - Form mutation with auto Laravel error handling\n *\n * @example\n * ```typescript\n * import { useFormMutation } from '@famgia/omnify-react/
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/use-form-mutation.ts"],"sourcesContent":["/**\n * useFormMutation - Form mutation with auto Laravel error handling\n *\n * Features:\n * - Auto parse Laravel validation errors to Ant Design form format\n * - Support nested field paths (e.g., \"items.0.name\" → [\"items\", 0, \"name\"])\n * - Auto invalidate queries on success\n * - Optional redirect after success\n * - Optional i18n translation for messages\n *\n * @example\n * ```typescript\n * import { useFormMutation } from '@famgia/omnify-react';\n * import { useRouter } from 'next/navigation';\n * import { useTranslations } from 'next-intl';\n *\n * function MyForm() {\n * const [form] = Form.useForm();\n * const router = useRouter();\n * const t = useTranslations();\n *\n * const mutation = useFormMutation({\n * form,\n * mutationFn: (data) => api.post('/api/customers', data),\n * invalidateKeys: [['customers']],\n * successMessage: 'messages.saved',\n * redirectTo: '/customers',\n * router, // Optional: for redirect\n * translateFn: t, // Optional: for i18n\n * });\n *\n * return (\n * <Form form={form} onFinish={mutation.mutate}>\n * ...\n * <Button loading={mutation.isPending}>保存</Button>\n * </Form>\n * );\n * }\n * ```\n */\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { App } from 'antd';\nimport type { FormInstance } from 'antd';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Router interface - compatible with Next.js useRouter */\nexport interface FormMutationRouter {\n push: (path: string) => void;\n}\n\n/** Translation function - compatible with next-intl useTranslations */\nexport type TranslateFn = (key: string) => string;\n\nexport interface UseFormMutationOptions<TData, TResult> {\n /** Ant Design form instance */\n form: FormInstance;\n /** API call function */\n mutationFn: (data: TData) => Promise<TResult>;\n /** Query keys to invalidate on success */\n invalidateKeys?: readonly (readonly unknown[])[];\n /** Success message to show (will be translated if translateFn provided) */\n successMessage?: string;\n /** Redirect path after success (requires router) */\n redirectTo?: string;\n /** Router instance for redirect (e.g., useRouter from next/navigation) */\n router?: FormMutationRouter;\n /** Translation function for messages (e.g., useTranslations from next-intl) */\n translateFn?: TranslateFn;\n /** Callback on success */\n onSuccess?: (data: TResult) => void;\n /** Callback on error */\n onError?: (error: unknown) => void;\n}\n\n// =============================================================================\n// Laravel Error Helpers (exported for reuse)\n// =============================================================================\n\n/** Form field error for Ant Design */\nexport interface FormFieldError {\n name: string | (string | number)[];\n errors: string[];\n}\n\n/**\n * Parse Laravel validation errors to Ant Design form format\n *\n * Supports:\n * - Simple fields: \"email\" → name: \"email\"\n * - Dot notation: \"user.name\" → name: [\"user\", \"name\"]\n * - Array notation: \"items.0.name\" → name: [\"items\", 0, \"name\"]\n *\n * @example\n * form.setFields(getFormErrors(error))\n */\nexport function getFormErrors(error: unknown): FormFieldError[] {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const data = (error as any)?.response?.data;\n const errors = data?.errors;\n\n if (!errors || typeof errors !== 'object') return [];\n\n return Object.entries(errors).map(([fieldName, messages]) => ({\n // Convert \"user.name\" or \"items.0.name\" to array path for Ant Design\n name: fieldName.includes('.')\n ? fieldName.split('.').map((part) => (/^\\d+$/.test(part) ? parseInt(part, 10) : part))\n : fieldName,\n errors: Array.isArray(messages) ? (messages as string[]) : [String(messages)],\n }));\n}\n\n/**\n * Get general validation message from Laravel 422 response\n * @example \"The name_lastname field is required. (and 3 more errors)\"\n */\nexport function getValidationMessage(error: unknown): string | null {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const axiosError = error as any;\n\n // Only for 422 validation errors\n if (axiosError?.response?.status !== 422) return null;\n\n return axiosError?.response?.data?.message ?? null;\n}\n\n/**\n * Get first error message from validation errors\n * Useful when field names don't match form fields\n */\nexport function getFirstValidationError(error: unknown): string | null {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors = (error as any)?.response?.data?.errors;\n\n if (!errors || typeof errors !== 'object') return null;\n\n const firstField = Object.keys(errors)[0];\n return firstField ? errors[firstField][0] : null;\n}\n\n// =============================================================================\n// Hook\n// =============================================================================\n\nexport function useFormMutation<TData, TResult = unknown>({\n form,\n mutationFn,\n invalidateKeys = [],\n successMessage,\n redirectTo,\n router,\n translateFn,\n onSuccess,\n onError,\n}: UseFormMutationOptions<TData, TResult>) {\n const queryClient = useQueryClient();\n const { message } = App.useApp();\n\n return useMutation({\n mutationFn,\n onSuccess: (data) => {\n // Invalidate queries\n invalidateKeys.forEach((key) => {\n queryClient.invalidateQueries({ queryKey: [...key] });\n });\n\n // Show success message (translate if translateFn provided)\n if (successMessage) {\n const msg = translateFn ? translateFn(successMessage) : successMessage;\n message.success(msg);\n }\n\n // Redirect if router and redirectTo provided\n if (redirectTo && router) {\n router.push(redirectTo);\n }\n\n // Custom callback\n onSuccess?.(data);\n },\n onError: (error) => {\n // Set form field errors from Laravel validation\n const formErrors = getFormErrors(error);\n if (formErrors.length > 0) {\n form.setFields(formErrors);\n }\n\n // Show general validation message (from Laravel)\n const validationMessage = getValidationMessage(error);\n if (validationMessage) {\n message.error(validationMessage);\n }\n\n // Custom callback\n onError?.(error);\n },\n });\n}\n"],"mappings":";AAyCA,SAAS,aAAa,sBAAsB;AAC5C,SAAS,WAAW;AAyDb,SAAS,cAAc,OAAkC;AAE9D,QAAM,OAAQ,OAAe,UAAU;AACvC,QAAM,SAAS,MAAM;AAErB,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO,CAAC;AAEnD,SAAO,OAAO,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,WAAW,QAAQ,OAAO;AAAA;AAAA,IAE5D,MAAM,UAAU,SAAS,GAAG,IACxB,UAAU,MAAM,GAAG,EAAE,IAAI,CAAC,SAAU,QAAQ,KAAK,IAAI,IAAI,SAAS,MAAM,EAAE,IAAI,IAAK,IACnF;AAAA,IACJ,QAAQ,MAAM,QAAQ,QAAQ,IAAK,WAAwB,CAAC,OAAO,QAAQ,CAAC;AAAA,EAC9E,EAAE;AACJ;AAMO,SAAS,qBAAqB,OAA+B;AAElE,QAAM,aAAa;AAGnB,MAAI,YAAY,UAAU,WAAW,IAAK,QAAO;AAEjD,SAAO,YAAY,UAAU,MAAM,WAAW;AAChD;AAMO,SAAS,wBAAwB,OAA+B;AAErE,QAAM,SAAU,OAAe,UAAU,MAAM;AAE/C,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAElD,QAAM,aAAa,OAAO,KAAK,MAAM,EAAE,CAAC;AACxC,SAAO,aAAa,OAAO,UAAU,EAAE,CAAC,IAAI;AAC9C;AAMO,SAAS,gBAA0C;AAAA,EACxD;AAAA,EACA;AAAA,EACA,iBAAiB,CAAC;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2C;AACzC,QAAM,cAAc,eAAe;AACnC,QAAM,EAAE,QAAQ,IAAI,IAAI,OAAO;AAE/B,SAAO,YAAY;AAAA,IACjB;AAAA,IACA,WAAW,CAAC,SAAS;AAEnB,qBAAe,QAAQ,CAAC,QAAQ;AAC9B,oBAAY,kBAAkB,EAAE,UAAU,CAAC,GAAG,GAAG,EAAE,CAAC;AAAA,MACtD,CAAC;AAGD,UAAI,gBAAgB;AAClB,cAAM,MAAM,cAAc,YAAY,cAAc,IAAI;AACxD,gBAAQ,QAAQ,GAAG;AAAA,MACrB;AAGA,UAAI,cAAc,QAAQ;AACxB,eAAO,KAAK,UAAU;AAAA,MACxB;AAGA,kBAAY,IAAI;AAAA,IAClB;AAAA,IACA,SAAS,CAAC,UAAU;AAElB,YAAM,aAAa,cAAc,KAAK;AACtC,UAAI,WAAW,SAAS,GAAG;AACzB,aAAK,UAAU,UAAU;AAAA,MAC3B;AAGA,YAAM,oBAAoB,qBAAqB,KAAK;AACpD,UAAI,mBAAmB;AACrB,gBAAQ,MAAM,iBAAiB;AAAA,MACjC;AAGA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -32,10 +32,14 @@ __export(src_exports, {
|
|
|
32
32
|
KATAKANA_HALF_WIDTH: () => KATAKANA_HALF_WIDTH,
|
|
33
33
|
KATAKANA_PATTERN: () => KATAKANA_PATTERN,
|
|
34
34
|
KATAKANA_WITH_NUMBERS: () => KATAKANA_WITH_NUMBERS,
|
|
35
|
+
OmnifyForm: () => OmnifyForm,
|
|
35
36
|
addZodMessages: () => addZodMessages,
|
|
36
37
|
createKanaRegex: () => createKanaRegex,
|
|
38
|
+
getFirstValidationError: () => getFirstValidationError,
|
|
39
|
+
getFormErrors: () => getFormErrors,
|
|
37
40
|
getKanaErrorMessage: () => getKanaErrorMessage,
|
|
38
41
|
getKanaPattern: () => getKanaPattern,
|
|
42
|
+
getValidationMessage: () => getValidationMessage,
|
|
39
43
|
getZodFallbackLocale: () => getZodFallbackLocale,
|
|
40
44
|
getZodLocale: () => getZodLocale,
|
|
41
45
|
getZodMessage: () => getZodMessage,
|
|
@@ -747,20 +751,31 @@ function getFormErrors(error) {
|
|
|
747
751
|
const data = error?.response?.data;
|
|
748
752
|
const errors = data?.errors;
|
|
749
753
|
if (!errors || typeof errors !== "object") return [];
|
|
750
|
-
return Object.entries(errors).map(([
|
|
751
|
-
name
|
|
754
|
+
return Object.entries(errors).map(([fieldName, messages]) => ({
|
|
755
|
+
// Convert "user.name" or "items.0.name" to array path for Ant Design
|
|
756
|
+
name: fieldName.includes(".") ? fieldName.split(".").map((part) => /^\d+$/.test(part) ? parseInt(part, 10) : part) : fieldName,
|
|
752
757
|
errors: Array.isArray(messages) ? messages : [String(messages)]
|
|
753
758
|
}));
|
|
754
759
|
}
|
|
755
760
|
function getValidationMessage(error) {
|
|
756
|
-
const
|
|
757
|
-
|
|
761
|
+
const axiosError = error;
|
|
762
|
+
if (axiosError?.response?.status !== 422) return null;
|
|
763
|
+
return axiosError?.response?.data?.message ?? null;
|
|
764
|
+
}
|
|
765
|
+
function getFirstValidationError(error) {
|
|
766
|
+
const errors = error?.response?.data?.errors;
|
|
767
|
+
if (!errors || typeof errors !== "object") return null;
|
|
768
|
+
const firstField = Object.keys(errors)[0];
|
|
769
|
+
return firstField ? errors[firstField][0] : null;
|
|
758
770
|
}
|
|
759
771
|
function useFormMutation({
|
|
760
772
|
form,
|
|
761
773
|
mutationFn,
|
|
762
774
|
invalidateKeys = [],
|
|
763
775
|
successMessage,
|
|
776
|
+
redirectTo,
|
|
777
|
+
router,
|
|
778
|
+
translateFn,
|
|
764
779
|
onSuccess,
|
|
765
780
|
onError
|
|
766
781
|
}) {
|
|
@@ -773,7 +788,11 @@ function useFormMutation({
|
|
|
773
788
|
queryClient.invalidateQueries({ queryKey: [...key] });
|
|
774
789
|
});
|
|
775
790
|
if (successMessage) {
|
|
776
|
-
|
|
791
|
+
const msg = translateFn ? translateFn(successMessage) : successMessage;
|
|
792
|
+
message2.success(msg);
|
|
793
|
+
}
|
|
794
|
+
if (redirectTo && router) {
|
|
795
|
+
router.push(redirectTo);
|
|
777
796
|
}
|
|
778
797
|
onSuccess?.(data);
|
|
779
798
|
},
|
|
@@ -967,6 +986,13 @@ var kanaRules = {
|
|
|
967
986
|
any: KANA_ANY_PATTERN
|
|
968
987
|
}
|
|
969
988
|
};
|
|
989
|
+
|
|
990
|
+
// src/index.ts
|
|
991
|
+
var OmnifyForm = {
|
|
992
|
+
JapaneseName: JapaneseNameField,
|
|
993
|
+
JapaneseAddress: JapaneseAddressField,
|
|
994
|
+
JapaneseBank: JapaneseBankField
|
|
995
|
+
};
|
|
970
996
|
// Annotate the CommonJS export names for ESM import in node:
|
|
971
997
|
0 && (module.exports = {
|
|
972
998
|
HIRAGANA,
|
|
@@ -981,10 +1007,14 @@ var kanaRules = {
|
|
|
981
1007
|
KATAKANA_HALF_WIDTH,
|
|
982
1008
|
KATAKANA_PATTERN,
|
|
983
1009
|
KATAKANA_WITH_NUMBERS,
|
|
1010
|
+
OmnifyForm,
|
|
984
1011
|
addZodMessages,
|
|
985
1012
|
createKanaRegex,
|
|
1013
|
+
getFirstValidationError,
|
|
1014
|
+
getFormErrors,
|
|
986
1015
|
getKanaErrorMessage,
|
|
987
1016
|
getKanaPattern,
|
|
1017
|
+
getValidationMessage,
|
|
988
1018
|
getZodFallbackLocale,
|
|
989
1019
|
getZodLocale,
|
|
990
1020
|
getZodMessage,
|