@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.
@@ -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(([name, messages]) => ({
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 data = error?.response?.data;
41
- return data?.message || null;
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
- message.success(successMessage);
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
@@ -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 { useFormMutation, type UseFormMutationOptions } from './use-form-mutation';\n","/**\n * useFormMutation - Form mutation with auto Laravel error handling\n *\n * @example\n * ```typescript\n * import { useFormMutation } from '@famgia/omnify-react/hooks';\n *\n * const mutation = useFormMutation({\n * form,\n * mutationFn: (data) => axios.post('/api/customers', data),\n * invalidateKeys: [['customers']],\n * successMessage: '保存しました',\n * });\n *\n * <Form form={form} onFinish={mutation.mutate}>\n * ...\n * <Button loading={mutation.isPending}>保存</Button>\n * </Form>\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\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 */\n successMessage?: string;\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\n// =============================================================================\n\n/**\n * Parse Laravel validation errors to Ant Design form format\n */\nfunction getFormErrors(error: unknown): { name: string; errors: string[] }[] {\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(([name, messages]) => ({\n name,\n errors: Array.isArray(messages) ? messages : [String(messages)],\n }));\n}\n\n/**\n * Get general validation message from Laravel response\n */\nfunction getValidationMessage(error: unknown): string | null {\n const data = (error as any)?.response?.data;\n return data?.message || null;\n}\n\n// =============================================================================\n// Hook\n// =============================================================================\n\nexport function useFormMutation<TData, TResult = unknown>({\n form,\n mutationFn,\n invalidateKeys = [],\n successMessage,\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\n if (successMessage) {\n message.success(successMessage);\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 error message\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;;;ACqBA,yBAA4C;AAC5C,kBAAoB;AA6BpB,SAAS,cAAc,OAAsD;AAC3E,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,MAAM,QAAQ,OAAO;AAAA,IACvD;AAAA,IACA,QAAQ,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,OAAO,QAAQ,CAAC;AAAA,EAChE,EAAE;AACJ;AAKA,SAAS,qBAAqB,OAA+B;AAC3D,QAAM,OAAQ,OAAe,UAAU;AACvC,SAAO,MAAM,WAAW;AAC1B;AAMO,SAAS,gBAA0C;AAAA,EACxD;AAAA,EACA;AAAA,EACA,iBAAiB,CAAC;AAAA,EAClB;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,gBAAQ,QAAQ,cAAc;AAAA,MAChC;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":[]}
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":[]}
@@ -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
- declare function useFormMutation<TData, TResult = unknown>({ form, mutationFn, invalidateKeys, successMessage, onSuccess, onError, }: UseFormMutationOptions<TData, TResult>): _tanstack_react_query.UseMutationResult<TResult, Error, TData, unknown>;
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 };
@@ -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
- declare function useFormMutation<TData, TResult = unknown>({ form, mutationFn, invalidateKeys, successMessage, onSuccess, onError, }: UseFormMutationOptions<TData, TResult>): _tanstack_react_query.UseMutationResult<TResult, Error, TData, unknown>;
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 };
@@ -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(([name, messages]) => ({
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 data = error?.response?.data;
15
- return data?.message || null;
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
- message.success(successMessage);
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
@@ -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/hooks';\n *\n * const mutation = useFormMutation({\n * form,\n * mutationFn: (data) => axios.post('/api/customers', data),\n * invalidateKeys: [['customers']],\n * successMessage: '保存しました',\n * });\n *\n * <Form form={form} onFinish={mutation.mutate}>\n * ...\n * <Button loading={mutation.isPending}>保存</Button>\n * </Form>\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\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 */\n successMessage?: string;\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\n// =============================================================================\n\n/**\n * Parse Laravel validation errors to Ant Design form format\n */\nfunction getFormErrors(error: unknown): { name: string; errors: string[] }[] {\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(([name, messages]) => ({\n name,\n errors: Array.isArray(messages) ? messages : [String(messages)],\n }));\n}\n\n/**\n * Get general validation message from Laravel response\n */\nfunction getValidationMessage(error: unknown): string | null {\n const data = (error as any)?.response?.data;\n return data?.message || null;\n}\n\n// =============================================================================\n// Hook\n// =============================================================================\n\nexport function useFormMutation<TData, TResult = unknown>({\n form,\n mutationFn,\n invalidateKeys = [],\n successMessage,\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\n if (successMessage) {\n message.success(successMessage);\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 error message\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":";AAqBA,SAAS,aAAa,sBAAsB;AAC5C,SAAS,WAAW;AA6BpB,SAAS,cAAc,OAAsD;AAC3E,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,MAAM,QAAQ,OAAO;AAAA,IACvD;AAAA,IACA,QAAQ,MAAM,QAAQ,QAAQ,IAAI,WAAW,CAAC,OAAO,QAAQ,CAAC;AAAA,EAChE,EAAE;AACJ;AAKA,SAAS,qBAAqB,OAA+B;AAC3D,QAAM,OAAQ,OAAe,UAAU;AACvC,SAAO,MAAM,WAAW;AAC1B;AAMO,SAAS,gBAA0C;AAAA,EACxD;AAAA,EACA;AAAA,EACA,iBAAiB,CAAC;AAAA,EAClB;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,gBAAQ,QAAQ,cAAc;AAAA,MAChC;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":[]}
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(([name, messages]) => ({
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 data = error?.response?.data;
757
- return data?.message || null;
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
- message2.success(successMessage);
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,