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.
- package/generators/app/index.js +63 -18
- package/generators/app/templates/.env.dev +4 -0
- package/generators/app/templates/.env.sandbox +4 -0
- package/generators/app/templates/STRUCTURE.md +661 -0
- package/generators/app/templates/components.json +22 -0
- package/generators/app/templates/gitignore.template +78 -2
- package/generators/app/templates/index.html +14 -6
- package/generators/app/templates/openapi-ts.config.ts +29 -0
- package/generators/app/templates/package.json +40 -26
- package/generators/app/templates/public/favicon.svg +4 -0
- package/generators/app/templates/src/app.tsx +8 -8
- package/generators/app/templates/src/components/layout/language-switcher.tsx +40 -0
- package/generators/app/templates/src/components/layout/theme-switcher.tsx +37 -0
- package/generators/app/templates/src/components/theme/theme-provider.tsx +22 -28
- package/generators/app/templates/src/components/ui/button.tsx +34 -32
- package/generators/app/templates/src/components/ui/card.tsx +76 -0
- package/generators/app/templates/src/components/ui/field.tsx +242 -0
- package/generators/app/templates/src/components/ui/form.tsx +129 -0
- package/generators/app/templates/src/components/ui/input-group.tsx +170 -0
- package/generators/app/templates/src/components/ui/input.tsx +19 -21
- package/generators/app/templates/src/components/ui/label.tsx +24 -0
- package/generators/app/templates/src/components/ui/select.tsx +184 -0
- package/generators/app/templates/src/components/ui/separator.tsx +31 -0
- package/generators/app/templates/src/components/ui/textarea.tsx +22 -0
- package/generators/app/templates/src/index.css +83 -26
- package/generators/app/templates/src/lib/i18n.ts +31 -16
- package/generators/app/templates/src/lib/routes.ts +14 -0
- package/generators/app/templates/src/lib/utils.ts +3 -4
- package/generators/app/templates/src/locales/en/common.json +5 -0
- package/generators/app/templates/src/locales/en/theme.json +5 -0
- package/generators/app/templates/src/locales/index.ts +31 -0
- package/generators/app/templates/src/locales/ja/common.json +5 -0
- package/generators/app/templates/src/locales/ja/theme.json +6 -0
- package/generators/app/templates/src/main.tsx +19 -15
- package/generators/app/templates/src/modules/app/layouts/app-layout.tsx +37 -0
- package/generators/app/templates/src/modules/app/locales/app-en.json +9 -0
- package/generators/app/templates/src/modules/app/locales/app-ja.json +9 -0
- package/generators/app/templates/src/modules/auth/components/forgot-password-form.tsx +74 -0
- package/generators/app/templates/src/modules/auth/components/login-form.tsx +95 -0
- package/generators/app/templates/src/modules/auth/components/reset-password-form.tsx +112 -0
- package/generators/app/templates/src/modules/auth/components/signup-form.tsx +92 -0
- package/generators/app/templates/src/modules/auth/{auth-context.tsx → contexts/auth-context.tsx} +3 -3
- package/generators/app/templates/src/modules/auth/hooks/use-auth-hook.ts +180 -0
- package/generators/app/templates/src/modules/auth/layouts/auth-layout.tsx +28 -0
- package/generators/app/templates/src/modules/auth/locales/auth-en.json +105 -0
- package/generators/app/templates/src/modules/auth/locales/auth-ja.json +105 -0
- package/generators/app/templates/src/modules/auth/schemas/form-schemas.ts +26 -0
- package/generators/app/templates/src/modules/landing/components/auth-hero.tsx +34 -0
- package/generators/app/templates/src/modules/landing/components/welcome-hero.tsx +24 -0
- package/generators/app/templates/src/modules/landing/landing-page-layout.tsx +24 -0
- package/generators/app/templates/src/modules/landing/landing-page.tsx +17 -0
- package/generators/app/templates/src/modules/landing/layouts/landing-page-layout.tsx +24 -0
- package/generators/app/templates/src/modules/landing/locales/landing-en.json +12 -0
- package/generators/app/templates/src/modules/landing/locales/landing-ja.json +11 -0
- package/generators/app/templates/src/openapi-client-config.ts +6 -0
- package/generators/app/templates/src/routeTree.gen.ts +268 -3
- package/generators/app/templates/src/router.tsx +2 -2
- package/generators/app/templates/src/routes/__root.tsx +2 -2
- package/generators/app/templates/src/routes/_landing/index.tsx +10 -0
- package/generators/app/templates/src/routes/_landing/route.tsx +14 -0
- package/generators/app/templates/src/routes/app/index.tsx +4 -21
- package/generators/app/templates/src/routes/app/route.tsx +12 -8
- package/generators/app/templates/src/routes/auth/forgot-password.tsx +10 -0
- package/generators/app/templates/src/routes/auth/login.tsx +6 -7
- package/generators/app/templates/src/routes/auth/reset-password.tsx +15 -0
- package/generators/app/templates/src/routes/auth/route.tsx +23 -6
- package/generators/app/templates/src/routes/auth/signup.tsx +11 -0
- package/generators/app/templates/src/sdk/@tanstack/react-query.gen.ts +91 -0
- package/generators/app/templates/src/sdk/client/client.gen.ts +167 -0
- package/generators/app/templates/src/sdk/client/index.ts +23 -0
- package/generators/app/templates/src/sdk/client/types.gen.ts +197 -0
- package/generators/app/templates/src/sdk/client/utils.gen.ts +213 -0
- package/generators/app/templates/src/sdk/client.gen.ts +18 -0
- package/generators/app/templates/src/sdk/core/auth.gen.ts +42 -0
- package/generators/app/templates/src/sdk/core/bodySerializer.gen.ts +100 -0
- package/generators/app/templates/src/sdk/core/params.gen.ts +176 -0
- package/generators/app/templates/src/sdk/core/pathSerializer.gen.ts +181 -0
- package/generators/app/templates/src/sdk/core/queryKeySerializer.gen.ts +136 -0
- package/generators/app/templates/src/sdk/core/serverSentEvents.gen.ts +266 -0
- package/generators/app/templates/src/sdk/core/types.gen.ts +118 -0
- package/generators/app/templates/src/sdk/core/utils.gen.ts +143 -0
- package/generators/app/templates/src/sdk/index.ts +4 -0
- package/generators/app/templates/src/sdk/schemas.gen.ts +195 -0
- package/generators/app/templates/src/sdk/sdk.gen.ts +80 -0
- package/generators/app/templates/src/sdk/types.gen.ts +158 -0
- package/generators/app/templates/src/sdk/zod.gen.ts +148 -0
- package/generators/app/templates/src/vite-env.d.ts +2 -1
- package/generators/app/templates/tsconfig.json +1 -1
- package/generators/app/templates/vite.config.js +35 -21
- package/generators/constants.js +1 -1
- package/package.json +3 -2
- package/generators/app/templates/.env.example +0 -5
- package/generators/app/templates/README.md +0 -62
- package/generators/app/templates/src/lib/api/client.ts +0 -35
- package/generators/app/templates/src/locales/en.json +0 -18
- package/generators/app/templates/src/modules/auth/auth-types.ts +0 -24
- package/generators/app/templates/src/modules/auth/login/login-form.tsx +0 -49
- package/generators/app/templates/src/modules/auth/login/login-page.tsx +0 -12
- package/generators/app/templates/src/modules/auth/use-auth-hook.ts +0 -88
- package/generators/app/templates/src/routes/index.tsx +0 -12
- package/generators/app/templates/types.d.ts +0 -3
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
3
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function Select({
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
10
|
+
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function SelectGroup({
|
|
14
|
+
...props
|
|
15
|
+
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
|
16
|
+
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SelectValue({
|
|
20
|
+
...props
|
|
21
|
+
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
22
|
+
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function SelectTrigger({
|
|
26
|
+
className,
|
|
27
|
+
size = "default",
|
|
28
|
+
children,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
|
31
|
+
size?: "sm" | "default";
|
|
32
|
+
}) {
|
|
33
|
+
return (
|
|
34
|
+
<SelectPrimitive.Trigger
|
|
35
|
+
data-slot="select-trigger"
|
|
36
|
+
data-size={size}
|
|
37
|
+
className={cn(
|
|
38
|
+
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<SelectPrimitive.Icon asChild>
|
|
45
|
+
<ChevronDownIcon className="size-4 opacity-50" />
|
|
46
|
+
</SelectPrimitive.Icon>
|
|
47
|
+
</SelectPrimitive.Trigger>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function SelectContent({
|
|
52
|
+
className,
|
|
53
|
+
children,
|
|
54
|
+
position = "popper",
|
|
55
|
+
...props
|
|
56
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
57
|
+
return (
|
|
58
|
+
<SelectPrimitive.Portal>
|
|
59
|
+
<SelectPrimitive.Content
|
|
60
|
+
data-slot="select-content"
|
|
61
|
+
className={cn(
|
|
62
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
|
63
|
+
position === "popper" &&
|
|
64
|
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
65
|
+
className,
|
|
66
|
+
)}
|
|
67
|
+
position={position}
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
<SelectScrollUpButton />
|
|
71
|
+
<SelectPrimitive.Viewport
|
|
72
|
+
className={cn(
|
|
73
|
+
"p-1",
|
|
74
|
+
position === "popper" &&
|
|
75
|
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</SelectPrimitive.Viewport>
|
|
80
|
+
<SelectScrollDownButton />
|
|
81
|
+
</SelectPrimitive.Content>
|
|
82
|
+
</SelectPrimitive.Portal>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function SelectLabel({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
|
90
|
+
return (
|
|
91
|
+
<SelectPrimitive.Label
|
|
92
|
+
data-slot="select-label"
|
|
93
|
+
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function SelectItem({
|
|
100
|
+
className,
|
|
101
|
+
children,
|
|
102
|
+
...props
|
|
103
|
+
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
104
|
+
return (
|
|
105
|
+
<SelectPrimitive.Item
|
|
106
|
+
data-slot="select-item"
|
|
107
|
+
className={cn(
|
|
108
|
+
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
109
|
+
className,
|
|
110
|
+
)}
|
|
111
|
+
{...props}
|
|
112
|
+
>
|
|
113
|
+
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
|
114
|
+
<SelectPrimitive.ItemIndicator>
|
|
115
|
+
<CheckIcon className="size-4" />
|
|
116
|
+
</SelectPrimitive.ItemIndicator>
|
|
117
|
+
</span>
|
|
118
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
119
|
+
</SelectPrimitive.Item>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function SelectSeparator({
|
|
124
|
+
className,
|
|
125
|
+
...props
|
|
126
|
+
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
|
127
|
+
return (
|
|
128
|
+
<SelectPrimitive.Separator
|
|
129
|
+
data-slot="select-separator"
|
|
130
|
+
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function SelectScrollUpButton({
|
|
137
|
+
className,
|
|
138
|
+
...props
|
|
139
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
140
|
+
return (
|
|
141
|
+
<SelectPrimitive.ScrollUpButton
|
|
142
|
+
data-slot="select-scroll-up-button"
|
|
143
|
+
className={cn(
|
|
144
|
+
"flex cursor-default items-center justify-center py-1",
|
|
145
|
+
className,
|
|
146
|
+
)}
|
|
147
|
+
{...props}
|
|
148
|
+
>
|
|
149
|
+
<ChevronUpIcon className="size-4" />
|
|
150
|
+
</SelectPrimitive.ScrollUpButton>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function SelectScrollDownButton({
|
|
155
|
+
className,
|
|
156
|
+
...props
|
|
157
|
+
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
|
158
|
+
return (
|
|
159
|
+
<SelectPrimitive.ScrollDownButton
|
|
160
|
+
data-slot="select-scroll-down-button"
|
|
161
|
+
className={cn(
|
|
162
|
+
"flex cursor-default items-center justify-center py-1",
|
|
163
|
+
className,
|
|
164
|
+
)}
|
|
165
|
+
{...props}
|
|
166
|
+
>
|
|
167
|
+
<ChevronDownIcon className="size-4" />
|
|
168
|
+
</SelectPrimitive.ScrollDownButton>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
Select,
|
|
174
|
+
SelectContent,
|
|
175
|
+
SelectGroup,
|
|
176
|
+
SelectItem,
|
|
177
|
+
SelectLabel,
|
|
178
|
+
SelectScrollDownButton,
|
|
179
|
+
SelectScrollUpButton,
|
|
180
|
+
SelectSeparator,
|
|
181
|
+
SelectTrigger,
|
|
182
|
+
SelectValue,
|
|
183
|
+
};
|
|
184
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
const Separator = React.forwardRef<
|
|
9
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
10
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
11
|
+
>(
|
|
12
|
+
(
|
|
13
|
+
{ className, orientation = "horizontal", decorative = true, ...props },
|
|
14
|
+
ref
|
|
15
|
+
) => (
|
|
16
|
+
<SeparatorPrimitive.Root
|
|
17
|
+
ref={ref}
|
|
18
|
+
decorative={decorative}
|
|
19
|
+
orientation={orientation}
|
|
20
|
+
className={cn(
|
|
21
|
+
"shrink-0 bg-border",
|
|
22
|
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName
|
|
30
|
+
|
|
31
|
+
export { Separator }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
const Textarea = React.forwardRef<
|
|
6
|
+
HTMLTextAreaElement,
|
|
7
|
+
React.ComponentProps<"textarea">
|
|
8
|
+
>(({ className, ...props }, ref) => {
|
|
9
|
+
return (
|
|
10
|
+
<textarea
|
|
11
|
+
className={cn(
|
|
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
|
|
14
|
+
)}
|
|
15
|
+
ref={ref}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
})
|
|
20
|
+
Textarea.displayName = "Textarea"
|
|
21
|
+
|
|
22
|
+
export { Textarea }
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
|
|
3
|
+
@plugin "tailwindcss-animate";
|
|
4
|
+
@import "tw-animate-css";
|
|
5
|
+
|
|
3
6
|
@custom-variant dark (&:is(.dark *));
|
|
4
7
|
|
|
5
8
|
body {
|
|
@@ -11,39 +14,73 @@ body {
|
|
|
11
14
|
|
|
12
15
|
:root {
|
|
13
16
|
--background: oklch(1 0 0);
|
|
14
|
-
--foreground: oklch(0.
|
|
15
|
-
--primary: oklch(0.
|
|
16
|
-
--primary-foreground: oklch(0.985 0
|
|
17
|
-
--secondary: oklch(0.
|
|
18
|
-
--secondary-foreground: oklch(0.
|
|
19
|
-
--muted: oklch(0.
|
|
20
|
-
--muted-foreground: oklch(
|
|
21
|
-
--accent: oklch(0.
|
|
22
|
-
--accent-foreground: oklch(0.
|
|
17
|
+
--foreground: oklch(0.147 0.004 49.25);
|
|
18
|
+
--primary: oklch(0.216 0.006 56.043);
|
|
19
|
+
--primary-foreground: oklch(0.985 0.001 106.423);
|
|
20
|
+
--secondary: oklch(0.97 0.001 106.424);
|
|
21
|
+
--secondary-foreground: oklch(0.216 0.006 56.043);
|
|
22
|
+
--muted: oklch(0.97 0.001 106.424);
|
|
23
|
+
--muted-foreground: oklch(0.553 0.013 58.071);
|
|
24
|
+
--accent: oklch(0.97 0.001 106.424);
|
|
25
|
+
--accent-foreground: oklch(0.216 0.006 56.043);
|
|
23
26
|
--destructive: oklch(0.577 0.245 27.325);
|
|
24
27
|
--destructive-foreground: oklch(0.985 0 0);
|
|
25
|
-
--border: oklch(0.
|
|
26
|
-
--input: oklch(0.
|
|
27
|
-
--ring: oklch(
|
|
28
|
+
--border: oklch(0.923 0.003 48.717);
|
|
29
|
+
--input: oklch(0.923 0.003 48.717);
|
|
30
|
+
--ring: oklch(0.709 0.01 56.259);
|
|
28
31
|
--radius: 0.625rem;
|
|
32
|
+
--card: oklch(1 0 0);
|
|
33
|
+
--card-foreground: oklch(0.147 0.004 49.25);
|
|
34
|
+
--popover: oklch(1 0 0);
|
|
35
|
+
--popover-foreground: oklch(0.147 0.004 49.25);
|
|
36
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
37
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
38
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
39
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
40
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
41
|
+
--sidebar: oklch(0.985 0.001 106.423);
|
|
42
|
+
--sidebar-foreground: oklch(0.147 0.004 49.25);
|
|
43
|
+
--sidebar-primary: oklch(0.216 0.006 56.043);
|
|
44
|
+
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
|
45
|
+
--sidebar-accent: oklch(0.97 0.001 106.424);
|
|
46
|
+
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
|
|
47
|
+
--sidebar-border: oklch(0.923 0.003 48.717);
|
|
48
|
+
--sidebar-ring: oklch(0.709 0.01 56.259);
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
.dark {
|
|
32
|
-
--background: oklch(0.
|
|
33
|
-
--foreground: oklch(0.985 0
|
|
34
|
-
--primary: oklch(0.
|
|
35
|
-
--primary-foreground: oklch(0.
|
|
36
|
-
--secondary: oklch(0.
|
|
37
|
-
--secondary-foreground: oklch(0.985 0
|
|
38
|
-
--muted: oklch(0.
|
|
39
|
-
--muted-foreground: oklch(0.
|
|
40
|
-
--accent: oklch(0.
|
|
41
|
-
--accent-foreground: oklch(0.985 0
|
|
42
|
-
--destructive: oklch(0.
|
|
52
|
+
--background: oklch(0.147 0.004 49.25);
|
|
53
|
+
--foreground: oklch(0.985 0.001 106.423);
|
|
54
|
+
--primary: oklch(0.923 0.003 48.717);
|
|
55
|
+
--primary-foreground: oklch(0.216 0.006 56.043);
|
|
56
|
+
--secondary: oklch(0.268 0.007 34.298);
|
|
57
|
+
--secondary-foreground: oklch(0.985 0.001 106.423);
|
|
58
|
+
--muted: oklch(0.268 0.007 34.298);
|
|
59
|
+
--muted-foreground: oklch(0.709 0.01 56.259);
|
|
60
|
+
--accent: oklch(0.268 0.007 34.298);
|
|
61
|
+
--accent-foreground: oklch(0.985 0.001 106.423);
|
|
62
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
43
63
|
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
44
|
-
--border: oklch(0
|
|
45
|
-
--input: oklch(0
|
|
46
|
-
--ring: oklch(0.
|
|
64
|
+
--border: oklch(1 0 0 / 10%);
|
|
65
|
+
--input: oklch(1 0 0 / 15%);
|
|
66
|
+
--ring: oklch(0.553 0.013 58.071);
|
|
67
|
+
--card: oklch(0.216 0.006 56.043);
|
|
68
|
+
--card-foreground: oklch(0.985 0.001 106.423);
|
|
69
|
+
--popover: oklch(0.216 0.006 56.043);
|
|
70
|
+
--popover-foreground: oklch(0.985 0.001 106.423);
|
|
71
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
72
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
73
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
74
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
75
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
76
|
+
--sidebar: oklch(0.216 0.006 56.043);
|
|
77
|
+
--sidebar-foreground: oklch(0.985 0.001 106.423);
|
|
78
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
79
|
+
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
|
80
|
+
--sidebar-accent: oklch(0.268 0.007 34.298);
|
|
81
|
+
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
|
|
82
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
83
|
+
--sidebar-ring: oklch(0.553 0.013 58.071);
|
|
47
84
|
}
|
|
48
85
|
|
|
49
86
|
@theme inline {
|
|
@@ -66,6 +103,26 @@ body {
|
|
|
66
103
|
--radius-md: calc(var(--radius) - 2px);
|
|
67
104
|
--radius-lg: var(--radius);
|
|
68
105
|
--radius-xl: calc(var(--radius) + 4px);
|
|
106
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
107
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
108
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
109
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
110
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
111
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
112
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
113
|
+
--color-sidebar: var(--sidebar);
|
|
114
|
+
--color-chart-5: var(--chart-5);
|
|
115
|
+
--color-chart-4: var(--chart-4);
|
|
116
|
+
--color-chart-3: var(--chart-3);
|
|
117
|
+
--color-chart-2: var(--chart-2);
|
|
118
|
+
--color-chart-1: var(--chart-1);
|
|
119
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
120
|
+
--color-popover: var(--popover);
|
|
121
|
+
--color-card-foreground: var(--card-foreground);
|
|
122
|
+
--color-card: var(--card);
|
|
123
|
+
--radius-2xl: calc(var(--radius) + 8px);
|
|
124
|
+
--radius-3xl: calc(var(--radius) + 12px);
|
|
125
|
+
--radius-4xl: calc(var(--radius) + 16px);
|
|
69
126
|
}
|
|
70
127
|
|
|
71
128
|
@layer base {
|
|
@@ -1,46 +1,61 @@
|
|
|
1
|
-
import i18n from
|
|
2
|
-
import { initReactI18next } from
|
|
3
|
-
import
|
|
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';
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
export type SupportedLanguages = 'en' | 'ja';
|
|
8
|
+
const supportedLanguages: SupportedLanguages[] = ['en', 'ja'];
|
|
9
|
+
const defaultLanguage: SupportedLanguages = 'en';
|
|
10
|
+
const localStorageKey = 'app-language';
|
|
11
|
+
|
|
12
|
+
const setLanguageHooks = (language: SupportedLanguages) => {
|
|
13
|
+
setLocaleInClient(language);
|
|
14
|
+
z.config((z.locales[language] ?? z.locales[defaultLanguage] ?? z.locales['en'])());
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const getStoredLanguage = (): SupportedLanguages => {
|
|
6
18
|
try {
|
|
7
|
-
const stored = localStorage.getItem(
|
|
19
|
+
const stored = localStorage.getItem(localStorageKey);
|
|
8
20
|
if (!stored) {
|
|
9
|
-
localStorage.setItem(
|
|
10
|
-
return
|
|
21
|
+
localStorage.setItem(localStorageKey, defaultLanguage);
|
|
22
|
+
return defaultLanguage;
|
|
11
23
|
}
|
|
12
|
-
return
|
|
24
|
+
return stored as SupportedLanguages;
|
|
13
25
|
} catch {
|
|
14
|
-
return
|
|
26
|
+
return defaultLanguage;
|
|
15
27
|
}
|
|
16
28
|
};
|
|
17
29
|
|
|
18
30
|
if (!i18n.isInitialized) {
|
|
31
|
+
const lng = getStoredLanguage();
|
|
19
32
|
i18n.use(initReactI18next).init({
|
|
20
33
|
resources: {
|
|
21
|
-
en: { translation: en },
|
|
34
|
+
en: { translation: mergeTranslations('en') },
|
|
35
|
+
ja: { translation: mergeTranslations('ja') },
|
|
22
36
|
},
|
|
23
|
-
supportedLngs:
|
|
24
|
-
lng
|
|
25
|
-
fallbackLng:
|
|
37
|
+
supportedLngs: supportedLanguages,
|
|
38
|
+
lng,
|
|
39
|
+
fallbackLng: defaultLanguage,
|
|
26
40
|
interpolation: {
|
|
27
41
|
escapeValue: false,
|
|
28
42
|
},
|
|
29
43
|
});
|
|
44
|
+
setLanguageHooks(lng);
|
|
30
45
|
}
|
|
31
46
|
|
|
32
47
|
export const getLanguage = () => {
|
|
33
48
|
return i18n.language;
|
|
34
49
|
};
|
|
35
50
|
|
|
36
|
-
export const changeLanguage = (language:
|
|
51
|
+
export const changeLanguage = (language: SupportedLanguages) => {
|
|
37
52
|
try {
|
|
38
|
-
localStorage.setItem(
|
|
53
|
+
localStorage.setItem(localStorageKey, language);
|
|
39
54
|
} catch {
|
|
40
55
|
// Fallback if localStorage is not available
|
|
41
56
|
}
|
|
42
57
|
i18n.changeLanguage(language);
|
|
58
|
+
setLanguageHooks(language);
|
|
43
59
|
};
|
|
44
60
|
|
|
45
61
|
export default i18n;
|
|
46
|
-
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized route constants for default navigation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Default route for authenticated users
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_LOGGED_IN_ROUTE = '/app';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default route for non-authenticated users (landing page)
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_NON_LOGGED_IN_ROUTE = '/';
|
|
14
|
+
|
|
@@ -1,7 +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
|
}
|
|
7
|
-
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
|
|
13
|
+
export const mergeTranslations = (lang: SupportedLanguages) => {
|
|
14
|
+
if (lang === 'en') {
|
|
15
|
+
return {
|
|
16
|
+
auth: { ...enAuth },
|
|
17
|
+
app: { ...enApp },
|
|
18
|
+
landing: { ...enLanding },
|
|
19
|
+
common: { ...enCommon },
|
|
20
|
+
theme: { ...enTheme },
|
|
21
|
+
};
|
|
22
|
+
} else {
|
|
23
|
+
return {
|
|
24
|
+
auth: { ...jaAuth },
|
|
25
|
+
app: { ...jaApp },
|
|
26
|
+
landing: { ...jaLanding },
|
|
27
|
+
common: { ...jaCommon },
|
|
28
|
+
theme: { ...jaTheme },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
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';
|
|
7
8
|
|
|
8
9
|
const queryClient = new QueryClient();
|
|
9
10
|
|
|
10
|
-
const rootElement = document.getElementById(
|
|
11
|
+
const rootElement = document.getElementById('app');
|
|
11
12
|
if (rootElement && !rootElement.innerHTML) {
|
|
12
13
|
const root = ReactDOM.createRoot(rootElement);
|
|
13
14
|
root.render(
|
|
14
|
-
<
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
<React.StrictMode>
|
|
16
|
+
<I18nextProvider i18n={i18n}>
|
|
17
|
+
<ThemeProvider
|
|
18
|
+
defaultTheme='light'
|
|
19
|
+
storageKey='vite-ui-theme'>
|
|
20
|
+
<QueryClientProvider client={queryClient}>
|
|
21
|
+
<App />
|
|
22
|
+
</QueryClientProvider>
|
|
23
|
+
</ThemeProvider>
|
|
24
|
+
</I18nextProvider>
|
|
25
|
+
</React.StrictMode>
|
|
21
26
|
);
|
|
22
27
|
}
|
|
23
|
-
|
|
@@ -0,0 +1,37 @@
|
|
|
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';
|
|
6
|
+
|
|
7
|
+
export function AppLayout({ children }: { children: React.ReactNode }) {
|
|
8
|
+
const { t } = useTranslation();
|
|
9
|
+
const { mutate: logout, isPending } = useLogout();
|
|
10
|
+
|
|
11
|
+
const handleLogout = () => {
|
|
12
|
+
logout({});
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
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>
|
|
36
|
+
);
|
|
37
|
+
}
|