generator-kodly-react-app 1.0.15 → 1.0.16

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.
Files changed (90) hide show
  1. package/generators/app/index.js +4 -0
  2. package/generators/app/templates/I18N_MIGRATION.md +13 -13
  3. package/generators/app/templates/STRUCTURE.md +26 -26
  4. package/generators/app/templates/index.html +6 -14
  5. package/generators/app/templates/openapi-ts.config.ts +12 -12
  6. package/generators/app/templates/postcss.config.js +0 -1
  7. package/generators/app/templates/src/app.tsx +20 -20
  8. package/generators/app/templates/src/components/layout/language-switcher.tsx +20 -16
  9. package/generators/app/templates/src/components/layout/page-layout.tsx +31 -0
  10. package/generators/app/templates/src/components/layout/theme-switcher.tsx +15 -14
  11. package/generators/app/templates/src/components/theme/theme-provider.tsx +20 -13
  12. package/generators/app/templates/src/components/ui/button.tsx +15 -14
  13. package/generators/app/templates/src/components/ui/card.tsx +23 -16
  14. package/generators/app/templates/src/components/ui/field.tsx +35 -35
  15. package/generators/app/templates/src/components/ui/form.tsx +66 -30
  16. package/generators/app/templates/src/components/ui/input-group.tsx +24 -24
  17. package/generators/app/templates/src/components/ui/input.tsx +8 -8
  18. package/generators/app/templates/src/components/ui/label.tsx +9 -9
  19. package/generators/app/templates/src/components/ui/select.tsx +0 -1
  20. package/generators/app/templates/src/components/ui/separator.tsx +10 -10
  21. package/generators/app/templates/src/components/ui/textarea.tsx +7 -7
  22. package/generators/app/templates/src/index.css +12 -2
  23. package/generators/app/templates/src/lib/i18n.ts +16 -14
  24. package/generators/app/templates/src/lib/routes.ts +2 -3
  25. package/generators/app/templates/src/lib/utils/error-handler.ts +21 -14
  26. package/generators/app/templates/src/lib/utils.ts +3 -3
  27. package/generators/app/templates/src/locales/index.ts +12 -12
  28. package/generators/app/templates/src/locales/ja/theme.json +0 -1
  29. package/generators/app/templates/src/main.tsx +10 -12
  30. package/generators/app/templates/src/modules/app/layouts/app-layout.tsx +14 -25
  31. package/generators/app/templates/src/modules/app/locales/app-en.json +0 -1
  32. package/generators/app/templates/src/modules/app/locales/app-ja.json +0 -1
  33. package/generators/app/templates/src/modules/auth/components/auth-form.tsx +83 -0
  34. package/generators/app/templates/src/modules/auth/components/forgot-password-form.tsx +27 -52
  35. package/generators/app/templates/src/modules/auth/components/login-form.tsx +42 -72
  36. package/generators/app/templates/src/modules/auth/components/login-link.tsx +12 -0
  37. package/generators/app/templates/src/modules/auth/components/reset-password-form.tsx +46 -92
  38. package/generators/app/templates/src/modules/auth/components/signup-form.tsx +35 -71
  39. package/generators/app/templates/src/modules/auth/contexts/auth-context.tsx +10 -6
  40. package/generators/app/templates/src/modules/auth/hooks/use-auth-hook.ts +51 -36
  41. package/generators/app/templates/src/modules/auth/layouts/auth-layout.tsx +12 -15
  42. package/generators/app/templates/src/modules/auth/locales/auth-en.json +0 -1
  43. package/generators/app/templates/src/modules/auth/locales/auth-ja.json +0 -1
  44. package/generators/app/templates/src/modules/auth/schemas/form-schemas.ts +14 -10
  45. package/generators/app/templates/src/modules/landing/components/auth-hero.tsx +20 -18
  46. package/generators/app/templates/src/modules/landing/components/welcome-hero.tsx +13 -11
  47. package/generators/app/templates/src/modules/landing/landing-page-layout.tsx +1 -24
  48. package/generators/app/templates/src/modules/landing/landing-page.tsx +11 -6
  49. package/generators/app/templates/src/modules/landing/layouts/landing-page-layout.tsx +2 -21
  50. package/generators/app/templates/src/modules/landing/locales/landing-en.json +0 -1
  51. package/generators/app/templates/src/openapi-client-config.ts +1 -1
  52. package/generators/app/templates/src/routeTree.gen.ts +188 -188
  53. package/generators/app/templates/src/router.tsx +2 -2
  54. package/generators/app/templates/src/routes/$.tsx +0 -1
  55. package/generators/app/templates/src/routes/__root.tsx +2 -2
  56. package/generators/app/templates/src/routes/_landing/index.tsx +3 -3
  57. package/generators/app/templates/src/routes/_landing/route.tsx +3 -3
  58. package/generators/app/templates/src/routes/app/index.tsx +6 -6
  59. package/generators/app/templates/src/routes/app/route.tsx +4 -4
  60. package/generators/app/templates/src/routes/auth/forgot-password.tsx +1 -1
  61. package/generators/app/templates/src/routes/auth/login.tsx +3 -3
  62. package/generators/app/templates/src/routes/auth/reset-password.tsx +3 -3
  63. package/generators/app/templates/src/routes/auth/route.tsx +12 -7
  64. package/generators/app/templates/src/routes/auth/signup.tsx +1 -2
  65. package/generators/app/templates/src/sdk/@tanstack/react-query.gen.ts +157 -76
  66. package/generators/app/templates/src/sdk/client/client.gen.ts +35 -37
  67. package/generators/app/templates/src/sdk/client/index.ts +8 -8
  68. package/generators/app/templates/src/sdk/client/types.gen.ts +22 -19
  69. package/generators/app/templates/src/sdk/client/utils.gen.ts +53 -45
  70. package/generators/app/templates/src/sdk/client.gen.ts +18 -5
  71. package/generators/app/templates/src/sdk/core/auth.gen.ts +6 -6
  72. package/generators/app/templates/src/sdk/core/bodySerializer.gen.ts +25 -27
  73. package/generators/app/templates/src/sdk/core/params.gen.ts +12 -12
  74. package/generators/app/templates/src/sdk/core/pathSerializer.gen.ts +40 -41
  75. package/generators/app/templates/src/sdk/core/queryKeySerializer.gen.ts +12 -29
  76. package/generators/app/templates/src/sdk/core/serverSentEvents.gen.ts +26 -24
  77. package/generators/app/templates/src/sdk/core/types.gen.ts +17 -15
  78. package/generators/app/templates/src/sdk/core/utils.gen.ts +18 -18
  79. package/generators/app/templates/src/sdk/index.ts +40 -2
  80. package/generators/app/templates/src/sdk/schemas.gen.ts +108 -166
  81. package/generators/app/templates/src/sdk/sdk.gen.ts +145 -63
  82. package/generators/app/templates/src/sdk/types.gen.ts +90 -84
  83. package/generators/app/templates/src/sdk/zod.gen.ts +57 -74
  84. package/generators/app/templates/src/types/i18n.d.ts +0 -1
  85. package/generators/app/templates/tailwind.config.js +0 -1
  86. package/generators/app/templates/tsconfig.app.json +0 -1
  87. package/generators/app/templates/tsconfig.json +0 -1
  88. package/generators/app/templates/tsr.config.json +4 -0
  89. package/generators/app/templates/vite.config.js +12 -12
  90. package/package.json +2 -4
