create-sonamu 0.0.1 → 0.0.3

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 (73) hide show
  1. package/README.md +365 -2
  2. package/index.js +632 -0
  3. package/package.json +30 -20
  4. package/template/src/README.md +274 -0
  5. package/template/src/packages/api/.swcrc +18 -0
  6. package/template/src/packages/api/custom-sequencer.ts +23 -0
  7. package/template/src/packages/api/database/docker-compose.yml +19 -0
  8. package/template/src/packages/api/database/fixtures/init.sh +15 -0
  9. package/template/src/packages/api/database/scripts/dump.sh +62 -0
  10. package/template/src/packages/api/database/scripts/seed.sh +60 -0
  11. package/template/src/packages/api/package.json +55 -0
  12. package/template/src/packages/api/package.json.bak +55 -0
  13. package/template/src/packages/api/src/application/.gitkeep +1 -0
  14. package/template/src/packages/api/src/i18n/en.ts +59 -0
  15. package/template/src/packages/api/src/i18n/ko.ts +57 -0
  16. package/template/src/packages/api/src/index.ts +6 -0
  17. package/template/src/packages/api/src/migrations/.gitkeep +1 -0
  18. package/template/src/packages/api/src/sonamu.config.ts +162 -0
  19. package/template/src/packages/api/src/testing/fixture.ts +6 -0
  20. package/template/src/packages/api/src/testing/global.ts +6 -0
  21. package/template/src/packages/api/src/testing/setup-mocks.ts +44 -0
  22. package/template/src/packages/api/src/typings/fastify.d.ts +7 -0
  23. package/template/src/packages/api/src/typings/sonamu.d.ts +19 -0
  24. package/template/src/packages/api/src/utils/subset-loaders.ts +11 -0
  25. package/template/src/packages/api/tsconfig.json +60 -0
  26. package/template/src/packages/api/tsconfig.schemas.json +5 -0
  27. package/template/src/packages/api/tsconfig.types.json +5 -0
  28. package/template/src/packages/api/vitest.config.ts +36 -0
  29. package/template/src/packages/web/.sonamu.env +2 -0
  30. package/template/src/{web → packages/web}/index.html +3 -3
  31. package/template/src/packages/web/package.json +49 -0
  32. package/template/src/packages/web/package.json.bak +49 -0
  33. package/template/src/packages/web/src/App.tsx +17 -0
  34. package/template/src/packages/web/src/admin-common/ApiLogViewer.tsx +285 -0
  35. package/template/src/packages/web/src/admin-common/CommonModal.tsx +91 -0
  36. package/template/src/packages/web/src/contexts/sonamu-provider.tsx +41 -0
  37. package/template/src/packages/web/src/entry-client.tsx +72 -0
  38. package/template/src/packages/web/src/entry-server.generated.tsx +58 -0
  39. package/template/src/packages/web/src/i18n/en.ts +63 -0
  40. package/template/src/packages/web/src/i18n/ko.ts +61 -0
  41. package/template/src/packages/web/src/routeTree.gen.ts +27 -0
  42. package/template/src/packages/web/src/routes/__root.tsx +44 -0
  43. package/template/src/packages/web/src/routes/index.tsx +14 -0
  44. package/template/src/packages/web/src/styles/tailwind.css +5 -0
  45. package/template/src/packages/web/src/vite-env.d.ts +2 -0
  46. package/template/src/packages/web/tailwind.config.ts +8 -0
  47. package/template/src/{web → packages/web}/tsconfig.json +5 -3
  48. package/template/src/packages/web/vite.config.ts +51 -0
  49. package/template/src/api/README.md +0 -3
  50. package/template/src/api/database/docker-compose.yml +0 -17
  51. package/template/src/api/package.json +0 -39
  52. package/template/src/api/sonamu.config.json +0 -11
  53. package/template/src/api/src/configs/db.ts +0 -25
  54. package/template/src/api/src/index.ts +0 -36
  55. package/template/src/api/src/testing/bootstrap.ts +0 -20
  56. package/template/src/api/src/testing/fixture.ts +0 -18
  57. package/template/src/api/src/testing/global.ts +0 -7
  58. package/template/src/api/src/typings/sonamu.d.ts +0 -5
  59. package/template/src/api/tsconfig.json +0 -115
  60. package/template/src/api/vite.config.mts +0 -15
  61. package/template/src/web/package.json +0 -40
  62. package/template/src/web/public/vite.svg +0 -1
  63. package/template/src/web/src/App.css +0 -34
  64. package/template/src/web/src/App.tsx +0 -15
  65. package/template/src/web/src/assets/react.svg +0 -1
  66. package/template/src/web/src/index.css +0 -76
  67. package/template/src/web/src/main.tsx +0 -30
  68. package/template/src/web/src/pages/index.tsx +0 -11
  69. package/template/src/web/src/vite-env.d.ts +0 -1
  70. package/template/src/web/vite.config.ts +0 -20
  71. /package/template/src/{web/src/services → packages/api/database/dumps}/.gitkeep +0 -0
  72. /package/template/src/{api/database/scripts/init.sql → packages/web/src/services/.gitkeep} +0 -0
  73. /package/template/src/{web → packages/web}/tsconfig.node.json +0 -0
