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.
Files changed (156) hide show
  1. package/dist/index.js +382 -0
  2. package/package.json +31 -0
  3. package/templates/template-nextjs/Dockerfile +29 -0
  4. package/templates/template-nextjs/README.md +215 -0
  5. package/templates/template-nextjs/_env.example +7 -0
  6. package/templates/template-nextjs/_gitignore +8 -0
  7. package/templates/template-nextjs/_prettierignore +4 -0
  8. package/templates/template-nextjs/_prettierrc +12 -0
  9. package/templates/template-nextjs/components.json +19 -0
  10. package/templates/template-nextjs/docker-compose.yml +12 -0
  11. package/templates/template-nextjs/docs/swagger/api.json +77 -0
  12. package/templates/template-nextjs/eslint.config.ts +25 -0
  13. package/templates/template-nextjs/generate/.gitkeep +0 -0
  14. package/templates/template-nextjs/knip.config.ts +8 -0
  15. package/templates/template-nextjs/messages/en.json +14 -0
  16. package/templates/template-nextjs/messages/vi.json +14 -0
  17. package/templates/template-nextjs/next.config.ts +13 -0
  18. package/templates/template-nextjs/optional/charts/deps.json +7 -0
  19. package/templates/template-nextjs/optional/dark-mode/deps.json +5 -0
  20. package/templates/template-nextjs/optional/dark-mode/files/providers/theme-provider.tsx +17 -0
  21. package/templates/template-nextjs/optional/dnd/deps.json +8 -0
  22. package/templates/template-nextjs/optional/editor/deps.json +10 -0
  23. package/templates/template-nextjs/optional/i18n/deps.json +5 -0
  24. package/templates/template-nextjs/optional/sentry/deps.json +5 -0
  25. package/templates/template-nextjs/package.json +82 -0
  26. package/templates/template-nextjs/postcss.config.js +1 -0
  27. package/templates/template-nextjs/public/images/.gitkeep +0 -0
  28. package/templates/template-nextjs/scripts/build-and-scan.sh +13 -0
  29. package/templates/template-nextjs/scripts/trivy-scan.sh +24 -0
  30. package/templates/template-nextjs/src/app/(auth)/layout.tsx +10 -0
  31. package/templates/template-nextjs/src/app/(auth)/sign-in/page.tsx +9 -0
  32. package/templates/template-nextjs/src/app/(dashboard)/layout.tsx +17 -0
  33. package/templates/template-nextjs/src/app/(dashboard)/page.tsx +27 -0
  34. package/templates/template-nextjs/src/app/(dashboard)/profile/page.tsx +39 -0
  35. package/templates/template-nextjs/src/app/(dashboard)/settings/page.tsx +15 -0
  36. package/templates/template-nextjs/src/app/api/health/route.ts +3 -0
  37. package/templates/template-nextjs/src/app/layout.tsx +22 -0
  38. package/templates/template-nextjs/src/app/not-found.tsx +18 -0
  39. package/templates/template-nextjs/src/app/page.tsx +5 -0
  40. package/templates/template-nextjs/src/components/common/error-boundary.tsx +56 -0
  41. package/templates/template-nextjs/src/components/common/loading-spinner.tsx +28 -0
  42. package/templates/template-nextjs/src/components/common/theme-toggle.tsx +21 -0
  43. package/templates/template-nextjs/src/components/layout/header.tsx +91 -0
  44. package/templates/template-nextjs/src/components/layout/page-layout.tsx +23 -0
  45. package/templates/template-nextjs/src/components/layout/sidebar.tsx +126 -0
  46. package/templates/template-nextjs/src/components/ui/avatar.tsx +45 -0
  47. package/templates/template-nextjs/src/components/ui/badge.tsx +31 -0
  48. package/templates/template-nextjs/src/components/ui/button.tsx +44 -0
  49. package/templates/template-nextjs/src/components/ui/card.tsx +55 -0
  50. package/templates/template-nextjs/src/components/ui/checkbox.tsx +26 -0
  51. package/templates/template-nextjs/src/components/ui/dialog.tsx +99 -0
  52. package/templates/template-nextjs/src/components/ui/dropdown-menu.tsx +180 -0
  53. package/templates/template-nextjs/src/components/ui/form.tsx +158 -0
  54. package/templates/template-nextjs/src/components/ui/input.tsx +24 -0
  55. package/templates/template-nextjs/src/components/ui/label.tsx +19 -0
  56. package/templates/template-nextjs/src/components/ui/scroll-area.tsx +40 -0
  57. package/templates/template-nextjs/src/components/ui/select.tsx +148 -0
  58. package/templates/template-nextjs/src/components/ui/separator.tsx +24 -0
  59. package/templates/template-nextjs/src/components/ui/sheet.tsx +96 -0
  60. package/templates/template-nextjs/src/components/ui/skeleton.tsx +7 -0
  61. package/templates/template-nextjs/src/components/ui/switch.tsx +27 -0
  62. package/templates/template-nextjs/src/components/ui/tabs.tsx +53 -0
  63. package/templates/template-nextjs/src/components/ui/tooltip.tsx +28 -0
  64. package/templates/template-nextjs/src/config/site.ts +5 -0
  65. package/templates/template-nextjs/src/features/auth/components/sign-in-form.tsx +93 -0
  66. package/templates/template-nextjs/src/features/auth/hooks/use-auth.ts +47 -0
  67. package/templates/template-nextjs/src/features/auth/schemas/auth-schema.ts +21 -0
  68. package/templates/template-nextjs/src/features/auth/services/auth-api.ts +30 -0
  69. package/templates/template-nextjs/src/hooks/use-media-query.ts +15 -0
  70. package/templates/template-nextjs/src/i18n/config.ts +3 -0
  71. package/templates/template-nextjs/src/lib/api-client.ts +43 -0
  72. package/templates/template-nextjs/src/lib/query-client.ts +17 -0
  73. package/templates/template-nextjs/src/lib/utils.ts +19 -0
  74. package/templates/template-nextjs/src/middleware.ts +23 -0
  75. package/templates/template-nextjs/src/providers/app-providers.tsx +18 -0
  76. package/templates/template-nextjs/src/providers/query-provider.tsx +16 -0
  77. package/templates/template-nextjs/src/providers/theme-provider.tsx +17 -0
  78. package/templates/template-nextjs/src/stores/auth-store.ts +82 -0
  79. package/templates/template-nextjs/src/styles/globals.css +65 -0
  80. package/templates/template-nextjs/src/types/index.ts +13 -0
  81. package/templates/template-nextjs/tsconfig.json +23 -0
  82. package/templates/template-vite/Dockerfile +20 -0
  83. package/templates/template-vite/README.md +241 -0
  84. package/templates/template-vite/_env.example +8 -0
  85. package/templates/template-vite/_gitignore +7 -0
  86. package/templates/template-vite/_prettierignore +4 -0
  87. package/templates/template-vite/_prettierrc +13 -0
  88. package/templates/template-vite/components.json +19 -0
  89. package/templates/template-vite/docker-compose.yml +11 -0
  90. package/templates/template-vite/docs/swagger/api.json +77 -0
  91. package/templates/template-vite/eslint.config.ts +30 -0
  92. package/templates/template-vite/generate/.gitkeep +0 -0
  93. package/templates/template-vite/index.html +13 -0
  94. package/templates/template-vite/knip.config.ts +8 -0
  95. package/templates/template-vite/nginx.conf +37 -0
  96. package/templates/template-vite/optional/charts/deps.json +7 -0
  97. package/templates/template-vite/optional/dark-mode/deps.json +5 -0
  98. package/templates/template-vite/optional/dnd/deps.json +8 -0
  99. package/templates/template-vite/optional/editor/deps.json +10 -0
  100. package/templates/template-vite/optional/i18n/deps.json +7 -0
  101. package/templates/template-vite/optional/sentry/deps.json +6 -0
  102. package/templates/template-vite/package.json +91 -0
  103. package/templates/template-vite/public/favicon.svg +5 -0
  104. package/templates/template-vite/scripts/build-and-scan.sh +13 -0
  105. package/templates/template-vite/scripts/trivy-scan.sh +24 -0
  106. package/templates/template-vite/src/components/common/error-boundary.tsx +51 -0
  107. package/templates/template-vite/src/components/common/loading-spinner.tsx +38 -0
  108. package/templates/template-vite/src/components/common/theme-toggle.tsx +19 -0
  109. package/templates/template-vite/src/components/layout/header.tsx +58 -0
  110. package/templates/template-vite/src/components/layout/page-layout.tsx +31 -0
  111. package/templates/template-vite/src/components/layout/sidebar.tsx +74 -0
  112. package/templates/template-vite/src/components/ui/avatar.tsx +45 -0
  113. package/templates/template-vite/src/components/ui/badge.tsx +31 -0
  114. package/templates/template-vite/src/components/ui/button.tsx +49 -0
  115. package/templates/template-vite/src/components/ui/card.tsx +56 -0
  116. package/templates/template-vite/src/components/ui/checkbox.tsx +26 -0
  117. package/templates/template-vite/src/components/ui/dialog.tsx +99 -0
  118. package/templates/template-vite/src/components/ui/dropdown-menu.tsx +174 -0
  119. package/templates/template-vite/src/components/ui/form.tsx +161 -0
  120. package/templates/template-vite/src/components/ui/input.tsx +24 -0
  121. package/templates/template-vite/src/components/ui/label.tsx +19 -0
  122. package/templates/template-vite/src/components/ui/scroll-area.tsx +44 -0
  123. package/templates/template-vite/src/components/ui/select.tsx +148 -0
  124. package/templates/template-vite/src/components/ui/separator.tsx +24 -0
  125. package/templates/template-vite/src/components/ui/sheet.tsx +118 -0
  126. package/templates/template-vite/src/components/ui/skeleton.tsx +7 -0
  127. package/templates/template-vite/src/components/ui/switch.tsx +27 -0
  128. package/templates/template-vite/src/components/ui/tabs.tsx +53 -0
  129. package/templates/template-vite/src/components/ui/tooltip.tsx +26 -0
  130. package/templates/template-vite/src/config/site.ts +5 -0
  131. package/templates/template-vite/src/context/theme-provider.tsx +10 -0
  132. package/templates/template-vite/src/features/auth/components/sign-in-form.tsx +86 -0
  133. package/templates/template-vite/src/features/auth/hooks/use-auth.ts +46 -0
  134. package/templates/template-vite/src/features/auth/schemas/auth-schema.ts +8 -0
  135. package/templates/template-vite/src/features/auth/services/auth-api.ts +24 -0
  136. package/templates/template-vite/src/hooks/use-media-query.ts +26 -0
  137. package/templates/template-vite/src/lib/api-client.ts +38 -0
  138. package/templates/template-vite/src/lib/i18n.ts +34 -0
  139. package/templates/template-vite/src/lib/query-client.ts +14 -0
  140. package/templates/template-vite/src/lib/utils.ts +28 -0
  141. package/templates/template-vite/src/main.tsx +37 -0
  142. package/templates/template-vite/src/routeTree.gen.ts +6 -0
  143. package/templates/template-vite/src/routes/(auth)/sign-in.tsx +17 -0
  144. package/templates/template-vite/src/routes/(errors)/404.tsx +19 -0
  145. package/templates/template-vite/src/routes/__root.tsx +17 -0
  146. package/templates/template-vite/src/routes/_authenticated/dashboard.tsx +22 -0
  147. package/templates/template-vite/src/routes/_authenticated/profile.tsx +47 -0
  148. package/templates/template-vite/src/routes/_authenticated/settings.tsx +17 -0
  149. package/templates/template-vite/src/routes/_authenticated.tsx +32 -0
  150. package/templates/template-vite/src/stores/auth-store.ts +56 -0
  151. package/templates/template-vite/src/styles/globals.css +81 -0
  152. package/templates/template-vite/src/types/index.ts +39 -0
  153. package/templates/template-vite/tsconfig.app.json +24 -0
  154. package/templates/template-vite/tsconfig.json +7 -0
  155. package/templates/template-vite/tsconfig.node.json +16 -0
  156. 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,12 @@
