generator-kodly-react-app 1.0.7 → 1.0.11

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 (101) hide show
  1. package/generators/app/index.js +63 -18
  2. package/generators/app/templates/.env.dev +4 -0
  3. package/generators/app/templates/.env.sandbox +4 -0
  4. package/generators/app/templates/STRUCTURE.md +661 -0
  5. package/generators/app/templates/components.json +22 -0
  6. package/generators/app/templates/gitignore.template +78 -2
  7. package/generators/app/templates/index.html +14 -6
  8. package/generators/app/templates/openapi-ts.config.ts +29 -0
  9. package/generators/app/templates/package.json +40 -26
  10. package/generators/app/templates/public/favicon.svg +4 -0
  11. package/generators/app/templates/src/app.tsx +8 -8
  12. package/generators/app/templates/src/components/layout/language-switcher.tsx +40 -0
  13. package/generators/app/templates/src/components/layout/theme-switcher.tsx +37 -0
  14. package/generators/app/templates/src/components/theme/theme-provider.tsx +22 -28
  15. package/generators/app/templates/src/components/ui/button.tsx +34 -32
  16. package/generators/app/templates/src/components/ui/card.tsx +76 -0
  17. package/generators/app/templates/src/components/ui/field.tsx +242 -0
  18. package/generators/app/templates/src/components/ui/form.tsx +129 -0
  19. package/generators/app/templates/src/components/ui/input-group.tsx +170 -0
  20. package/generators/app/templates/src/components/ui/input.tsx +19 -21
  21. package/generators/app/templates/src/components/ui/label.tsx +24 -0
  22. package/generators/app/templates/src/components/ui/select.tsx +184 -0
  23. package/generators/app/templates/src/components/ui/separator.tsx +31 -0
  24. package/generators/app/templates/src/components/ui/textarea.tsx +22 -0
  25. package/generators/app/templates/src/index.css +83 -26
  26. package/generators/app/templates/src/lib/i18n.ts +31 -16
  27. package/generators/app/templates/src/lib/routes.ts +14 -0
  28. package/generators/app/templates/src/lib/utils.ts +3 -4
  29. package/generators/app/templates/src/locales/en/common.json +5 -0
  30. package/generators/app/templates/src/locales/en/theme.json +5 -0
  31. package/generators/app/templates/src/locales/index.ts +31 -0
  32. package/generators/app/templates/src/locales/ja/common.json +5 -0
  33. package/generators/app/templates/src/locales/ja/theme.json +6 -0
  34. package/generators/app/templates/src/main.tsx +19 -15
  35. package/generators/app/templates/src/modules/app/layouts/app-layout.tsx +37 -0
  36. package/generators/app/templates/src/modules/app/locales/app-en.json +9 -0
  37. package/generators/app/templates/src/modules/app/locales/app-ja.json +9 -0
  38. package/generators/app/templates/src/modules/auth/components/forgot-password-form.tsx +74 -0
  39. package/generators/app/templates/src/modules/auth/components/login-form.tsx +95 -0
  40. package/generators/app/templates/src/modules/auth/components/reset-password-form.tsx +112 -0
  41. package/generators/app/templates/src/modules/auth/components/signup-form.tsx +92 -0
  42. package/generators/app/templates/src/modules/auth/{auth-context.tsx → contexts/auth-context.tsx} +3 -3
  43. package/generators/app/templates/src/modules/auth/hooks/use-auth-hook.ts +180 -0
  44. package/generators/app/templates/src/modules/auth/layouts/auth-layout.tsx +28 -0
  45. package/generators/app/templates/src/modules/auth/locales/auth-en.json +105 -0
  46. package/generators/app/templates/src/modules/auth/locales/auth-ja.json +105 -0
  47. package/generators/app/templates/src/modules/auth/schemas/form-schemas.ts +26 -0
  48. package/generators/app/templates/src/modules/landing/components/auth-hero.tsx +34 -0
  49. package/generators/app/templates/src/modules/landing/components/welcome-hero.tsx +24 -0
  50. package/generators/app/templates/src/modules/landing/landing-page-layout.tsx +24 -0
  51. package/generators/app/templates/src/modules/landing/landing-page.tsx +17 -0
  52. package/generators/app/templates/src/modules/landing/layouts/landing-page-layout.tsx +24 -0
  53. package/generators/app/templates/src/modules/landing/locales/landing-en.json +12 -0
  54. package/generators/app/templates/src/modules/landing/locales/landing-ja.json +11 -0
  55. package/generators/app/templates/src/openapi-client-config.ts +6 -0
  56. package/generators/app/templates/src/routeTree.gen.ts +268 -3
  57. package/generators/app/templates/src/router.tsx +2 -2
  58. package/generators/app/templates/src/routes/__root.tsx +2 -2
  59. package/generators/app/templates/src/routes/_landing/index.tsx +10 -0
  60. package/generators/app/templates/src/routes/_landing/route.tsx +14 -0
  61. package/generators/app/templates/src/routes/app/index.tsx +4 -21
  62. package/generators/app/templates/src/routes/app/route.tsx +12 -8
  63. package/generators/app/templates/src/routes/auth/forgot-password.tsx +10 -0
  64. package/generators/app/templates/src/routes/auth/login.tsx +6 -7
  65. package/generators/app/templates/src/routes/auth/reset-password.tsx +15 -0
  66. package/generators/app/templates/src/routes/auth/route.tsx +23 -6
  67. package/generators/app/templates/src/routes/auth/signup.tsx +11 -0
  68. package/generators/app/templates/src/sdk/@tanstack/react-query.gen.ts +91 -0
  69. package/generators/app/templates/src/sdk/client/client.gen.ts +167 -0
  70. package/generators/app/templates/src/sdk/client/index.ts +23 -0
  71. package/generators/app/templates/src/sdk/client/types.gen.ts +197 -0
  72. package/generators/app/templates/src/sdk/client/utils.gen.ts +213 -0
  73. package/generators/app/templates/src/sdk/client.gen.ts +18 -0
  74. package/generators/app/templates/src/sdk/core/auth.gen.ts +42 -0
  75. package/generators/app/templates/src/sdk/core/bodySerializer.gen.ts +100 -0
  76. package/generators/app/templates/src/sdk/core/params.gen.ts +176 -0
  77. package/generators/app/templates/src/sdk/core/pathSerializer.gen.ts +181 -0
  78. package/generators/app/templates/src/sdk/core/queryKeySerializer.gen.ts +136 -0
  79. package/generators/app/templates/src/sdk/core/serverSentEvents.gen.ts +266 -0
  80. package/generators/app/templates/src/sdk/core/types.gen.ts +118 -0
  81. package/generators/app/templates/src/sdk/core/utils.gen.ts +143 -0
  82. package/generators/app/templates/src/sdk/index.ts +4 -0
  83. package/generators/app/templates/src/sdk/schemas.gen.ts +195 -0
  84. package/generators/app/templates/src/sdk/sdk.gen.ts +80 -0
  85. package/generators/app/templates/src/sdk/types.gen.ts +158 -0
  86. package/generators/app/templates/src/sdk/zod.gen.ts +148 -0
  87. package/generators/app/templates/src/vite-env.d.ts +2 -1
  88. package/generators/app/templates/tsconfig.json +1 -1
  89. package/generators/app/templates/vite.config.js +35 -21
  90. package/generators/constants.js +1 -1
  91. package/package.json +3 -2
  92. package/generators/app/templates/.env.example +0 -5
  93. package/generators/app/templates/README.md +0 -62
  94. package/generators/app/templates/src/lib/api/client.ts +0 -35
  95. package/generators/app/templates/src/locales/en.json +0 -18
  96. package/generators/app/templates/src/modules/auth/auth-types.ts +0 -24
  97. package/generators/app/templates/src/modules/auth/login/login-form.tsx +0 -49
  98. package/generators/app/templates/src/modules/auth/login/login-page.tsx +0 -12
  99. package/generators/app/templates/src/modules/auth/use-auth-hook.ts +0 -88
  100. package/generators/app/templates/src/routes/index.tsx +0 -12
  101. package/generators/app/templates/types.d.ts +0 -3