@@ -0,0 +1,59 @@
1
+ import { plural } from "sonamu/dict";
2
+
3
+ /**
4
+ * Project EN Dictionary
5
+ */
6
+ export default {
7
+ "common.all": "All",
8
+ "common.backToList": "Back to List",
9
+ "common.cancel": "Cancel",
10
+ "common.close": "Close",
11
+ "common.confirm": "Confirm",
12
+ "common.create": "Create",
13
+ "common.createdAt": "Created At",
14
+ "common.delete": "Delete",
15
+ "common.edit": "Edit",
16
+ "common.login": "Login",
17
+ "common.logout": "Logout",
18
+ "common.manage": "Manage",
19
+ "common.results": (count: number) =>
20
+ plural(count, { one: `${count} result`, other: `${count} results` }),
21
+ "common.save": "Save",
22
+ "common.search": "Search",
23
+ "common.searchPlaceholder": "Search...",
24
+ "common.searchType": "Search Type",
25
+ "common.sort": "Sort",
26
+ "confirm.delete": "Are you sure you want to delete?",
27
+ "confirm.save": "Do you want to save?",
28
+ "dashboard.adminMenu": "Admin Menu",
29
+ "dashboard.createdAt": "Joined",
30
+ "dashboard.email": "Email",
31
+ "dashboard.loginRequired": "Please login to continue.",
32
+ "dashboard.name": "Name",
33
+ "dashboard.role": "Role",
34
+ "dashboard.title": "Admin Dashboard",
35
+ "dashboard.welcome": "Welcome!",
36
+ "delete.confirm.description":
37
+ "This action cannot be undone. This will permanently delete this item.",
38
+ "delete.confirm.title": "Are you sure?",
39
+ "entity.create": (name: string) => `Create ${name}`,
40
+ "entity.edit": (name: string, id: number) => `Edit ${name} (#${id})`,
41
+ "entity.list": (name: string) => `${name} List`,
42
+ "entity.listManage": (name: string) => `Manage ${name} List`,
43
+ "error.alreadyProcessed": "Already processed",
44
+ "error.badRequest": "Bad request",
45
+ "error.duplicateRow": "Duplicate data",
46
+ "error.forbidden": "Forbidden",
47
+ "error.internalServerError": "Internal server error",
48
+ "error.notFound": "Not found",
49
+ "error.serviceUnavailable": "Service unavailable",
50
+ "error.unauthorized": "Unauthorized",
51
+ notFound: (name: string, id: number) => `${name} ID ${id} not found`,
52
+ "validation.email": "Invalid email format",
53
+ "validation.maxLength": (field: string, max: number) =>
54
+ `${field} must be at most ${max} characters`,
55
+ "validation.minLength": (field: string, min: number) =>
56
+ `${field} must be at least ${min} characters`,
57
+ "validation.required": (field: string) => `${field} is required`,
58
+ "validation.url": "Invalid URL format",
59
+ };
@@ -0,0 +1,57 @@
1
+ import { josa } from "sonamu/dict";
2
+
3
+ /**
4
+ * Project KO Dictionary
5
+ */
6
+ export default {
7
+ "common.all": "전체",
8
+ "common.backToList": "목록으로",
9
+ "common.cancel": "취소",
10
+ "common.close": "닫기",
11
+ "common.confirm": "확인",
12
+ "common.create": "생성",
13
+ "common.createdAt": "등록",
14
+ "common.delete": "삭제",
15
+ "common.edit": "수정",
16
+ "common.login": "로그인",
17
+ "common.logout": "로그아웃",
18
+ "common.manage": "관리",
19
+ "common.results": (count: number) => `${count}개 결과`,
20
+ "common.save": "저장",
21
+ "common.search": "검색",
22
+ "common.searchPlaceholder": "검색...",
23
+ "common.searchType": "검색 유형",
24
+ "common.sort": "정렬",
25
+ "confirm.delete": "정말 삭제하시겠습니까?",
26
+ "confirm.save": "저장하시겠습니까?",
27
+ "dashboard.adminMenu": "관리 메뉴",
28
+ "dashboard.createdAt": "가입일",
29
+ "dashboard.email": "이메일",
30
+ "dashboard.loginRequired": "로그인이 필요합니다.",
31
+ "dashboard.name": "이름",
32
+ "dashboard.role": "역할",
33
+ "dashboard.title": "관리자 대시보드",
34
+ "dashboard.welcome": "환영합니다!",
35
+ "delete.confirm.description": "이 작업은 취소할 수 없습니다. 항목이 영구적으로 삭제됩니다.",
36
+ "delete.confirm.title": "정말 삭제하시겠습니까?",
37
+ "entity.create": (name: string) => `${name} 생성`,
38
+ "entity.edit": (name: string, id: number) => `${name} 수정 (#${id})`,
39
+ "entity.list": (name: string) => `${name} 목록`,
40
+ "entity.listManage": (name: string) => `${name} 목록 관리`,
41
+ "error.alreadyProcessed": "이미 처리되었습니다",
42
+ "error.badRequest": "잘못된 요청입니다",
43
+ "error.duplicateRow": "중복된 데이터입니다",
44
+ "error.forbidden": "권한이 없습니다",
45
+ "error.internalServerError": "서버 오류가 발생했습니다",
46
+ "error.notFound": "찾을 수 없습니다",
47
+ "error.serviceUnavailable": "서비스를 사용할 수 없습니다",
48
+ "error.unauthorized": "인증이 필요합니다",
49
+ notFound: (name: string, id: number) => `존재하지 않는 ${name} ID ${id}`,
50
+ "validation.email": "올바른 이메일 형식이 아닙니다",
51
+ "validation.maxLength": (field: string, max: number) =>
52
+ `${field}${josa(field, "은는")} 최대 ${max}자까지 입력할 수 있습니다`,
53
+ "validation.minLength": (field: string, min: number) =>
54
+ `${field}${josa(field, "은는")} 최소 ${min}자 이상이어야 합니다`,
55
+ "validation.required": (field: string) => `${josa(field, "은는")} 필수입니다`,
56
+ "validation.url": "올바른 URL 형식이 아닙니다",
57
+ };
@@ -0,0 +1,6 @@
1
+ import { Sonamu } from "sonamu";
2
+
3
+ async function bootstrap() {
4
+ await Sonamu.createServer();
5
+ }
6
+ bootstrap();
@@ -0,0 +1 @@
1
+ # This directory will contain database migration files generated by Sonamu UI.
@@ -0,0 +1,162 @@
1
+ import path from "node:path";
2
+ import { CachePresets, defineConfig } from "sonamu";
3
+ import { drivers as cacheDrivers, store } from "sonamu/cache";
4
+ import { drivers } from "sonamu/storage";
5
+
6
+ const host = "localhost";
7
+ const port = 1028;
8
+
9
+ export default defineConfig({
10
+ projectName: process.env.PROJECT_NAME ?? "SonamuProject",
11
+ api: {
12
+ dir: "api",
13
+ timezone: "Asia/Seoul",
14
+ route: {
15
+ prefix: "/api",
16
+ },
17
+ },
18
+ i18n: {
19
+ defaultLocale: "ko",
20
+ supportedLocales: ["ko", "en"],
21
+ },
22
+ sync: {
23
+ targets: ["web"],
24
+ },
25
+ database: {
26
+ database: "pg",
27
+ name: process.env.DATABASE_NAME ?? "database_name",
28
+ defaultOptions: {
29
+ connection: {
30
+ host: process.env.DB_HOST || "0.0.0.0",
31
+ port: Number(process.env.DB_PORT) || 5432,
32
+ user: process.env.DB_USER || "postgres",
33
+ password: process.env.DB_PASSWORD,
34
+ },
35
+ },
36
+ },
37
+
38
+ server: {
39
+ listen: { port, host },
40
+ plugins: {
41
+ formbody: true,
42
+ qs: true,
43
+ multipart: { limits: { fileSize: 1024 * 1024 * 30 } },
44
+ static: {
45
+ root: path.join(import.meta.dirname, "/../", "public"),
46
+ prefix: "/api/public",
47
+ },
48
+ session: {
49
+ secret: process.env.SESSION_SECRET || "sonamu-secret-key-change-this-in-production",
50
+ salt: process.env.SESSION_SALT || "mq9hDxBCDbsQDR6N",
51
+ cookie: {
52
+ domain: "localhost",
53
+ path: "/",
54
+ maxAge: 60 * 60 * 24 * 365 * 10,
55
+ },
56
+ },
57
+ custom: (_server) => {
58
+ // nothing yet
59
+ },
60
+ },
61
+
62
+ auth: true,
63
+ // auth: {
64
+ // userSerializer: async (user, _request) => user,
65
+ // userDeserializer: async (serialized, _request) => serialized,
66
+ // },
67
+
68
+ apiConfig: {
69
+ contextProvider: (defaultContext, request) => {
70
+ return {
71
+ ...defaultContext,
72
+ ip: request.ip,
73
+ session: request.session,
74
+ body: request.body,
75
+ };
76
+ },
77
+ guardHandler: (_guard, _request, _api) => {
78
+ console.log("NOTHING YET");
79
+ },
80
+ cacheControlHandler: (req) => {
81
+ switch (req.type) {
82
+ case "assets":
83
+ // Hash 포함된 파일: 영구 캐시
84
+ if (req.path.match(/-[a-f0-9]+\./)) {
85
+ return CachePresets.immutable;
86
+ }
87
+ return CachePresets.longLived;
88
+
89
+ case "api":
90
+ // GET 요청만 캐싱 고려
91
+ if (req.method === "GET") {
92
+ // 특정 경로는 짧은 캐시
93
+ if (req.path.startsWith("/api/static-data")) {
94
+ return CachePresets.shortLived;
95
+ }
96
+ }
97
+ // 기본: 캐시 없음
98
+ return CachePresets.noCache;
99
+
100
+ case "ssr":
101
+ // SSR 페이지: 10초 캐시
102
+ return CachePresets.ssr;
103
+
104
+ case "csr":
105
+ // CSR fallback (index.html): 1분 캐시
106
+ return CachePresets.shortLived;
107
+ }
108
+ },
109
+ },
110
+
111
+ storage: {
112
+ drivers: {
113
+ fs: drivers.fs({
114
+ location: path.join(import.meta.dirname, "/../public/uploaded"),
115
+ visibility: "public",
116
+ urlBuilder: {
117
+ async generateURL(key) {
118
+ return `/api/public/uploaded/${key}`;
119
+ },
120
+ async generateSignedURL(key) {
121
+ return `/api/public/uploaded/${key}`;
122
+ },
123
+ },
124
+ }),
125
+ s3: drivers.s3({
126
+ credentials: {
127
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? "",
128
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? "",
129
+ },
130
+ region: process.env.S3_REGION ?? "ap-northeast-2",
131
+ bucket: process.env.S3_BUCKET ?? "sonamu_default_bucket",
132
+ visibility: "private",
133
+ }),
134
+ },
135
+ },
136
+
137
+ cache: {
138
+ default: "main",
139
+ stores: {
140
+ main: store().useL1Layer(cacheDrivers.memory({ maxSize: "50mb" })),
141
+ },
142
+ ttl: "5m",
143
+ prefix: "",
144
+ },
145
+
146
+ lifecycle: {
147
+ onStart: () => {
148
+ console.log(`🌲 Server listening on http://${host}:${port}`);
149
+ },
150
+ onShutdown: () => {
151
+ console.log("graceful shutdown");
152
+ },
153
+ onError: (error, _request, reply) => {
154
+ console.error(error);
155
+ reply.status(500).send({
156
+ name: error.name,
157
+ message: error.message,
158
+ });
159
+ },
160
+ },
161
+ },
162
+ });
@@ -0,0 +1,6 @@
1
+ import { createFixtureLoader } from "sonamu/test";
2
+
3
+ export const loadFixtures = createFixtureLoader({
4
+ // Define your fixtures...
5
+ fixture01: async () => {},
6
+ });
@@ -0,0 +1,6 @@
1
+ import dotenv from "dotenv";
2
+
3
+ dotenv.config();
4
+
5
+ // sonamu.config.ts의 test 설정을 기반으로 병렬 테스트 환경을 구성합니다.
6
+ export { setup } from "sonamu/test";
@@ -0,0 +1,44 @@
1
+ import type { MakeDirectoryOptions, Mode, PathLike, RmOptions } from "fs";
2
+ import type { FileHandle } from "fs/promises";
3
+ import { Naite } from "sonamu";
4
+ import { vi } from "vitest";
5
+
6
+ // GlobalMock: fs/promises
7
+ vi.mock("fs/promises", async (importOriginal) => {
8
+ const actual = (await importOriginal()) as typeof import("fs/promises");
9
+ return {
10
+ ...actual,
11
+ access: vi.fn((path: PathLike, mode?: number) => {
12
+ const vfs = Naite.get("mock:fs/promises:virtualFileSystem").result();
13
+ if (vfs.some((v) => v === path)) {
14
+ return Promise.resolve();
15
+ }
16
+
17
+ return actual.access(path, mode);
18
+ }),
19
+ mkdir: vi.fn(
20
+ async (
21
+ path: PathLike,
22
+ options?: MakeDirectoryOptions | Mode | null,
23
+ ): Promise<string | undefined> => {
24
+ Naite.t("fs:mkdir", { path, options });
25
+ if (typeof options === "object" && options?.recursive) {
26
+ return typeof path === "string" ? path : path.toString();
27
+ }
28
+ return undefined;
29
+ },
30
+ ),
31
+ writeFile: vi.fn((path: PathLike | FileHandle, data: string | Buffer | Uint8Array) => {
32
+ const filePath = typeof path === "string" ? path : path.toString();
33
+
34
+ Naite.t(`fs/promises:writeFile`, { path: filePath, data });
35
+ }),
36
+ rm: vi.fn(async (path: PathLike, options?: RmOptions) => {
37
+ const filePath = typeof path === "string" ? path : path.toString();
38
+
39
+ Naite.t(`fs/promises:rm`, { path: filePath, options });
40
+ // 실제 삭제하지 않고 기록만 함
41
+ return Promise.resolve();
42
+ }),
43
+ };
44
+ });
@@ -0,0 +1,7 @@
1
+ /** biome-ignore-all lint/correctness/noUnusedImports: d.ts */
2
+ import type fastify from "fastify";
3
+ import type { UserSubsetSS } from "../application/sonamu.generated";
4
+
5
+ declare module "fastify" {
6
+ export interface PassportUser extends UserSubsetSS {}
7
+ }
@@ -0,0 +1,19 @@
1
+ /** biome-ignore-all lint/correctness/noUnusedImports: d.ts */
2
+
3
+ import type { Session } from "@fastify/secure-session";
4
+ import {} from "sonamu";
5
+
6
+ declare module "sonamu" {
7
+ export interface ContextExtend {
8
+ ip: string;
9
+ session: Session;
10
+ }
11
+
12
+ export interface GuardKeys {
13
+ query: true;
14
+ user: true;
15
+ admin: true;
16
+ // 새로운 커스텀 가드키를 추가하는 경우
17
+ // CustomGuardKey: true
18
+ }
19
+ }
@@ -0,0 +1,11 @@
1
+ import type { SubsetQuery } from "sonamu";
2
+
3
+ export function getSubsetLoaders(subsets: string[], subsetQueries: Record<string, SubsetQuery>) {
4
+ return subsets.reduce(
5
+ (acc, subset) => {
6
+ acc[subset] = subsetQueries[subset]?.loaders ?? [];
7
+ return acc;
8
+ },
9
+ {} as Record<string, SubsetQuery["loaders"]>,
10
+ );
11
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "compilerOptions": {
3
+ /* Basic Options */
4
+ "target": "esnext",
5
+ "module": "esnext",
6
+ "outDir": "dist",
7
+ "sourceMap": true,
8
+ "lib": ["esnext", "dom"],
9
+
10
+ /* Strict Type-Checking Options */
11
+ "strict": true,
12
+ "noImplicitAny": true,
13
+ "strictNullChecks": true,
14
+ "strictFunctionTypes": true,
15
+ "strictBindCallApply": true,
16
+ "strictPropertyInitialization": true,
17
+ "noImplicitThis": true,
18
+ "alwaysStrict": true,
19
+
20
+ /* Additional Checks */
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "noImplicitReturns": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "useUnknownInCatchVariables": true,
26
+
27
+ /* Module Resolution Options */
28
+ "moduleResolution": "bundler",
29
+ "esModuleInterop": true,
30
+
31
+ /* Experimental Options */
32
+ "experimentalDecorators": true,
33
+ "emitDecoratorMetadata": true,
34
+
35
+ /* Advanced Options */
36
+ "forceConsistentCasingInFileNames": true,
37
+ "noErrorTruncation": true,
38
+
39
+ "resolveJsonModule": true,
40
+
41
+ "skipLibCheck": true,
42
+ "noUncheckedIndexedAccess": true
43
+ },
44
+ "include": ["src/**/*.ts", "src/**/*.json"],
45
+ "exclude": [
46
+ "node_modules",
47
+ "dist/*",
48
+ "public",
49
+ // "src/**/*.test.ts",
50
+ "src/**/*.test-hold.ts",
51
+ "src/**/*.ignore.ts",
52
+ "wasted_src/**",
53
+ "_templates/**",
54
+ "**/__mocks__/**",
55
+ "vite.config.ts"
56
+ ],
57
+ "tsc-alias": {
58
+ "resolveFullPaths": true
59
+ }
60
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["src/sonamu.generated.ts"],
4
+ "exclude": ["src/**/*.types.ts"]
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": ["src/**/*.types.ts"],
4
+ "references": [{ "path": "./tsconfig.schemas.json" }]
5
+ }
@@ -0,0 +1,36 @@
1
+ import { getSonamuTestConfig, NaiteVitestReporter } from "sonamu/test";
2
+ import { defineConfig } from "vitest/config";
3
+ import { PrioritySequencer } from "./custom-sequencer";
4
+
5
+ export default defineConfig(async () => ({
6
+ plugins: [],
7
+ test: await getSonamuTestConfig({
8
+ include: ["src/**/*.test.ts"],
9
+ exclude: ["src/**/*.test-hold.ts", "**/node_modules/**", "**/.yarn/**", "**/dist/**"],
10
+ globals: true,
11
+ globalSetup: ["./src/testing/global.ts"],
12
+ setupFiles: ["./src/testing/setup-mocks.ts"],
13
+ sequence: {
14
+ sequencer: PrioritySequencer,
15
+ },
16
+ reporters: ["default", NaiteVitestReporter],
17
+ restoreMocks: true,
18
+ typecheck: {
19
+ enabled: true,
20
+ tsconfig: "./tsconfig.json",
21
+ include: ["src/**/*type-safety.test.ts"],
22
+ },
23
+ coverage: {
24
+ provider: "v8",
25
+ reporter: ["text", "html"],
26
+ include: ["src/**/*.ts"],
27
+ exclude: ["**/*.test.ts", "**/testing/**", "**/node_modules/**", "**/dist/**"],
28
+ },
29
+ includeTaskLocation: true,
30
+ server: {
31
+ deps: {
32
+ inline: ["sonamu"],
33
+ },
34
+ },
35
+ }),
36
+ }));
@@ -0,0 +1,2 @@
1
+ API_HOST=localhost
2
+ API_PORT=1028
@@ -2,12 +2,12 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
5
+ <link rel="icon" type="image/svg+xml" href="/sonamu.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>sonamu-tutorial</title>
7
+ <title>Sonamu Project</title>
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
11
- <script type="module" src="/src/main.tsx"></script>
11
+ <script type="module" src="/src/entry-client.tsx"></script>
12
12
  </body>