1
+ services:
2
+ app:
3
+ build:
4
+ context: .
5
+ dockerfile: Dockerfile
6
+ args:
7
+ - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:8000}
8
+ container_name: {{PROJECT_NAME}}
9
+ ports:
10
+ - "3000:3000"
11
+ env_file: .env
12
+ restart: unless-stopped
@@ -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,8 @@
1
+ import type { KnipConfig } from 'knip'
2
+
3
+ const config: KnipConfig = {
4
+ entry: ['src/app/**/*.{ts,tsx}', 'src/middleware.ts'],
5
+ project: ['src/**/*.{ts,tsx}'],
6
+ ignore: ['src/components/ui/**', 'src/features/*/services/gen/**'],
7
+ }
8
+ export default config
@@ -0,0 +1,14 @@
1
+ {
2
+ "auth": {
3
+ "signIn": "Sign In",
4
+ "signUp": "Sign Up",
5
+ "signOut": "Sign Out",
6
+ "email": "Email",
7
+ "password": "Password"
8
+ },
9
+ "nav": {
10
+ "dashboard": "Dashboard",
11
+ "settings": "Settings",
12
+ "profile": "Profile"
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "auth": {
3
+ "signIn": "Đăng nhập",
4
+ "signUp": "Đăng ký",
5
+ "signOut": "Đăng xuất",
6
+ "email": "Email",
7
+ "password": "Mật khẩu"
8
+ },
9
+ "nav": {
10
+ "dashboard": "Bảng điều khiển",
11
+ "settings": "Cài đặt",
12
+ "profile": "Hồ sơ"
13
+ }
14
+ }
@@ -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,7 @@
1
+ {
2
+ "dependencies": {
3
+ "echarts": "^6.0.0",
4
+ "echarts-for-react": "^3.0.2",
5
+ "recharts": "^2.15.1"
6
+ }
7
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "next-themes": "^0.4.6"
4
+ }
5
+ }
@@ -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,8 @@
1
+ {
2
+ "dependencies": {
3
+ "@dnd-kit/core": "^6.3.1",
4
+ "@dnd-kit/sortable": "^8.0.0",
5
+ "@dnd-kit/modifiers": "^7.0.0",
6
+ "@dnd-kit/utilities": "^3.2.2"
7
+ }
8
+ }
@@ -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,5 @@
1
+ {
2
+ "dependencies": {
3
+ "next-intl": "^4.3.4"
4
+ }
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "@sentry/nextjs": "^9.19.0"
4
+ }
5
+ }
@@ -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': {} } }
@@ -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,9 @@
1
+ import { SignInForm } from '@/features/auth/components/sign-in-form'
2
+
3
+ export const metadata = {
4
+ title: 'Sign In',
5
+ }
6
+
7
+ export default function SignInPage() {
8
+ return <SignInForm />
9
+ }
@@ -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,3 @@
1
+ export function GET() {
2
+ return new Response('ok', { status: 200 })
3
+ }
@@ -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,5 @@
1
+ import { redirect } from 'next/navigation'
2
+
3
+ export default function RootPage() {
4
+ redirect('/dashboard')
5
+ }
@@ -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
+ }