@@ -1,9 +1,9 @@
1
- "use client"
1
+ "use client";
2
2
 
3
- import * as React from "react"
4
- import * as SeparatorPrimitive from "@radix-ui/react-separator"
3
+ import * as React from "react";
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator";
5
5
 
6
- import { cn } from "@/lib/utils"
6
+ import { cn } from "@/lib/utils";
7
7
 
8
8
  const Separator = React.forwardRef<
9
9
  React.ElementRef<typeof SeparatorPrimitive.Root>,
@@ -11,7 +11,7 @@ const Separator = React.forwardRef<
11
11
  >(
12
12
  (
13
13
  { className, orientation = "horizontal", decorative = true, ...props },
14
- ref
14
+ ref,
15
15
  ) => (
16
16
  <SeparatorPrimitive.Root
17
17
  ref={ref}
@@ -20,12 +20,12 @@ const Separator = React.forwardRef<
20
20
  className={cn(
21
21
  "shrink-0 bg-border",
22
22
  orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
23
- className
23
+ className,
24
24
  )}
25
25
  {...props}
26
26
  />
27
- )
28
- )
29
- Separator.displayName = SeparatorPrimitive.Root.displayName
27
+ ),
28
+ );
29
+ Separator.displayName = SeparatorPrimitive.Root.displayName;
30
30
 
