stackkit 0.3.0 → 0.3.2
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/modules/auth/better-auth/files/shared/prisma/{schema.prisma → auth.prisma} +0 -12
- package/modules/auth/better-auth/files/shared/prisma/enums.prisma +11 -0
- package/modules/auth/better-auth/generator.json +4 -9
- package/modules/database/prisma/files/prisma/schema.prisma +3 -2
- package/modules/database/prisma/files/prisma.config.ts +1 -1
- package/modules/database/prisma/generator.json +13 -13
- package/package.json +2 -2
- package/templates/express/README.md +81 -0
- package/templates/express/src/server.ts +50 -1
- package/templates/nextjs/README.md +96 -0
- package/templates/nextjs/app/layout.tsx +9 -3
- package/templates/nextjs/components/providers/query-provider.tsx +37 -0
- package/templates/nextjs/lib/api/http.ts +48 -0
- package/templates/nextjs/lib/env.ts +3 -1
- package/templates/nextjs/package.json +6 -2
- package/templates/nextjs/template.json +1 -0
- package/templates/react/README.md +56 -0
|
@@ -222,15 +222,10 @@
|
|
|
222
222
|
"condition": { "framework": "react" }
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
|
-
"type": "
|
|
226
|
-
"
|
|
227
|
-
"
|
|
228
|
-
"
|
|
229
|
-
{
|
|
230
|
-
"type": "add-to-bottom",
|
|
231
|
-
"source": "shared/prisma/schema.prisma"
|
|
232
|
-
}
|
|
233
|
-
]
|
|
225
|
+
"type": "create-file",
|
|
226
|
+
"source": "shared/prisma/*",
|
|
227
|
+
"destination": "prisma/*",
|
|
228
|
+
"condition": { "database": "prisma" }
|
|
234
229
|
},
|
|
235
230
|
{
|
|
236
231
|
"type": "add-dependency",
|
|
@@ -34,23 +34,23 @@
|
|
|
34
34
|
{
|
|
35
35
|
"type": "add-script",
|
|
36
36
|
"scripts": {
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
37
|
+
"generate": "prisma generate",
|
|
38
|
+
"push": "prisma db push",
|
|
39
|
+
"seed": "tsx prisma/seed.ts",
|
|
40
|
+
"migrate": "prisma migrate dev",
|
|
41
|
+
"studio": "prisma studio"
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"type": "add-dependency",
|
|
46
46
|
"condition": { "prismaProvider": "postgresql" },
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@prisma/client": "^7.2
|
|
48
|
+
"@prisma/client": "^7.4.2",
|
|
49
49
|
"@prisma/adapter-pg": "^7.0.0",
|
|
50
50
|
"pg": "^8.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"prisma": "^7.2
|
|
53
|
+
"prisma": "^7.4.2"
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
{
|
|
@@ -64,12 +64,12 @@
|
|
|
64
64
|
"type": "add-dependency",
|
|
65
65
|
"condition": { "prismaProvider": "mysql" },
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@prisma/client": "^7.2
|
|
67
|
+
"@prisma/client": "^7.4.2",
|
|
68
68
|
"@prisma/adapter-mariadb": "^7.0.0",
|
|
69
69
|
"mysql2": "^3.0.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"prisma": "^7.2
|
|
72
|
+
"prisma": "^7.4.2"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
{
|
|
@@ -83,12 +83,12 @@
|
|
|
83
83
|
"type": "add-dependency",
|
|
84
84
|
"condition": { "prismaProvider": "sqlite" },
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@prisma/client": "^7.2
|
|
86
|
+
"@prisma/client": "^7.4.2",
|
|
87
87
|
"@prisma/adapter-better-sqlite3": "^7.0.0",
|
|
88
88
|
"better-sqlite3": "^9.0.0"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
|
-
"prisma": "^7.2
|
|
91
|
+
"prisma": "^7.4.2"
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
{
|
|
@@ -102,10 +102,10 @@
|
|
|
102
102
|
"type": "add-dependency",
|
|
103
103
|
"condition": { "prismaProvider": "mongodb" },
|
|
104
104
|
"dependencies": {
|
|
105
|
-
"@prisma/client": "^
|
|
105
|
+
"@prisma/client": "^7.4.2"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"prisma": "^
|
|
108
|
+
"prisma": "^7.4.2"
|
|
109
109
|
}
|
|
110
110
|
},
|
|
111
111
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stackkit",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Production-ready CLI to create and extend JavaScript or TypeScript apps with modular stacks.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -80,4 +80,4 @@
|
|
|
80
80
|
"@types/validate-npm-package-name": "^4.0.2",
|
|
81
81
|
"typescript": "^5.3.3"
|
|
82
82
|
}
|
|
83
|
-
}
|
|
83
|
+
}
|
|
@@ -31,6 +31,87 @@ npm run dev
|
|
|
31
31
|
|
|
32
32
|
Use a `.env` file or environment variables for configuration. See `.env.example` for available keys.
|
|
33
33
|
|
|
34
|
+
## Recommended Folder & File Structure
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
express-api/
|
|
38
|
+
├── src/
|
|
39
|
+
│ ├── app.ts
|
|
40
|
+
│ ├── server.ts
|
|
41
|
+
│
|
|
42
|
+
│ ├── config/
|
|
43
|
+
│ │ ├── env.ts
|
|
44
|
+
│ │ ├── cors.ts
|
|
45
|
+
│ │ ├── rateLimit.ts
|
|
46
|
+
│ │ └── logger.ts # (optional)
|
|
47
|
+
│
|
|
48
|
+
│ ├── database/
|
|
49
|
+
│ │ └── prisma.ts # PrismaClient singleton
|
|
50
|
+
│
|
|
51
|
+
│ ├── lib/
|
|
52
|
+
│ └── auth.ts # Auth server config
|
|
53
|
+
│
|
|
54
|
+
│ ├── shared/
|
|
55
|
+
│ │ ├── middlewares/
|
|
56
|
+
│ │ │ ├── authorize.middleware.ts # reads session + attaches req.user
|
|
57
|
+
│ │ │ ├── error.middleware.ts
|
|
58
|
+
│ │ │ └── notFound.middleware.ts
|
|
59
|
+
│ │ ├── errors/
|
|
60
|
+
│ │ │ ├── ApiError.ts
|
|
61
|
+
│ │ │ └── errorCodes.ts
|
|
62
|
+
│ │ ├── utils/
|
|
63
|
+
│ │ │ ├── catchAsync.ts
|
|
64
|
+
│ │ │ ├── sendResponse.ts
|
|
65
|
+
│ │ │ └── pagination.ts
|
|
66
|
+
│ │ └── logger/
|
|
67
|
+
│ │ └── logger.ts
|
|
68
|
+
│
|
|
69
|
+
│ ├── modules/
|
|
70
|
+
│ │ ├── auth/
|
|
71
|
+
│ │ │ ├── auth.routes.ts
|
|
72
|
+
│ │ │ ├── auth.controller.ts
|
|
73
|
+
│ │ │ ├── auth.service.ts
|
|
74
|
+
│ │ │ ├── auth.validator.ts
|
|
75
|
+
│ │ │ └── auth.types.ts
|
|
76
|
+
│ │ ├── users/
|
|
77
|
+
│ │ │ ├── users.routes.ts
|
|
78
|
+
│ │ │ ├── users.controller.ts
|
|
79
|
+
│ │ │ ├── users.service.ts
|
|
80
|
+
│ │ │ ├── users.repository.ts
|
|
81
|
+
│ │ │ ├── users.validator.ts
|
|
82
|
+
│ │ │ └── users.types.ts
|
|
83
|
+
│ │ └── products/
|
|
84
|
+
│ │ ├── products.routes.ts
|
|
85
|
+
│ │ ├── products.controller.ts
|
|
86
|
+
│ │ ├── products.service.ts
|
|
87
|
+
│ │ ├── products.repository.ts
|
|
88
|
+
│ │ ├── products.validator.ts
|
|
89
|
+
│ │ └── products.types.ts
|
|
90
|
+
│
|
|
91
|
+
│ ├── routes/
|
|
92
|
+
│ │ └── index.ts # mounts all module routes
|
|
93
|
+
│ │
|
|
94
|
+
│ └── types/
|
|
95
|
+
│ └── express.d.ts # Request typing (req.user)
|
|
96
|
+
│
|
|
97
|
+
├── prisma/
|
|
98
|
+
│ ├── schema.prisma
|
|
99
|
+
│ ├── migrations/
|
|
100
|
+
│ └── seed.ts
|
|
101
|
+
│
|
|
102
|
+
├── tests/
|
|
103
|
+
│ ├── unit/
|
|
104
|
+
│ └── integration/
|
|
105
|
+
│
|
|
106
|
+
├── Dockerfile
|
|
107
|
+
├── docker-compose.yml
|
|
108
|
+
├── package.json
|
|
109
|
+
├── tsconfig.json
|
|
110
|
+
├── eslint.config.js
|
|
111
|
+
├── .env.example
|
|
112
|
+
└── README.md
|
|
113
|
+
```
|
|
114
|
+
|
|
34
115
|
---
|
|
35
116
|
|
|
36
117
|
## Generated with StackKit
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { Server } from "http";
|
|
1
2
|
import { app } from "./app";
|
|
2
3
|
import { envVars } from "./config/env";
|
|
3
4
|
|
|
5
|
+
let server: Server | null = null;
|
|
6
|
+
|
|
4
7
|
const bootstrap = async () => {
|
|
5
8
|
try {
|
|
6
|
-
app.listen(envVars.PORT, () => {
|
|
9
|
+
server = app.listen(envVars.PORT, () => {
|
|
7
10
|
console.log(`Server is running on http://localhost:${envVars.PORT}`);
|
|
8
11
|
});
|
|
9
12
|
} catch (error) {
|
|
@@ -11,4 +14,50 @@ const bootstrap = async () => {
|
|
|
11
14
|
}
|
|
12
15
|
};
|
|
13
16
|
|
|
17
|
+
process.on("SIGTERM", () => {
|
|
18
|
+
console.log("SIGTERM signal received: closing HTTP server");
|
|
19
|
+
if (server) {
|
|
20
|
+
server.close(() => {
|
|
21
|
+
console.log("HTTP server closed");
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
process.exit(0);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
process.on("SIGINT", () => {
|
|
29
|
+
console.log("SIGINT signal received: closing HTTP server");
|
|
30
|
+
if (server) {
|
|
31
|
+
server.close(() => {
|
|
32
|
+
console.log("HTTP server closed");
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
process.exit(0);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
process.on("uncaughtException", (err) => {
|
|
40
|
+
console.error("Uncaught Exception:", err);
|
|
41
|
+
|
|
42
|
+
if (server) {
|
|
43
|
+
server.close(() => {
|
|
44
|
+
console.log("HTTP server closed due to uncaught exception");
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
52
|
+
console.error("Unhandled Rejection at:", promise, "reason:", reason);
|
|
53
|
+
|
|
54
|
+
if (server) {
|
|
55
|
+
server.close(() => {
|
|
56
|
+
console.log("HTTP server closed due to unhandled rejection");
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
62
|
+
|
|
14
63
|
bootstrap();
|
|
@@ -50,6 +50,102 @@ lib/
|
|
|
50
50
|
public/ # Static assets
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
## Recommended Folder & File Structure
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
next-app/
|
|
57
|
+
├── src/
|
|
58
|
+
│ ├── app/
|
|
59
|
+
│ │ ├── (public)/
|
|
60
|
+
│ │ │ ├── layout.tsx
|
|
61
|
+
│ │ │ ├── page.tsx
|
|
62
|
+
│ │ │ └── pricing/page.tsx
|
|
63
|
+
│ │ ├── (dashboard)/
|
|
64
|
+
│ │ │ ├── layout.tsx
|
|
65
|
+
│ │ │ ├── dashboard/page.tsx
|
|
66
|
+
│ │ │ ├── products/page.tsx
|
|
67
|
+
│ │ │ └── orders/page.tsx
|
|
68
|
+
│ │ ├── api/
|
|
69
|
+
│ │ │ ├── health/route.ts
|
|
70
|
+
│ │ │ └── auth/route.ts # (optional) if you expose auth endpoints
|
|
71
|
+
│ │ ├── layout.tsx
|
|
72
|
+
│ │ ├── globals.css
|
|
73
|
+
│ │ ├── error.tsx
|
|
74
|
+
│ │ └── not-found.tsx
|
|
75
|
+
│ │
|
|
76
|
+
│ ├── features/
|
|
77
|
+
│ │ ├── auth/
|
|
78
|
+
│ │ │ ├── components/
|
|
79
|
+
│ │ │ ├── actions/
|
|
80
|
+
│ │ │ ├── schemas/
|
|
81
|
+
│ │ │ ├── types/
|
|
82
|
+
│ │ │ └── index.ts
|
|
83
|
+
│ │ ├── products/
|
|
84
|
+
│ │ │ ├── components/
|
|
85
|
+
│ │ │ ├── actions/
|
|
86
|
+
│ │ │ ├── queries/
|
|
87
|
+
│ │ │ ├── schemas/
|
|
88
|
+
│ │ │ ├── types/
|
|
89
|
+
│ │ │ └── index.ts
|
|
90
|
+
│ │ └── orders/
|
|
91
|
+
│ │ ├── components/
|
|
92
|
+
│ │ ├── actions/
|
|
93
|
+
│ │ ├── queries/
|
|
94
|
+
│ │ ├── schemas/
|
|
95
|
+
│ │ ├── types/
|
|
96
|
+
│ │ └── index.ts
|
|
97
|
+
│ │
|
|
98
|
+
│ ├── components/
|
|
99
|
+
│ │ ├── ui/ # shadcn/ui only
|
|
100
|
+
│ │ └── shared/
|
|
101
|
+
│ │ ├── Header.tsx
|
|
102
|
+
│ │ ├── Footer.tsx
|
|
103
|
+
│ │ └── Sidebar.tsx
|
|
104
|
+
│ │
|
|
105
|
+
│ ├── server/ # server-only boundary
|
|
106
|
+
│ │ ├── auth/
|
|
107
|
+
│ │ │ ├── auth.ts # Auth (server config)
|
|
108
|
+
│ │ │ └── guards.ts
|
|
109
|
+
│ │ ├── db/
|
|
110
|
+
│ │ │ └── prisma.ts # PrismaClient singleton
|
|
111
|
+
│ │ ├── repositories/
|
|
112
|
+
│ │ │ ├── product.repo.ts
|
|
113
|
+
│ │ │ └── order.repo.ts
|
|
114
|
+
│ │ └── services/
|
|
115
|
+
│ │ ├── email.service.ts
|
|
116
|
+
│ │ └── storage.service.ts
|
|
117
|
+
│ │
|
|
118
|
+
│ ├── lib/
|
|
119
|
+
│ │ ├── env.ts
|
|
120
|
+
│ │ ├── utils.ts
|
|
121
|
+
│ │ ├── logger.ts
|
|
122
|
+
│ │ └── auth/
|
|
123
|
+
│ │ └── auth-client.ts # Auth (client helper)
|
|
124
|
+
│ │
|
|
125
|
+
│ ├── hooks/
|
|
126
|
+
│ │ └── useDebounce.ts
|
|
127
|
+
│ │
|
|
128
|
+
│ └── types/
|
|
129
|
+
│ └── global.d.ts
|
|
130
|
+
│
|
|
131
|
+
├── prisma/
|
|
132
|
+
│ ├── schema.prisma
|
|
133
|
+
│ ├── migrations/
|
|
134
|
+
│ └── seed.ts
|
|
135
|
+
│
|
|
136
|
+
├── public/
|
|
137
|
+
├── tests/
|
|
138
|
+
│ ├── unit/
|
|
139
|
+
│ └── e2e/
|
|
140
|
+
│
|
|
141
|
+
├── middleware.ts
|
|
142
|
+
├── next.config.js
|
|
143
|
+
├── package.json
|
|
144
|
+
├── tsconfig.json
|
|
145
|
+
├── .env.example
|
|
146
|
+
└── README.md
|
|
147
|
+
```
|
|
148
|
+
|
|
53
149
|
## Environment Variables
|
|
54
150
|
|
|
55
151
|
Create a `.env.local` (Next.js) file for local environment variables. Keep secrets out of the repository.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { QueryProviders } from "@/components/providers/query-provider";
|
|
1
2
|
import type { Metadata } from "next";
|
|
2
3
|
import { Geist, Geist_Mono } from "next/font/google";
|
|
4
|
+
import { Toaster } from "sonner";
|
|
3
5
|
import "./globals.css";
|
|
4
6
|
|
|
5
7
|
const geistSans = Geist({
|
|
@@ -13,8 +15,8 @@ const geistMono = Geist_Mono({
|
|
|
13
15
|
});
|
|
14
16
|
|
|
15
17
|
export const metadata: Metadata = {
|
|
16
|
-
title: "Create Next App",
|
|
17
|
-
description: "Generated by create next app",
|
|
18
|
+
title: "Create Next App - StackKit",
|
|
19
|
+
description: "Generated by create next app with StackKit template",
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
export default function RootLayout({
|
|
@@ -24,7 +26,11 @@ export default function RootLayout({
|
|
|
24
26
|
}>) {
|
|
25
27
|
return (
|
|
26
28
|
<html lang="en">
|
|
27
|
-
<
|
|
29
|
+
<head />
|
|
30
|
+
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
|
|
31
|
+
<QueryProviders>{children}</QueryProviders>
|
|
32
|
+
<Toaster theme="system" position="top-right" richColors />
|
|
33
|
+
</body>
|
|
28
34
|
</html>
|
|
29
35
|
);
|
|
30
36
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { isServer, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";
|
|
5
|
+
|
|
6
|
+
function makeQueryClient() {
|
|
7
|
+
return new QueryClient({
|
|
8
|
+
defaultOptions: {
|
|
9
|
+
queries: {
|
|
10
|
+
staleTime: 60 * 1000,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let browserQueryClient: QueryClient | undefined = undefined;
|
|
17
|
+
|
|
18
|
+
function getQueryClient() {
|
|
19
|
+
if (isServer) {
|
|
20
|
+
return makeQueryClient();
|
|
21
|
+
} else {
|
|
22
|
+
if (!browserQueryClient) browserQueryClient = makeQueryClient();
|
|
23
|
+
return browserQueryClient;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function QueryProviders({ children }: { children: React.ReactNode }) {
|
|
28
|
+
const queryClient = getQueryClient();
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<QueryClientProvider client={queryClient}>
|
|
32
|
+
<ReactQueryStreamedHydration>{children}</ReactQueryStreamedHydration>
|
|
33
|
+
</QueryClientProvider>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { getQueryClient, QueryProviders };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { AxiosResponse } from "axios";
|
|
2
|
+
import axios, { AxiosError } from "axios";
|
|
3
|
+
import { toast } from "sonner";
|
|
4
|
+
import { envVars } from "../env";
|
|
5
|
+
|
|
6
|
+
const api = axios.create({
|
|
7
|
+
baseURL: envVars.API_URL || "http://localhost:5000/api",
|
|
8
|
+
timeout: 30000,
|
|
9
|
+
headers: {
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
api.interceptors.request.use(
|
|
15
|
+
(config) => {
|
|
16
|
+
const token = localStorage.getItem("auth_token");
|
|
17
|
+
if (token) {
|
|
18
|
+
(config.headers as Record<string, string>).Authorization = `Bearer ${token}`;
|
|
19
|
+
}
|
|
20
|
+
return config;
|
|
21
|
+
},
|
|
22
|
+
(error: AxiosError) => {
|
|
23
|
+
return Promise.reject(error);
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
api.interceptors.response.use(
|
|
28
|
+
(response: AxiosResponse) => response,
|
|
29
|
+
(error: AxiosError) => {
|
|
30
|
+
if (error.response?.status === 401) {
|
|
31
|
+
localStorage.removeItem("auth_token");
|
|
32
|
+
toast.error("Session expired. Please login again.");
|
|
33
|
+
} else if (error.response?.status === 403) {
|
|
34
|
+
toast.error("You do not have permission to perform this action.");
|
|
35
|
+
} else if (error.response?.status === 404) {
|
|
36
|
+
toast.error("Resource not found.");
|
|
37
|
+
} else if (error.response?.status === 500) {
|
|
38
|
+
toast.error("Server error. Please try again later.");
|
|
39
|
+
} else if (error.code === "ECONNABORTED") {
|
|
40
|
+
toast.error("Request timeout. Please try again.");
|
|
41
|
+
} else if (!error.response) {
|
|
42
|
+
toast.error("Network error. Please check your connection.");
|
|
43
|
+
}
|
|
44
|
+
return Promise.reject(error);
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export { api };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
interface EnvVars {
|
|
2
2
|
APP_URL: string;
|
|
3
|
+
API_URL: string;
|
|
3
4
|
}
|
|
4
5
|
|
|
5
6
|
const loadEnvVars = (): EnvVars => {
|
|
6
|
-
const requiredEnvVars = [""];
|
|
7
|
+
const requiredEnvVars = ["NEXT_PUBLIC_API_URL"];
|
|
7
8
|
|
|
8
9
|
requiredEnvVars.forEach((varName) => {
|
|
9
10
|
if (!process.env[varName]) {
|
|
@@ -13,6 +14,7 @@ const loadEnvVars = (): EnvVars => {
|
|
|
13
14
|
|
|
14
15
|
return {
|
|
15
16
|
APP_URL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
|
|
17
|
+
API_URL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api",
|
|
16
18
|
};
|
|
17
19
|
};
|
|
18
20
|
|
|
@@ -10,9 +10,13 @@
|
|
|
10
10
|
"lint": "eslint"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
+
"@tanstack/react-query": "^5.90.21",
|
|
14
|
+
"@tanstack/react-query-next-experimental": "^5.91.0",
|
|
15
|
+
"axios": "^1.13.5",
|
|
13
16
|
"next": "16.1.1",
|
|
14
17
|
"react": "19.2.3",
|
|
15
|
-
"react-dom": "19.2.3"
|
|
18
|
+
"react-dom": "19.2.3",
|
|
19
|
+
"sonner": "^2.0.7"
|
|
16
20
|
},
|
|
17
21
|
"devDependencies": {
|
|
18
22
|
"@tailwindcss/postcss": "^4.1.18",
|
|
@@ -24,4 +28,4 @@
|
|
|
24
28
|
"tailwindcss": "^4.1.18",
|
|
25
29
|
"typescript": "^5.9.3"
|
|
26
30
|
}
|
|
27
|
-
}
|
|
31
|
+
}
|
|
@@ -61,6 +61,62 @@ src/
|
|
|
61
61
|
└── utils/ # Helper functions
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
+
## Recommended Folder & File Structure
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
react-vite-app/
|
|
68
|
+
├── src/
|
|
69
|
+
│ ├── app/
|
|
70
|
+
│ │ ├── router.tsx
|
|
71
|
+
│ │ ├── providers.tsx
|
|
72
|
+
│ │ └── layouts/
|
|
73
|
+
│ │ ├── PublicLayout.tsx
|
|
74
|
+
│ │ └── DashboardLayout.tsx
|
|
75
|
+
│
|
|
76
|
+
│ ├── features/
|
|
77
|
+
│ │ ├── auth/
|
|
78
|
+
│ │ │ ├── pages/
|
|
79
|
+
│ │ │ │ ├── LoginPage.tsx
|
|
80
|
+
│ │ │ │ └── SignupPage.tsx
|
|
81
|
+
│ │ │ ├── components/
|
|
82
|
+
│ │ │ ├── api/
|
|
83
|
+
│ │ │ │ └── auth.api.ts
|
|
84
|
+
│ │ │ ├── hooks/
|
|
85
|
+
│ │ │ │ └── useAuth.ts
|
|
86
|
+
│ │ │ ├── schemas/
|
|
87
|
+
│ │ │ ├── types/
|
|
88
|
+
│ │ │ └── index.ts
|
|
89
|
+
│ │ ├── products/
|
|
90
|
+
│ │ └── orders/
|
|
91
|
+
│
|
|
92
|
+
│ ├── shared/
|
|
93
|
+
│ │ ├── ui/
|
|
94
|
+
│ │ ├── components/
|
|
95
|
+
│ │ │ ├── Header.tsx
|
|
96
|
+
│ │ │ └── Sidebar.tsx
|
|
97
|
+
│ │ ├── api/
|
|
98
|
+
│ │ │ ├── http.ts # axios/fetch client
|
|
99
|
+
│ │ │ └── endpoints.ts
|
|
100
|
+
│ │ └── lib/
|
|
101
|
+
│ │ ├── env.ts
|
|
102
|
+
│ │ ├── utils.ts
|
|
103
|
+
│ │ └── auth-client.ts # Auth client helper
|
|
104
|
+
│
|
|
105
|
+
│ ├── assets/
|
|
106
|
+
│ ├── main.tsx
|
|
107
|
+
│ └── index.css
|
|
108
|
+
│
|
|
109
|
+
├── public/
|
|
110
|
+
├── tests/
|
|
111
|
+
│ ├── unit/
|
|
112
|
+
│ └── e2e/
|
|
113
|
+
├── vite.config.ts
|
|
114
|
+
├── tsconfig.json
|
|
115
|
+
├── package.json
|
|
116
|
+
├── .env.example
|
|
117
|
+
└── README.md
|
|
118
|
+
```
|
|
119
|
+
|
|
64
120
|
## Deployment
|
|
65
121
|
|
|
66
122
|
Build for production and serve or deploy the static output:
|