leedstack 3.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 (136) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +364 -0
  3. package/bin/create-stack.js +277 -0
  4. package/package.json +60 -0
  5. package/tools/templates/backend/go-echo/backend/.env.example +10 -0
  6. package/tools/templates/backend/go-echo/backend/cmd/server/main.go.ejs +57 -0
  7. package/tools/templates/backend/go-echo/backend/go.mod.ejs +10 -0
  8. package/tools/templates/backend/go-echo/backend/internal/handlers/example.go +15 -0
  9. package/tools/templates/backend/go-echo/backend/internal/handlers/health.go +13 -0
  10. package/tools/templates/backend/java-spring/backend/.env.example +10 -0
  11. package/tools/templates/backend/java-spring/backend/pom.xml.ejs +64 -0
  12. package/tools/templates/backend/java-spring/backend/src/main/java/com/app/Application.java.ejs +11 -0
  13. package/tools/templates/backend/java-spring/backend/src/main/java/com/app/config/SecurityConfig.java.ejs +64 -0
  14. package/tools/templates/backend/java-spring/backend/src/main/java/com/app/controller/ExampleController.java +19 -0
  15. package/tools/templates/backend/java-spring/backend/src/main/java/com/app/controller/HealthController.java +15 -0
  16. package/tools/templates/backend/java-spring/backend/src/main/resources/application.yml.ejs +20 -0
  17. package/tools/templates/backend/node-express/backend/.env.example +10 -0
  18. package/tools/templates/backend/node-express/backend/.eslintrc.json +21 -0
  19. package/tools/templates/backend/node-express/backend/.prettierrc +10 -0
  20. package/tools/templates/backend/node-express/backend/Dockerfile +52 -0
  21. package/tools/templates/backend/node-express/backend/README.md +68 -0
  22. package/tools/templates/backend/node-express/backend/package.json.ejs +37 -0
  23. package/tools/templates/backend/node-express/backend/src/index.ts.ejs +75 -0
  24. package/tools/templates/backend/node-express/backend/src/routes/health.ts +54 -0
  25. package/tools/templates/backend/node-express/backend/tsconfig.json +17 -0
  26. package/tools/templates/backend/python-fastapi/backend/.env.example +18 -0
  27. package/tools/templates/backend/python-fastapi/backend/README.md +73 -0
  28. package/tools/templates/backend/python-fastapi/backend/app/__init__.py +1 -0
  29. package/tools/templates/backend/python-fastapi/backend/app/main.py.ejs +68 -0
  30. package/tools/templates/backend/python-fastapi/backend/requirements.txt.ejs +22 -0
  31. package/tools/templates/base/.dockerignore +16 -0
  32. package/tools/templates/base/.env.example +31 -0
  33. package/tools/templates/base/.github/workflows/ci.yml.ejs +124 -0
  34. package/tools/templates/base/.github/workflows/deploy-separate.yml.example +144 -0
  35. package/tools/templates/base/.vscode/extensions.json +17 -0
  36. package/tools/templates/base/.vscode/settings.json +49 -0
  37. package/tools/templates/base/Makefile +98 -0
  38. package/tools/templates/base/README.md.ejs +118 -0
  39. package/tools/templates/base/docker-compose.yml.ejs +49 -0
  40. package/tools/templates/base/package.json.ejs +30 -0
  41. package/tools/templates/base/scripts/split-repos.sh +189 -0
  42. package/tools/templates/db/postgres/backend/java-spring/backend/pom.xml.ejs +81 -0
  43. package/tools/templates/db/postgres/backend/java-spring/backend/src/main/resources/application.yml.ejs +39 -0
  44. package/tools/templates/db/postgres/backend/java-spring/backend/src/main/resources/db/migration/V1__init.sql +17 -0
  45. package/tools/templates/db/postgres/backend/node-express/backend/.env.example +10 -0
  46. package/tools/templates/db/postgres/backend/node-express/backend/package.json.ejs +32 -0
  47. package/tools/templates/db/postgres/backend/node-express/backend/prisma/schema.prisma.ejs +39 -0
  48. package/tools/templates/frontend/angular/frontend/.env.example +9 -0
  49. package/tools/templates/frontend/angular/frontend/angular.json +66 -0
  50. package/tools/templates/frontend/angular/frontend/package.json.ejs +31 -0
  51. package/tools/templates/frontend/angular/frontend/src/app/app.component.ts +30 -0
  52. package/tools/templates/frontend/angular/frontend/src/app/app.routes.ts +18 -0
  53. package/tools/templates/frontend/angular/frontend/src/app/components/home.component.ts +24 -0
  54. package/tools/templates/frontend/angular/frontend/src/app/services/api.service.ts +48 -0
  55. package/tools/templates/frontend/angular/frontend/src/favicon.ico +1 -0
  56. package/tools/templates/frontend/angular/frontend/src/index.html +13 -0
  57. package/tools/templates/frontend/angular/frontend/src/main.ts +10 -0
  58. package/tools/templates/frontend/angular/frontend/src/styles.css +31 -0
  59. package/tools/templates/frontend/angular/frontend/tsconfig.app.json +9 -0
  60. package/tools/templates/frontend/angular/frontend/tsconfig.json +27 -0
  61. package/tools/templates/frontend/nextjs/frontend/.env.example +9 -0
  62. package/tools/templates/frontend/nextjs/frontend/next.config.js +37 -0
  63. package/tools/templates/frontend/nextjs/frontend/package.json.ejs +25 -0
  64. package/tools/templates/frontend/nextjs/frontend/src/app/globals.css +31 -0
  65. package/tools/templates/frontend/nextjs/frontend/src/app/layout.tsx +36 -0
  66. package/tools/templates/frontend/nextjs/frontend/src/app/page.tsx +19 -0
  67. package/tools/templates/frontend/nextjs/frontend/src/lib/api.ts +45 -0
  68. package/tools/templates/frontend/nextjs/frontend/tsconfig.json +27 -0
  69. package/tools/templates/frontend/react/frontend/.env.example +9 -0
  70. package/tools/templates/frontend/react/frontend/.eslintrc.json +32 -0
  71. package/tools/templates/frontend/react/frontend/.prettierrc +10 -0
  72. package/tools/templates/frontend/react/frontend/Dockerfile +37 -0
  73. package/tools/templates/frontend/react/frontend/README.md +54 -0
  74. package/tools/templates/frontend/react/frontend/index.html +13 -0
  75. package/tools/templates/frontend/react/frontend/nginx.conf +35 -0
  76. package/tools/templates/frontend/react/frontend/package.json.ejs +41 -0
  77. package/tools/templates/frontend/react/frontend/public/vite.svg +4 -0
  78. package/tools/templates/frontend/react/frontend/src/App.css +65 -0
  79. package/tools/templates/frontend/react/frontend/src/App.jsx +41 -0
  80. package/tools/templates/frontend/react/frontend/src/assets/react.svg +7 -0
  81. package/tools/templates/frontend/react/frontend/src/components/ErrorBoundary.jsx +62 -0
  82. package/tools/templates/frontend/react/frontend/src/components/Home.jsx +58 -0
  83. package/tools/templates/frontend/react/frontend/src/components/__tests__/Home.test.jsx +74 -0
  84. package/tools/templates/frontend/react/frontend/src/index.css +31 -0
  85. package/tools/templates/frontend/react/frontend/src/lib/api.js +42 -0
  86. package/tools/templates/frontend/react/frontend/src/lib/env.js +58 -0
  87. package/tools/templates/frontend/react/frontend/src/main.jsx +16 -0
  88. package/tools/templates/frontend/react/frontend/src/setupTests.js +8 -0
  89. package/tools/templates/frontend/react/frontend/vite.config.js +30 -0
  90. package/tools/templates/frontend/react/frontend/vitest.config.js +20 -0
  91. package/tools/templates/frontend/svelte/frontend/.env.example +9 -0
  92. package/tools/templates/frontend/svelte/frontend/package.json.ejs +21 -0
  93. package/tools/templates/frontend/svelte/frontend/src/app.html +12 -0
  94. package/tools/templates/frontend/svelte/frontend/src/lib/api.ts +45 -0
  95. package/tools/templates/frontend/svelte/frontend/src/routes/+layout.svelte +56 -0
  96. package/tools/templates/frontend/svelte/frontend/src/routes/+page.svelte +20 -0
  97. package/tools/templates/frontend/svelte/frontend/static/favicon.png +1 -0
  98. package/tools/templates/frontend/svelte/frontend/svelte.config.js +10 -0
  99. package/tools/templates/frontend/svelte/frontend/vite.config.js +9 -0
  100. package/tools/templates/frontend/vue/frontend/.env.example +9 -0
  101. package/tools/templates/frontend/vue/frontend/index.html +13 -0
  102. package/tools/templates/frontend/vue/frontend/package.json.ejs +20 -0
  103. package/tools/templates/frontend/vue/frontend/src/App.vue +60 -0
  104. package/tools/templates/frontend/vue/frontend/src/lib/api.js +42 -0
  105. package/tools/templates/frontend/vue/frontend/src/main.js +33 -0
  106. package/tools/templates/frontend/vue/frontend/src/views/ApiTest.vue +39 -0
  107. package/tools/templates/frontend/vue/frontend/src/views/Home.vue +30 -0
  108. package/tools/templates/frontend/vue/frontend/vite.config.js +9 -0
  109. package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/controller/AdminController.java +41 -0
  110. package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/entity/User.java +55 -0
  111. package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/repository/UserRepository.java +8 -0
  112. package/tools/templates/modules/admin/frontend/svelte/frontend/src/routes/dashboard/+page.svelte +93 -0
  113. package/tools/templates/modules/auth/backend/node-express/backend/src/middleware/auth.ts +42 -0
  114. package/tools/templates/modules/auth/frontend/svelte/frontend/src/hooks.client.ts +3 -0
  115. package/tools/templates/modules/auth/frontend/svelte/frontend/src/lib/auth.ts +104 -0
  116. package/tools/templates/modules/auth/frontend/svelte/frontend/src/routes/callback/+page.svelte +18 -0
  117. package/tools/templates/modules/auth/frontend/svelte/frontend/src/routes/login/+page.svelte +12 -0
  118. package/tools/templates/modules/chatbot/backend/node-express/backend/src/index.ts.ejs +69 -0
  119. package/tools/templates/modules/chatbot/backend/node-express/backend/src/routes/chatbot.ts.ejs +37 -0
  120. package/tools/templates/modules/chatbot/backend/node-express/backend/src/services/chatbotService.ts +124 -0
  121. package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/main.py.ejs +69 -0
  122. package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/routes/chatbot.py +38 -0
  123. package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/services/chatbot_service.py +123 -0
  124. package/tools/templates/modules/chatbot/backend/python-fastapi/backend/requirements.txt +1 -0
  125. package/tools/templates/modules/chatbot/frontend/react/frontend/src/App.jsx.ejs +74 -0
  126. package/tools/templates/modules/chatbot/frontend/react/frontend/src/components/Chatbot.css +198 -0
  127. package/tools/templates/modules/chatbot/frontend/react/frontend/src/components/Chatbot.jsx +113 -0
  128. package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/controller/ContactController.java +29 -0
  129. package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/entity/ContactMessage.java +66 -0
  130. package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/repository/ContactMessageRepository.java +8 -0
  131. package/tools/templates/modules/contact/backend/java-spring/backend/src/main/resources/db/migration/V2__contact.sql +7 -0
  132. package/tools/templates/modules/contact/frontend/svelte/frontend/src/routes/contact/+page.svelte +80 -0
  133. package/tools/templates/modules/payments/backend/java-spring/backend/src/main/java/com/app/controller/PaymentController.java +69 -0
  134. package/tools/templates/modules/payments/backend/node-express/backend/src/routes/payments.ts +30 -0
  135. package/tools/templates/modules/payments/backend/node-express/backend/src/routes/webhook.ts +36 -0
  136. package/tools/templates/modules/payments/frontend/svelte/frontend/src/lib/payments.ts +28 -0
