stackkit 0.3.5 → 0.3.6
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/README.md +50 -42
- package/dist/cli/add.js +122 -56
- package/dist/cli/create.d.ts +2 -0
- package/dist/cli/create.js +271 -95
- package/dist/cli/doctor.js +1 -0
- package/dist/cli/list.d.ts +1 -1
- package/dist/cli/list.js +6 -4
- package/dist/index.js +234 -191
- package/dist/lib/constants.d.ts +4 -0
- package/dist/lib/constants.js +4 -0
- package/dist/lib/discovery/module-discovery.d.ts +4 -0
- package/dist/lib/discovery/module-discovery.js +56 -0
- package/dist/lib/generation/code-generator.d.ts +11 -2
- package/dist/lib/generation/code-generator.js +42 -3
- package/dist/lib/generation/generator-utils.js +3 -1
- package/dist/lib/pm/package-manager.js +16 -13
- package/dist/lib/ui/logger.js +3 -2
- package/dist/lib/utils/path-resolver.d.ts +2 -0
- package/dist/lib/utils/path-resolver.js +8 -0
- package/dist/meta.json +8312 -0
- package/modules/auth/better-auth/files/{shared → express}/config/env.ts +48 -50
- package/modules/auth/better-auth/files/express/middlewares/authorize.ts +20 -1
- package/modules/auth/better-auth/files/express/modules/auth.controller.ts +349 -0
- package/modules/auth/better-auth/files/express/modules/{auth/auth.route.ts → auth.route.ts} +9 -4
- package/modules/auth/better-auth/files/express/modules/auth.service.ts +664 -0
- package/modules/auth/better-auth/files/express/modules/{auth/auth.type.ts → auth.type.ts} +22 -9
- package/modules/auth/better-auth/files/{shared/mongoose/auth/helper.ts → express/mongo-modules/auth.helper.ts} +11 -1
- package/modules/auth/better-auth/files/express/types/express.d.ts +11 -0
- package/modules/auth/better-auth/files/nextjs/api-route.ts +74 -0
- package/modules/auth/better-auth/files/nextjs/dashboard/pages/(user)/page.tsx +6 -0
- package/modules/auth/better-auth/files/nextjs/dashboard/pages/admin/page.tsx +6 -0
- package/modules/auth/better-auth/files/nextjs/dashboard/pages/layout.tsx +48 -0
- package/modules/auth/better-auth/files/nextjs/dashboard/pages/my-profile/page.tsx +5 -0
- package/modules/auth/better-auth/files/nextjs/features/services/auth.service.ts +102 -0
- package/modules/auth/better-auth/files/nextjs/layout/layout.tsx +13 -0
- package/modules/auth/better-auth/files/nextjs/lib/axios/http.ts +158 -0
- package/modules/auth/better-auth/files/nextjs/lib/env.ts +35 -0
- package/modules/auth/better-auth/files/nextjs/lib/utils/auth.ts +75 -0
- package/modules/auth/better-auth/files/nextjs/lib/utils/cookie.ts +29 -0
- package/modules/auth/better-auth/files/nextjs/lib/utils/jwt.ts +28 -0
- package/modules/auth/better-auth/files/nextjs/lib/utils/token.ts +49 -0
- package/modules/auth/better-auth/files/nextjs/pages/forgot-password/page.tsx +5 -0
- package/modules/auth/better-auth/files/nextjs/pages/layout.tsx +11 -0
- package/modules/auth/better-auth/files/nextjs/pages/login/page.tsx +9 -0
- package/modules/auth/better-auth/files/nextjs/pages/register/page.tsx +5 -0
- package/modules/auth/better-auth/files/nextjs/pages/reset-password/page.tsx +10 -0
- package/modules/auth/better-auth/files/nextjs/pages/verify-email/page.tsx +10 -0
- package/modules/auth/better-auth/files/nextjs/proxy.ts +154 -42
- package/modules/auth/better-auth/files/nextjs/theme/providers/theme-provider.tsx +11 -0
- package/modules/auth/better-auth/files/nextjs/types/api.types.ts +18 -0
- package/modules/auth/better-auth/files/react/components/protected-route.tsx +39 -0
- package/modules/auth/better-auth/files/react/components/route-guards.tsx +13 -0
- package/modules/auth/better-auth/files/react/dashboard/admin/pages/overview.tsx +3 -0
- package/modules/auth/better-auth/files/react/dashboard/pages/overview.tsx +3 -0
- package/modules/auth/better-auth/files/react/features/pages/forgot-password.tsx +5 -0
- package/modules/auth/better-auth/files/react/features/pages/login.tsx +5 -0
- package/modules/auth/better-auth/files/react/features/pages/my-profile.tsx +5 -0
- package/modules/auth/better-auth/files/react/features/pages/oauth-callback.tsx +59 -0
- package/modules/auth/better-auth/files/react/features/pages/register.tsx +5 -0
- package/modules/auth/better-auth/files/react/features/pages/reset-password.tsx +10 -0
- package/modules/auth/better-auth/files/react/features/pages/verify-email.tsx +10 -0
- package/modules/auth/better-auth/files/react/layout/dashboard-layout.tsx +54 -0
- package/modules/auth/better-auth/files/react/lib/axios/http.ts +68 -0
- package/modules/auth/better-auth/files/react/lib/env.ts +25 -0
- package/modules/auth/better-auth/files/react/router.tsx +73 -0
- package/modules/auth/better-auth/files/react/theme/components/providers/theme-provider-context.ts +13 -0
- package/modules/auth/better-auth/files/react/theme/components/providers/theme-provider.tsx +51 -0
- package/modules/auth/better-auth/files/react/theme/hooks/use-theme.ts +8 -0
- package/modules/auth/better-auth/files/shared/features/components/change-password-dialog.tsx +113 -0
- package/modules/auth/better-auth/files/shared/features/components/forgot-password-form.tsx +84 -0
- package/modules/auth/better-auth/files/shared/features/components/login-form.tsx +134 -0
- package/modules/auth/better-auth/files/shared/features/components/my-profile.tsx +147 -0
- package/modules/auth/better-auth/files/shared/features/components/profile-form.tsx +205 -0
- package/modules/auth/better-auth/files/shared/features/components/register-form.tsx +100 -0
- package/modules/auth/better-auth/files/shared/features/components/reset-password-form.tsx +111 -0
- package/modules/auth/better-auth/files/shared/features/components/social-login-buttons.tsx +47 -0
- package/modules/auth/better-auth/files/shared/features/components/user-profile-menu.tsx +106 -0
- package/modules/auth/better-auth/files/shared/features/components/verify-email-form.tsx +110 -0
- package/modules/auth/better-auth/files/shared/features/queries/auth.mutations.tsx +312 -0
- package/modules/auth/better-auth/files/shared/features/queries/auth.querie.ts +19 -0
- package/modules/auth/better-auth/files/shared/features/services/auth.api.ts +81 -0
- package/modules/auth/better-auth/files/shared/features/types/auth.type.ts +47 -0
- package/modules/auth/better-auth/files/shared/features/validators/change-password.validator.ts +18 -0
- package/modules/auth/better-auth/files/shared/features/validators/forgot.validator.ts +7 -0
- package/modules/auth/better-auth/files/shared/features/validators/login.validator.ts +14 -0
- package/modules/auth/better-auth/files/shared/features/validators/profile.validator.ts +8 -0
- package/modules/auth/better-auth/files/shared/features/validators/register.validator.ts +9 -0
- package/modules/auth/better-auth/files/shared/features/validators/reset.validator.ts +9 -0
- package/modules/auth/better-auth/files/shared/features/validators/verify.validator.ts +8 -0
- package/modules/auth/better-auth/files/shared/lib/auth-client.ts +2 -1
- package/modules/auth/better-auth/files/shared/lib/auth.ts +5 -19
- package/modules/auth/better-auth/files/shared/lib/constant/dashboard.ts +90 -0
- package/modules/auth/better-auth/files/shared/theme/mode-toggle.tsx +30 -0
- package/modules/auth/better-auth/files/shared/ui/shadcn/components/dashboard/dashboard-header.tsx +94 -0
- package/modules/auth/better-auth/files/shared/ui/shadcn/components/dashboard/dashboard-sidebar.tsx +255 -0
- package/modules/auth/better-auth/files/shared/ui/shadcn/components/footer.tsx +35 -0
- package/modules/auth/better-auth/files/shared/ui/shadcn/components/navbar.tsx +145 -0
- package/modules/auth/better-auth/files/shared/ui/shadcn/form-field/input-field.tsx +440 -0
- package/modules/auth/better-auth/files/shared/utils/email.ts +2 -17
- package/modules/auth/better-auth/generator.json +172 -51
- package/modules/auth/better-auth/module.json +2 -2
- package/modules/components/files/shared/hooks/use-file-upload.ts +412 -0
- package/modules/components/files/shared/lib/utils/url-helpers.ts +110 -0
- package/modules/components/files/shared/shadcn/dashboard/data-table-column-selector.tsx +52 -0
- package/modules/components/files/shared/shadcn/dashboard/data-table-footer.tsx +156 -0
- package/modules/components/files/shared/shadcn/dashboard/data-table.tsx +405 -0
- package/modules/components/files/shared/shadcn/global/form-field/input-field.tsx +440 -0
- package/modules/components/files/shared/shadcn/global/form-field/media-uploader-field.tsx +745 -0
- package/modules/components/files/shared/shadcn/global/form-field/multi-select-field.tsx +207 -0
- package/modules/components/files/shared/shadcn/global/form-field/select-field.tsx +247 -0
- package/modules/components/files/shared/shadcn/global/form-field/textarea-field.tsx +277 -0
- package/modules/components/files/shared/shadcn/global/form-field/tiptap-editor-field.tsx +35 -0
- package/modules/components/files/shared/shadcn/global/no-results.tsx +41 -0
- package/modules/components/files/shared/shadcn/tiptap-editor/editor-menu-bar.tsx +217 -0
- package/modules/components/files/shared/shadcn/tiptap-editor/tiptap-editor.tsx +104 -0
- package/modules/components/files/shared/url/load-more.tsx +93 -0
- package/modules/components/files/shared/url/search-bar.tsx +131 -0
- package/modules/components/files/shared/url/sort-select.tsx +118 -0
- package/modules/components/files/shared/url/url-tabs.tsx +77 -0
- package/modules/components/generator.json +109 -0
- package/modules/components/module.json +11 -0
- package/modules/database/mongoose/generator.json +3 -14
- package/modules/database/mongoose/module.json +2 -2
- package/modules/database/prisma/generator.json +6 -12
- package/modules/database/prisma/module.json +2 -2
- package/modules/storage/cloudinary/files/express/config/env.ts +65 -0
- package/modules/storage/cloudinary/files/express/config/media.ts +103 -0
- package/modules/storage/cloudinary/files/express/modules/media/media.controller.ts +59 -0
- package/modules/storage/cloudinary/files/express/modules/media/media.route.ts +29 -0
- package/modules/storage/cloudinary/files/express/modules/media/media.service.ts +113 -0
- package/modules/storage/cloudinary/files/express/modules/media/media.type.ts +32 -0
- package/modules/storage/cloudinary/generator.json +34 -0
- package/modules/storage/cloudinary/module.json +11 -0
- package/modules/ui/shadcn/generator.json +21 -0
- package/modules/ui/shadcn/module.json +11 -0
- package/package.json +24 -26
- package/templates/express/README.md +11 -16
- package/templates/express/src/config/env.ts +7 -5
- package/templates/nextjs/README.md +13 -18
- package/templates/nextjs/app/favicon.ico +0 -0
- package/templates/nextjs/app/layout.tsx +6 -4
- package/templates/nextjs/components/providers/query-provider.tsx +3 -0
- package/templates/nextjs/env.example +3 -1
- package/templates/nextjs/lib/axios/http.ts +23 -0
- package/templates/nextjs/lib/env.ts +7 -5
- package/templates/nextjs/package.json +2 -1
- package/templates/nextjs/template.json +1 -2
- package/templates/react/README.md +9 -14
- package/templates/react/index.html +1 -1
- package/templates/react/package.json +1 -1
- package/templates/react/src/assets/favicon.ico +0 -0
- package/templates/react/src/components/providers/query-provider.tsx +38 -0
- package/templates/react/src/{shared/components → components}/seo.tsx +4 -8
- package/templates/react/src/lib/axios/http.ts +24 -0
- package/templates/react/src/main.tsx +8 -11
- package/templates/react/src/{features/about/pages → pages}/about.tsx +1 -1
- package/templates/react/src/{features/home/pages → pages}/home.tsx +1 -1
- package/templates/react/src/router.tsx +6 -6
- package/templates/react/src/vite-env.d.ts +2 -1
- package/templates/react/template.json +0 -1
- package/templates/react/tsconfig.app.json +6 -0
- package/templates/react/tsconfig.json +7 -1
- package/templates/react/vite.config.ts +12 -0
- package/modules/auth/authjs/files/nextjs/api/auth/[...nextauth]/route.ts +0 -3
- package/modules/auth/authjs/files/nextjs/proxy.ts +0 -1
- package/modules/auth/authjs/files/shared/lib/auth.ts +0 -119
- package/modules/auth/authjs/files/shared/prisma/schema.prisma +0 -61
- package/modules/auth/authjs/generator.json +0 -64
- package/modules/auth/authjs/module.json +0 -13
- package/modules/auth/better-auth/files/express/modules/auth/auth.controller.ts +0 -264
- package/modules/auth/better-auth/files/express/modules/auth/auth.service.ts +0 -549
- package/modules/auth/better-auth/files/express/templates/google-redirect.ejs +0 -24
- package/modules/auth/better-auth/files/nextjs/api/auth/[...all]/route.ts +0 -4
- package/modules/auth/better-auth/files/nextjs/lib/auth/auth-guards.ts +0 -31
- package/modules/auth/better-auth/files/nextjs/templates/email-otp.tsx +0 -74
- package/templates/nextjs/lib/api/http.ts +0 -40
- package/templates/react/public/vite.svg +0 -1
- package/templates/react/src/app/layouts/dashboard-layout.tsx +0 -8
- package/templates/react/src/app/layouts/public-layout.tsx +0 -5
- package/templates/react/src/app/providers.tsx +0 -20
- package/templates/react/src/app/router.tsx +0 -21
- package/templates/react/src/assets/react.svg +0 -1
- package/templates/react/src/shared/api/http.ts +0 -39
- package/templates/react/src/shared/components/loading.tsx +0 -8
- package/templates/react/src/shared/lib/query-client.ts +0 -12
- package/templates/react/src/utils/storage.ts +0 -35
- package/templates/react/src/utils/utils.ts +0 -3
- /package/modules/auth/better-auth/files/{shared/mongoose/auth/constants.ts → express/mongo-modules/auth.constants.ts} +0 -0
- /package/templates/nextjs/app/{page.tsx → (public)/(root)/page.tsx} +0 -0
- /package/templates/react/src/{shared/components → components}/error-boundary.tsx +0 -0
- /package/templates/react/src/{shared/components → components}/layout.tsx +0 -0
- /package/templates/react/src/{shared/pages → pages}/not-found.tsx +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# Next.js
|
|
1
|
+
# Next.js App
|
|
2
2
|
|
|
3
|
-
Production-ready Next.js starter with TypeScript and App Router.
|
|
3
|
+
Production-ready Next.js starter with TypeScript and App Router, generated by [StackKit](https://github.com/tariqul420/stackkit).
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -30,10 +30,12 @@ npm run dev
|
|
|
30
30
|
|
|
31
31
|
## Scripts
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
| Command | Description |
|
|
34
|
+
| ------------ | ------------------------ |
|
|
35
|
+
| `pnpm dev` | Start development server |
|
|
36
|
+
| `pnpm build` | Build for production |
|
|
37
|
+
| `pnpm start` | Start production server |
|
|
38
|
+
| `pnpm lint` | Run ESLint |
|
|
37
39
|
|
|
38
40
|
## Recommended Folder & File Structure
|
|
39
41
|
|
|
@@ -137,20 +139,13 @@ Create a `.env.local` (Next.js) file for local environment variables. Keep secre
|
|
|
137
139
|
|
|
138
140
|
## Deployment
|
|
139
141
|
|
|
142
|
+
Deploy to Vercel, Netlify, or any Node.js hosting service:
|
|
143
|
+
|
|
140
144
|
```bash
|
|
141
|
-
|
|
142
|
-
|
|
145
|
+
pnpm build
|
|
146
|
+
pnpm start
|
|
143
147
|
```
|
|
144
148
|
|
|
145
|
-
Deploy to Vercel, Netlify, or any Node.js hosting service.
|
|
146
|
-
|
|
147
149
|
---
|
|
148
150
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
This project was scaffolded using **StackKit** — a CLI toolkit for building production-ready applications.
|
|
152
|
-
|
|
153
|
-
- Generated via: `npx stackkit@latest create`
|
|
154
|
-
|
|
155
|
-
Learn more about StackKit:
|
|
156
|
-
https://github.com/tariqul420/stackkit
|
|
151
|
+
Generated with [StackKit](https://github.com/tariqul420/stackkit) — `npx stackkit@latest create`
|
|
Binary file
|
|
@@ -4,14 +4,16 @@ import { Geist, Geist_Mono } from "next/font/google";
|
|
|
4
4
|
import { Toaster } from "sonner";
|
|
5
5
|
import "./globals.css";
|
|
6
6
|
|
|
7
|
-
const
|
|
8
|
-
variable: "--font-
|
|
7
|
+
const geist = Geist({
|
|
8
|
+
variable: "--font-sans",
|
|
9
9
|
subsets: ["latin"],
|
|
10
|
+
weight: ["400", "500", "600", "700", "800", "900"],
|
|
10
11
|
});
|
|
11
12
|
|
|
12
13
|
const geistMono = Geist_Mono({
|
|
13
14
|
variable: "--font-geist-mono",
|
|
14
15
|
subsets: ["latin"],
|
|
16
|
+
weight: ["400", "500", "600", "700", "800", "900"],
|
|
15
17
|
});
|
|
16
18
|
|
|
17
19
|
export const metadata: Metadata = {
|
|
@@ -25,9 +27,9 @@ export default function RootLayout({
|
|
|
25
27
|
children: React.ReactNode;
|
|
26
28
|
}>) {
|
|
27
29
|
return (
|
|
28
|
-
<html lang="en">
|
|
30
|
+
<html lang="en" suppressHydrationWarning className={`${geist.variable} ${geistMono.variable}`}>
|
|
29
31
|
<head />
|
|
30
|
-
<body className=
|
|
32
|
+
<body className="font-sans antialiased" suppressHydrationWarning>
|
|
31
33
|
<QueryProviders>{children}</QueryProviders>
|
|
32
34
|
<Toaster theme="system" position="top-right" richColors />
|
|
33
35
|
</body>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { isServer, QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
4
5
|
import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";
|
|
5
6
|
|
|
6
7
|
function makeQueryClient() {
|
|
@@ -26,10 +27,12 @@ function getQueryClient() {
|
|
|
26
27
|
|
|
27
28
|
function QueryProviders({ children }: { children: React.ReactNode }) {
|
|
28
29
|
const queryClient = getQueryClient();
|
|
30
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
29
31
|
|
|
30
32
|
return (
|
|
31
33
|
<QueryClientProvider client={queryClient}>
|
|
32
34
|
<ReactQueryStreamedHydration>{children}</ReactQueryStreamedHydration>
|
|
35
|
+
{!isProduction && <ReactQueryDevtools initialIsOpen={false} />}
|
|
33
36
|
</QueryClientProvider>
|
|
34
37
|
);
|
|
35
38
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { envVars } from "../env";
|
|
3
|
+
|
|
4
|
+
const api = axios.create({
|
|
5
|
+
baseURL: envVars.API_URL || "http://localhost:5000/api",
|
|
6
|
+
withCredentials: true,
|
|
7
|
+
timeout: 30000,
|
|
8
|
+
headers: {
|
|
9
|
+
"Content-Type": "application/json",
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
api.interceptors.request.use(
|
|
14
|
+
(config) => config,
|
|
15
|
+
(error) => Promise.reject(error),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
api.interceptors.response.use(
|
|
19
|
+
(response) => response,
|
|
20
|
+
(error) => Promise.reject(error),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export default api;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
interface EnvVars {
|
|
2
|
+
APP_NAME: string;
|
|
2
3
|
APP_URL: string;
|
|
3
4
|
API_URL: string;
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
const loadEnvVars = (): EnvVars => {
|
|
7
|
-
const
|
|
8
|
+
const requiredVars: (keyof EnvVars)[] = ["APP_NAME", "APP_URL", "API_URL"];
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
if (!process.env[varName]) {
|
|
11
|
-
|
|
10
|
+
for (const varName of requiredVars) {
|
|
11
|
+
if (!process.env[`NEXT_PUBLIC_${varName}`]) {
|
|
12
|
+
console.warn(`Environment variable NEXT_PUBLIC_${varName} is not set. Using default value.`);
|
|
12
13
|
}
|
|
13
|
-
}
|
|
14
|
+
}
|
|
14
15
|
|
|
15
16
|
return {
|
|
17
|
+
APP_NAME: process.env.NEXT_PUBLIC_APP_NAME || "App Name",
|
|
16
18
|
APP_URL: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
|
|
17
19
|
API_URL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000/api",
|
|
18
20
|
};
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@tanstack/react-query": "^5.90.21",
|
|
14
14
|
"@tanstack/react-query-next-experimental": "^5.91.0",
|
|
15
|
+
"@tanstack/react-query-devtools": "^5.91.2",
|
|
15
16
|
"axios": "^1.13.5",
|
|
16
17
|
"next": "16.1.1",
|
|
17
18
|
"react": "19.2.3",
|
|
@@ -28,4 +29,4 @@
|
|
|
28
29
|
"tailwindcss": "^4.1.18",
|
|
29
30
|
"typescript": "^5.9.3"
|
|
30
31
|
}
|
|
31
|
-
}
|
|
32
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# React
|
|
1
|
+
# React App
|
|
2
2
|
|
|
3
|
-
Production-ready React starter with TypeScript, Vite, and essential libraries.
|
|
3
|
+
Production-ready React starter with TypeScript, Vite, and essential libraries, generated by [StackKit](https://github.com/tariqul420/stackkit).
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -32,10 +32,12 @@ npm run dev
|
|
|
32
32
|
|
|
33
33
|
## Scripts
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
| Command | Description |
|
|
36
|
+
| -------------- | ------------------------ |
|
|
37
|
+
| `pnpm dev` | Start development server |
|
|
38
|
+
| `pnpm build` | Build for production |
|
|
39
|
+
| `pnpm preview` | Preview production build |
|
|
40
|
+
| `pnpm lint` | Run ESLint |
|
|
39
41
|
|
|
40
42
|
## Environment Variables
|
|
41
43
|
|
|
@@ -122,11 +124,4 @@ Deploy the resulting `dist`/build output to your hosting platform (Vercel, Netli
|
|
|
122
124
|
|
|
123
125
|
---
|
|
124
126
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
This project was scaffolded using **StackKit** — a CLI toolkit for building production-ready applications.
|
|
128
|
-
|
|
129
|
-
- Generated via: `npx stackkit@latest create`
|
|
130
|
-
|
|
131
|
-
Learn more about StackKit:
|
|
132
|
-
https://github.com/tariqul420/stackkit
|
|
127
|
+
Generated with [StackKit](https://github.com/tariqul420/stackkit) — `npx stackkit@latest create`
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/
|
|
5
|
+
<link rel="icon" type="image/x-icon" href="/src/assets/favicon.ico" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<link href="/src/index.css" rel="stylesheet">
|
|
8
8
|
<title>React</title>
|
|
Binary file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
|
+
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
|
|
5
|
+
const isProduction = import.meta.env.PROD;
|
|
6
|
+
|
|
7
|
+
const NO_RETRY_STATUSES = new Set([401, 403, 404, 422]);
|
|
8
|
+
|
|
9
|
+
const queryClient = new QueryClient({
|
|
10
|
+
defaultOptions: {
|
|
11
|
+
queries: {
|
|
12
|
+
staleTime: 1000 * 60 * 5,
|
|
13
|
+
gcTime: 1000 * 60 * 10,
|
|
14
|
+
refetchOnWindowFocus: false,
|
|
15
|
+
retry: (failureCount, error) => {
|
|
16
|
+
if (
|
|
17
|
+
error instanceof Error &&
|
|
18
|
+
NO_RETRY_STATUSES.has(Number((error as Error & { status?: number }).status))
|
|
19
|
+
) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return failureCount < 2;
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
mutations: {
|
|
26
|
+
retry: false,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export default function QueryProvider({ children }: { children: ReactNode }) {
|
|
32
|
+
return (
|
|
33
|
+
<QueryClientProvider client={queryClient}>
|
|
34
|
+
{children}
|
|
35
|
+
{!isProduction && <ReactQueryDevtools initialIsOpen={false} />}
|
|
36
|
+
</QueryClientProvider>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Helmet
|
|
1
|
+
import { Helmet } from "react-helmet-async";
|
|
2
2
|
|
|
3
3
|
interface SEOProps {
|
|
4
4
|
title?: string;
|
|
@@ -9,16 +9,12 @@ interface SEOProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const defaultSEO = {
|
|
12
|
-
title: "React
|
|
13
|
-
description: "
|
|
14
|
-
keywords: "react, vite, typescript,
|
|
12
|
+
title: "Stackkit - Production-ready React starter template",
|
|
13
|
+
description: "Production-ready React starter template",
|
|
14
|
+
keywords: "react, vite, typescript, stackkit, production-ready, starter template",
|
|
15
15
|
image: "/og-image.png",
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
export function SEOProvider({ children }: { children: React.ReactNode }) {
|
|
19
|
-
return <HelmetProvider>{children}</HelmetProvider>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
18
|
export function SEO({ title, description, keywords, image, url }: SEOProps) {
|
|
23
19
|
const siteTitle = title ? `${title} | ${defaultSEO.title}` : defaultSEO.title;
|
|
24
20
|
const siteDescription = description || defaultSEO.description;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
|
|
3
|
+
const API_URL = import.meta.env.VITE_API_URL;
|
|
4
|
+
|
|
5
|
+
const api = axios.create({
|
|
6
|
+
baseURL: API_URL || "http://localhost:5000/api",
|
|
7
|
+
withCredentials: true,
|
|
8
|
+
timeout: 30000,
|
|
9
|
+
headers: {
|
|
10
|
+
"Content-Type": "application/json",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
api.interceptors.request.use(
|
|
15
|
+
(config) => config,
|
|
16
|
+
(error) => Promise.reject(error),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
api.interceptors.response.use(
|
|
20
|
+
(response) => response,
|
|
21
|
+
(error) => Promise.reject(error),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export default api;
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
import { QueryClientProvider } from "@tanstack/react-query";
|
|
2
|
-
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
3
1
|
import { StrictMode } from "react";
|
|
4
2
|
import { createRoot } from "react-dom/client";
|
|
5
|
-
import {
|
|
3
|
+
import { HelmetProvider } from "react-helmet-async";
|
|
6
4
|
import { RouterProvider } from "react-router";
|
|
5
|
+
import { Toaster } from "sonner";
|
|
6
|
+
import QueryProvider from "./components/providers/query-provider";
|
|
7
7
|
import "./index.css";
|
|
8
8
|
import { router } from "./router";
|
|
9
|
-
import { queryClient } from "./shared/lib/query-client";
|
|
10
|
-
import { SEOProvider } from "./shared/components/seo";
|
|
11
9
|
|
|
12
10
|
createRoot(document.getElementById("root")!).render(
|
|
13
11
|
<StrictMode>
|
|
14
|
-
<
|
|
15
|
-
<
|
|
12
|
+
<HelmetProvider>
|
|
13
|
+
<QueryProvider>
|
|
16
14
|
<RouterProvider router={router} />
|
|
17
|
-
<Toaster position="top-right" />
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
</SEOProvider>
|
|
15
|
+
<Toaster theme="system" position="top-right" richColors />
|
|
16
|
+
</QueryProvider>
|
|
17
|
+
</HelmetProvider>
|
|
21
18
|
</StrictMode>,
|
|
22
19
|
);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createBrowserRouter } from "react-router";
|
|
2
|
-
import { ErrorBoundary } from "./components/
|
|
3
|
-
import Layout from "./components/
|
|
4
|
-
import About from "./pages/
|
|
5
|
-
import Home from "./pages/
|
|
6
|
-
import NotFound from "./pages/
|
|
2
|
+
import { ErrorBoundary } from "./components/error-boundary";
|
|
3
|
+
import Layout from "./components/layout";
|
|
4
|
+
import About from "./pages/about";
|
|
5
|
+
import Home from "./pages/home";
|
|
6
|
+
import NotFound from "./pages/not-found";
|
|
7
7
|
|
|
8
8
|
export const router = createBrowserRouter([
|
|
9
9
|
{
|
|
@@ -13,9 +13,9 @@ export const router = createBrowserRouter([
|
|
|
13
13
|
children: [
|
|
14
14
|
{ index: true, Component: Home },
|
|
15
15
|
{ path: "about", Component: About },
|
|
16
|
-
{ path: "*", Component: NotFound },
|
|
17
16
|
],
|
|
18
17
|
},
|
|
18
|
+
{ path: "*", Component: NotFound },
|
|
19
19
|
]);
|
|
20
20
|
|
|
21
21
|
export default router;
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": [],
|
|
3
|
-
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
|
3
|
+
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }],
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"baseUrl": "./",
|
|
6
|
+
"paths": {
|
|
7
|
+
"@/*": ["src/*"]
|
|
8
|
+
}
|
|
9
|
+
}
|
|
4
10
|
}
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import tailwindcss from "@tailwindcss/vite";
|
|
2
2
|
import react from "@vitejs/plugin-react";
|
|
3
|
+
import path from "path";
|
|
3
4
|
import { defineConfig } from "vite";
|
|
4
5
|
|
|
5
6
|
export default defineConfig({
|
|
6
7
|
plugins: [react(), tailwindcss()],
|
|
8
|
+
server: {
|
|
9
|
+
port: 3000,
|
|
10
|
+
},
|
|
11
|
+
preview: {
|
|
12
|
+
port: 3000,
|
|
13
|
+
},
|
|
14
|
+
resolve: {
|
|
15
|
+
alias: {
|
|
16
|
+
"@": path.resolve(__dirname, "./src"),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
7
19
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { auth as proxy } from "@/lib/auth";
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { PrismaAdapter } from "@auth/prisma-adapter";
|
|
3
|
-
import bcrypt from "bcryptjs";
|
|
4
|
-
import NextAuth from "next-auth";
|
|
5
|
-
import Credentials from "next-auth/providers/credentials";
|
|
6
|
-
import EmailProvider from "next-auth/providers/email";
|
|
7
|
-
import Google from "next-auth/providers/google";
|
|
8
|
-
import {
|
|
9
|
-
getUserById,
|
|
10
|
-
getUserByProviderAccountId,
|
|
11
|
-
getUserByUsername,
|
|
12
|
-
} from "./actions/auth";
|
|
13
|
-
import { sendEmail } from "./email/email-service";
|
|
14
|
-
import { getVerificationEmailTemplate } from "./email/email-templates";
|
|
15
|
-
import { prisma } from "./prisma";
|
|
16
|
-
|
|
17
|
-
const options: any = {
|
|
18
|
-
adapter: PrismaAdapter(prisma),
|
|
19
|
-
providers: [
|
|
20
|
-
Google({
|
|
21
|
-
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
|
22
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
|
|
23
|
-
profile(profile: any) {
|
|
24
|
-
const p = profile as Record<string, any>;
|
|
25
|
-
return {
|
|
26
|
-
id: p.sub ?? p.id,
|
|
27
|
-
name: p.name,
|
|
28
|
-
email: p.email,
|
|
29
|
-
image: p.picture,
|
|
30
|
-
role: p.role ?? "USER",
|
|
31
|
-
};
|
|
32
|
-
},
|
|
33
|
-
}),
|
|
34
|
-
(Credentials({
|
|
35
|
-
id: "credentials",
|
|
36
|
-
name: "Credentials",
|
|
37
|
-
credentials: {
|
|
38
|
-
username: { label: "Email", type: "text" },
|
|
39
|
-
password: { label: "Password", type: "password" },
|
|
40
|
-
},
|
|
41
|
-
async authorize(credentials: any) {
|
|
42
|
-
if (!credentials?.username || !credentials?.password) return null;
|
|
43
|
-
const user = await getUserByUsername(credentials.username as string);
|
|
44
|
-
if (!user) return null;
|
|
45
|
-
const hashed = (user as any).password;
|
|
46
|
-
if (!hashed) return null; // OAuth-only account
|
|
47
|
-
const ok = await bcrypt.compare(credentials.password as string, hashed);
|
|
48
|
-
if (!ok) return null;
|
|
49
|
-
// strip sensitive fields
|
|
50
|
-
const safe = { ...user } as Record<string, any>;
|
|
51
|
-
delete safe.password;
|
|
52
|
-
return safe;
|
|
53
|
-
},
|
|
54
|
-
}),
|
|
55
|
-
EmailProvider({
|
|
56
|
-
server: process.env.EMAIL_SERVER,
|
|
57
|
-
from: process.env.EMAIL_FROM,
|
|
58
|
-
async sendVerificationRequest({ identifier, url, provider }: any) {
|
|
59
|
-
const { html, text } = getVerificationEmailTemplate(
|
|
60
|
-
{ email: identifier },
|
|
61
|
-
url,
|
|
62
|
-
);
|
|
63
|
-
await sendEmail({
|
|
64
|
-
to: identifier,
|
|
65
|
-
subject: "Verify your email",
|
|
66
|
-
html,
|
|
67
|
-
text,
|
|
68
|
-
from: provider?.from,
|
|
69
|
-
});
|
|
70
|
-
},
|
|
71
|
-
})),
|
|
72
|
-
],
|
|
73
|
-
callbacks: {
|
|
74
|
-
// ensure provider-account linking is safe
|
|
75
|
-
async signIn({ user, account }: any) {
|
|
76
|
-
if (account && account.provider !== "credentials") {
|
|
77
|
-
const linked = await getUserByProviderAccountId(
|
|
78
|
-
account.providerAccountId as string,
|
|
79
|
-
);
|
|
80
|
-
if (linked && linked.id !== user.id)
|
|
81
|
-
return "/sign-in/?error=OAuthAccountNotLinked";
|
|
82
|
-
}
|
|
83
|
-
return true;
|
|
84
|
-
},
|
|
85
|
-
// keep session payload minimal
|
|
86
|
-
async jwt({ token, user }: any) {
|
|
87
|
-
if (user) {
|
|
88
|
-
token.user = {
|
|
89
|
-
id: user.id,
|
|
90
|
-
email: user.email,
|
|
91
|
-
name: user.name,
|
|
92
|
-
role: user.role ?? "USER",
|
|
93
|
-
};
|
|
94
|
-
token.role = token.user.role;
|
|
95
|
-
}
|
|
96
|
-
if (token.sub && !token.user) {
|
|
97
|
-
const dbUser = await getUserById(token.sub as string);
|
|
98
|
-
if (dbUser)
|
|
99
|
-
token.user = {
|
|
100
|
-
id: dbUser.id,
|
|
101
|
-
email: dbUser.email,
|
|
102
|
-
name: dbUser.name,
|
|
103
|
-
role: (dbUser as any).role ?? "USER",
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
return token;
|
|
107
|
-
},
|
|
108
|
-
async session({ session, token }: any) {
|
|
109
|
-
session.user = token.user;
|
|
110
|
-
session.role = token.role ?? "USER";
|
|
111
|
-
return session;
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
session: { strategy: "jwt" },
|
|
115
|
-
secret: process.env.AUTH_SECRET,
|
|
116
|
-
debug: process.env.NODE_ENV !== "production",
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
export const { handlers, signIn, signOut, auth } = NextAuth(options);
|