31
- export { Separator }
31
+ export { Separator };
@@ -1,6 +1,6 @@
1
- import * as React from "react"
1
+ import * as React from "react";
2
2
 
3
- import { cn } from "@/lib/utils"
3
+ import { cn } from "@/lib/utils";
4
4
 
5
5
  const Textarea = React.forwardRef<
6
6
  HTMLTextAreaElement,
@@ -10,13 +10,13 @@ const Textarea = React.forwardRef<
10
10
  <textarea
11
11
  className={cn(
12
12
  "flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
13
- className
13
+ className,
14
14
  )}
15
15
  ref={ref}
16
16
  {...props}
17
17
  />
18
- )
19
- })
20
- Textarea.displayName = "Textarea"
18
+ );
19
+ });
20
+ Textarea.displayName = "Textarea";
21
21
 
22
- export { Textarea }
22
+ export { Textarea };
@@ -7,7 +7,18 @@
7
7
 
8
8
  body {
9
9
  @apply m-0;
10
- font-family: system-ui, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
10
+ font-family:
11
+ system-ui,
12
+ -apple-system,
13
+ "Segoe UI",
14
+ "Roboto",
15
+ "Oxygen",
16
+ "Ubuntu",
17
+ "Cantarell",
18
+ "Fira Sans",
19
+ "Droid Sans",
20
+ "Helvetica Neue",
21
+ sans-serif;
11
22
  -webkit-font-smoothing: antialiased;
12
23
  -moz-osx-font-smoothing: grayscale;
13
24
  }
@@ -133,4 +144,3 @@ body {
133
144
  @apply bg-background text-foreground;
134
145
  }
135
146
  }
136
-
@@ -1,17 +1,19 @@
1
- import i18n from 'i18next';
2
- import { initReactI18next } from 'react-i18next';
3
- import { mergeTranslations } from '@/locales';
4
- import * as z from 'zod';
5
- import { setLocaleInClient } from '@/modules/auth/hooks/use-auth-hook';
1
+ import i18n from "i18next";
2
+ import { initReactI18next } from "react-i18next";
3
+ import { mergeTranslations } from "@/locales";
4
+ import * as z from "zod";
5
+ import { setLocaleInClient } from "@/modules/auth/hooks/use-auth-hook";
6
6
 
7
- export type SupportedLanguages = 'en' | 'ja';
8
- const supportedLanguages: SupportedLanguages[] = ['en', 'ja'];
9
- const defaultLanguage: SupportedLanguages = 'en';
10
- const localStorageKey = 'app-language';
7
+ export type SupportedLanguages = "en" | "ja";
8
+ const supportedLanguages: SupportedLanguages[] = ["en", "ja"];
9
+ const defaultLanguage: SupportedLanguages = "en";
10
+ const localStorageKey = "app-language";
11
11
 
12
12
  const setLanguageHooks = (language: SupportedLanguages) => {
13
13
  setLocaleInClient(language);
14
- z.config((z.locales[language] ?? z.locales[defaultLanguage] ?? z.locales['en'])());
14
+ z.config(
15
+ (z.locales[language] ?? z.locales[defaultLanguage] ?? z.locales["en"])(),
16
+ );
15
17
  };
16
18
 