@@ -0,0 +1,74 @@
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';
12
+
13
+ const zForgotPasswordDto = z.object({
14
+ email: z.email().min(1).max(256),
15
+ });
16
+
17
+ type ForgotPasswordFormData = z.infer<typeof zForgotPasswordDto>;
18
+
19
+ export function ForgotPasswordForm() {
20
+ const { t } = useTranslation();
21
+ const { mutate, isPending } = useSendOtp();
22
+ const form = useForm<ForgotPasswordFormData>({
23
+ resolver: zodResolver(zForgotPasswordDto),
24
+ defaultValues: forgotPasswordFormDefaults,
25
+ });
26
+
27
+ const onSubmit = (data: ForgotPasswordFormData) => {
28
+ mutate({ query: { authId: data.email } });
29
+ };
30
+
31
+ 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>
73
+ );
74
+ }
@@ -0,0 +1,95 @@
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 { useLogin } from '@/modules/auth/hooks/use-auth-hook';
5
+ import { zUserLoginDto } from '@/sdk/zod.gen';
6
+ import { zodResolver } from '@hookform/resolvers/zod';
7
+ import { useForm } from 'react-hook-form';
8
+ import { useTranslation } from 'react-i18next';
9
+ import type { z } from 'zod';
10
+ import { Link } from '@tanstack/react-router';
11
+ import { Route as SignupRoute } from '@/routes/auth/signup';
12
+ import { Route as ForgotPasswordRoute } from '@/routes/auth/forgot-password';
13
+ import { loginFormDefaults } from '@/modules/auth/schemas/form-schemas';
14
+
15
+ type LoginFormData = z.infer<typeof zUserLoginDto>;
16
+
17
+ export function LoginForm() {
18
+ const { t } = useTranslation();
19
+ const { mutate, isPending } = useLogin();
20
+ const form = useForm<LoginFormData>({
21
+ resolver: zodResolver(zUserLoginDto),
22
+ defaultValues: loginFormDefaults,
23
+ });
24
+
25
+ const onSubmit = (data: LoginFormData) => {
26
+ mutate({ body: data });
27
+ };
28
+
29
+ return (
30
+ <div className='flex flex-col gap-6'>
31
+ <h1 className='text-2xl font-semibold'>{t('auth.login.title')}</h1>
32
+ <Form {...form}>
33
+ <form
34
+ noValidate
35
+ className='flex flex-col gap-4'
36
+ onSubmit={form.handleSubmit(onSubmit)}>
37
+ <FormField
38
+ control={form.control}
39
+ name='email'
40
+ render={({ field }) => (
41
+ <FormItem>
42
+ <FormLabel>{t('auth.login.email')} *</FormLabel>
43
+ <FormControl>
44
+ <Input
45
+ type='email'
46
+ placeholder={t('auth.login.emailPlaceholder')}
47
+ autoComplete='email'
48
+ autoFocus
49
+ {...field}
50
+ />
51
+ </FormControl>
52
+ <FormMessage />
53
+ </FormItem>
54
+ )}
55
+ />
56
+ <FormField
57
+ control={form.control}
58
+ name='password'
59
+ render={({ field }) => (
60
+ <FormItem>
61
+ <FormLabel>{t('auth.login.password')} *</FormLabel>
62
+ <FormControl>
63
+ <Input
64
+ type='password'
65
+ placeholder={t('auth.login.passwordPlaceholder')}
66
+ autoComplete='current-password'
67
+ {...field}
68
+ />
69
+ </FormControl>
70
+ <FormMessage />
71
+ </FormItem>
72
+ )}
73
+ />
74
+ <Button
75
+ type='submit'
76
+ disabled={isPending}>
77
+ {t('auth.login.submit')}
78
+ </Button>
79
+ </form>
80
+ </Form>
81
+ <div className='flex flex-col gap-2 text-sm text-center'>
82
+ <Link
83
+ to={ForgotPasswordRoute.to}
84
+ className='text-primary hover:underline'>
85
+ {t('auth.login.forgotPasswordLink')}
86
+ </Link>
87
+ <Link
88
+ to={SignupRoute.to}
89
+ className='text-primary hover:underline'>
90
+ {t('auth.login.signupLink')}
91
+ </Link>
92
+ </div>
93
+ </div>
94
+ );
95
+ }
@@ -0,0 +1,112 @@
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 { useResetPassword } from '@/modules/auth/hooks/use-auth-hook';
5
+ import { zUserResetPasswordDto } from '@/sdk/zod.gen';
6
+ import { zodResolver } from '@hookform/resolvers/zod';
7
+ import { useForm } from 'react-hook-form';
8
+ import { useTranslation } from 'react-i18next';
9
+ import type { z } from 'zod';
10
+ import { Link, useSearch } from '@tanstack/react-router';
11
+ import { Route as LoginRoute } from '@/routes/auth/login';
12
+ import { resetPasswordFormDefaults } from '@/modules/auth/schemas/form-schemas';
13
+
14
+ type ResetPasswordFormData = z.infer<typeof zUserResetPasswordDto>;
15
+
16
+ export function ResetPasswordForm() {
17
+ const { t } = useTranslation();
18
+ const { mutate, isPending } = useResetPassword();
19
+ const search = useSearch({ from: '/auth/reset-password' });
20
+ const form = useForm<ResetPasswordFormData>({
21
+ resolver: zodResolver(zUserResetPasswordDto),
22
+ defaultValues: {
23
+ ...resetPasswordFormDefaults,
24
+ email: search.email || '',
25
+ },
26
+ });
27
+
28
+ const onSubmit = (data: ResetPasswordFormData) => {
29
+ mutate({ body: data });
30
+ };
31
+
32
+ return (
33
+ <div className='flex flex-col gap-6'>
34
+ <h1 className='text-2xl font-semibold'>{t('auth.resetPassword.title')}</h1>
35
+ <Form {...form}>
36
+ <form
37
+ noValidate
38
+ className='flex flex-col gap-4'
39
+ onSubmit={form.handleSubmit(onSubmit)}>
40
+ <FormField
41
+ control={form.control}
42
+ name='email'
43
+ render={({ field }) => (
44
+ <FormItem>
45
+ <FormLabel>{t('auth.resetPassword.email')} *</FormLabel>
46
+ <FormControl>
47
+ <Input
48
+ type='email'
49
+ placeholder={t('auth.resetPassword.emailPlaceholder')}
50
+ autoComplete='email'
51
+ readOnly
52
+ {...field}
53
+ />
54
+ </FormControl>
55
+ <FormMessage />
56
+ </FormItem>
57
+ )}
58
+ />
59
+ <FormField
60
+ control={form.control}
61
+ name='otp'
62
+ render={({ field }) => (
63
+ <FormItem>
64
+ <FormLabel>{t('auth.resetPassword.otp')} *</FormLabel>
65
+ <FormControl>
66
+ <Input
67
+ type='text'
68
+ placeholder={t('auth.resetPassword.otpPlaceholder')}
69
+ autoComplete='one-time-code'
70
+ autoFocus
71
+ {...field}
72
+ />
73
+ </FormControl>
74
+ <FormMessage />
75
+ </FormItem>
76
+ )}
77
+ />
78
+ <FormField
79
+ control={form.control}
80
+ name='password'
81
+ render={({ field }) => (
82
+ <FormItem>
83
+ <FormLabel>{t('auth.resetPassword.password')} *</FormLabel>
84
+ <FormControl>
85
+ <Input
86
+ type='password'
87
+ placeholder={t('auth.resetPassword.passwordPlaceholder')}
88
+ autoComplete='new-password'
89
+ {...field}
90
+ />
91
+ </FormControl>
92
+ <FormMessage />
93
+ </FormItem>
94
+ )}
95
+ />
96
+ <Button
97
+ type='submit'
98
+ disabled={isPending}>
99
+ {t('auth.resetPassword.submit')}
100
+ </Button>
101
+ </form>
102
+ </Form>
103
+ <div className='text-sm text-center'>
104
+ <Link
105
+ to={LoginRoute.to}
106
+ className='text-primary hover:underline'>
107
+ {t('auth.forgotPassword.loginLink')}
108
+ </Link>
109
+ </div>
110
+ </div>
111
+ );
112
+ }
@@ -0,0 +1,92 @@
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 { useSignup } from '@/modules/auth/hooks/use-auth-hook';
5
+ import { zUserSignupDto } from '@/sdk/zod.gen';
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 { z } from 'zod';
12
+ import { signupFormDefaults } from '@/modules/auth/schemas/form-schemas';
13
+
14
+ type SignupFormData = z.infer<typeof zUserSignupDto>;
15
+
16
+ export function SignupForm() {
17
+ const { t } = useTranslation();
18
+ const { mutate, isPending } = useSignup();
19
+ const extendedSchema = zUserSignupDto.extend({
20
+ password: z.string().min(6).max(32),
21
+ });
22
+ const form = useForm<SignupFormData>({
23
+ resolver: zodResolver(extendedSchema),
24
+ defaultValues: signupFormDefaults,
25
+ });
26
+
27
+ const onSubmit = (data: SignupFormData) => {
28
+ mutate({ body: data });
29
+ };
30
+
31
+ return (
32
+ <div className='flex flex-col gap-6'>
33
+ <h1 className='text-2xl font-semibold'>{t('auth.signup.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.signup.email')} *</FormLabel>
45
+ <FormControl>
46
+ <Input
47
+ type='email'
48
+ placeholder={t('auth.signup.emailPlaceholder')}
49
+ autoComplete='email'
50
+ autoFocus
51
+ {...field}
52
+ />
53
+ </FormControl>
54
+ <FormMessage />
55
+ </FormItem>
56
+ )}
57
+ />
58
+ <FormField
59
+ control={form.control}
60
+ name='password'
61
+ render={({ field }) => (
62
+ <FormItem>
63
+ <FormLabel>{t('auth.signup.password')} *</FormLabel>
64
+ <FormControl>
65
+ <Input
66
+ type='password'
67
+ placeholder={t('auth.signup.passwordPlaceholder')}
68
+ autoComplete='new-password'
69
+ {...field}
70
+ />
71
+ </FormControl>
72
+ <FormMessage />
73
+ </FormItem>
74
+ )}
75
+ />
76
+ <Button
77
+ type='submit'
78
+ disabled={isPending}>
79
+ {t('auth.signup.submit')}
80
+ </Button>
81
+ </form>
82
+ </Form>
83
+ <div className='text-sm text-center'>
84
+ <Link
85
+ to={LoginRoute.to}
86
+ className='text-primary hover:underline'>
87
+ {t('auth.signup.loginLink')}
88
+ </Link>
89
+ </div>
90
+ </div>
91
+ );
92
+ }
@@ -1,9 +1,9 @@
1
1
  import { createContext } from 'react';
