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.
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/bin/create-stack.js +277 -0
- package/package.json +60 -0
- package/tools/templates/backend/go-echo/backend/.env.example +10 -0
- package/tools/templates/backend/go-echo/backend/cmd/server/main.go.ejs +57 -0
- package/tools/templates/backend/go-echo/backend/go.mod.ejs +10 -0
- package/tools/templates/backend/go-echo/backend/internal/handlers/example.go +15 -0
- package/tools/templates/backend/go-echo/backend/internal/handlers/health.go +13 -0
- package/tools/templates/backend/java-spring/backend/.env.example +10 -0
- package/tools/templates/backend/java-spring/backend/pom.xml.ejs +64 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/Application.java.ejs +11 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/config/SecurityConfig.java.ejs +64 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/controller/ExampleController.java +19 -0
- package/tools/templates/backend/java-spring/backend/src/main/java/com/app/controller/HealthController.java +15 -0
- package/tools/templates/backend/java-spring/backend/src/main/resources/application.yml.ejs +20 -0
- package/tools/templates/backend/node-express/backend/.env.example +10 -0
- package/tools/templates/backend/node-express/backend/.eslintrc.json +21 -0
- package/tools/templates/backend/node-express/backend/.prettierrc +10 -0
- package/tools/templates/backend/node-express/backend/Dockerfile +52 -0
- package/tools/templates/backend/node-express/backend/README.md +68 -0
- package/tools/templates/backend/node-express/backend/package.json.ejs +37 -0
- package/tools/templates/backend/node-express/backend/src/index.ts.ejs +75 -0
- package/tools/templates/backend/node-express/backend/src/routes/health.ts +54 -0
- package/tools/templates/backend/node-express/backend/tsconfig.json +17 -0
- package/tools/templates/backend/python-fastapi/backend/.env.example +18 -0
- package/tools/templates/backend/python-fastapi/backend/README.md +73 -0
- package/tools/templates/backend/python-fastapi/backend/app/__init__.py +1 -0
- package/tools/templates/backend/python-fastapi/backend/app/main.py.ejs +68 -0
- package/tools/templates/backend/python-fastapi/backend/requirements.txt.ejs +22 -0
- package/tools/templates/base/.dockerignore +16 -0
- package/tools/templates/base/.env.example +31 -0
- package/tools/templates/base/.github/workflows/ci.yml.ejs +124 -0
- package/tools/templates/base/.github/workflows/deploy-separate.yml.example +144 -0
- package/tools/templates/base/.vscode/extensions.json +17 -0
- package/tools/templates/base/.vscode/settings.json +49 -0
- package/tools/templates/base/Makefile +98 -0
- package/tools/templates/base/README.md.ejs +118 -0
- package/tools/templates/base/docker-compose.yml.ejs +49 -0
- package/tools/templates/base/package.json.ejs +30 -0
- package/tools/templates/base/scripts/split-repos.sh +189 -0
- package/tools/templates/db/postgres/backend/java-spring/backend/pom.xml.ejs +81 -0
- package/tools/templates/db/postgres/backend/java-spring/backend/src/main/resources/application.yml.ejs +39 -0
- package/tools/templates/db/postgres/backend/java-spring/backend/src/main/resources/db/migration/V1__init.sql +17 -0
- package/tools/templates/db/postgres/backend/node-express/backend/.env.example +10 -0
- package/tools/templates/db/postgres/backend/node-express/backend/package.json.ejs +32 -0
- package/tools/templates/db/postgres/backend/node-express/backend/prisma/schema.prisma.ejs +39 -0
- package/tools/templates/frontend/angular/frontend/.env.example +9 -0
- package/tools/templates/frontend/angular/frontend/angular.json +66 -0
- package/tools/templates/frontend/angular/frontend/package.json.ejs +31 -0
- package/tools/templates/frontend/angular/frontend/src/app/app.component.ts +30 -0
- package/tools/templates/frontend/angular/frontend/src/app/app.routes.ts +18 -0
- package/tools/templates/frontend/angular/frontend/src/app/components/home.component.ts +24 -0
- package/tools/templates/frontend/angular/frontend/src/app/services/api.service.ts +48 -0
- package/tools/templates/frontend/angular/frontend/src/favicon.ico +1 -0
- package/tools/templates/frontend/angular/frontend/src/index.html +13 -0
- package/tools/templates/frontend/angular/frontend/src/main.ts +10 -0
- package/tools/templates/frontend/angular/frontend/src/styles.css +31 -0
- package/tools/templates/frontend/angular/frontend/tsconfig.app.json +9 -0
- package/tools/templates/frontend/angular/frontend/tsconfig.json +27 -0
- package/tools/templates/frontend/nextjs/frontend/.env.example +9 -0
- package/tools/templates/frontend/nextjs/frontend/next.config.js +37 -0
- package/tools/templates/frontend/nextjs/frontend/package.json.ejs +25 -0
- package/tools/templates/frontend/nextjs/frontend/src/app/globals.css +31 -0
- package/tools/templates/frontend/nextjs/frontend/src/app/layout.tsx +36 -0
- package/tools/templates/frontend/nextjs/frontend/src/app/page.tsx +19 -0
- package/tools/templates/frontend/nextjs/frontend/src/lib/api.ts +45 -0
- package/tools/templates/frontend/nextjs/frontend/tsconfig.json +27 -0
- package/tools/templates/frontend/react/frontend/.env.example +9 -0
- package/tools/templates/frontend/react/frontend/.eslintrc.json +32 -0
- package/tools/templates/frontend/react/frontend/.prettierrc +10 -0
- package/tools/templates/frontend/react/frontend/Dockerfile +37 -0
- package/tools/templates/frontend/react/frontend/README.md +54 -0
- package/tools/templates/frontend/react/frontend/index.html +13 -0
- package/tools/templates/frontend/react/frontend/nginx.conf +35 -0
- package/tools/templates/frontend/react/frontend/package.json.ejs +41 -0
- package/tools/templates/frontend/react/frontend/public/vite.svg +4 -0
- package/tools/templates/frontend/react/frontend/src/App.css +65 -0
- package/tools/templates/frontend/react/frontend/src/App.jsx +41 -0
- package/tools/templates/frontend/react/frontend/src/assets/react.svg +7 -0
- package/tools/templates/frontend/react/frontend/src/components/ErrorBoundary.jsx +62 -0
- package/tools/templates/frontend/react/frontend/src/components/Home.jsx +58 -0
- package/tools/templates/frontend/react/frontend/src/components/__tests__/Home.test.jsx +74 -0
- package/tools/templates/frontend/react/frontend/src/index.css +31 -0
- package/tools/templates/frontend/react/frontend/src/lib/api.js +42 -0
- package/tools/templates/frontend/react/frontend/src/lib/env.js +58 -0
- package/tools/templates/frontend/react/frontend/src/main.jsx +16 -0
- package/tools/templates/frontend/react/frontend/src/setupTests.js +8 -0
- package/tools/templates/frontend/react/frontend/vite.config.js +30 -0
- package/tools/templates/frontend/react/frontend/vitest.config.js +20 -0
- package/tools/templates/frontend/svelte/frontend/.env.example +9 -0
- package/tools/templates/frontend/svelte/frontend/package.json.ejs +21 -0
- package/tools/templates/frontend/svelte/frontend/src/app.html +12 -0
- package/tools/templates/frontend/svelte/frontend/src/lib/api.ts +45 -0
- package/tools/templates/frontend/svelte/frontend/src/routes/+layout.svelte +56 -0
- package/tools/templates/frontend/svelte/frontend/src/routes/+page.svelte +20 -0
- package/tools/templates/frontend/svelte/frontend/static/favicon.png +1 -0
- package/tools/templates/frontend/svelte/frontend/svelte.config.js +10 -0
- package/tools/templates/frontend/svelte/frontend/vite.config.js +9 -0
- package/tools/templates/frontend/vue/frontend/.env.example +9 -0
- package/tools/templates/frontend/vue/frontend/index.html +13 -0
- package/tools/templates/frontend/vue/frontend/package.json.ejs +20 -0
- package/tools/templates/frontend/vue/frontend/src/App.vue +60 -0
- package/tools/templates/frontend/vue/frontend/src/lib/api.js +42 -0
- package/tools/templates/frontend/vue/frontend/src/main.js +33 -0
- package/tools/templates/frontend/vue/frontend/src/views/ApiTest.vue +39 -0
- package/tools/templates/frontend/vue/frontend/src/views/Home.vue +30 -0
- package/tools/templates/frontend/vue/frontend/vite.config.js +9 -0
- package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/controller/AdminController.java +41 -0
- package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/entity/User.java +55 -0
- package/tools/templates/modules/admin/backend/java-spring/backend/src/main/java/com/app/repository/UserRepository.java +8 -0
- package/tools/templates/modules/admin/frontend/svelte/frontend/src/routes/dashboard/+page.svelte +93 -0
- package/tools/templates/modules/auth/backend/node-express/backend/src/middleware/auth.ts +42 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/hooks.client.ts +3 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/lib/auth.ts +104 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/routes/callback/+page.svelte +18 -0
- package/tools/templates/modules/auth/frontend/svelte/frontend/src/routes/login/+page.svelte +12 -0
- package/tools/templates/modules/chatbot/backend/node-express/backend/src/index.ts.ejs +69 -0
- package/tools/templates/modules/chatbot/backend/node-express/backend/src/routes/chatbot.ts.ejs +37 -0
- package/tools/templates/modules/chatbot/backend/node-express/backend/src/services/chatbotService.ts +124 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/main.py.ejs +69 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/routes/chatbot.py +38 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/app/services/chatbot_service.py +123 -0
- package/tools/templates/modules/chatbot/backend/python-fastapi/backend/requirements.txt +1 -0
- package/tools/templates/modules/chatbot/frontend/react/frontend/src/App.jsx.ejs +74 -0
- package/tools/templates/modules/chatbot/frontend/react/frontend/src/components/Chatbot.css +198 -0
- package/tools/templates/modules/chatbot/frontend/react/frontend/src/components/Chatbot.jsx +113 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/controller/ContactController.java +29 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/entity/ContactMessage.java +66 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/java/com/app/repository/ContactMessageRepository.java +8 -0
- package/tools/templates/modules/contact/backend/java-spring/backend/src/main/resources/db/migration/V2__contact.sql +7 -0
- package/tools/templates/modules/contact/frontend/svelte/frontend/src/routes/contact/+page.svelte +80 -0
- package/tools/templates/modules/payments/backend/java-spring/backend/src/main/java/com/app/controller/PaymentController.java +69 -0
- package/tools/templates/modules/payments/backend/node-express/backend/src/routes/payments.ts +30 -0
- package/tools/templates/modules/payments/backend/node-express/backend/src/routes/webhook.ts +36 -0
- 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,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
|
+
);
|