17
19
  const getStoredLanguage = (): SupportedLanguages => {
@@ -29,8 +31,8 @@ const getStoredLanguage = (): SupportedLanguages => {
29
31
 
30
32
  if (!i18n.isInitialized) {
31
33
  const lng = getStoredLanguage();
32
- const enResources = mergeTranslations('en');
33
- const jaResources = mergeTranslations('ja');
34
+ const enResources = mergeTranslations("en");
35
+ const jaResources = mergeTranslations("ja");
34
36
  i18n.use(initReactI18next).init({
35
37
  resources: {
36
38
  en: {
@@ -42,8 +44,8 @@ if (!i18n.isInitialized) {
42
44
  ...jaResources,
43
45
  },
44
46
  },
45
- defaultNS: 'translation',
46
- ns: ['translation', 'auth', 'app', 'landing', 'common', 'theme'],
47
+ defaultNS: "translation",
48
+ ns: ["translation", "auth", "app", "landing", "common", "theme"],
47
49
  supportedLngs: supportedLanguages,
48
50
  lng,
49
51
  fallbackLng: defaultLanguage,
@@ -5,10 +5,9 @@
5
5
  /**
6
6
  * Default route for authenticated users
7
7
  */
8
- export const DEFAULT_LOGGED_IN_ROUTE = '/app';
8
+ export const DEFAULT_LOGGED_IN_ROUTE = "/app";
9
9
 
10
10
  /**
11
11
  * Default route for non-authenticated users (landing page)
12
12
  */
13
- export const DEFAULT_NON_LOGGED_IN_ROUTE = '/';
14
-
13
+ export const DEFAULT_NON_LOGGED_IN_ROUTE = "/";
@@ -1,5 +1,5 @@
1
- import { toast } from 'sonner';
2
- import { AxiosError } from 'axios';
1
+ import { toast } from "sonner";
2
+ import { AxiosError } from "axios";
3
3
 
4
4
  interface BackendErrorResponse {
5
5
  message?: string;
@@ -22,7 +22,7 @@ interface BackendErrorResponse {
22
22
  */
23
23
  export const handleBackendError = (error: unknown): void => {
24
24
  // Check if it's an AxiosError
25
- if (error instanceof Error && 'isAxiosError' in error) {
25
+ if (error instanceof Error && "isAxiosError" in error) {
26
26
  const axiosError = error as AxiosError<BackendErrorResponse>;
27
27
  const response = axiosError.response;
28
28
 
@@ -31,46 +31,53 @@ export const handleBackendError = (error: unknown): void => {
31
31
  const errorData = response.data;
32
32
 
33
33
  // Check if the response matches the expected format
34
- if (errorData && typeof errorData === 'object' && 'message' in errorData && 'status' in errorData) {
34
+ if (
35
+ errorData &&
36
+ typeof errorData === "object" &&
37
+ "message" in errorData &&
38
+ "status" in errorData
39
+ ) {
35
40
  // Use the message from the backend response
36
- const errorMessage = errorData.message || 'An error occurred';
41
+ const errorMessage = errorData.message || "An error occurred";
37
42
  toast.error(errorMessage);
38
43
  return;
39
44
  }
40
45
 
41
46
  // If the response doesn't match the expected format,
42
47
  // try to extract a message from the response data
43
- if (errorData && typeof errorData === 'object') {
48
+ if (errorData && typeof errorData === "object") {
44
49
  const errorObj = errorData as Record<string, unknown>;
45
50
  const message =
46
- (typeof errorObj.message === 'string' ? errorObj.message : null) ||
47
- (typeof errorObj.error === 'string' ? errorObj.error : null) ||
51
+ (typeof errorObj.message === "string" ? errorObj.message : null) ||
52
+ (typeof errorObj.error === "string" ? errorObj.error : null) ||
48
53
  response.statusText ||
49
- 'An error occurred';
54
+ "An error occurred";
50
55
  toast.error(message);
51
56
  return;
52
57
  }
53
58
  }
54
59
 
55
60
  // Handle network errors (no response) - connection refused, timeout, etc.
56
- if (axiosError.code === 'ERR_NETWORK' || !response) {
57
- const networkErrorMessage = axiosError.message || 'Network error. Please check your connection and try again.';
61
+ if (axiosError.code === "ERR_NETWORK" || !response) {
62
+ const networkErrorMessage =
63
+ axiosError.message ||
64
+ "Network error. Please check your connection and try again.";
58
65
  toast.error(networkErrorMessage);
59
66
  return;
60
67
  }
61
68
 
62
69
  // Fallback for other AxiosError cases
63
- const errorMessage = axiosError.message || 'An error occurred';
70
+ const errorMessage = axiosError.message || "An error occurred";
64
71
  toast.error(errorMessage);
65
72
  return;
66
73
  }
67
74
 
68
75
  // If it's not an axios error, show a generic message
69
76
  if (error instanceof Error) {
70
- toast.error(error.message || 'An error occurred');
77
+ toast.error(error.message || "An error occurred");
71
78
  return;
72
79
  }
73
80
 
74
81
  // Final fallback
75
- toast.error('An unexpected error occurred');
82
+ toast.error("An unexpected error occurred");
76
83
  };
@@ -1,6 +1,6 @@
1
- import { clsx, type ClassValue } from "clsx"
2
- import { twMerge } from "tailwind-merge"
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
3
 
4
4
  export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
5
+ return twMerge(clsx(inputs));
6
6
  }
@@ -1,17 +1,17 @@
1
- import { SupportedLanguages } from '@/lib/i18n';
2
- import enAuth from '@/modules/auth/locales/auth-en.json';
3
- import enApp from '@/modules/app/locales/app-en.json';
4
- import enLanding from '@/modules/landing/locales/landing-en.json';
5
- import enCommon from '@/locales/en/common.json';
6
- import enTheme from '@/locales/en/theme.json';
7
- import jaAuth from '@/modules/auth/locales/auth-ja.json';
8
- import jaApp from '@/modules/app/locales/app-ja.json';
9
- import jaLanding from '@/modules/landing/locales/landing-ja.json';
10
- import jaCommon from '@/locales/ja/common.json';
11
- import jaTheme from '@/locales/ja/theme.json';
1
+ import { SupportedLanguages } from "@/lib/i18n";
2
+ import enAuth from "@/modules/auth/locales/auth-en.json";
3
+ import enApp from "@/modules/app/locales/app-en.json";
4
+ import enLanding from "@/modules/landing/locales/landing-en.json";
5
+ import enCommon from "@/locales/en/common.json";
6
+ import enTheme from "@/locales/en/theme.json";
7
+ import jaAuth from "@/modules/auth/locales/auth-ja.json";
8
+ import jaApp from "@/modules/app/locales/app-ja.json";
9
+ import jaLanding from "@/modules/landing/locales/landing-ja.json";
10
+ import jaCommon from "@/locales/ja/common.json";
11
+ import jaTheme from "@/locales/ja/theme.json";
12
12
 
13
13
  export const mergeTranslations = (lang: SupportedLanguages) => {
14
- if (lang === 'en') {
14
+ if (lang === "en") {
15
15
  return {
16
16
  auth: { ...enAuth },
17
17
  app: { ...enApp },
@@ -3,4 +3,3 @@
3
3
  "dark": "ダーク",
4
4
  "system": "システム"
5
5
  }
6
-
@@ -1,27 +1,25 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom/client';
3
- import { I18nextProvider } from 'react-i18next';
4
- import i18n from '@/lib/i18n';
5
- import { ThemeProvider } from '@/components/theme/theme-provider';
6
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
7
- import App from '@/app';
1
+ import React from "react";
2
+ import ReactDOM from "react-dom/client";
3
+ import { I18nextProvider } from "react-i18next";
4
+ import i18n from "@/lib/i18n";
5
+ import { ThemeProvider } from "@/components/theme/theme-provider";
6
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
7
+ import App from "@/app";
8
8
 
9
9
  const queryClient = new QueryClient();
10
10
 
11
- const rootElement = document.getElementById('app');
11
+ const rootElement = document.getElementById("app");
12
12
  if (rootElement && !rootElement.innerHTML) {
13
13
  const root = ReactDOM.createRoot(rootElement);
14
14
  root.render(
15
15
  <React.StrictMode>
16
16
  <I18nextProvider i18n={i18n}>
17
- <ThemeProvider
18
- defaultTheme='light'
19
- storageKey='vite-ui-theme'>
17
+ <ThemeProvider defaultTheme="light" storageKey="vite-ui-theme">
20
18
  <QueryClientProvider client={queryClient}>
21
19
  <App />
22
20
  </QueryClientProvider>
23
21
  </ThemeProvider>
24
22
  </I18nextProvider>
25
- </React.StrictMode>
23
+ </React.StrictMode>,
26
24
  );
27
25
  }
@@ -1,8 +1,7 @@
1
- import { LanguageSwitcher } from '@/components/layout/language-switcher';
2
- import { ThemeSwitcher } from '@/components/layout/theme-switcher';
3
- import { Button } from '@/components/ui/button';
4
- import { useLogout } from '@/modules/auth/hooks/use-auth-hook';
5
- import { useTranslation } from 'react-i18next';
1
+ import { PageLayout } from "@/components/layout/page-layout";
2
+ import { Button } from "@/components/ui/button";
3
+ import { useLogout } from "@/modules/auth/hooks/use-auth-hook";
4
+ import { useTranslation } from "react-i18next";
6
5
 
7
6
  export function AppLayout({ children }: { children: React.ReactNode }) {
8
7
  const { t } = useTranslation();
@@ -13,25 +12,15 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
13
12
  };
14
13
 
15
14
  return (
16
- <div className='min-h-screen bg-background'>
17
- <header className='border-b'>
18
- <div className='container mx-auto flex h-16 items-center justify-between px-4'>
19
- <div className='flex items-center gap-4'>
20
- <h1 className='text-xl font-semibold'>{t('app.title')}</h1>
21
- </div>
22
- <div className='flex items-center gap-4'>
23
- <ThemeSwitcher />
24
- <LanguageSwitcher />
25
- <Button
26
- onClick={handleLogout}
27
- disabled={isPending}
28
- variant='outline'>
29
- {t('app.welcome.logout')}
30
- </Button>
31
- </div>
32
- </div>
33
- </header>
34
- <main className='container mx-auto p-8'>{children}</main>
35
- </div>
15
+ <PageLayout
16
+ mainClassName="container mx-auto p-8"
17
+ actions={
18
+ <Button onClick={handleLogout} disabled={isPending} variant="outline">
19
+ {t("app.welcome.logout")}
20
+ </Button>
21
+ }
22
+ >
23
+ {children}
24
+ </PageLayout>
36
25
  );
37
26
  }
@@ -6,4 +6,3 @@
6
6
  "logout": "Logout"
7
7
  }
8
8
  }
9
-
@@ -6,4 +6,3 @@
6
6
  "logout": "ログアウト"
7
7
  }
8
8
  }
9
-
@@ -0,0 +1,83 @@
1
+ import type { ReactNode } from "react";
2
+ import type {
3
+ FieldPath,
4
+ FieldValues,
5
+ SubmitHandler,
6
+ UseFormReturn,
7
+ } from "react-hook-form";
8
+ import { Button } from "@/components/ui/button";
9
+ import { Input } from "@/components/ui/input";
10
+ import {
11
+ Form,
12
+ FormControl,
13
+ FormField,
14
+ FormItem,
15
+ FormLabel,
16
+ FormMessage,
17
+ } from "@/components/ui/form";
18
+
19
+ type AuthField<T extends FieldValues> = {
20
+ name: FieldPath<T>;
21
+ label: string;
22
+ placeholder: string;
23
+ type: "text" | "email" | "password";
24
+ autoComplete: string;
25
+ autoFocus?: boolean;
26
+ readOnly?: boolean;
27
+ };
28
+
29
+ export function AuthForm<T extends FieldValues>({
30
+ form,
31
+ title,
32
+ fields,
33
+ onSubmit,
34
+ isPending,
35
+ submitLabel,
36
+ children,
37
+ }: {
38
+ form: UseFormReturn<T>;
39
+ title: string;
40
+ fields: AuthField<T>[];
41
+ onSubmit: SubmitHandler<T>;
42
+ isPending: boolean;
43
+ submitLabel: string;
44
+ children?: ReactNode;
45
+ }) {
46
+ return (
47
+ <div className="flex flex-col gap-6">
48
+ <h1 className="text-2xl font-semibold">{title}</h1>
49
+ <Form {...form}>
50
+ <form
51
+ noValidate
52
+ className="flex flex-col gap-4"
53
+ onSubmit={form.handleSubmit(onSubmit)}
54
+ >
55
+ {fields.map(({ name, label, ...input }) => (
56
+ <FormField
57
+ key={name}
58
+ control={form.control}
59
+ name={name}
60
+ render={({ field }) => (
61
+ <FormItem>
62
+ <FormLabel>{label} *</FormLabel>
63
+ <FormControl>
64
+ <Input
65
+ {...input}
66
+ {...field}
67
+ value={typeof field.value === "string" ? field.value : ""}
68
+ />
69
+ </FormControl>
70
+ <FormMessage />
71
+ </FormItem>
72
+ )}
73
+ />
74
+ ))}
75
+ <Button type="submit" disabled={isPending}>
76
+ {submitLabel}
77
+ </Button>
78
+ </form>
79
+ </Form>
80
+ {children}
81
+ </div>
82
+ );
83
+ }
@@ -1,14 +1,11 @@
1
- import { Button } from '@/components/ui/button';
2
- import { Input } from '@/components/ui/input';
3
- import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
4
- import { useSendOtp } from '@/modules/auth/hooks/use-auth-hook';
5
- import { z } from 'zod';
6
- import { zodResolver } from '@hookform/resolvers/zod';
7
- import { useForm } from 'react-hook-form';
8
- import { useTranslation } from 'react-i18next';
9
- import { Link } from '@tanstack/react-router';
10
- import { Route as LoginRoute } from '@/routes/auth/login';
11
- import { forgotPasswordFormDefaults } from '@/modules/auth/schemas/form-schemas';
1
+ import { AuthForm } from "./auth-form";
2
+ import { useSendOtp } from "@/modules/auth/hooks/use-auth-hook";
3
+ import { z } from "zod";
4
+ import { zodResolver } from "@hookform/resolvers/zod";
5
+ import { useForm } from "react-hook-form";
6
+ import { useTranslation } from "react-i18next";
7
+ import { LoginLink } from "./login-link";
8
+ import { forgotPasswordFormDefaults } from "@/modules/auth/schemas/form-schemas";
12
9
 
13
10
  const zForgotPasswordDto = z.object({
14
11
  email: z.email().min(1).max(256),
@@ -29,46 +26,24 @@ export function ForgotPasswordForm() {
29
26
  };
30
27
 
31
28
  return (
32
- <div className='flex flex-col gap-6'>
33
- <h1 className='text-2xl font-semibold'>{t('auth.forgotPassword.title')}</h1>
34
- <Form {...form}>
35
- <form
36
- noValidate
37
- className='flex flex-col gap-4'
38
- onSubmit={form.handleSubmit(onSubmit)}>
39
- <FormField
40
- control={form.control}
41
- name='email'
42
- render={({ field }) => (
43
- <FormItem>
44
- <FormLabel>{t('auth.forgotPassword.email')} *</FormLabel>
45
- <FormControl>
46
- <Input
47
- type='email'
48
- placeholder={t('auth.forgotPassword.emailPlaceholder')}
49
- autoComplete='email'
50
- autoFocus
51
- {...field}
52
- />
53
- </FormControl>
54
- <FormMessage />
55
- </FormItem>
56
- )}
57
- />
58
- <Button
59
- type='submit'
60
- disabled={isPending}>
61
- {t('auth.forgotPassword.submit')}
62
- </Button>
63
- </form>
64
- </Form>
65
- <div className='text-sm text-center'>
66
- <Link
67
- to={LoginRoute.to}
68
- className='text-primary hover:underline'>
69
- {t('auth.forgotPassword.loginLink')}
70
- </Link>
71
- </div>
72
- </div>
29
+ <AuthForm
30
+ form={form}
31
+ title={t("auth.forgotPassword.title")}
32
+ onSubmit={onSubmit}
33
+ isPending={isPending}
34
+ submitLabel={t("auth.forgotPassword.submit")}
35
+ fields={[
36
+ {
37
+ name: "email",
38
+ label: t("auth.forgotPassword.email"),
39
+ placeholder: t("auth.forgotPassword.emailPlaceholder"),
40
+ type: "email",
41
+ autoComplete: "email",
42
+ autoFocus: true,
43
+ },
44
+ ]}
45
+ >
46
+ <LoginLink label={t("auth.forgotPassword.loginLink")} />
47
+ </AuthForm>
73
48
  );
74
49
  }