2
- import { currentUserDetailsAtom } from './use-auth-hook';
3
- import { AuthData } from './auth-types';
2
+ import { currentUserDetailsAtom } from '@/modules/auth/hooks/use-auth-hook';
3
+ import type { AuthResponseDtoUserDetailDto } from '@/sdk';
4
4
  import { useAtomValue } from 'jotai';
5
5
 
6
- export const AuthContext = createContext<AuthData | undefined>(undefined);
6
+ export const AuthContext = createContext<AuthResponseDtoUserDetailDto | undefined>(undefined);
7
7
 
8
8
  export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
9
9
  const authData = useAtomValue(currentUserDetailsAtom);
@@ -0,0 +1,180 @@
1
+ import { Route as LoginRoute } from '@/routes/auth/login';
2
+ import type {
3
+ AuthResponseDtoUserDetailDto,
4
+ LoginUserAuthResponse,
5
+ RegisterUserAuthResponse,
6
+ ResetPasswordUserAuthResponse,
7
+ SendOtpUserAuthResponse,
8
+ ValidateTokenUserAuthResponse,
9
+ } from '@/sdk';
10
+ import {
11
+ loginUserAuthMutation,
12
+ logoutUserAuthMutation,
13
+ registerUserAuthMutation,
14
+ resetPasswordUserAuthMutation,
15
+ sendOtpUserAuthMutation,
16
+ validateTokenUserAuthMutation,
17
+ } from '@/sdk/@tanstack/react-query.gen';
18
+ import { client } from '@/sdk/client.gen';
19
+ import { useMutation } from '@tanstack/react-query';
20
+ import { useNavigate } from '@tanstack/react-router';
21
+ import { atom, useAtomValue, useSetAtom } from 'jotai';
22
+ import { atomWithStorage } from 'jotai/utils';
23
+ import { useTranslation } from 'react-i18next';
24
+ import { toast } from 'sonner';
25
+ import { SupportedLanguages } from '@/lib/i18n';
26
+ import { DEFAULT_NON_LOGGED_IN_ROUTE } from '@/lib/routes';
27
+
28
+ export const HEADER_KEYS = {
29
+ AUTH_TOKEN: 'x-auth-token',
30
+ ACCEPT_LANGUAGE: 'accept-language',
31
+ };
32
+
33
+ export const LOCAL_STORAGE_KEYS = {
34
+ AUTH_TOKEN: 'authTokenAtom',
35
+ };
36
+
37
+ export const authTokenAtom = atomWithStorage<string>(LOCAL_STORAGE_KEYS.AUTH_TOKEN, '', undefined, {
38
+ getOnInit: true,
39
+ });
40
+
41
+ export const currentUserDetailsAtom = atom<AuthResponseDtoUserDetailDto | undefined>();
42
+
43
+ export const setTokenInClient = (token?: string | null) => {
44
+ if (token) {
45
+ client.setConfig({
46
+ headers: {
47
+ [HEADER_KEYS.AUTH_TOKEN]: token,
48
+ },
49
+ });
50
+ } else {
51
+ client.setConfig({
52
+ headers: {
53
+ [HEADER_KEYS.AUTH_TOKEN]: undefined,
54
+ },
55
+ });
56
+ }
57
+ };
58
+
59
+ export const setLocaleInClient = (locale: SupportedLanguages) => {
60
+ client.setConfig({
61
+ headers: {
62
+ [HEADER_KEYS.ACCEPT_LANGUAGE]: locale,
63
+ },
64
+ });
65
+ };
66
+
67
+ export const useLogin = () => {
68
+ const { t } = useTranslation();
69
+ const setAuthToken = useSetAtom(authTokenAtom);
70
+ const setCurrentUser = useSetAtom(currentUserDetailsAtom);
71
+ return useMutation({
72
+ ...loginUserAuthMutation({}),
73
+ onSuccess: (response: LoginUserAuthResponse) => {
74
+ const authData = response.data;
75
+ setAuthToken(authData?.token || '');
76
+ setCurrentUser(authData);
77
+ setTokenInClient(authData?.token);
78
+ },
79
+ onError: (err) => {
80
+ console.error('Login error:', err);
81
+ toast.error(err.response?.data.message || t('auth.messages.loginFailed'));
82
+ },
83
+ });
84
+ };
85
+
86
+ export const useSignup = () => {
87
+ const { t } = useTranslation();
88
+ const setAuthToken = useSetAtom(authTokenAtom);
89
+ const setCurrentUser = useSetAtom(currentUserDetailsAtom);
90
+ return useMutation({
91
+ ...registerUserAuthMutation({}),
92
+ onSuccess: (response: RegisterUserAuthResponse) => {
93
+ const authData = response.data;
94
+ setAuthToken(authData?.token || '');
95
+ setCurrentUser(authData);
96
+ setTokenInClient(authData?.token);
97
+ },
98
+ onError: (err) => {
99
+ console.error('Signup error:', err);
100
+ toast.error(err.response?.data.message || t('auth.messages.signupFailed'));
101
+ },
102
+ });
103
+ };
104
+
105
+ export const useValidateToken = () => {
106
+ const setCurrentUser = useSetAtom(currentUserDetailsAtom);
107
+ const setAuthToken = useSetAtom(authTokenAtom);
108
+ const authToken = useAtomValue(authTokenAtom);
109
+ setTokenInClient(authToken);
110
+ return useMutation({
111
+ ...validateTokenUserAuthMutation({}),
112
+ retry: false,
113
+ onSuccess: (response: ValidateTokenUserAuthResponse) => {
114
+ const authData = response.data;
115
+ setCurrentUser(authData);
116
+ setAuthToken(authData?.token || '');
117
+ setTokenInClient(authData?.token);
118
+ },
119
+ onError: (err) => {
120
+ console.error('Validate token error:', err);
121
+ setCurrentUser(undefined);
122
+ setAuthToken('');
123
+ setTokenInClient();
124
+ },
125
+ });
126
+ };
127
+
128
+ export const useLogout = () => {
129
+ const setAuthToken = useSetAtom(authTokenAtom);
130
+ const setCurrentUser = useSetAtom(currentUserDetailsAtom);
131
+ const navigate = useNavigate();
132
+ return useMutation({
133
+ ...logoutUserAuthMutation({}),
134
+ onSuccess: () => {
135
+ setAuthToken('');
136
+ setCurrentUser(undefined);
137
+ setTokenInClient();
138
+ navigate({ to: DEFAULT_NON_LOGGED_IN_ROUTE });
139
+ },
140
+ });
141
+ };
142
+
143
+ export const useSendOtp = () => {
144
+ const { t } = useTranslation();
145
+ const navigate = useNavigate();
146
+ return useMutation({
147
+ ...sendOtpUserAuthMutation({}),
148
+ onSuccess: (response: SendOtpUserAuthResponse, variables) => {
149
+ toast.success(response.message || t('auth.messages.otpSentSuccess'));
150
+ // Extract email from the mutation variables and navigate to reset-password
151
+ const email = variables.query?.authId;
152
+ if (email) {
153
+ navigate({
154
+ to: '/auth/reset-password',
155
+ search: { email },
156
+ });
157
+ }
158
+ },
159
+ onError: (err) => {
160
+ console.error('Send OTP error:', err);
161
+ toast.error(err.response?.data.message || t('auth.messages.otpSendFailed'));
162
+ },
163
+ });
164
+ };
165
+
166
+ export const useResetPassword = () => {
167
+ const { t } = useTranslation();
168
+ const navigate = useNavigate();
169
+ return useMutation({
170
+ ...resetPasswordUserAuthMutation({}),
171
+ onSuccess: (response: ResetPasswordUserAuthResponse) => {
172
+ toast.success(response.message || t('auth.messages.passwordResetSuccess'));
173
+ navigate({ to: LoginRoute.to });
174
+ },
175
+ onError: (err) => {
176
+ console.error('Reset password error:', err);
177
+ toast.error(err.response?.data.message || t('auth.messages.passwordResetFailed'));
178
+ },
179
+ });
180
+ };
@@ -0,0 +1,28 @@
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 { Home } from 'lucide-react';
5
+ import { Link } from '@tanstack/react-router';
6
+ import { DEFAULT_NON_LOGGED_IN_ROUTE } from '@/lib/routes';
7
+
8
+ export function AuthLayout({ children }: { children: React.ReactNode }) {
9
+ return (
10
+ <div className='flex h-screen items-center justify-center bg-background relative'>
11
+ <div className='absolute top-4 left-4'>
12
+ <Link to={DEFAULT_NON_LOGGED_IN_ROUTE}>
13
+ <Button
14
+ variant='outline'
15
+ size='icon'
16
+ aria-label='Go to home'>
17
+ <Home className='h-4 w-4' />
18
+ </Button>
19
+ </Link>
20
+ </div>
21
+ <div className='absolute top-4 right-4 flex gap-2'>
22
+ <ThemeSwitcher />
23
+ <LanguageSwitcher />
24
+ </div>
25
+ <div className='w-full max-w-md p-8'>{children}</div>
26
+ </div>
27
+ );
28
+ }