create-sonamu 0.1.14 → 0.1.15
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/catalog.json +5 -3
- package/package.json +1 -1
- package/template/src/.zed/settings.json +55 -0
- package/template/src/package.json +1 -1
- package/template/src/packages/api/package.json +2 -2
- package/template/src/packages/web/package.json +1 -1
- package/template/src/packages/web/src/App.tsx +2 -2
- package/template/src/packages/web/src/components/Sidebar.tsx +4 -2
- package/template/src/packages/web/src/contexts/sonamu-provider.tsx +19 -41
- package/template/src/packages/web/src/i18n/en.ts +1 -2
- package/template/src/packages/web/src/routes/__root.tsx +3 -3
- package/template/src/packages/web/src/routes/admin/index.tsx +1 -1
- package/template/src/packages/web/src/routes/index.tsx +1 -1
- package/template/src/packages/web/src/services/sonamu.shared.ts +1 -2
package/catalog.json
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"@aws-sdk/ecr-public": "^3.958.0",
|
|
13
13
|
"@aws-sdk/lib-storage": "^3.971.0",
|
|
14
14
|
"@aws-sdk/s3-request-presigner": "^3.958.0",
|
|
15
|
-
"@better-auth/passkey": "
|
|
16
|
-
"@better-auth/sso": "
|
|
15
|
+
"@better-auth/passkey": "~1.4.18",
|
|
16
|
+
"@better-auth/sso": "~1.4.18",
|
|
17
17
|
"@biomejs/biome": "^2.3.13",
|
|
18
18
|
"@biomejs/js-api": "^4.0.0",
|
|
19
19
|
"@biomejs/wasm-nodejs": "^2.3.7",
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
"@swc/cli": "^0.7.8",
|
|
80
80
|
"@swc/core": "^1.13.5",
|
|
81
81
|
"@tailwindcss/postcss": "^4.0.0",
|
|
82
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
82
83
|
"@tailwindcss/vite": "^4.1.17",
|
|
83
84
|
"@tanstack/react-query": "^5.90.12",
|
|
84
85
|
"@tanstack/react-query-devtools": "^5.91.1",
|
|
@@ -91,6 +92,7 @@
|
|
|
91
92
|
"@types/lodash-es": "^4.17.12",
|
|
92
93
|
"@types/luxon": "^3.0.3",
|
|
93
94
|
"@types/mime-types": "^3.0.1",
|
|
95
|
+
"@types/minimist": "^1.2.5",
|
|
94
96
|
"@types/node": "25.0.7",
|
|
95
97
|
"@types/pg": "^8.15.6",
|
|
96
98
|
"@types/picomatch": "^4.0.0",
|
|
@@ -111,7 +113,7 @@
|
|
|
111
113
|
"axios": "^1.13.2",
|
|
112
114
|
"bcrypt": "^6.0.0",
|
|
113
115
|
"bentocache": "^1.5.0",
|
|
114
|
-
"better-auth": "
|
|
116
|
+
"better-auth": "~1.4.18",
|
|
115
117
|
"c12": "^3.3.2",
|
|
116
118
|
"c8": "^10.1.3",
|
|
117
119
|
"chalk": "^4.1.2",
|
package/package.json
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"languages": {
|
|
3
|
+
"JavaScript": {
|
|
4
|
+
"format_on_save": "on",
|
|
5
|
+
"code_actions_on_format": {
|
|
6
|
+
"source.organizeImports.biome": true,
|
|
7
|
+
"source.fixAll.biome": true,
|
|
8
|
+
},
|
|
9
|
+
"prettier": { "allowed": false },
|
|
10
|
+
"formatter": {
|
|
11
|
+
"language_server": {
|
|
12
|
+
"name": "biome",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
"language_servers": ["!typescript-language-server", "vtsls", "biome"],
|
|
16
|
+
},
|
|
17
|
+
"TypeScript": {
|
|
18
|
+
"format_on_save": "on",
|
|
19
|
+
"code_actions_on_format": {
|
|
20
|
+
"source.organizeImports.biome": true,
|
|
21
|
+
"source.fixAll.biome": true,
|
|
22
|
+
},
|
|
23
|
+
"prettier": { "allowed": false },
|
|
24
|
+
"formatter": {
|
|
25
|
+
"language_server": {
|
|
26
|
+
"name": "biome",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
"language_servers": ["!typescript-language-server", "vtsls", "biome"],
|
|
30
|
+
},
|
|
31
|
+
"TSX": {
|
|
32
|
+
"format_on_save": "on",
|
|
33
|
+
"code_actions_on_format": {
|
|
34
|
+
"source.organizeImports.biome": true,
|
|
35
|
+
"source.fixAll.biome": true,
|
|
36
|
+
},
|
|
37
|
+
"prettier": { "allowed": false },
|
|
38
|
+
"formatter": {
|
|
39
|
+
"language_server": {
|
|
40
|
+
"name": "biome",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
"language_servers": ["!typescript-language-server", "vtsls", "biome", "tailwindcss-language-server"],
|
|
44
|
+
},
|
|
45
|
+
"JSON": {
|
|
46
|
+
"format_on_save": "on",
|
|
47
|
+
"formatter": {
|
|
48
|
+
"language_server": {
|
|
49
|
+
"name": "biome",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
"language_servers": ["biome"],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
"@swc/core": "^1.13.5",
|
|
33
33
|
"ai": "^6.0.1",
|
|
34
34
|
"bcrypt": "^6.0.0",
|
|
35
|
-
"better-auth": "
|
|
35
|
+
"better-auth": "~1.4.18",
|
|
36
36
|
"chalk": "^4.1.2",
|
|
37
37
|
"date-fns": "^4.1.0",
|
|
38
38
|
"dotenv": "^16",
|
|
39
39
|
"knex": "^3.1.0",
|
|
40
40
|
"pg": "^8.16.3",
|
|
41
41
|
"radashi": "^12.2.0",
|
|
42
|
-
"sonamu": "^0.8.
|
|
42
|
+
"sonamu": "^0.8.12",
|
|
43
43
|
"zod": "^4.3.6"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
@@ -17,8 +17,8 @@ function App({ children }: AppProps) {
|
|
|
17
17
|
useEffect(() => {
|
|
18
18
|
// 브라우저 locale 감지
|
|
19
19
|
const browserLocale = navigator.language.split("-")[0];
|
|
20
|
-
if (SUPPORTED_LOCALES.includes(browserLocale as typeof SUPPORTED_LOCALES[number])) {
|
|
21
|
-
setLocale(browserLocale as typeof SUPPORTED_LOCALES[number]);
|
|
20
|
+
if (SUPPORTED_LOCALES.includes(browserLocale as (typeof SUPPORTED_LOCALES)[number])) {
|
|
21
|
+
setLocale(browserLocale as (typeof SUPPORTED_LOCALES)[number]);
|
|
22
22
|
}
|
|
23
23
|
}, []);
|
|
24
24
|
|
|
@@ -47,7 +47,9 @@ const userMenuItems: MenuItemProps[] = [
|
|
|
47
47
|
export default function Sidebar({ className }: SidebarProps) {
|
|
48
48
|
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
|
49
49
|
const { auth } = useSonamuContext();
|
|
50
|
-
const
|
|
50
|
+
const session = auth?.useSession?.();
|
|
51
|
+
const user = session?.data?.user ?? null;
|
|
52
|
+
const logout = () => auth?.signOut?.();
|
|
51
53
|
|
|
52
54
|
// 경로에 따라 메뉴 및 타이틀 분기
|
|
53
55
|
const isAdmin = pathname.startsWith("/admin");
|
|
@@ -70,7 +72,7 @@ export default function Sidebar({ className }: SidebarProps) {
|
|
|
70
72
|
</div>
|
|
71
73
|
{user && (
|
|
72
74
|
<div className="text-sm text-sidebar-foreground/70 mt-1">
|
|
73
|
-
{user.
|
|
75
|
+
{user.name ?? user.email ?? "User"}
|
|
74
76
|
</div>
|
|
75
77
|
)}
|
|
76
78
|
</SidebarHeader>
|
|
@@ -1,59 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type SonamuFile,
|
|
3
|
-
SonamuProvider,
|
|
3
|
+
SonamuProvider as BaseSonamuProvider,
|
|
4
4
|
useSonamuBaseContext,
|
|
5
5
|
} from "@sonamu-kit/react-components";
|
|
6
6
|
import type { ReactNode } from "react";
|
|
7
7
|
import type { DictKey, MergedDictionary } from "@/i18n/sd.generated";
|
|
8
8
|
import { SD } from "@/i18n/sd.generated";
|
|
9
9
|
|
|
10
|
-
// TODO: User 엔티티 추가 후
|
|
11
|
-
//
|
|
12
|
-
//
|
|
10
|
+
// TODO: User 엔티티 추가 후 authOptions를 설정하세요
|
|
11
|
+
// import type { BetterAuthClientOptions } from "better-auth/client";
|
|
12
|
+
// import { inferAdditionalFields } from "better-auth/client/plugins";
|
|
13
|
+
// const authOptions = { plugins: [...] } satisfies BetterAuthClientOptions;
|
|
14
|
+
|
|
13
15
|
export function useSonamuContext() {
|
|
14
|
-
return useSonamuBaseContext<MergedDictionary
|
|
16
|
+
return useSonamuBaseContext<MergedDictionary>();
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
user: null,
|
|
22
|
-
loading: false,
|
|
23
|
-
login: async (_loginParams: any) => {
|
|
24
|
-
// TODO: Implement login logic
|
|
25
|
-
console.log("Login not implemented yet");
|
|
26
|
-
},
|
|
27
|
-
logout: async () => {
|
|
28
|
-
// TODO: Implement logout logic
|
|
29
|
-
console.log("Logout not implemented yet");
|
|
30
|
-
},
|
|
31
|
-
refetch: async () => {
|
|
32
|
-
// 세션 정보 다시 불러오기
|
|
33
|
-
},
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
// Uploader 설정
|
|
37
|
-
// TODO: File 엔티티 추가 후 FileService.useUploadMutation()을 사용하세요
|
|
38
|
-
const uploader = async (files: File[]): Promise<SonamuFile[]> => {
|
|
39
|
-
if (files.length === 0) {
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
console.log("File upload not implemented yet");
|
|
19
|
+
// Uploader 설정
|
|
20
|
+
// TODO: File 엔티티 추가 후 FileService.useUploadMutation()을 사용하세요
|
|
21
|
+
const uploader = async (files: File[]): Promise<SonamuFile[]> => {
|
|
22
|
+
if (files.length === 0) {
|
|
43
23
|
return [];
|
|
44
|
-
}
|
|
24
|
+
}
|
|
25
|
+
console.log("File upload not implemented yet");
|
|
26
|
+
return [];
|
|
27
|
+
};
|
|
45
28
|
|
|
46
|
-
|
|
47
|
-
const sd = <K extends DictKey>(key: K): ReturnType<typeof SD<K>> => SD(key);
|
|
48
|
-
|
|
49
|
-
return { auth, uploader, SD: sd };
|
|
50
|
-
}
|
|
29
|
+
const sd = <K extends DictKey>(key: K): ReturnType<typeof SD<K>> => SD(key);
|
|
51
30
|
|
|
52
|
-
export function
|
|
53
|
-
const config = useSonamuConfig();
|
|
31
|
+
export function SonamuProvider({ children }: { children: ReactNode }) {
|
|
54
32
|
return (
|
|
55
|
-
<
|
|
33
|
+
<BaseSonamuProvider<MergedDictionary> uploader={uploader} SD={sd}>
|
|
56
34
|
{children}
|
|
57
|
-
</
|
|
35
|
+
</BaseSonamuProvider>
|
|
58
36
|
);
|
|
59
37
|
}
|
|
@@ -19,8 +19,7 @@ export default {
|
|
|
19
19
|
"common.login": "Login",
|
|
20
20
|
"common.logout": "Logout",
|
|
21
21
|
"common.manage": "Manage",
|
|
22
|
-
"common.results": (count: number) =>
|
|
23
|
-
plural(count, `${count} result`, `${count} results`),
|
|
22
|
+
"common.results": (count: number) => plural(count, `${count} result`, `${count} results`),
|
|
24
23
|
"common.save": "Save",
|
|
25
24
|
"common.search": "Search",
|
|
26
25
|
"common.searchPlaceholder": "Search...",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
2
|
import { createRootRouteWithContext, HeadContent, Outlet, Scripts } from "@tanstack/react-router";
|
|
3
3
|
import App from "@/App";
|
|
4
|
-
import {
|
|
4
|
+
import { SonamuProvider } from "@/contexts/sonamu-provider";
|
|
5
5
|
|
|
6
6
|
export interface RouterContext {
|
|
7
7
|
queryClient: QueryClient;
|
|
@@ -30,11 +30,11 @@ function RootComponent() {
|
|
|
30
30
|
<body>
|
|
31
31
|
<div id="root">
|
|
32
32
|
<QueryClientProvider client={queryClient}>
|
|
33
|
-
<
|
|
33
|
+
<SonamuProvider>
|
|
34
34
|
<App>
|
|
35
35
|
<Outlet />
|
|
36
36
|
</App>
|
|
37
|
-
</
|
|
37
|
+
</SonamuProvider>
|
|
38
38
|
</QueryClientProvider>
|
|
39
39
|
</div>
|
|
40
40
|
<Scripts />
|
|
@@ -9,7 +9,7 @@ function AdminDashboard() {
|
|
|
9
9
|
<div>
|
|
10
10
|
<h1 className="text-2xl font-bold mb-4">Admin Dashboard</h1>
|
|
11
11
|
<p className="text-gray-600">Welcome to the admin panel. Start building your application.</p>
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
{/* TODO: 대시보드 위젯 추가 */}
|
|
14
14
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-8">
|
|
15
15
|
<div className="p-6 bg-gray-50 rounded-lg border">
|
|
@@ -9,7 +9,7 @@ function HomePage() {
|
|
|
9
9
|
<div>
|
|
10
10
|
<h1 className="text-2xl font-bold mb-4">Welcome to Sonamu</h1>
|
|
11
11
|
<p className="text-gray-600">Start building your application.</p>
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
{/* TODO: 사용자용 콘텐츠 추가 */}
|
|
14
14
|
<div className="mt-8">
|
|
15
15
|
<p className="text-sm text-gray-500">
|
|
@@ -221,14 +221,13 @@ export type ApplySonamuFilter<
|
|
|
221
221
|
TNumericKeys extends Exclude<keyof TEntity, TOmitKeys> = never,
|
|
222
222
|
> = FilterQuery<Omit<TEntity, TOmitKeys>, TNumericKeys>;
|
|
223
223
|
|
|
224
|
-
|
|
225
224
|
/**
|
|
226
225
|
* 필드명과 값을 기반으로 FilterPropType을 추론
|
|
227
226
|
*/
|
|
228
227
|
export function getFieldPropType(
|
|
229
228
|
fieldName: string,
|
|
230
229
|
value: any,
|
|
231
|
-
numericColumns: readonly string[]
|
|
230
|
+
numericColumns: readonly string[],
|
|
232
231
|
): FilterPropType {
|
|
233
232
|
// numeric 타입 체크 (명시적으로 지정된 컬럼)
|
|
234
233
|
if (numericColumns.includes(fieldName)) {
|