doo-boilerplate 0.1.0
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/dist/index.js +382 -0
- package/package.json +31 -0
- package/templates/template-nextjs/Dockerfile +29 -0
- package/templates/template-nextjs/README.md +215 -0
- package/templates/template-nextjs/_env.example +7 -0
- package/templates/template-nextjs/_gitignore +8 -0
- package/templates/template-nextjs/_prettierignore +4 -0
- package/templates/template-nextjs/_prettierrc +12 -0
- package/templates/template-nextjs/components.json +19 -0
- package/templates/template-nextjs/docker-compose.yml +12 -0
- package/templates/template-nextjs/docs/swagger/api.json +77 -0
- package/templates/template-nextjs/eslint.config.ts +25 -0
- package/templates/template-nextjs/generate/.gitkeep +0 -0
- package/templates/template-nextjs/knip.config.ts +8 -0
- package/templates/template-nextjs/messages/en.json +14 -0
- package/templates/template-nextjs/messages/vi.json +14 -0
- package/templates/template-nextjs/next.config.ts +13 -0
- package/templates/template-nextjs/optional/charts/deps.json +7 -0
- package/templates/template-nextjs/optional/dark-mode/deps.json +5 -0
- package/templates/template-nextjs/optional/dark-mode/files/providers/theme-provider.tsx +17 -0
- package/templates/template-nextjs/optional/dnd/deps.json +8 -0
- package/templates/template-nextjs/optional/editor/deps.json +10 -0
- package/templates/template-nextjs/optional/i18n/deps.json +5 -0
- package/templates/template-nextjs/optional/sentry/deps.json +5 -0
- package/templates/template-nextjs/package.json +82 -0
- package/templates/template-nextjs/postcss.config.js +1 -0
- package/templates/template-nextjs/public/images/.gitkeep +0 -0
- package/templates/template-nextjs/scripts/build-and-scan.sh +13 -0
- package/templates/template-nextjs/scripts/trivy-scan.sh +24 -0
- package/templates/template-nextjs/src/app/(auth)/layout.tsx +10 -0
- package/templates/template-nextjs/src/app/(auth)/sign-in/page.tsx +9 -0
- package/templates/template-nextjs/src/app/(dashboard)/layout.tsx +17 -0
- package/templates/template-nextjs/src/app/(dashboard)/page.tsx +27 -0
- package/templates/template-nextjs/src/app/(dashboard)/profile/page.tsx +39 -0
- package/templates/template-nextjs/src/app/(dashboard)/settings/page.tsx +15 -0
- package/templates/template-nextjs/src/app/api/health/route.ts +3 -0
- package/templates/template-nextjs/src/app/layout.tsx +22 -0
- package/templates/template-nextjs/src/app/not-found.tsx +18 -0
- package/templates/template-nextjs/src/app/page.tsx +5 -0
- package/templates/template-nextjs/src/components/common/error-boundary.tsx +56 -0
- package/templates/template-nextjs/src/components/common/loading-spinner.tsx +28 -0
- package/templates/template-nextjs/src/components/common/theme-toggle.tsx +21 -0
- package/templates/template-nextjs/src/components/layout/header.tsx +91 -0
- package/templates/template-nextjs/src/components/layout/page-layout.tsx +23 -0
- package/templates/template-nextjs/src/components/layout/sidebar.tsx +126 -0
- package/templates/template-nextjs/src/components/ui/avatar.tsx +45 -0
- package/templates/template-nextjs/src/components/ui/badge.tsx +31 -0
- package/templates/template-nextjs/src/components/ui/button.tsx +44 -0
- package/templates/template-nextjs/src/components/ui/card.tsx +55 -0
- package/templates/template-nextjs/src/components/ui/checkbox.tsx +26 -0
- package/templates/template-nextjs/src/components/ui/dialog.tsx +99 -0
- package/templates/template-nextjs/src/components/ui/dropdown-menu.tsx +180 -0
- package/templates/template-nextjs/src/components/ui/form.tsx +158 -0
- package/templates/template-nextjs/src/components/ui/input.tsx +24 -0
- package/templates/template-nextjs/src/components/ui/label.tsx +19 -0
- package/templates/template-nextjs/src/components/ui/scroll-area.tsx +40 -0
- package/templates/template-nextjs/src/components/ui/select.tsx +148 -0
- package/templates/template-nextjs/src/components/ui/separator.tsx +24 -0
- package/templates/template-nextjs/src/components/ui/sheet.tsx +96 -0
- package/templates/template-nextjs/src/components/ui/skeleton.tsx +7 -0
- package/templates/template-nextjs/src/components/ui/switch.tsx +27 -0
- package/templates/template-nextjs/src/components/ui/tabs.tsx +53 -0
- package/templates/template-nextjs/src/components/ui/tooltip.tsx +28 -0
- package/templates/template-nextjs/src/config/site.ts +5 -0
- package/templates/template-nextjs/src/features/auth/components/sign-in-form.tsx +93 -0
- package/templates/template-nextjs/src/features/auth/hooks/use-auth.ts +47 -0
- package/templates/template-nextjs/src/features/auth/schemas/auth-schema.ts +21 -0
- package/templates/template-nextjs/src/features/auth/services/auth-api.ts +30 -0
- package/templates/template-nextjs/src/hooks/use-media-query.ts +15 -0
- package/templates/template-nextjs/src/i18n/config.ts +3 -0
- package/templates/template-nextjs/src/lib/api-client.ts +43 -0
- package/templates/template-nextjs/src/lib/query-client.ts +17 -0
- package/templates/template-nextjs/src/lib/utils.ts +19 -0
- package/templates/template-nextjs/src/middleware.ts +23 -0
- package/templates/template-nextjs/src/providers/app-providers.tsx +18 -0
- package/templates/template-nextjs/src/providers/query-provider.tsx +16 -0
- package/templates/template-nextjs/src/providers/theme-provider.tsx +17 -0
- package/templates/template-nextjs/src/stores/auth-store.ts +82 -0
- package/templates/template-nextjs/src/styles/globals.css +65 -0
- package/templates/template-nextjs/src/types/index.ts +13 -0
- package/templates/template-nextjs/tsconfig.json +23 -0
- package/templates/template-vite/Dockerfile +20 -0
- package/templates/template-vite/README.md +241 -0
- package/templates/template-vite/_env.example +8 -0
- package/templates/template-vite/_gitignore +7 -0
- package/templates/template-vite/_prettierignore +4 -0
- package/templates/template-vite/_prettierrc +13 -0
- package/templates/template-vite/components.json +19 -0
- package/templates/template-vite/docker-compose.yml +11 -0
- package/templates/template-vite/docs/swagger/api.json +77 -0
- package/templates/template-vite/eslint.config.ts +30 -0
- package/templates/template-vite/generate/.gitkeep +0 -0
- package/templates/template-vite/index.html +13 -0
- package/templates/template-vite/knip.config.ts +8 -0
- package/templates/template-vite/nginx.conf +37 -0
- package/templates/template-vite/optional/charts/deps.json +7 -0
- package/templates/template-vite/optional/dark-mode/deps.json +5 -0
- package/templates/template-vite/optional/dnd/deps.json +8 -0
- package/templates/template-vite/optional/editor/deps.json +10 -0
- package/templates/template-vite/optional/i18n/deps.json +7 -0
- package/templates/template-vite/optional/sentry/deps.json +6 -0
- package/templates/template-vite/package.json +91 -0
- package/templates/template-vite/public/favicon.svg +5 -0
- package/templates/template-vite/scripts/build-and-scan.sh +13 -0
- package/templates/template-vite/scripts/trivy-scan.sh +24 -0
- package/templates/template-vite/src/components/common/error-boundary.tsx +51 -0
- package/templates/template-vite/src/components/common/loading-spinner.tsx +38 -0
- package/templates/template-vite/src/components/common/theme-toggle.tsx +19 -0
- package/templates/template-vite/src/components/layout/header.tsx +58 -0
- package/templates/template-vite/src/components/layout/page-layout.tsx +31 -0
- package/templates/template-vite/src/components/layout/sidebar.tsx +74 -0
- package/templates/template-vite/src/components/ui/avatar.tsx +45 -0
- package/templates/template-vite/src/components/ui/badge.tsx +31 -0
- package/templates/template-vite/src/components/ui/button.tsx +49 -0
- package/templates/template-vite/src/components/ui/card.tsx +56 -0
- package/templates/template-vite/src/components/ui/checkbox.tsx +26 -0
- package/templates/template-vite/src/components/ui/dialog.tsx +99 -0
- package/templates/template-vite/src/components/ui/dropdown-menu.tsx +174 -0
- package/templates/template-vite/src/components/ui/form.tsx +161 -0
- package/templates/template-vite/src/components/ui/input.tsx +24 -0
- package/templates/template-vite/src/components/ui/label.tsx +19 -0
- package/templates/template-vite/src/components/ui/scroll-area.tsx +44 -0
- package/templates/template-vite/src/components/ui/select.tsx +148 -0
- package/templates/template-vite/src/components/ui/separator.tsx +24 -0
- package/templates/template-vite/src/components/ui/sheet.tsx +118 -0
- package/templates/template-vite/src/components/ui/skeleton.tsx +7 -0
- package/templates/template-vite/src/components/ui/switch.tsx +27 -0
- package/templates/template-vite/src/components/ui/tabs.tsx +53 -0
- package/templates/template-vite/src/components/ui/tooltip.tsx +26 -0
- package/templates/template-vite/src/config/site.ts +5 -0
- package/templates/template-vite/src/context/theme-provider.tsx +10 -0
- package/templates/template-vite/src/features/auth/components/sign-in-form.tsx +86 -0
- package/templates/template-vite/src/features/auth/hooks/use-auth.ts +46 -0
- package/templates/template-vite/src/features/auth/schemas/auth-schema.ts +8 -0
- package/templates/template-vite/src/features/auth/services/auth-api.ts +24 -0
- package/templates/template-vite/src/hooks/use-media-query.ts +26 -0
- package/templates/template-vite/src/lib/api-client.ts +38 -0
- package/templates/template-vite/src/lib/i18n.ts +34 -0
- package/templates/template-vite/src/lib/query-client.ts +14 -0
- package/templates/template-vite/src/lib/utils.ts +28 -0
- package/templates/template-vite/src/main.tsx +37 -0
- package/templates/template-vite/src/routeTree.gen.ts +6 -0
- package/templates/template-vite/src/routes/(auth)/sign-in.tsx +17 -0
- package/templates/template-vite/src/routes/(errors)/404.tsx +19 -0
- package/templates/template-vite/src/routes/__root.tsx +17 -0
- package/templates/template-vite/src/routes/_authenticated/dashboard.tsx +22 -0
- package/templates/template-vite/src/routes/_authenticated/profile.tsx +47 -0
- package/templates/template-vite/src/routes/_authenticated/settings.tsx +17 -0
- package/templates/template-vite/src/routes/_authenticated.tsx +32 -0
- package/templates/template-vite/src/stores/auth-store.ts +56 -0
- package/templates/template-vite/src/styles/globals.css +81 -0
- package/templates/template-vite/src/types/index.ts +39 -0
- package/templates/template-vite/tsconfig.app.json +24 -0
- package/templates/template-vite/tsconfig.json +7 -0
- package/templates/template-vite/tsconfig.node.json +16 -0
- package/templates/template-vite/vite.config.ts +36 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "default",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/styles/globals.css",
|
|
9
|
+
"baseColor": "slate",
|
|
10
|
+
"cssVariables": true
|
|
11
|
+
},
|
|
12
|
+
"aliases": {
|
|
13
|
+
"components": "@/components",
|
|
14
|
+
"utils": "@/lib/utils",
|
|
15
|
+
"ui": "@/components/ui",
|
|
16
|
+
"lib": "@/lib",
|
|
17
|
+
"hooks": "@/hooks"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.3",
|
|
3
|
+
"info": {
|
|
4
|
+
"title": "{{PROJECT_NAME}} API",
|
|
5
|
+
"description": "API documentation. Replace this file with your actual OpenAPI spec.",
|
|
6
|
+
"version": "1.0.0"
|
|
7
|
+
},
|
|
8
|
+
"servers": [
|
|
9
|
+
{ "url": "http://localhost:8000", "description": "Local development" }
|
|
10
|
+
],
|
|
11
|
+
"paths": {
|
|
12
|
+
"/auth/login": {
|
|
13
|
+
"post": {
|
|
14
|
+
"tags": ["auth"],
|
|
15
|
+
"summary": "Login",
|
|
16
|
+
"requestBody": {
|
|
17
|
+
"required": true,
|
|
18
|
+
"content": {
|
|
19
|
+
"application/json": {
|
|
20
|
+
"schema": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"required": ["email", "password"],
|
|
23
|
+
"properties": {
|
|
24
|
+
"email": { "type": "string", "format": "email" },
|
|
25
|
+
"password": { "type": "string", "minLength": 6 }
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"responses": {
|
|
32
|
+
"200": {
|
|
33
|
+
"description": "Login successful",
|
|
34
|
+
"content": {
|
|
35
|
+
"application/json": {
|
|
36
|
+
"schema": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"accessToken": { "type": "string" },
|
|
40
|
+
"refreshToken": { "type": "string" },
|
|
41
|
+
"user": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"id": { "type": "string" },
|
|
45
|
+
"email": { "type": "string" },
|
|
46
|
+
"name": { "type": "string" }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"/auth/me": {
|
|
58
|
+
"get": {
|
|
59
|
+
"tags": ["auth"],
|
|
60
|
+
"summary": "Get current user",
|
|
61
|
+
"security": [{ "bearerAuth": [] }],
|
|
62
|
+
"responses": {
|
|
63
|
+
"200": { "description": "Current user info" }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"components": {
|
|
69
|
+
"securitySchemes": {
|
|
70
|
+
"bearerAuth": {
|
|
71
|
+
"type": "http",
|
|
72
|
+
"scheme": "bearer",
|
|
73
|
+
"bearerFormat": "JWT"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import tseslint from 'typescript-eslint'
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
4
|
+
import unusedImports from 'eslint-plugin-unused-imports'
|
|
5
|
+
|
|
6
|
+
export default tseslint.config(
|
|
7
|
+
js.configs.recommended,
|
|
8
|
+
...tseslint.configs.recommended,
|
|
9
|
+
{
|
|
10
|
+
plugins: {
|
|
11
|
+
'react-hooks': reactHooks,
|
|
12
|
+
'unused-imports': unusedImports,
|
|
13
|
+
},
|
|
14
|
+
rules: {
|
|
15
|
+
'react-hooks/rules-of-hooks': 'error',
|
|
16
|
+
'react-hooks/exhaustive-deps': 'warn',
|
|
17
|
+
'unused-imports/no-unused-imports': 'error',
|
|
18
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
19
|
+
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
ignores: ['dist/', '.next/', 'src/components/ui/', '*.config.*'],
|
|
24
|
+
}
|
|
25
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NextConfig } from 'next'
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
output: 'standalone',
|
|
5
|
+
images: {
|
|
6
|
+
remotePatterns: [
|
|
7
|
+
{ protocol: 'https', hostname: '**.fptcloud.com' },
|
|
8
|
+
{ protocol: 'https', hostname: 'lh3.googleusercontent.com' },
|
|
9
|
+
],
|
|
10
|
+
},
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default nextConfig
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ThemeProvider as NextThemesProvider } from 'next-themes'
|
|
4
|
+
import { type ReactNode } from 'react'
|
|
5
|
+
|
|
6
|
+
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
7
|
+
return (
|
|
8
|
+
<NextThemesProvider
|
|
9
|
+
attribute='class'
|
|
10
|
+
defaultTheme='system'
|
|
11
|
+
enableSystem
|
|
12
|
+
disableTransitionOnChange
|
|
13
|
+
>
|
|
14
|
+
{children}
|
|
15
|
+
</NextThemesProvider>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"@tiptap/react": "^2.11.7",
|
|
4
|
+
"@tiptap/starter-kit": "^2.11.7",
|
|
5
|
+
"@tiptap/extension-link": "^2.11.7",
|
|
6
|
+
"@tiptap/extension-image": "^2.11.7",
|
|
7
|
+
"@tiptap/extension-placeholder": "^2.11.7",
|
|
8
|
+
"@tiptap/extension-underline": "^2.11.7"
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev --turbopack",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"lint:fix": "eslint . --fix",
|
|
11
|
+
"type-check": "tsc --noEmit",
|
|
12
|
+
"format": "prettier --write .",
|
|
13
|
+
"format:check": "prettier -c .",
|
|
14
|
+
"knip": "knip",
|
|
15
|
+
"gen:api": "swagger-typescript-api -p ./docs/swagger/api.json -o ./src/features/auth/services/gen --no-client --modular",
|
|
16
|
+
"gen:api:watch": "swagger-typescript-api -p ./docs/swagger/api.json -o ./src/features/auth/services/gen --no-client --modular --watch",
|
|
17
|
+
"docker:build": "bash scripts/build-and-scan.sh",
|
|
18
|
+
"docker:scan": "bash scripts/trivy-scan.sh"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"next": "16.0.10",
|
|
22
|
+
"react": "19.2.0",
|
|
23
|
+
"react-dom": "19.2.0",
|
|
24
|
+
"next-intl": "^4.3.4",
|
|
25
|
+
"next-themes": "^0.4.6",
|
|
26
|
+
"zustand": "^5.0.2",
|
|
27
|
+
"@tanstack/react-query": "^5.83.0",
|
|
28
|
+
"@tanstack/react-table": "^8.21.2",
|
|
29
|
+
"axios": "^1.11.0",
|
|
30
|
+
"react-hook-form": "^7.54.1",
|
|
31
|
+
"zod": "^3.24.1",
|
|
32
|
+
"@hookform/resolvers": "^3.9.1",
|
|
33
|
+
"clsx": "^2.1.1",
|
|
34
|
+
"tailwind-merge": "^3.0.2",
|
|
35
|
+
"class-variance-authority": "^0.7.1",
|
|
36
|
+
"lucide-react": "^0.476.0",
|
|
37
|
+
"date-fns": "^4.1.0",
|
|
38
|
+
"sonner": "^2.0.7",
|
|
39
|
+
"jwt-decode": "^4.0.0",
|
|
40
|
+
"@radix-ui/react-accordion": "^1.2.3",
|
|
41
|
+
"@radix-ui/react-alert-dialog": "^1.1.6",
|
|
42
|
+
"@radix-ui/react-avatar": "^1.1.3",
|
|
43
|
+
"@radix-ui/react-checkbox": "^1.1.4",
|
|
44
|
+
"@radix-ui/react-dialog": "^1.1.6",
|
|
45
|
+
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
|
46
|
+
"@radix-ui/react-label": "^2.1.2",
|
|
47
|
+
"@radix-ui/react-popover": "^1.1.6",
|
|
48
|
+
"@radix-ui/react-scroll-area": "^1.2.3",
|
|
49
|
+
"@radix-ui/react-select": "^2.1.6",
|
|
50
|
+
"@radix-ui/react-separator": "^1.1.2",
|
|
51
|
+
"@radix-ui/react-slot": "^1.1.2",
|
|
52
|
+
"@radix-ui/react-switch": "^1.1.3",
|
|
53
|
+
"@radix-ui/react-tabs": "^1.1.3",
|
|
54
|
+
"@radix-ui/react-tooltip": "^1.1.8",
|
|
55
|
+
"cmdk": "^1.1.1"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^22",
|
|
59
|
+
"@types/react": "^19",
|
|
60
|
+
"@types/react-dom": "^19",
|
|
61
|
+
"typescript": "^5.7.2",
|
|
62
|
+
"tailwindcss": "^4.0.0",
|
|
63
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
64
|
+
"postcss": "^8.4.49",
|
|
65
|
+
"eslint": "^9",
|
|
66
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
67
|
+
"eslint-plugin-unused-imports": "^4.1.4",
|
|
68
|
+
"typescript-eslint": "^8.31.1",
|
|
69
|
+
"@eslint/js": "^9.25.0",
|
|
70
|
+
"prettier": "^3.4.2",
|
|
71
|
+
"prettier-plugin-tailwindcss": "^0.6.11",
|
|
72
|
+
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
|
73
|
+
"husky": "^9.1.7",
|
|
74
|
+
"lint-staged": "^15.2.11",
|
|
75
|
+
"knip": "^5.45.0",
|
|
76
|
+
"swagger-typescript-api": "^13.2.7",
|
|
77
|
+
"@tanstack/react-query-devtools": "^5.83.0"
|
|
78
|
+
},
|
|
79
|
+
"lint-staged": {
|
|
80
|
+
"*.{js,ts,tsx,css}": ["eslint --fix", "prettier --write"]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = { plugins: { '@tailwindcss/postcss': {} } }
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PROJECT_NAME=$(node -p "require('./package.json').name")
|
|
5
|
+
IMAGE_TAG="${PROJECT_NAME}:latest"
|
|
6
|
+
|
|
7
|
+
echo "Building Docker image: $IMAGE_TAG"
|
|
8
|
+
docker build -t "$IMAGE_TAG" .
|
|
9
|
+
|
|
10
|
+
echo "Scanning with Trivy..."
|
|
11
|
+
bash "$(dirname "$0")/trivy-scan.sh" "$IMAGE_TAG"
|
|
12
|
+
|
|
13
|
+
echo "Build and scan complete: $IMAGE_TAG"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
IMAGE_NAME="${1:-app:latest}"
|
|
5
|
+
SEVERITY="${TRIVY_SEVERITY:-HIGH,CRITICAL}"
|
|
6
|
+
|
|
7
|
+
echo "Running Trivy security scan on: $IMAGE_NAME"
|
|
8
|
+
|
|
9
|
+
# Check if trivy is installed
|
|
10
|
+
if ! command -v trivy &> /dev/null; then
|
|
11
|
+
echo "Trivy not found. Installing via Docker..."
|
|
12
|
+
docker run --rm \
|
|
13
|
+
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
14
|
+
-v "$HOME/.cache/trivy:/root/.cache" \
|
|
15
|
+
aquasec/trivy:latest image \
|
|
16
|
+
--severity "$SEVERITY" \
|
|
17
|
+
--exit-code 1 \
|
|
18
|
+
"$IMAGE_NAME"
|
|
19
|
+
else
|
|
20
|
+
trivy image \
|
|
21
|
+
--severity "$SEVERITY" \
|
|
22
|
+
--exit-code 1 \
|
|
23
|
+
"$IMAGE_NAME"
|
|
24
|
+
fi
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
// Centered layout for auth pages
|
|
4
|
+
export default function AuthLayout({ children }: { children: ReactNode }) {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex min-h-screen items-center justify-center bg-muted/40 p-4">
|
|
7
|
+
{children}
|
|
8
|
+
</div>
|
|
9
|
+
)
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Header } from '@/components/layout/header'
|
|
4
|
+
import { MobileSidebar, Sidebar } from '@/components/layout/sidebar'
|
|
5
|
+
|
|
6
|
+
export default function DashboardLayout({ children }: { children: ReactNode }) {
|
|
7
|
+
return (
|
|
8
|
+
<div className="flex h-screen overflow-hidden">
|
|
9
|
+
<Sidebar />
|
|
10
|
+
<MobileSidebar />
|
|
11
|
+
<div className="flex flex-1 flex-col overflow-hidden">
|
|
12
|
+
<Header />
|
|
13
|
+
<main className="flex-1 overflow-y-auto">{children}</main>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PageLayout } from '@/components/layout/page-layout'
|
|
2
|
+
|
|
3
|
+
// Stat card placeholder component
|
|
4
|
+
function StatCard({ label, value }: { label: string; value: string }) {
|
|
5
|
+
return (
|
|
6
|
+
<div className="rounded-lg border bg-card p-6 text-card-foreground shadow-sm">
|
|
7
|
+
<p className="text-sm font-medium text-muted-foreground">{label}</p>
|
|
8
|
+
<p className="mt-2 text-3xl font-bold">{value}</p>
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function DashboardPage() {
|
|
14
|
+
return (
|
|
15
|
+
<PageLayout
|
|
16
|
+
title="Dashboard"
|
|
17
|
+
description="Welcome to your portal. Here's an overview of your account."
|
|
18
|
+
>
|
|
19
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
20
|
+
<StatCard label="Total Users" value="1,024" />
|
|
21
|
+
<StatCard label="Active Sessions" value="42" />
|
|
22
|
+
<StatCard label="Requests Today" value="8,390" />
|
|
23
|
+
<StatCard label="Uptime" value="99.9%" />
|
|
24
|
+
</div>
|
|
25
|
+
</PageLayout>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useAuthStore } from '@/stores/auth-store'
|
|
4
|
+
import { PageLayout } from '@/components/layout/page-layout'
|
|
5
|
+
|
|
6
|
+
export default function ProfilePage() {
|
|
7
|
+
const { user } = useAuthStore()
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<PageLayout title="Profile" description="View and manage your profile information.">
|
|
11
|
+
<div className="rounded-lg border bg-card p-6 text-card-foreground shadow-sm">
|
|
12
|
+
{user ? (
|
|
13
|
+
<div className="space-y-3">
|
|
14
|
+
<div>
|
|
15
|
+
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
16
|
+
Name
|
|
17
|
+
</p>
|
|
18
|
+
<p className="mt-1 text-sm font-medium">{user.name}</p>
|
|
19
|
+
</div>
|
|
20
|
+
<div>
|
|
21
|
+
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
22
|
+
Email
|
|
23
|
+
</p>
|
|
24
|
+
<p className="mt-1 text-sm font-medium">{user.email}</p>
|
|
25
|
+
</div>
|
|
26
|
+
<div>
|
|
27
|
+
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
|
28
|
+
Roles
|
|
29
|
+
</p>
|
|
30
|
+
<p className="mt-1 text-sm font-medium">{user.roles.join(', ') || 'No roles'}</p>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
) : (
|
|
34
|
+
<p className="text-sm text-muted-foreground">No profile information available.</p>
|
|
35
|
+
)}
|
|
36
|
+
</div>
|
|
37
|
+
</PageLayout>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PageLayout } from '@/components/layout/page-layout'
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
title: 'Settings',
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default function SettingsPage() {
|
|
8
|
+
return (
|
|
9
|
+
<PageLayout title="Settings" description="Manage your application settings and preferences.">
|
|
10
|
+
<div className="rounded-lg border bg-card p-6 text-card-foreground shadow-sm">
|
|
11
|
+
<p className="text-sm text-muted-foreground">Settings configuration coming soon.</p>
|
|
12
|
+
</div>
|
|
13
|
+
</PageLayout>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { Inter } from 'next/font/google'
|
|
3
|
+
|
|
4
|
+
import { AppProviders } from '@/providers/app-providers'
|
|
5
|
+
import '@/styles/globals.css'
|
|
6
|
+
|
|
7
|
+
const inter = Inter({ subsets: ['latin'], variable: '--font-sans' })
|
|
8
|
+
|
|
9
|
+
export const metadata: Metadata = {
|
|
10
|
+
title: '{{PROJECT_NAME}}',
|
|
11
|
+
description: 'Portal frontend application',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
15
|
+
return (
|
|
16
|
+
<html lang="en" suppressHydrationWarning>
|
|
17
|
+
<body className={inter.variable}>
|
|
18
|
+
<AppProviders>{children}</AppProviders>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
|
|
5
|
+
export default function NotFound() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="flex min-h-screen flex-col items-center justify-center gap-4 text-center">
|
|
8
|
+
<h1 className="text-6xl font-bold text-muted-foreground">404</h1>
|
|
9
|
+
<h2 className="text-2xl font-semibold">Page Not Found</h2>
|
|
10
|
+
<p className="max-w-md text-muted-foreground">
|
|
11
|
+
The page you are looking for does not exist or has been moved.
|
|
12
|
+
</p>
|
|
13
|
+
<Button asChild>
|
|
14
|
+
<Link href="/dashboard">Go to Dashboard</Link>
|
|
15
|
+
</Button>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { type ErrorInfo } from 'react'
|
|
4
|
+
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
|
|
7
|
+
interface ErrorBoundaryProps {
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
fallback?: React.ReactNode
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface ErrorBoundaryState {
|
|
13
|
+
hasError: boolean
|
|
14
|
+
error?: Error
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
18
|
+
constructor(props: ErrorBoundaryProps) {
|
|
19
|
+
super(props)
|
|
20
|
+
this.state = { hasError: false }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
24
|
+
return { hasError: true, error }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
28
|
+
console.error('ErrorBoundary caught an error:', error, info)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
handleReset = () => {
|
|
32
|
+
this.setState({ hasError: false, error: undefined })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render() {
|
|
36
|
+
if (this.state.hasError) {
|
|
37
|
+
if (this.props.fallback) {
|
|
38
|
+
return this.props.fallback
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex min-h-[400px] flex-col items-center justify-center gap-4 p-6 text-center">
|
|
43
|
+
<h2 className="text-xl font-semibold">Something went wrong</h2>
|
|
44
|
+
<p className="max-w-md text-sm text-muted-foreground">
|
|
45
|
+
{this.state.error?.message ?? 'An unexpected error occurred. Please try again.'}
|
|
46
|
+
</p>
|
|
47
|
+
<Button variant="outline" onClick={this.handleReset}>
|
|
48
|
+
Try again
|
|
49
|
+
</Button>
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this.props.children
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Loader2 } from 'lucide-react'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
|
|
5
|
+
interface LoadingSpinnerProps {
|
|
6
|
+
size?: 'sm' | 'md' | 'lg'
|
|
7
|
+
className?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const sizeMap = {
|
|
11
|
+
sm: 'h-4 w-4',
|
|
12
|
+
md: 'h-6 w-6',
|
|
13
|
+
lg: 'h-10 w-10',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function LoadingSpinner({ size = 'md', className }: LoadingSpinnerProps) {
|
|
17
|
+
return (
|
|
18
|
+
<Loader2 className={cn('animate-spin text-muted-foreground', sizeMap[size], className)} />
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function FullPageSpinner() {
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex min-h-screen items-center justify-center">
|
|
25
|
+
<LoadingSpinner size="lg" />
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|