@@ -0,0 +1,37 @@
1
+ # Multi-stage build for production
2
+
3
+ # Stage 1: Build
4
+ FROM node:20-alpine AS builder
5
+
6
+ WORKDIR /app
7
+
8
+ # Copy package files
9
+ COPY package*.json ./
10
+
11
+ # Install dependencies
12
+ RUN npm ci --only=production=false
13
+
14
+ # Copy source code
15
+ COPY . .
16
+
17
+ # Build the application
18
+ RUN npm run build
19
+
20
+ # Stage 2: Production
21
+ FROM nginx:alpine
22
+
23
+ # Copy built assets from builder stage
24
+ COPY --from=builder /app/dist /usr/share/nginx/html
25
+
26
+ # Copy nginx configuration
27
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
28
+
29
+ # Expose port 80
30
+ EXPOSE 80
31
+
32
+ # Health check
33
+ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
34
+ CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
35
+
36
+ # Start nginx
37
+ CMD ["nginx", "-g", "daemon off;"]
@@ -0,0 +1,54 @@
1
+ # <%= AppName %> - Frontend
2
+
3
+ React frontend built with Vite.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Install dependencies
9
+ npm install
10
+
11
+ # Set up environment
12
+ cp .env.example .env
13
+
14
+ # Start development server
15
+ npm run dev
16
+ ```
17
+
18
+ Visit http://localhost:5173
19
+
20
+ ## Available Scripts
21
+
22
+ - `npm run dev` - Start development server
23
+ - `npm run build` - Build for production
24
+ - `npm run preview` - Preview production build
25
+
26
+ ## Environment Variables
27
+
28
+ See `.env.example` for required variables:
29
+
30
+ - `VITE_API_BASE` - Backend API URL (default: http://localhost:8080)
31
+ <% if (modules.auth) { -%>
32
+ - `VITE_AUTH0_DOMAIN` - Auth0 domain
33
+ - `VITE_AUTH0_CLIENT_ID` - Auth0 client ID
34
+ - `VITE_AUTH0_AUDIENCE` - Auth0 API audience
35
+ <% } -%>
36
+
37
+ ## API Usage
38
+
39
+ Use the `apiFetch` helper to make API calls:
40
+
41
+ ```javascript
42
+ import { apiFetch } from './lib/api';
43
+
44
+ const data = await apiFetch('/api/example');
45
+ ```
46
+
47
+ ## Tech Stack
48
+
49
+ - **React 19** - UI library
50
+ - **Vite** - Build tool and dev server
51
+ - **React Router** - Client-side routing
52
+ <% if (modules.auth) { -%>
53
+ - **Auth0 SPA JS** - Authentication
54
+ <% } -%>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title><%= AppName %></title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,35 @@
1
+ server {
2
+ listen 80;
3
+ server_name localhost;
4
+ root /usr/share/nginx/html;
5
+ index index.html;
6
+
7
+ # Gzip compression
8
+ gzip on;
9
+ gzip_vary on;
10
+ gzip_min_length 1024;
11
+ gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json;
12
+
13
+ # Security headers
14
+ add_header X-Frame-Options "SAMEORIGIN" always;
15
+ add_header X-Content-Type-Options "nosniff" always;
16
+ add_header X-XSS-Protection "1; mode=block" always;
17
+
18
+ # Cache static assets
19
+ location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|svg|woff|woff2|ttf|eot)$ {
20
+ expires 1y;
21
+ add_header Cache-Control "public, immutable";
22
+ }
23
+
24
+ # SPA routing - send all requests to index.html
25
+ location / {
26
+ try_files $uri $uri/ /index.html;
27
+ }
28
+
29
+ # Health check endpoint
30
+ location /health {
31
+ access_log off;
32
+ return 200 "healthy\n";
33
+ add_header Content-Type text/plain;
34
+ }
35
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "<%= appSlug %>-frontend",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "test": "vitest",
11
+ "test:ui": "vitest --ui",
12
+ "test:coverage": "vitest --coverage",
13
+ "lint": "eslint . --ext .js,.jsx --report-unused-disable-directives --max-warnings 0",
14
+ "lint:fix": "eslint . --ext .js,.jsx --fix",
15
+ "format": "prettier --write \"src/**/*.{js,jsx,json,css,md}\"",
16
+ "format:check": "prettier --check \"src/**/*.{js,jsx,json,css,md}\""
17
+ },
18
+ "dependencies": {
19
+ "react": "^19.0.0",
20
+ "react-dom": "^19.0.0",
21
+ "react-router-dom": "^7.1.0",
22
+ "@auth0/auth0-spa-js": "^2.1.3"
23
+ },
24
+ "devDependencies": {
25
+ "@types/react": "^19.0.0",
26
+ "@types/react-dom": "^19.0.0",
27
+ "@vitejs/plugin-react": "^4.3.4",
28
+ "vite": "^6.0.0",
29
+ "vitest": "^1.2.0",
30
+ "@vitest/ui": "^1.2.0",
31
+ "@testing-library/react": "^16.0.0",
32
+ "@testing-library/jest-dom": "^6.2.0",
33
+ "@testing-library/user-event": "^14.5.2",
34
+ "jsdom": "^23.2.0",
35
+ "@vitest/coverage-v8": "^1.2.0",
36
+ "eslint": "^8.57.0",
37
+ "eslint-plugin-react": "^7.33.2",
38
+ "eslint-plugin-react-hooks": "^4.6.0",
39
+ "prettier": "^3.2.4"
40
+ }
41
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" fill="none">
2
+ <rect width="64" height="64" rx="8" fill="#646CFF"/>
3
+ <path d="M32 12L44 52H20L32 12Z" fill="#FFFFFF"/>
4
+ </svg>
@@ -0,0 +1,65 @@
1
+ :root {
2
+ font-family: "Segoe UI", Arial, sans-serif;
3
+ color: #0f172a;
4
+ background: #f8fafc;
5
+ }
6
+
7
+ body {
8
+ margin: 0;
9
+ min-height: 100vh;
10
+ }
11
+
12
+ #root {
13
+ max-width: 960px;
14
+ margin: 0 auto;
15
+ padding: 48px 24px;
16
+ }
17
+
18
+ .app {
19
+ display: grid;
20
+ gap: 16px;
21
+ }
22
+
23
+ .header {
24
+ display: flex;
25
+ align-items: center;
26
+ gap: 12px;
27
+ }
28
+
29
+ .header img {
30
+ width: 40px;
31
+ height: 40px;
32
+ }
33
+
34
+ .card {
35
+ padding: 16px;
36
+ border-radius: 12px;
37
+ background: #ffffff;
38
+ border: 1px solid #e2e8f0;
39
+ box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
40
+ }
41
+
42
+ .api-status {
43
+ margin-top: 24px;
44
+ padding: 16px;
45
+ border-radius: 12px;
46
+ background: #ffffff;
47
+ border: 1px solid #e2e8f0;
48
+ }
49
+
50
+ .logo {
51
+ height: 48px;
52
+ width: 48px;
53
+ padding: 6px;
54
+ border-radius: 12px;
55
+ background: #ffffff;
56
+ box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
57
+ }
58
+
59
+ .logo.react {
60
+ box-shadow: 0 0 0 2px rgba(97, 218, 251, 0.25);
61
+ }
62
+
63
+ .read-the-docs {
64
+ color: #64748b;
65
+ }
@@ -0,0 +1,41 @@
1
+ import { Routes, Route, Link } from 'react-router-dom';
2
+ import Home from './components/Home';
3
+ <% if (modules.contact) { -%>
4
+ import Contact from './components/Contact';
5
+ <% } -%>
6
+ <% if (modules.admin) { -%>
7
+ import Dashboard from './components/Dashboard';
8
+ <% } -%>
9
+
10
+ function App() {
11
+ return (
12
+ <div className="app">
13
+ <nav>
14
+ <Link to="/">Home</Link>
15
+ <% if (modules.contact) { -%>
16
+ <Link to="/contact">Contact</Link>
17
+ <% } -%>
18
+ <% if (modules.auth) { -%>
19
+ <Link to="/login">Login</Link>
20
+ <% } -%>
21
+ <% if (modules.admin) { -%>
22
+ <Link to="/dashboard">Dashboard</Link>
23
+ <% } -%>
24
+ </nav>
25
+
26
+ <main>
27
+ <Routes>
28
+ <Route path="/" element={<Home />} />
29
+ <% if (modules.contact) { -%>
30
+ <Route path="/contact" element={<Contact />} />
31
+ <% } -%>
32
+ <% if (modules.admin) { -%>
33
+ <Route path="/dashboard" element={<Dashboard />} />
34
+ <% } -%>
35
+ </Routes>
36
+ </main>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ export default App;
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" fill="none">
2
+ <rect width="64" height="64" rx="8" fill="#20232A"/>
3
+ <circle cx="32" cy="32" r="6" fill="#61DAFB"/>
4
+ <ellipse cx="32" cy="32" rx="20" ry="8" stroke="#61DAFB" stroke-width="3"/>
5
+ <ellipse cx="32" cy="32" rx="20" ry="8" stroke="#61DAFB" stroke-width="3" transform="rotate(60 32 32)"/>
6
+ <ellipse cx="32" cy="32" rx="20" ry="8" stroke="#61DAFB" stroke-width="3" transform="rotate(120 32 32)"/>
7
+ </svg>
@@ -0,0 +1,62 @@
1
+ import { Component } from 'react';
2
+
3
+ class ErrorBoundary extends Component {
4
+ constructor(props) {
5
+ super(props);
6
+ this.state = { hasError: false, error: null, errorInfo: null };
7
+ }
8
+
9
+ static getDerivedStateFromError(error) {
10
+ return { hasError: true };
11
+ }
12
+
13
+ componentDidCatch(error, errorInfo) {
14
+ console.error('Error caught by boundary:', error, errorInfo);
15
+ this.setState({
16
+ error,
17
+ errorInfo,
18
+ });
19
+
20
+ // You can also log to an error reporting service here
21
+ // logErrorToService(error, errorInfo);
22
+ }
23
+
24
+ render() {
25
+ if (this.state.hasError) {
26
+ return (
27
+ <div style={{ padding: '2rem', textAlign: 'center' }}>
28
+ <h1>Something went wrong</h1>
29
+ <p>We're sorry for the inconvenience. Please try refreshing the page.</p>
30
+ {process.env.NODE_ENV === 'development' && (
31
+ <details style={{ marginTop: '2rem', textAlign: 'left' }}>
32
+ <summary>Error details (development only)</summary>
33
+ <pre style={{ background: '#f5f5f5', padding: '1rem', overflow: 'auto' }}>
34
+ {this.state.error && this.state.error.toString()}
35
+ {'\n'}
36
+ {this.state.errorInfo && this.state.errorInfo.componentStack}
37
+ </pre>
38
+ </details>
39
+ )}
40
+ <button
41
+ onClick={() => window.location.reload()}
42
+ style={{
43
+ marginTop: '1rem',
44
+ padding: '0.5rem 1rem',
45
+ background: '#007bff',
46
+ color: 'white',
47
+ border: 'none',
48
+ borderRadius: '4px',
49
+ cursor: 'pointer',
50
+ }}
51
+ >
52
+ Refresh Page
53
+ </button>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ return this.props.children;
59
+ }
60
+ }
61
+
62
+ export default ErrorBoundary;
@@ -0,0 +1,58 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { apiFetch } from '../lib/api';
3
+
4
+ function Home() {
5
+ const [apiMessage, setApiMessage] = useState('');
6
+ const [loading, setLoading] = useState(true);
7
+ const [error, setError] = useState('');
8
+
9
+ useEffect(() => {
10
+ let isMounted = true;
11
+
12
+ // Test API connection
13
+ apiFetch('/api/example')
14
+ .then(data => {
15
+ if (isMounted) {
16
+ setApiMessage(data.message);
17
+ setLoading(false);
18
+ }
19
+ })
20
+ .catch(err => {
21
+ if (isMounted) {
22
+ setError(err.message);
23
+ setLoading(false);
24
+ }
25
+ });
26
+
27
+ return () => {
28
+ isMounted = false;
29
+ };
30
+ }, []);
31
+
32
+ return (
33
+ <div>
34
+ <h1>Welcome to <%= AppName %></h1>
35
+ <p>This is a full-stack application built with:</p>
36
+ <ul>
37
+ <li>Frontend: <%= frontend %></li>
38
+ <li>Backend: <%= backend %></li>
39
+ <li>Database: <%= db %></li>
40
+ <% if (modules.auth) { -%>
41
+ <li>Auth: <%= auth %></li>
42
+ <% } -%>
43
+ <% if (modules.payments) { -%>
44
+ <li>Payments: <%= payments %></li>
45
+ <% } -%>
46
+ </ul>
47
+
48
+ <div style={{ marginTop: '2rem', padding: '1rem', background: '#f5f5f5', borderRadius: '8px' }}>
49
+ <h3>API Connection Test</h3>
50
+ {loading && <p>Testing backend connection...</p>}
51
+ {error && <p style={{ color: 'red' }}>❌ Error: {error}</p>}
52
+ {apiMessage && <p style={{ color: 'green' }}>✅ {apiMessage}</p>}
53
+ </div>
54
+ </div>
55
+ );
56
+ }
57
+
58
+ export default Home;
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import Home from '../Home';
4
+ import * as api from '../../lib/api';
5
+
6
+ // Mock the API module
7
+ vi.mock('../../lib/api');
8
+
9
+ describe('Home Component', () => {
10
+ beforeEach(() => {
11
+ vi.clearAllMocks();
12
+ });
13
+
14
+ it('renders welcome message', () => {
15
+ vi.mocked(api.apiFetch).mockRejectedValue(new Error('API not available'));
16
+
17
+ render(<Home />);
18
+
19
+ expect(screen.getByText(/Welcome to/i)).toBeInTheDocument();
20
+ });
21
+
22
+ it('displays loading state initially', () => {
23
+ vi.mocked(api.apiFetch).mockImplementation(() => new Promise(() => {}));
24
+
25
+ render(<Home />);
26
+
27
+ expect(screen.getByText(/Testing backend connection/i)).toBeInTheDocument();
28
+ });
29
+
30
+ it('displays API message on successful connection', async () => {
31
+ vi.mocked(api.apiFetch).mockResolvedValue({
32
+ message: 'Hello from backend!',
33
+ });
34
+
35
+ render(<Home />);
36
+
37
+ await waitFor(() => {
38
+ expect(screen.getByText(/Hello from backend!/i)).toBeInTheDocument();
39
+ });
40
+ });
41
+
42
+ it('displays error message on failed connection', async () => {
43
+ vi.mocked(api.apiFetch).mockRejectedValue(new Error('Connection failed'));
44
+
45
+ render(<Home />);
46
+
47
+ await waitFor(() => {
48
+ expect(screen.getByText(/Error: Connection failed/i)).toBeInTheDocument();
49
+ });
50
+ });
51
+
52
+ it('cleans up on unmount', async () => {
53
+ let resolvePromise;
54
+ const promise = new Promise(resolve => {
55
+ resolvePromise = resolve;
56
+ });
57
+
58
+ vi.mocked(api.apiFetch).mockReturnValue(promise);
59
+
60
+ const { unmount } = render(<Home />);
61
+
62
+ // Unmount before promise resolves
63
+ unmount();
64
+
65
+ // Resolve promise after unmount
66
+ resolvePromise({ message: 'Should not update' });
67
+
68
+ // Wait a bit
69
+ await new Promise(resolve => setTimeout(resolve, 50));
70
+
71
+ // If cleanup works, no state update errors should occur
72
+ expect(true).toBe(true);
73
+ });
74
+ });
@@ -0,0 +1,31 @@
1
+ body {
2
+ margin: 0;
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
4
+ }
5
+
6
+ .app {
7
+ display: flex;
8
+ flex-direction: column;
9
+ min-height: 100vh;
10
+ }
11
+
12
+ nav {
13
+ display: flex;
14
+ gap: 1rem;
15
+ padding: 1rem;
16
+ background: #f0f0f0;
17
+ }
18
+
19
+ nav a {
20
+ text-decoration: none;
21
+ color: #333;
22
+ }
23
+
24
+ nav a:hover {
25
+ text-decoration: underline;
26
+ }
27
+
28
+ main {
29
+ flex: 1;
30
+ padding: 2rem;
31
+ }
@@ -0,0 +1,42 @@
1
+ // API base URL from environment
2
+ const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8080';
3
+
4
+ /**
5
+ * Fetch wrapper that automatically adds API base URL
6
+ * @param {string} endpoint - API endpoint (e.g., '/api/example')
7
+ * @param {RequestInit} options - Fetch options
8
+ */
9
+ export async function apiFetch(endpoint, options = {}) {
10
+ const url = `${API_BASE}${endpoint}`;
11
+
12
+ const config = {
13
+ ...options,
14
+ headers: {
15
+ 'Content-Type': 'application/json',
16
+ ...options.headers,
17
+ },
18
+ };
19
+
20
+ const response = await fetch(url, config);
21
+
22
+ if (!response.ok) {
23
+ throw new Error(`API Error: ${response.statusText}`);
24
+ }
25
+
26
+ return response.json();
27
+ }
28
+
29
+ /**
30
+ * Example usage in components:
31
+ *
32
+ * import { apiFetch } from './lib/api';
33
+ *
34
+ * // GET request
35
+ * const data = await apiFetch('/api/example');
36
+ *
37
+ * // POST request
38
+ * const result = await apiFetch('/api/contact', {
39
+ * method: 'POST',
40
+ * body: JSON.stringify({ name: 'John', email: 'john@example.com' })
41
+ * });
42
+ */
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Environment variable validation
3
+ * Ensures required environment variables are set before app starts
4
+ */
5
+
6
+ const requiredEnvVars = [
7
+ 'VITE_API_BASE',
8
+ ];
9
+
10
+ const optionalEnvVars = {
11
+ 'VITE_AUTH0_DOMAIN': 'Auth0 domain (required if using auth)',
12
+ 'VITE_AUTH0_CLIENT_ID': 'Auth0 client ID (required if using auth)',
13
+ 'VITE_STRIPE_PUBLIC_KEY': 'Stripe public key (required if using payments)',
14
+ };
15
+
16
+ export function validateEnv() {
17
+ const missing = [];
18
+ const warnings = [];
19
+
20
+ // Check required variables
21
+ for (const envVar of requiredEnvVars) {
22
+ if (!import.meta.env[envVar]) {
23
+ missing.push(envVar);
24
+ }
25
+ }
26
+
27
+ // Check optional variables (warn if missing)
28
+ for (const [envVar, description] of Object.entries(optionalEnvVars)) {
29
+ if (!import.meta.env[envVar]) {
30
+ warnings.push(`${envVar}: ${description}`);
31
+ }
32
+ }
33
+
34
+ if (missing.length > 0) {
35
+ console.error('❌ Missing required environment variables:');
36
+ missing.forEach(v => console.error(` - ${v}`));
37
+ console.error('\nCreate a .env file based on .env.example');
38
+ throw new Error('Missing required environment variables');
39
+ }
40
+
41
+ if (warnings.length > 0 && import.meta.env.DEV) {
42
+ console.warn('⚠️ Optional environment variables not set:');
43
+ warnings.forEach(w => console.warn(` - ${w}`));
44
+ }
45
+
46
+ if (import.meta.env.DEV) {
47
+ console.log('✅ Environment variables validated');
48
+ }
49
+ }
50
+
51
+ export const env = {
52
+ apiBase: import.meta.env.VITE_API_BASE,
53
+ auth0Domain: import.meta.env.VITE_AUTH0_DOMAIN,
54
+ auth0ClientId: import.meta.env.VITE_AUTH0_CLIENT_ID,
55
+ stripePublicKey: import.meta.env.VITE_STRIPE_PUBLIC_KEY,
56
+ isDev: import.meta.env.DEV,
57
+ isProd: import.meta.env.PROD,
58
+ };
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import { BrowserRouter } from 'react-router-dom';
4
+ import App from './App';
5
+ import ErrorBoundary from './components/ErrorBoundary';
6
+ import './index.css';
7
+
8
+ ReactDOM.createRoot(document.getElementById('root')).render(
9
+ <React.StrictMode>
10
+ <ErrorBoundary>
11
+ <BrowserRouter>
12
+ <App />
13
+ </BrowserRouter>
14
+ </ErrorBoundary>
15
+ </React.StrictMode>
16
+ );
@@ -0,0 +1,8 @@
1
+ import { expect, afterEach } from 'vitest';
2
+ import { cleanup } from '@testing-library/react';
3
+ import '@testing-library/jest-dom/vitest';
4
+
5
+ // Cleanup after each test
6
+ afterEach(() => {
7
+ cleanup();
8
+ });