generator-kodly-react-app 1.0.14 → 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.
- package/generators/app/index.js +4 -0
- package/generators/app/templates/.env.dev +1 -1
- package/generators/app/templates/.env.sandbox +2 -2
- package/generators/app/templates/I18N_MIGRATION.md +28 -35
- package/generators/app/templates/STRUCTURE.md +26 -26
- package/generators/app/templates/index.html +6 -14
- package/generators/app/templates/openapi-ts.config.ts +12 -12
- package/generators/app/templates/postcss.config.js +0 -1
- package/generators/app/templates/src/app.tsx +20 -20
- package/generators/app/templates/src/components/layout/language-switcher.tsx +20 -16
- package/generators/app/templates/src/components/layout/page-layout.tsx +31 -0
- package/generators/app/templates/src/components/layout/theme-switcher.tsx +15 -14
- package/generators/app/templates/src/components/theme/theme-provider.tsx +20 -13
- package/generators/app/templates/src/components/ui/button.tsx +15 -14
- package/generators/app/templates/src/components/ui/card.tsx +23 -16
- package/generators/app/templates/src/components/ui/field.tsx +35 -35
- package/generators/app/templates/src/components/ui/form.tsx +66 -30
- package/generators/app/templates/src/components/ui/input-group.tsx +24 -24
- package/generators/app/templates/src/components/ui/input.tsx +8 -8
- package/generators/app/templates/src/components/ui/label.tsx +9 -9
- package/generators/app/templates/src/components/ui/select.tsx +0 -1
- package/generators/app/templates/src/components/ui/separator.tsx +10 -10
- package/generators/app/templates/src/components/ui/textarea.tsx +7 -7
- package/generators/app/templates/src/index.css +12 -2
- package/generators/app/templates/src/lib/i18n.ts +16 -14
- package/generators/app/templates/src/lib/routes.ts +2 -3
- package/generators/app/templates/src/lib/utils/error-handler.ts +21 -14
- package/generators/app/templates/src/lib/utils.ts +3 -3
- package/generators/app/templates/src/locales/index.ts +12 -12
- package/generators/app/templates/src/locales/ja/theme.json +0 -1
- package/generators/app/templates/src/main.tsx +10 -12
- package/generators/app/templates/src/modules/app/layouts/app-layout.tsx +14 -25
- package/generators/app/templates/src/modules/app/locales/app-en.json +0 -1
- package/generators/app/templates/src/modules/app/locales/app-ja.json +0 -1
- package/generators/app/templates/src/modules/auth/components/auth-form.tsx +83 -0
- package/generators/app/templates/src/modules/auth/components/forgot-password-form.tsx +27 -52
- package/generators/app/templates/src/modules/auth/components/login-form.tsx +42 -72
- package/generators/app/templates/src/modules/auth/components/login-link.tsx +12 -0
- package/generators/app/templates/src/modules/auth/components/reset-password-form.tsx +46 -92
- package/generators/app/templates/src/modules/auth/components/signup-form.tsx +35 -71
- package/generators/app/templates/src/modules/auth/contexts/auth-context.tsx +10 -6
- package/generators/app/templates/src/modules/auth/hooks/use-auth-hook.ts +51 -36
- package/generators/app/templates/src/modules/auth/layouts/auth-layout.tsx +12 -15
- package/generators/app/templates/src/modules/auth/locales/auth-en.json +0 -1
- package/generators/app/templates/src/modules/auth/locales/auth-ja.json +0 -1
- package/generators/app/templates/src/modules/auth/schemas/form-schemas.ts +14 -10
- package/generators/app/templates/src/modules/landing/components/auth-hero.tsx +20 -18
- package/generators/app/templates/src/modules/landing/components/welcome-hero.tsx +13 -11
- package/generators/app/templates/src/modules/landing/landing-page-layout.tsx +1 -24
- package/generators/app/templates/src/modules/landing/landing-page.tsx +11 -6
- package/generators/app/templates/src/modules/landing/layouts/landing-page-layout.tsx +2 -21
- package/generators/app/templates/src/modules/landing/locales/landing-en.json +0 -1
- package/generators/app/templates/src/openapi-client-config.ts +1 -1
- package/generators/app/templates/src/routeTree.gen.ts +188 -188
- package/generators/app/templates/src/router.tsx +2 -2
- package/generators/app/templates/src/routes/$.tsx +0 -1
- package/generators/app/templates/src/routes/__root.tsx +2 -2
- package/generators/app/templates/src/routes/_landing/index.tsx +3 -3
- package/generators/app/templates/src/routes/_landing/route.tsx +3 -3
- package/generators/app/templates/src/routes/app/index.tsx +6 -6
- package/generators/app/templates/src/routes/app/route.tsx +4 -4
- package/generators/app/templates/src/routes/auth/forgot-password.tsx +1 -1
- package/generators/app/templates/src/routes/auth/login.tsx +3 -3
- package/generators/app/templates/src/routes/auth/reset-password.tsx +3 -3
- package/generators/app/templates/src/routes/auth/route.tsx +12 -7
- package/generators/app/templates/src/routes/auth/signup.tsx +1 -2
- package/generators/app/templates/src/sdk/@tanstack/react-query.gen.ts +157 -76
- package/generators/app/templates/src/sdk/client/client.gen.ts +35 -37
- package/generators/app/templates/src/sdk/client/index.ts +8 -8
- package/generators/app/templates/src/sdk/client/types.gen.ts +22 -19
- package/generators/app/templates/src/sdk/client/utils.gen.ts +53 -45
- package/generators/app/templates/src/sdk/client.gen.ts +18 -5
- package/generators/app/templates/src/sdk/core/auth.gen.ts +6 -6
- package/generators/app/templates/src/sdk/core/bodySerializer.gen.ts +25 -27
- package/generators/app/templates/src/sdk/core/params.gen.ts +12 -12
- package/generators/app/templates/src/sdk/core/pathSerializer.gen.ts +40 -41
- package/generators/app/templates/src/sdk/core/queryKeySerializer.gen.ts +12 -29
- package/generators/app/templates/src/sdk/core/serverSentEvents.gen.ts +26 -24
- package/generators/app/templates/src/sdk/core/types.gen.ts +17 -15
- package/generators/app/templates/src/sdk/core/utils.gen.ts +18 -18
- package/generators/app/templates/src/sdk/index.ts +40 -2
- package/generators/app/templates/src/sdk/schemas.gen.ts +108 -166
- package/generators/app/templates/src/sdk/sdk.gen.ts +145 -63
- package/generators/app/templates/src/sdk/types.gen.ts +90 -84
- package/generators/app/templates/src/sdk/zod.gen.ts +57 -74
- package/generators/app/templates/src/types/i18n.d.ts +0 -1
- package/generators/app/templates/tailwind.config.js +0 -1
- package/generators/app/templates/tsconfig.app.json +0 -1
- package/generators/app/templates/tsconfig.json +0 -1
- package/generators/app/templates/tsr.config.json +4 -0
- package/generators/app/templates/vite.config.js +13 -12
- package/package.json +2 -4
package/generators/app/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export default class ReactAppGenerator extends BaseGenerator {
|
|
|
24
24
|
'vite.config.js',
|
|
25
25
|
'tsconfig.json',
|
|
26
26
|
'tsconfig.app.json',
|
|
27
|
+
'tsr.config.json',
|
|
27
28
|
'tailwind.config.js',
|
|
28
29
|
'postcss.config.js',
|
|
29
30
|
'index.html',
|
|
@@ -70,6 +71,8 @@ export default class ReactAppGenerator extends BaseGenerator {
|
|
|
70
71
|
_writeAuthFiles() {
|
|
71
72
|
const authFiles = [
|
|
72
73
|
'src/modules/auth/components/signup-form.tsx',
|
|
74
|
+
'src/modules/auth/components/auth-form.tsx',
|
|
75
|
+
'src/modules/auth/components/login-link.tsx',
|
|
73
76
|
'src/modules/auth/components/forgot-password-form.tsx',
|
|
74
77
|
'src/modules/auth/components/reset-password-form.tsx',
|
|
75
78
|
'src/modules/auth/components/login-form.tsx',
|
|
@@ -86,6 +89,7 @@ export default class ReactAppGenerator extends BaseGenerator {
|
|
|
86
89
|
_writeComponentFiles() {
|
|
87
90
|
const componentFiles = [
|
|
88
91
|
'src/components/layout/language-switcher.tsx',
|
|
92
|
+
'src/components/layout/page-layout.tsx',
|
|
89
93
|
'src/components/layout/theme-switcher.tsx',
|
|
90
94
|
'src/components/theme/theme-provider.tsx',
|
|
91
95
|
'src/components/ui/button.tsx',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
BASE_URL=/
|
|
2
2
|
PORT=3000
|
|
3
|
-
ALLOWED_HOSTS=sandbox.
|
|
4
|
-
VITE_API_BASE_URL=${VITE_API_BASE_URL} //injected by env variables - https://sandbox.
|
|
3
|
+
ALLOWED_HOSTS=sandbox.app.kodly.dev
|
|
4
|
+
VITE_API_BASE_URL=${VITE_API_BASE_URL} //injected by env variables - https://{port}-{projectSlug}-{userSlug}.sandbox.app.kodly.dev
|
|
@@ -6,12 +6,12 @@ This document describes the i18n configuration changes that enable **both** flat
|
|
|
6
6
|
|
|
7
7
|
The i18n setup now supports two usage patterns interchangeably:
|
|
8
8
|
|
|
9
|
-
| Pattern
|
|
10
|
-
|
|
11
|
-
| **Flat (no namespace)** | `useTranslation()` + `t('
|
|
12
|
-
| **Namespaced**
|
|
9
|
+
| Pattern | Example | Use case |
|
|
10
|
+
| ----------------------- | -------------------------------------------------- | -------------------------------- |
|
|
11
|
+
| **Flat (no namespace)** | `useTranslation()` + `t('namespace.section.key')` | Quick access, explicit full path |
|
|
12
|
+
| **Namespaced** | `useTranslation('namespace')` + `t('section.key')` | Modular components, shorter keys |
|
|
13
13
|
|
|
14
|
-
Previously, only the flat pattern worked because all translations lived under a single `translation` namespace. The namespaced pattern failed because i18next couldn't find
|
|
14
|
+
Previously, only the flat pattern worked because all translations lived under a single `translation` namespace. The namespaced pattern failed because i18next couldn't find the individual namespaces (the top-level keys in your merged translation object).
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -24,8 +24,8 @@ if (!i18n.isInitialized) {
|
|
|
24
24
|
const lng = getStoredLanguage();
|
|
25
25
|
i18n.use(initReactI18next).init({
|
|
26
26
|
resources: {
|
|
27
|
-
en: { translation: mergeTranslations(
|
|
28
|
-
ja: { translation: mergeTranslations(
|
|
27
|
+
en: { translation: mergeTranslations("en") },
|
|
28
|
+
ja: { translation: mergeTranslations("ja") },
|
|
29
29
|
},
|
|
30
30
|
supportedLngs: supportedLanguages,
|
|
31
31
|
lng,
|
|
@@ -43,8 +43,8 @@ if (!i18n.isInitialized) {
|
|
|
43
43
|
```typescript
|
|
44
44
|
if (!i18n.isInitialized) {
|
|
45
45
|
const lng = getStoredLanguage();
|
|
46
|
-
const enResources = mergeTranslations(
|
|
47
|
-
const jaResources = mergeTranslations(
|
|
46
|
+
const enResources = mergeTranslations("en");
|
|
47
|
+
const jaResources = mergeTranslations("ja");
|
|
48
48
|
i18n.use(initReactI18next).init({
|
|
49
49
|
resources: {
|
|
50
50
|
en: {
|
|
@@ -56,8 +56,8 @@ if (!i18n.isInitialized) {
|
|
|
56
56
|
...jaResources,
|
|
57
57
|
},
|
|
58
58
|
},
|
|
59
|
-
defaultNS:
|
|
60
|
-
ns: [
|
|
59
|
+
defaultNS: "translation",
|
|
60
|
+
ns: ["translation", ...Object.keys(enResources)],
|
|
61
61
|
supportedLngs: supportedLanguages,
|
|
62
62
|
lng,
|
|
63
63
|
fallbackLng: defaultLanguage,
|
|
@@ -72,12 +72,12 @@ if (!i18n.isInitialized) {
|
|
|
72
72
|
### What changed
|
|
73
73
|
|
|
74
74
|
1. **Resources structure**: Each language now exposes both:
|
|
75
|
-
- `translation` –
|
|
76
|
-
- Individual namespaces –
|
|
75
|
+
- `translation` – the full merged object for flat keys, e.g. `t('namespace.section.key')`
|
|
76
|
+
- Individual namespaces – one per top-level key in the merged object, for `useTranslation('namespace')` + `t('section.key')`
|
|
77
77
|
|
|
78
78
|
2. **`defaultNS: 'translation'`**: Ensures `useTranslation()` without arguments uses the flat namespace.
|
|
79
79
|
|
|
80
|
-
3. **`ns` array**:
|
|
80
|
+
3. **`ns` array**: Must list `'translation'` plus every top-level key returned by `mergeTranslations()` (e.g. `['translation', ...Object.keys(enResources)]`) so i18next can load and resolve them correctly.
|
|
81
81
|
|
|
82
82
|
---
|
|
83
83
|
|
|
@@ -89,17 +89,12 @@ Replace the `if (!i18n.isInitialized)` block with the new configuration above. T
|
|
|
89
89
|
|
|
90
90
|
### 2. Verify translation key paths (flat usage)
|
|
91
91
|
|
|
92
|
-
With the default `translation` namespace, keys must include the
|
|
92
|
+
With the default `translation` namespace, keys must include the full path from the root of your merged translations. The format is the namespace (top-level key) followed by the path inside it:
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
| Auth | `auth.<section>.<key>` | `auth.login.title`, `auth.messages.loginFailed` |
|
|
97
|
-
| App | `app.<key>` | `app.title`, `app.welcome.logout` |
|
|
98
|
-
| Landing | `landing.<section>.<key>` | `landing.hero.loginButton` |
|
|
99
|
-
| Common | `common.<key>` | `common.notFound.message` |
|
|
100
|
-
| Theme | `theme.<key>` | (if used) |
|
|
94
|
+
- **Format**: `t('<namespace>.<path.to.key>')`
|
|
95
|
+
- **Example**: If your merged object has a top-level key `auth` with `login.title` inside it, use `t('auth.login.title')`.
|
|
101
96
|
|
|
102
|
-
If
|
|
97
|
+
If a key used to work as `t('someKey')` and now fails, it likely lives under a namespace in your structure—update it to `t('<namespace>.<someKey>')` (or the full path that matches your locale JSON).
|
|
103
98
|
|
|
104
99
|
### 3. Optional: Use namespaced pattern in components
|
|
105
100
|
|
|
@@ -108,11 +103,11 @@ You can migrate components to the namespaced pattern for shorter keys:
|
|
|
108
103
|
```tsx
|
|
109
104
|
// Before (flat)
|
|
110
105
|
const { t } = useTranslation();
|
|
111
|
-
return <h1>{t(
|
|
106
|
+
return <h1>{t("namespace.section.title")}</h1>;
|
|
112
107
|
|
|
113
108
|
// After (namespaced) – optional
|
|
114
|
-
const { t } = useTranslation(
|
|
115
|
-
return <h1>{t(
|
|
109
|
+
const { t } = useTranslation("namespace");
|
|
110
|
+
return <h1>{t("section.title")}</h1>;
|
|
116
111
|
```
|
|
117
112
|
|
|
118
113
|
Both forms work. Choose what fits your component structure.
|
|
@@ -121,14 +116,12 @@ Both forms work. Choose what fits your component structure.
|
|
|
121
116
|
|
|
122
117
|
## Available Namespaces
|
|
123
118
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
| `common` | `@/locales/en/common.json`, `@/locales/ja/common.json` | notFound |
|
|
131
|
-
| `theme` | `@/locales/en/theme.json`, `@/locales/ja/theme.json` | theme-related keys |
|
|
119
|
+
Namespaces in your project are determined by **the top-level keys of the object returned by `mergeTranslations(lng)`**.
|
|
120
|
+
|
|
121
|
+
- **`translation`** – Always present. Contains the full merged tree so flat keys like `t('namespace.section.key')` work.
|
|
122
|
+
- **Other namespaces** – One for each top-level key in that merged object (e.g. if your merge yields `{ auth, app, common }`, you get namespaces `auth`, `app`, and `common`). Their contents match whatever structure your locale files and `mergeTranslations` produce.
|
|
123
|
+
|
|
124
|
+
To see which namespaces you have, inspect the keys of the result of `mergeTranslations('en')` (or your default language) and use that same list in the `ns` option in `i18n.ts`.
|
|
132
125
|
|
|
133
126
|
---
|
|
134
127
|
|
|
@@ -136,5 +129,5 @@ Both forms work. Choose what fits your component structure.
|
|
|
136
129
|
|
|
137
130
|
- **`src/locales/index.ts`** – `mergeTranslations` is unchanged
|
|
138
131
|
- **Locale JSON files** – No structure changes
|
|
139
|
-
- **Components using flat keys** – Existing `t('
|
|
132
|
+
- **Components using flat keys** – Existing `t('namespace.path.to.key')` calls continue to work
|
|
140
133
|
- **`LanguageSwitcher`** – Uses `i18n` only; no translation keys
|
|
@@ -279,10 +279,10 @@ routes/
|
|
|
279
279
|
|
|
280
280
|
```tsx
|
|
281
281
|
// src/routes/{module}/route.tsx (Parent route)
|
|
282
|
-
import { createFileRoute, Outlet } from
|
|
283
|
-
import { ModuleLayout } from
|
|
282
|
+
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
|
283
|
+
import { ModuleLayout } from "@/modules/{module}/layouts/{module}-layout";
|
|
284
284
|
|
|
285
|
-
export const Route = createFileRoute(
|
|
285
|
+
export const Route = createFileRoute("/{module}")({
|
|
286
286
|
beforeLoad: ({ context }) => {
|
|
287
287
|
// Guards, redirects, etc.
|
|
288
288
|
},
|
|
@@ -300,10 +300,10 @@ function RouteComponent() {
|
|
|
300
300
|
|
|
301
301
|
```tsx
|
|
302
302
|
// src/routes/{module}/{page}.tsx (Child route)
|
|
303
|
-
import { createFileRoute } from
|
|
304
|
-
import { PageComponent } from
|
|
303
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
304
|
+
import { PageComponent } from "@/modules/{module}/components/{page}-component";
|
|
305
305
|
|
|
306
|
-
export const Route = createFileRoute(
|
|
306
|
+
export const Route = createFileRoute("/{module}/{page}")({
|
|
307
307
|
component: RouteComponent,
|
|
308
308
|
});
|
|
309
309
|
|
|
@@ -363,7 +363,7 @@ Translations are merged in `src/locales/index.ts`:
|
|
|
363
363
|
|
|
364
364
|
```typescript
|
|
365
365
|
export const mergeTranslations = (lang: SupportedLanguages) => {
|
|
366
|
-
if (lang ===
|
|
366
|
+
if (lang === "en") {
|
|
367
367
|
return {
|
|
368
368
|
auth: { ...enAuth },
|
|
369
369
|
app: { ...enApp },
|
|
@@ -379,15 +379,15 @@ export const mergeTranslations = (lang: SupportedLanguages) => {
|
|
|
379
379
|
### Using Translations in Components
|
|
380
380
|
|
|
381
381
|
```tsx
|
|
382
|
-
import { useTranslation } from
|
|
382
|
+
import { useTranslation } from "react-i18next";
|
|
383
383
|
|
|
384
384
|
function MyComponent() {
|
|
385
385
|
const { t } = useTranslation();
|
|
386
386
|
|
|
387
387
|
return (
|
|
388
388
|
<div>
|
|
389
|
-
<h1>{t(
|
|
390
|
-
<p>{t(
|
|
389
|
+
<h1>{t("auth.login.title")}</h1>
|
|
390
|
+
<p>{t("common.welcome")}</p>
|
|
391
391
|
</div>
|
|
392
392
|
);
|
|
393
393
|
}
|
|
@@ -447,11 +447,11 @@ src/modules/dashboard/
|
|
|
447
447
|
Add module translations to `src/locales/index.ts`:
|
|
448
448
|
|
|
449
449
|
```typescript
|
|
450
|
-
import enDashboard from
|
|
451
|
-
import jaDashboard from
|
|
450
|
+
import enDashboard from "@/modules/dashboard/locales/dashboard-en.json";
|
|
451
|
+
import jaDashboard from "@/modules/dashboard/locales/dashboard-ja.json";
|
|
452
452
|
|
|
453
453
|
export const mergeTranslations = (lang: SupportedLanguages) => {
|
|
454
|
-
if (lang ===
|
|
454
|
+
if (lang === "en") {
|
|
455
455
|
return {
|
|
456
456
|
// ... existing modules
|
|
457
457
|
dashboard: { ...enDashboard },
|
|
@@ -472,7 +472,7 @@ export const mergeTranslations = (lang: SupportedLanguages) => {
|
|
|
472
472
|
```tsx
|
|
473
473
|
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
474
474
|
return (
|
|
475
|
-
<div className=
|
|
475
|
+
<div className="dashboard-container">
|
|
476
476
|
{/* Dashboard-specific layout */}
|
|
477
477
|
{children}
|
|
478
478
|
</div>
|
|
@@ -485,10 +485,10 @@ export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
|
485
485
|
**`src/routes/dashboard/route.tsx`**:
|
|
486
486
|
|
|
487
487
|
```tsx
|
|
488
|
-
import { createFileRoute, Outlet } from
|
|
489
|
-
import { DashboardLayout } from
|
|
488
|
+
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
|
489
|
+
import { DashboardLayout } from "@/modules/dashboard/layouts/dashboard-layout";
|
|
490
490
|
|
|
491
|
-
export const Route = createFileRoute(
|
|
491
|
+
export const Route = createFileRoute("/dashboard")({
|
|
492
492
|
component: RouteComponent,
|
|
493
493
|
});
|
|
494
494
|
|
|
@@ -504,10 +504,10 @@ function RouteComponent() {
|
|
|
504
504
|
**`src/routes/dashboard/index.tsx`**:
|
|
505
505
|
|
|
506
506
|
```tsx
|
|
507
|
-
import { createFileRoute } from
|
|
508
|
-
import { DashboardPage } from
|
|
507
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
508
|
+
import { DashboardPage } from "@/modules/dashboard/components/dashboard-page";
|
|
509
509
|
|
|
510
|
-
export const Route = createFileRoute(
|
|
510
|
+
export const Route = createFileRoute("/dashboard/")({
|
|
511
511
|
component: RouteComponent,
|
|
512
512
|
});
|
|
513
513
|
|
|
@@ -564,7 +564,7 @@ _writeDashboardFiles() {
|
|
|
564
564
|
|
|
565
565
|
```tsx
|
|
566
566
|
// src/modules/{module}/components/{component-name}.tsx
|
|
567
|
-
import { useTranslation } from
|
|
567
|
+
import { useTranslation } from "react-i18next";
|
|
568
568
|
|
|
569
569
|
export function ComponentName() {
|
|
570
570
|
const { t } = useTranslation();
|
|
@@ -577,7 +577,7 @@ export function ComponentName() {
|
|
|
577
577
|
|
|
578
578
|
```tsx
|
|
579
579
|
// src/modules/{module}/hooks/use-{hook-name}.ts
|
|
580
|
-
import { useAtom } from
|
|
580
|
+
import { useAtom } from "jotai";
|
|
581
581
|
|
|
582
582
|
export function useHookName() {
|
|
583
583
|
// Hook logic
|
|
@@ -593,7 +593,7 @@ export function useHookName() {
|
|
|
593
593
|
// src/modules/{module}/layouts/{module}-layout.tsx
|
|
594
594
|
export function ModuleLayout({ children }: { children: React.ReactNode }) {
|
|
595
595
|
return (
|
|
596
|
-
<div className=
|
|
596
|
+
<div className="module-container">
|
|
597
597
|
{/* Layout structure */}
|
|
598
598
|
{children}
|
|
599
599
|
</div>
|
|
@@ -605,10 +605,10 @@ export function ModuleLayout({ children }: { children: React.ReactNode }) {
|
|
|
605
605
|
|
|
606
606
|
```tsx
|
|
607
607
|
// src/routes/{module}/{page}.tsx
|
|
608
|
-
import { createFileRoute } from
|
|
609
|
-
import { ComponentName } from
|
|
608
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
609
|
+
import { ComponentName } from "@/modules/{module}/components/component-name";
|
|
610
610
|
|
|
611
|
-
export const Route = createFileRoute(
|
|
611
|
+
export const Route = createFileRoute("/{module}/{page}")({
|
|
612
612
|
component: RouteComponent,
|
|
613
613
|
});
|
|
614
614
|
|
|
@@ -4,23 +4,15 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta
|
|
6
6
|
name="viewport"
|
|
7
|
-
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
name="description"
|
|
13
|
-
content="<%= appNameTitleCase %>" />
|
|
14
|
-
<link
|
|
15
|
-
rel="icon"
|
|
16
|
-
type="image/svg+xml"
|
|
17
|
-
href="/favicon.svg" />
|
|
7
|
+
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
8
|
+
/>
|
|
9
|
+
<meta name="theme-color" content="#000000" />
|
|
10
|
+
<meta name="description" content="<%= appNameTitleCase %>" />
|
|
11
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
18
12
|
<title><%= appNameTitleCase %></title>
|
|
19
13
|
</head>
|
|
20
14
|
<body>
|
|
21
15
|
<div id="app"></div>
|
|
22
|
-
<script
|
|
23
|
-
type="module"
|
|
24
|
-
src="/src/main.tsx"></script>
|
|
16
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
25
17
|
</body>
|
|
26
18
|
</html>
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { defineConfig } from
|
|
2
|
-
import { loadEnv } from
|
|
1
|
+
import { defineConfig } from "@hey-api/openapi-ts";
|
|
2
|
+
import { loadEnv } from "vite";
|
|
3
3
|
|
|
4
4
|
// Get the mode from environment variable (set by pnpm scripts)
|
|
5
|
-
const mode = process.env.MODE ||
|
|
5
|
+
const mode = process.env.MODE || "development";
|
|
6
6
|
|
|
7
7
|
// Load environment variables from .env files
|
|
8
|
-
const env = loadEnv(mode, process.cwd(),
|
|
8
|
+
const env = loadEnv(mode, process.cwd(), "");
|
|
9
9
|
|
|
10
10
|
export default defineConfig({
|
|
11
11
|
input: `${env.VITE_API_BASE_URL}/v3/api-docs`,
|
|
12
|
-
output:
|
|
12
|
+
output: "src/sdk",
|
|
13
13
|
plugins: [
|
|
14
14
|
{
|
|
15
|
-
name:
|
|
16
|
-
runtimeConfigPath:
|
|
15
|
+
name: "@hey-api/client-axios",
|
|
16
|
+
runtimeConfigPath: "@/openapi-client-config.ts",
|
|
17
17
|
},
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
"@tanstack/react-query",
|
|
19
|
+
"zod",
|
|
20
20
|
{
|
|
21
|
-
name:
|
|
21
|
+
name: "@hey-api/sdk",
|
|
22
22
|
validator: true,
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
name:
|
|
26
|
-
type:
|
|
25
|
+
name: "@hey-api/schemas",
|
|
26
|
+
type: "json",
|
|
27
27
|
},
|
|
28
28
|
],
|
|
29
29
|
});
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { RouterProvider } from
|
|
2
|
-
import { useAtomValue } from
|
|
3
|
-
import { Loader2 } from
|
|
4
|
-
import { useEffect } from
|
|
5
|
-
import { Toaster } from
|
|
6
|
-
import
|
|
7
|
-
import { AuthProvider } from
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { RouterProvider } from "@tanstack/react-router";
|
|
2
|
+
import { useAtomValue } from "jotai";
|
|
3
|
+
import { Loader2 } from "lucide-react";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import { Toaster } from "sonner";
|
|
6
|
+
import "@/index.css";
|
|
7
|
+
import { AuthProvider } from "@/modules/auth/contexts/auth-context";
|
|
8
|
+
import {
|
|
9
|
+
authTokenAtom,
|
|
10
|
+
currentUserDetailsAtom,
|
|
11
|
+
useValidateToken,
|
|
12
|
+
setLocaleInClient,
|
|
13
|
+
} from "@/modules/auth/hooks/use-auth-hook";
|
|
14
|
+
import { router } from "@/router";
|
|
15
|
+
import { getLanguage, SupportedLanguages } from "@/lib/i18n";
|
|
11
16
|
|
|
12
|
-
declare module
|
|
17
|
+
declare module "@tanstack/react-router" {
|
|
13
18
|
interface Register {
|
|
14
19
|
router: typeof router;
|
|
15
20
|
}
|
|
@@ -18,12 +23,7 @@ declare module '@tanstack/react-router' {
|
|
|
18
23
|
function InnerApp() {
|
|
19
24
|
const token = useAtomValue(authTokenAtom);
|
|
20
25
|
const data = useAtomValue(currentUserDetailsAtom);
|
|
21
|
-
return
|
|
22
|
-
<RouterProvider
|
|
23
|
-
router={router}
|
|
24
|
-
context={{ token, data }}
|
|
25
|
-
/>
|
|
26
|
-
);
|
|
26
|
+
return <RouterProvider router={router} context={{ token, data }} />;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export default function App() {
|
|
@@ -44,14 +44,14 @@ export default function App() {
|
|
|
44
44
|
if (!token || (!isPending && (isError || isSuccess))) {
|
|
45
45
|
return (
|
|
46
46
|
<AuthProvider>
|
|
47
|
-
<Toaster position=
|
|
47
|
+
<Toaster position="top-right" />
|
|
48
48
|
<InnerApp />
|
|
49
49
|
</AuthProvider>
|
|
50
50
|
);
|
|
51
51
|
}
|
|
52
52
|
return (
|
|
53
|
-
<div className=
|
|
54
|
-
<Loader2 className=
|
|
53
|
+
<div className="flex h-screen w-screen items-center justify-center">
|
|
54
|
+
<Loader2 className="h-6 w-6 animate-spin" />
|
|
55
55
|
</div>
|
|
56
56
|
);
|
|
57
57
|
}
|
|
@@ -1,39 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
Select,
|
|
3
|
+
SelectContent,
|
|
4
|
+
SelectItem,
|
|
5
|
+
SelectTrigger,
|
|
6
|
+
SelectValue,
|
|
7
|
+
} from "@/components/ui/select";
|
|
8
|
+
import { getLanguage, changeLanguage } from "@/lib/i18n";
|
|
9
|
+
import { useTranslation } from "react-i18next";
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
5
11
|
|
|
6
12
|
export function LanguageSwitcher() {
|
|
7
13
|
const { i18n } = useTranslation();
|
|
8
|
-
const [currentLang, setCurrentLang] = useState<
|
|
9
|
-
return (getLanguage() as
|
|
14
|
+
const [currentLang, setCurrentLang] = useState<"en" | "ja">(() => {
|
|
15
|
+
return (getLanguage() as "en" | "ja") || "en";
|
|
10
16
|
});
|
|
11
17
|
|
|
12
18
|
useEffect(() => {
|
|
13
19
|
const handleLanguageChange = (lng: string) => {
|
|
14
|
-
setCurrentLang(lng as
|
|
20
|
+
setCurrentLang(lng as "en" | "ja");
|
|
15
21
|
};
|
|
16
22
|
|
|
17
|
-
i18n.on(
|
|
23
|
+
i18n.on("languageChanged", handleLanguageChange);
|
|
18
24
|
return () => {
|
|
19
|
-
i18n.off(
|
|
25
|
+
i18n.off("languageChanged", handleLanguageChange);
|
|
20
26
|
};
|
|
21
27
|
}, [i18n]);
|
|
22
28
|
|
|
23
29
|
const handleLanguageChange = (value: string) => {
|
|
24
|
-
changeLanguage(value as
|
|
30
|
+
changeLanguage(value as "en" | "ja");
|
|
25
31
|
};
|
|
26
32
|
|
|
27
33
|
return (
|
|
28
|
-
<Select
|
|
29
|
-
|
|
30
|
-
onValueChange={handleLanguageChange}>
|
|
31
|
-
<SelectTrigger className='min-w-[120px]'>
|
|
34
|
+
<Select value={currentLang} onValueChange={handleLanguageChange}>
|
|
35
|
+
<SelectTrigger className="min-w-[120px]">
|
|
32
36
|
<SelectValue />
|
|
33
37
|
</SelectTrigger>
|
|
34
38
|
<SelectContent>
|
|
35
|
-
<SelectItem value=
|
|
36
|
-
<SelectItem value=
|
|
39
|
+
<SelectItem value="en">English</SelectItem>
|
|
40
|
+
<SelectItem value="ja">日本語</SelectItem>
|
|
37
41
|
</SelectContent>
|
|
38
42
|
</Select>
|
|
39
43
|
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import { LanguageSwitcher } from "./language-switcher";
|
|
4
|
+
import { ThemeSwitcher } from "./theme-switcher";
|
|
5
|
+
|
|
6
|
+
export function PageLayout({
|
|
7
|
+
children,
|
|
8
|
+
actions,
|
|
9
|
+
mainClassName,
|
|
10
|
+
}: {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
actions?: ReactNode;
|
|
13
|
+
mainClassName?: string;
|
|
14
|
+
}) {
|
|
15
|
+
const { t } = useTranslation();
|
|
16
|
+
return (
|
|
17
|
+
<div className="min-h-screen bg-background">
|
|
18
|
+
<header className="border-b">
|
|
19
|
+
<div className="container mx-auto flex h-16 items-center justify-between px-4">
|
|
20
|
+
<h1 className="text-xl font-semibold">{t("app.title")}</h1>
|
|
21
|
+
<div className="flex items-center gap-4">
|
|
22
|
+
<ThemeSwitcher />
|
|
23
|
+
<LanguageSwitcher />
|
|
24
|
+
{actions}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
</header>
|
|
28
|
+
<main className={mainClassName}>{children}</main>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { Button } from
|
|
2
|
-
import { ThemeTypes, useTheme } from
|
|
3
|
-
import { Moon, Sun, Monitor } from
|
|
1
|
+
import { Button } from "@/components/ui/button";
|
|
2
|
+
import { ThemeTypes, useTheme } from "@/components/theme/theme-provider";
|
|
3
|
+
import { Moon, Sun, Monitor } from "lucide-react";
|
|
4
4
|
|
|
5
5
|
export function ThemeSwitcher() {
|
|
6
6
|
const { theme, setTheme } = useTheme();
|
|
7
7
|
|
|
8
8
|
const cycleTheme = () => {
|
|
9
|
-
const themes: ThemeTypes[] = [
|
|
9
|
+
const themes: ThemeTypes[] = ["light", "dark", "system"];
|
|
10
10
|
const currentIndex = themes.indexOf(theme);
|
|
11
11
|
const nextIndex = (currentIndex + 1) % themes.length;
|
|
12
12
|
setTheme(themes[nextIndex]);
|
|
@@ -14,23 +14,24 @@ export function ThemeSwitcher() {
|
|
|
14
14
|
|
|
15
15
|
const getIcon = () => {
|
|
16
16
|
switch (theme) {
|
|
17
|
-
case
|
|
18
|
-
return <Sun className=
|
|
19
|
-
case
|
|
20
|
-
return <Moon className=
|
|
21
|
-
case
|
|
22
|
-
return <Monitor className=
|
|
17
|
+
case "light":
|
|
18
|
+
return <Sun className="h-4 w-4" />;
|
|
19
|
+
case "dark":
|
|
20
|
+
return <Moon className="h-4 w-4" />;
|
|
21
|
+
case "system":
|
|
22
|
+
return <Monitor className="h-4 w-4" />;
|
|
23
23
|
default:
|
|
24
|
-
return <Monitor className=
|
|
24
|
+
return <Monitor className="h-4 w-4" />;
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<Button
|
|
30
|
-
variant=
|
|
31
|
-
size=
|
|
30
|
+
variant="outline"
|
|
31
|
+
size="icon"
|
|
32
32
|
onClick={cycleTheme}
|
|
33
|
-
aria-label=
|
|
33
|
+
aria-label="Toggle theme"
|
|
34
|
+
>
|
|
34
35
|
{getIcon()}
|
|
35
36
|
</Button>
|
|
36
37
|
);
|