13
13
  </html>
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "create-sonamu-web",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "description": "Sonamu web client",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "tsc && vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@sonamu-kit/react-components": "^0.1.6",
14
+ "@tanstack/react-query": "^5.90.12",
15
+ "@tanstack/react-router": "1.143.11",
16
+ "axios": "^1.13.2",
17
+ "clsx": "^2.1.1",
18
+ "dotenv": "^16",
19
+ "eventsource": "^4.1.0",
20
+ "jotai": "^2.14.0",
21
+ "qs": "^6.14.1",
22
+ "radashi": "^12.2.0",
23
+ "react": "^19.2.3",
24
+ "react-day-picker": "^8.10.1",
25
+ "react-dom": "^19.2.3",
26
+ "tailwind-merge": "^3.4.0",
27
+ "zod": "^4.1.12"
28
+ },
29
+ "devDependencies": {
30
+ "@iconify/json": "^2.2.421",
31
+ "@svgr/core": "^8.1.0",
32
+ "@svgr/plugin-jsx": "^8.1.0",
33
+ "@svgr/plugin-svgo": "^8.1.0",
34
+ "@tailwindcss/postcss": "^4.0.0",
35
+ "@tailwindcss/vite": "^4.1.17",
36
+ "@tanstack/react-router-devtools": "1.143.11",
37
+ "@tanstack/router-plugin": "1.143.11",
38
+ "@types/node": "25.0.7",
39
+ "@types/qs": "^6.14.0",
40
+ "@types/react": "^19.2.7",
41
+ "@types/react-dom": "^19.2.3",
42
+ "@vitejs/plugin-react-swc": "^4.1.3",
43
+ "sass": "^1.92.1",
44
+ "tailwindcss": "^4.0.0",
45
+ "typescript": "^5.9.3",
46
+ "unplugin-icons": "0.20.2",
47
+ "vite": "7.3.0"
48
+ }
49
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "create-sonamu-web",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "description": "Sonamu web client",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "tsc && vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@sonamu-kit/react-components": "workspace:^",
14
+ "@tanstack/react-query": "catalog:",
15
+ "@tanstack/react-router": "catalog:",
16
+ "axios": "catalog:",
17
+ "clsx": "catalog:",
18
+ "dotenv": "catalog:",
19
+ "eventsource": "catalog:",
20
+ "jotai": "catalog:",
21
+ "qs": "catalog:",
22
+ "radashi": "catalog:",
23
+ "react": "catalog:",
24
+ "react-day-picker": "catalog:",
25
+ "react-dom": "catalog:",
26
+ "tailwind-merge": "catalog:",
27
+ "zod": "catalog:"
28
+ },
29
+ "devDependencies": {
30
+ "@iconify/json": "catalog:",
31
+ "@svgr/core": "catalog:",
32
+ "@svgr/plugin-jsx": "catalog:",
33
+ "@svgr/plugin-svgo": "catalog:",
34
+ "@tailwindcss/postcss": "catalog:",
35
+ "@tailwindcss/vite": "catalog:",
36
+ "@tanstack/react-router-devtools": "catalog:",
37
+ "@tanstack/router-plugin": "catalog:",
38
+ "@types/node": "catalog:",
39
+ "@types/qs": "catalog:",
40
+ "@types/react": "catalog:",
41
+ "@types/react-dom": "catalog:",
42
+ "@vitejs/plugin-react-swc": "catalog:",
43
+ "sass": "catalog:",
44
+ "tailwindcss": "catalog:",
45
+ "typescript": "catalog:",
46
+ "unplugin-icons": "catalog:",
47
+ "vite": "catalog:"
48
+ }
49
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactNode, Suspense } from "react";
2
+
3
+ interface AppProps {
4
+ children?: ReactNode;
5
+ }
6
+
7
+ function App({ children }: AppProps) {
8
+ return (
9
+ <div className="min-h-screen bg-gray-50">
10
+ <div className="max-w-7xl mx-auto">
11
+ <Suspense fallback={<div>Loading...</div>}>{children}</Suspense>
12
+ </div>
13
+ </div>
14
+ );
15
+ }
16
+
17
+ export default App;