create-featurebased-architecture-backend 1.0.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 (77) hide show
  1. package/README.md +89 -0
  2. package/dist/index.js +828 -0
  3. package/package.json +42 -0
  4. package/templates/blank-hono/.env.example +4 -0
  5. package/templates/blank-hono/README.md +53 -0
  6. package/templates/blank-hono/package.json +18 -0
  7. package/templates/blank-hono/src/config/database.ts +4 -0
  8. package/templates/blank-hono/src/config/env.ts +5 -0
  9. package/templates/blank-hono/src/config/index.ts +2 -0
  10. package/templates/blank-hono/src/features/.gitkeep.ts +21 -0
  11. package/templates/blank-hono/src/index.ts +22 -0
  12. package/templates/blank-hono/src/routes/index.ts +8 -0
  13. package/templates/blank-hono/src/shared/index.ts +2 -0
  14. package/templates/blank-hono/src/shared/types/index.ts +16 -0
  15. package/templates/blank-hono/src/shared/utils/index.ts +1 -0
  16. package/templates/blank-hono/src/shared/utils/response.ts +10 -0
  17. package/templates/blank-hono/tsconfig.json +22 -0
  18. package/templates/blank-ts/README.md +30 -0
  19. package/templates/blank-ts/package.json +15 -0
  20. package/templates/blank-ts/src/features/.gitkeep.ts +16 -0
  21. package/templates/blank-ts/src/index.ts +7 -0
  22. package/templates/blank-ts/src/shared/utils/index.ts +3 -0
  23. package/templates/blank-ts/tsconfig.json +21 -0
  24. package/templates/react/README.md +34 -0
  25. package/templates/react/index.html +12 -0
  26. package/templates/react/package.json +22 -0
  27. package/templates/react/src/App.tsx +10 -0
  28. package/templates/react/src/features/home/components/HomePage.tsx +8 -0
  29. package/templates/react/src/features/home/index.ts +1 -0
  30. package/templates/react/src/index.css +10 -0
  31. package/templates/react/src/main.tsx +13 -0
  32. package/templates/react/src/shared/components/Button.tsx +29 -0
  33. package/templates/react/src/shared/components/index.ts +1 -0
  34. package/templates/react/tsconfig.json +27 -0
  35. package/templates/react/tsconfig.node.json +11 -0
  36. package/templates/react/vite.config.ts +12 -0
  37. package/templates/user-management-backend/.env.example +4 -0
  38. package/templates/user-management-backend/README.md +105 -0
  39. package/templates/user-management-backend/package.json +19 -0
  40. package/templates/user-management-backend/src/config/database.ts +4 -0
  41. package/templates/user-management-backend/src/config/env.ts +5 -0
  42. package/templates/user-management-backend/src/config/index.ts +2 -0
  43. package/templates/user-management-backend/src/features/users/controller.ts +100 -0
  44. package/templates/user-management-backend/src/features/users/index.ts +6 -0
  45. package/templates/user-management-backend/src/features/users/repository.ts +57 -0
  46. package/templates/user-management-backend/src/features/users/routes.ts +10 -0
  47. package/templates/user-management-backend/src/features/users/schema.ts +15 -0
  48. package/templates/user-management-backend/src/features/users/service.ts +40 -0
  49. package/templates/user-management-backend/src/features/users/types.ts +17 -0
  50. package/templates/user-management-backend/src/index.ts +22 -0
  51. package/templates/user-management-backend/src/routes/index.ts +8 -0
  52. package/templates/user-management-backend/src/shared/index.ts +2 -0
  53. package/templates/user-management-backend/src/shared/types/index.ts +16 -0
  54. package/templates/user-management-backend/src/shared/utils/index.ts +1 -0
  55. package/templates/user-management-backend/src/shared/utils/response.ts +10 -0
  56. package/templates/user-management-backend/tsconfig.json +22 -0
  57. package/templates/user-management-frontend/.env.example +1 -0
  58. package/templates/user-management-frontend/README.md +53 -0
  59. package/templates/user-management-frontend/index.html +12 -0
  60. package/templates/user-management-frontend/package.json +22 -0
  61. package/templates/user-management-frontend/src/App.tsx +19 -0
  62. package/templates/user-management-frontend/src/config/env.ts +1 -0
  63. package/templates/user-management-frontend/src/config/index.ts +1 -0
  64. package/templates/user-management-frontend/src/features/users/components/UserFormPage.tsx +76 -0
  65. package/templates/user-management-frontend/src/features/users/components/UsersPage.tsx +61 -0
  66. package/templates/user-management-frontend/src/features/users/components/index.ts +2 -0
  67. package/templates/user-management-frontend/src/features/users/hooks/index.ts +1 -0
  68. package/templates/user-management-frontend/src/features/users/hooks/useUsers.ts +45 -0
  69. package/templates/user-management-frontend/src/features/users/index.ts +4 -0
  70. package/templates/user-management-frontend/src/features/users/services/index.ts +1 -0
  71. package/templates/user-management-frontend/src/features/users/services/userService.ts +48 -0
  72. package/templates/user-management-frontend/src/features/users/types.ts +24 -0
  73. package/templates/user-management-frontend/src/index.css +108 -0
  74. package/templates/user-management-frontend/src/main.tsx +13 -0
  75. package/templates/user-management-frontend/tsconfig.json +27 -0
  76. package/templates/user-management-frontend/tsconfig.node.json +11 -0
  77. package/templates/user-management-frontend/vite.config.ts +12 -0
@@ -0,0 +1,105 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A User Management System backend with feature-based architecture using Bun, Hono, and NeonDB.
4
+
5
+ ## Project Structure
6
+
7
+ ```
8
+ src/
9
+ ├── config/ # Configuration (env, database)
10
+ ├── features/ # Feature modules
11
+ │ └── users/
12
+ │ ├── routes.ts
13
+ │ ├── controller.ts
14
+ │ ├── service.ts
15
+ │ ├── repository.ts
16
+ │ ├── types.ts
17
+ │ └── schema.ts
18
+ ├── routes/ # Route aggregation
19
+ ├── shared/ # Shared utilities and types
20
+ │ ├── types/
21
+ │ └── utils/
22
+ └── index.ts # Application entry
23
+ ```
24
+
25
+ ## Getting Started
26
+
27
+ ```bash
28
+ # Install dependencies
29
+ bun install
30
+
31
+ # Copy environment file
32
+ cp .env.example .env
33
+
34
+ # Update DATABASE_URL with your NeonDB connection string
35
+
36
+ # Run development server
37
+ bun run dev
38
+ ```
39
+
40
+ ## Environment Variables
41
+
42
+ | Variable | Description |
43
+ |----------|-------------|
44
+ | `DATABASE_URL` | NeonDB PostgreSQL connection string |
45
+ | `PORT` | Server port (default: 3000) |
46
+ | `NODE_ENV` | Environment (development/production) |
47
+
48
+ ## Database Setup (NeonDB)
49
+
50
+ Run the following SQL in your NeonDB console to create the users table:
51
+
52
+ ```sql
53
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
54
+
55
+ CREATE TABLE users (
56
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
57
+ email VARCHAR(255) UNIQUE NOT NULL,
58
+ name VARCHAR(100) NOT NULL,
59
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
60
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
61
+ );
62
+
63
+ -- Optional: Add index for email lookups
64
+ CREATE INDEX idx_users_email ON users(email);
65
+
66
+ -- Optional: Add trigger to auto-update updated_at
67
+ CREATE OR REPLACE FUNCTION update_updated_at_column()
68
+ RETURNS TRIGGER AS $$
69
+ BEGIN
70
+ NEW.updated_at = CURRENT_TIMESTAMP;
71
+ RETURN NEW;
72
+ END;
73
+ $$ language 'plpgsql';
74
+
75
+ CREATE TRIGGER update_users_updated_at
76
+ BEFORE UPDATE ON users
77
+ FOR EACH ROW
78
+ EXECUTE FUNCTION update_updated_at_column();
79
+ ```
80
+
81
+ ## API Endpoints
82
+
83
+ | Method | Endpoint | Description |
84
+ |--------|----------|-------------|
85
+ | GET | `/api/users` | Get all users |
86
+ | GET | `/api/users/:id` | Get user by ID |
87
+ | POST | `/api/users` | Create new user |
88
+ | PUT | `/api/users/:id` | Update user |
89
+ | DELETE | `/api/users/:id` | Delete user |
90
+
91
+ ### Request/Response Examples
92
+
93
+ **Create User**
94
+ ```bash
95
+ curl -X POST http://localhost:3000/api/users \
96
+ -H "Content-Type: application/json" \
97
+ -d '{"email": "user@example.com", "name": "John Doe"}'
98
+ ```
99
+
100
+ **Update User**
101
+ ```bash
102
+ curl -X PUT http://localhost:3000/api/users/{id} \
103
+ -H "Content-Type: application/json" \
104
+ -d '{"name": "Jane Doe"}'
105
+ ```
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "bun run --watch src/index.ts",
7
+ "start": "bun run src/index.ts",
8
+ "build": "bun build src/index.ts --outdir ./dist --target bun"
9
+ },
10
+ "dependencies": {
11
+ "hono": "^4.11.3",
12
+ "@neondatabase/serverless": "^0.9.0",
13
+ "zod": "^3.22.0"
14
+ },
15
+ "devDependencies": {
16
+ "@types/bun": "latest",
17
+ "typescript": "^5.0.0"
18
+ }
19
+ }
@@ -0,0 +1,4 @@
1
+ import { neon } from "@neondatabase/serverless";
2
+ import { env } from "./env";
3
+
4
+ export const sql = neon(env.DATABASE_URL);
@@ -0,0 +1,5 @@
1
+ export const env = {
2
+ DATABASE_URL: process.env.DATABASE_URL || "",
3
+ PORT: Number(process.env.PORT) || 3000,
4
+ NODE_ENV: process.env.NODE_ENV || "development",
5
+ } as const;
@@ -0,0 +1,2 @@
1
+ export * from "./env";
2
+ export * from "./database";
@@ -0,0 +1,100 @@
1
+ import type { Context } from "hono";
2
+ import { userService } from "./service";
3
+ import { createUserSchema, updateUserSchema, userIdSchema } from "./schema";
4
+ import { successResponse, errorResponse } from "../../shared/utils/response";
5
+
6
+ export const userController = {
7
+ async getAll(c: Context) {
8
+ try {
9
+ const users = await userService.getAllUsers();
10
+ return successResponse(c, users);
11
+ } catch (error) {
12
+ return errorResponse(c, "Failed to fetch users", 500);
13
+ }
14
+ },
15
+
16
+ async getById(c: Context) {
17
+ try {
18
+ const { id } = c.req.param();
19
+ const validation = userIdSchema.safeParse({ id });
20
+
21
+ if (!validation.success) {
22
+ return errorResponse(c, validation.error.errors[0].message);
23
+ }
24
+
25
+ const user = await userService.getUserById(id);
26
+ if (!user) {
27
+ return errorResponse(c, "User not found", 404);
28
+ }
29
+
30
+ return successResponse(c, user);
31
+ } catch (error) {
32
+ return errorResponse(c, "Failed to fetch user", 500);
33
+ }
34
+ },
35
+
36
+ async create(c: Context) {
37
+ try {
38
+ const body = await c.req.json();
39
+ const validation = createUserSchema.safeParse(body);
40
+
41
+ if (!validation.success) {
42
+ return errorResponse(c, validation.error.errors[0].message);
43
+ }
44
+
45
+ const user = await userService.createUser(validation.data);
46
+ return successResponse(c, user, "User created successfully", 201);
47
+ } catch (error) {
48
+ const message = error instanceof Error ? error.message : "Failed to create user";
49
+ return errorResponse(c, message, error instanceof Error && error.message === "Email already exists" ? 409 : 500);
50
+ }
51
+ },
52
+
53
+ async update(c: Context) {
54
+ try {
55
+ const { id } = c.req.param();
56
+ const idValidation = userIdSchema.safeParse({ id });
57
+
58
+ if (!idValidation.success) {
59
+ return errorResponse(c, idValidation.error.errors[0].message);
60
+ }
61
+
62
+ const body = await c.req.json();
63
+ const validation = updateUserSchema.safeParse(body);
64
+
65
+ if (!validation.success) {
66
+ return errorResponse(c, validation.error.errors[0].message);
67
+ }
68
+
69
+ const user = await userService.updateUser(id, validation.data);
70
+ if (!user) {
71
+ return errorResponse(c, "User not found", 404);
72
+ }
73
+
74
+ return successResponse(c, user, "User updated successfully");
75
+ } catch (error) {
76
+ const message = error instanceof Error ? error.message : "Failed to update user";
77
+ return errorResponse(c, message, error instanceof Error && error.message === "Email already exists" ? 409 : 500);
78
+ }
79
+ },
80
+
81
+ async delete(c: Context) {
82
+ try {
83
+ const { id } = c.req.param();
84
+ const validation = userIdSchema.safeParse({ id });
85
+
86
+ if (!validation.success) {
87
+ return errorResponse(c, validation.error.errors[0].message);
88
+ }
89
+
90
+ const deleted = await userService.deleteUser(id);
91
+ if (!deleted) {
92
+ return errorResponse(c, "User not found", 404);
93
+ }
94
+
95
+ return successResponse(c, null, "User deleted successfully");
96
+ } catch (error) {
97
+ return errorResponse(c, "Failed to delete user", 500);
98
+ }
99
+ },
100
+ };
@@ -0,0 +1,6 @@
1
+ export * from "./routes";
2
+ export * from "./controller";
3
+ export * from "./service";
4
+ export * from "./repository";
5
+ export * from "./types";
6
+ export * from "./schema";
@@ -0,0 +1,57 @@
1
+ import { sql } from "../../config/database";
2
+ import type { User, CreateUserDto, UpdateUserDto } from "./types";
3
+
4
+ export const userRepository = {
5
+ async findAll(): Promise<User[]> {
6
+ const result = await sql`SELECT * FROM users ORDER BY created_at DESC`;
7
+ return result as User[];
8
+ },
9
+
10
+ async findById(id: string): Promise<User | null> {
11
+ const result = await sql`SELECT * FROM users WHERE id = ${id}`;
12
+ return (result[0] as User) || null;
13
+ },
14
+
15
+ async findByEmail(email: string): Promise<User | null> {
16
+ const result = await sql`SELECT * FROM users WHERE email = ${email}`;
17
+ return (result[0] as User) || null;
18
+ },
19
+
20
+ async create(data: CreateUserDto): Promise<User> {
21
+ const result = await sql`
22
+ INSERT INTO users (email, name)
23
+ VALUES (${data.email}, ${data.name})
24
+ RETURNING *
25
+ `;
26
+ return result[0] as User;
27
+ },
28
+
29
+ async update(id: string, data: UpdateUserDto): Promise<User | null> {
30
+ const fields: string[] = [];
31
+ const values: unknown[] = [];
32
+
33
+ if (data.email) {
34
+ fields.push("email");
35
+ values.push(data.email);
36
+ }
37
+ if (data.name) {
38
+ fields.push("name");
39
+ values.push(data.name);
40
+ }
41
+
42
+ if (fields.length === 0) return this.findById(id);
43
+
44
+ const result = await sql`
45
+ UPDATE users
46
+ SET ${sql(Object.fromEntries(fields.map((f, i) => [f, values[i]])))}
47
+ WHERE id = ${id}
48
+ RETURNING *
49
+ `;
50
+ return (result[0] as User) || null;
51
+ },
52
+
53
+ async delete(id: string): Promise<boolean> {
54
+ const result = await sql`DELETE FROM users WHERE id = ${id} RETURNING id`;
55
+ return result.length > 0;
56
+ },
57
+ };
@@ -0,0 +1,10 @@
1
+ import { Hono } from "hono";
2
+ import { userController } from "./controller";
3
+
4
+ export const userRoutes = new Hono();
5
+
6
+ userRoutes.get("/", userController.getAll);
7
+ userRoutes.get("/:id", userController.getById);
8
+ userRoutes.post("/", userController.create);
9
+ userRoutes.put("/:id", userController.update);
10
+ userRoutes.delete("/:id", userController.delete);
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+
3
+ export const createUserSchema = z.object({
4
+ email: z.string().email("Invalid email format"),
5
+ name: z.string().min(1, "Name is required").max(100, "Name too long"),
6
+ });
7
+
8
+ export const updateUserSchema = z.object({
9
+ email: z.string().email("Invalid email format").optional(),
10
+ name: z.string().min(1).max(100).optional(),
11
+ });
12
+
13
+ export const userIdSchema = z.object({
14
+ id: z.string().uuid("Invalid user ID"),
15
+ });
@@ -0,0 +1,40 @@
1
+ import { userRepository } from "./repository";
2
+ import type { CreateUserDto, UpdateUserDto, User } from "./types";
3
+
4
+ export const userService = {
5
+ async getAllUsers(): Promise<User[]> {
6
+ return userRepository.findAll();
7
+ },
8
+
9
+ async getUserById(id: string): Promise<User | null> {
10
+ return userRepository.findById(id);
11
+ },
12
+
13
+ async createUser(data: CreateUserDto): Promise<User> {
14
+ const existing = await userRepository.findByEmail(data.email);
15
+ if (existing) {
16
+ throw new Error("Email already exists");
17
+ }
18
+ return userRepository.create(data);
19
+ },
20
+
21
+ async updateUser(id: string, data: UpdateUserDto): Promise<User | null> {
22
+ const user = await userRepository.findById(id);
23
+ if (!user) {
24
+ return null;
25
+ }
26
+
27
+ if (data.email && data.email !== user.email) {
28
+ const existing = await userRepository.findByEmail(data.email);
29
+ if (existing) {
30
+ throw new Error("Email already exists");
31
+ }
32
+ }
33
+
34
+ return userRepository.update(id, data);
35
+ },
36
+
37
+ async deleteUser(id: string): Promise<boolean> {
38
+ return userRepository.delete(id);
39
+ },
40
+ };
@@ -0,0 +1,17 @@
1
+ export interface User {
2
+ id: string;
3
+ email: string;
4
+ name: string;
5
+ created_at: Date;
6
+ updated_at: Date;
7
+ }
8
+
9
+ export interface CreateUserDto {
10
+ email: string;
11
+ name: string;
12
+ }
13
+
14
+ export interface UpdateUserDto {
15
+ email?: string;
16
+ name?: string;
17
+ }
@@ -0,0 +1,22 @@
1
+ import { Hono } from "hono";
2
+ import { cors } from "hono/cors";
3
+ import { logger } from "hono/logger";
4
+ import { env } from "./config/env";
5
+ import { appRouter } from "./routes";
6
+
7
+ const app = new Hono();
8
+
9
+ // Middleware
10
+ app.use("*", logger());
11
+ app.use("*", cors());
12
+
13
+ // Routes
14
+ app.route("/api", appRouter);
15
+
16
+ // Health check
17
+ app.get("/health", (c) => c.json({ status: "ok", timestamp: new Date().toISOString() }));
18
+
19
+ export default {
20
+ port: env.PORT,
21
+ fetch: app.fetch,
22
+ };
@@ -0,0 +1,8 @@
1
+ import { Hono } from "hono";
2
+ import { userRoutes } from "../features/users/routes";
3
+
4
+ export const appRouter = new Hono();
5
+
6
+ appRouter.route("/users", userRoutes);
7
+
8
+ appRouter.get("/", (c) => c.json({ message: "User Management API" }));
@@ -0,0 +1,2 @@
1
+ export * from "./types";
2
+ export * from "./utils";
@@ -0,0 +1,16 @@
1
+ // Shared types across features
2
+ export interface ApiResponse<T> {
3
+ success: boolean;
4
+ data?: T;
5
+ error?: string;
6
+ message?: string;
7
+ }
8
+
9
+ export interface PaginatedResponse<T> extends ApiResponse<T[]> {
10
+ pagination: {
11
+ page: number;
12
+ limit: number;
13
+ total: number;
14
+ totalPages: number;
15
+ };
16
+ }
@@ -0,0 +1 @@
1
+ export * from "./response";
@@ -0,0 +1,10 @@
1
+ import type { Context } from "hono";
2
+ import type { ApiResponse } from "../types";
3
+
4
+ export function successResponse<T>(c: Context, data: T, message?: string, status = 200) {
5
+ return c.json<ApiResponse<T>>({ success: true, data, message }, status);
6
+ }
7
+
8
+ export function errorResponse(c: Context, error: string, status = 400) {
9
+ return c.json<ApiResponse<never>>({ success: false, error }, status);
10
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@/*": ["src/*"],
15
+ "@/features/*": ["src/features/*"],
16
+ "@/shared/*": ["src/shared/*"],
17
+ "@/config/*": ["src/config/*"]
18
+ }
19
+ },
20
+ "include": ["src/**/*"],
21
+ "exclude": ["node_modules", "dist"]
22
+ }
@@ -0,0 +1 @@
1
+ VITE_API_URL=http://localhost:3000/api
@@ -0,0 +1,53 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A User Management System frontend with feature-based architecture using React, Vite, and Bun.
4
+
5
+ ## Project Structure
6
+
7
+ ```
8
+ src/
9
+ ├── config/ # Configuration
10
+ ├── features/ # Feature modules
11
+ │ └── users/
12
+ │ ├── components/
13
+ │ ├── hooks/
14
+ │ ├── services/
15
+ │ ├── types.ts
16
+ │ └── index.ts
17
+ ├── shared/ # Shared components
18
+ ├── App.tsx
19
+ ├── main.tsx
20
+ └── index.css
21
+ ```
22
+
23
+ ## Getting Started
24
+
25
+ ```bash
26
+ # Install dependencies
27
+ bun install
28
+
29
+ # Copy environment file
30
+ cp .env.example .env
31
+
32
+ # Update VITE_API_URL if your backend runs on a different port
33
+
34
+ # Run development server
35
+ bun run dev
36
+ ```
37
+
38
+ ## Environment Variables
39
+
40
+ | Variable | Description |
41
+ |----------|-------------|
42
+ | `VITE_API_URL` | Backend API URL (default: http://localhost:3000/api) |
43
+
44
+ ## Features
45
+
46
+ - List all users
47
+ - Create new user
48
+ - Edit existing user
49
+ - Delete user
50
+
51
+ ## Backend Setup
52
+
53
+ This frontend requires the User Management Backend to be running. See the backend template for setup instructions.
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{PROJECT_NAME}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "bunx --bun vite",
7
+ "build": "bunx --bun vite build",
8
+ "preview": "bunx --bun vite preview"
9
+ },
10
+ "dependencies": {
11
+ "react": "^18.2.0",
12
+ "react-dom": "^18.2.0",
13
+ "react-router-dom": "^6.20.0"
14
+ },
15
+ "devDependencies": {
16
+ "@types/react": "^18.2.0",
17
+ "@types/react-dom": "^18.2.0",
18
+ "@vitejs/plugin-react": "^4.2.0",
19
+ "typescript": "^5.0.0",
20
+ "vite": "^5.0.0"
21
+ }
22
+ }
@@ -0,0 +1,19 @@
1
+ import { Routes, Route } from "react-router-dom";
2
+ import { UsersPage, UserFormPage } from "./features/users";
3
+
4
+ export function App() {
5
+ return (
6
+ <div className="app">
7
+ <header className="header">
8
+ <h1>User Management</h1>
9
+ </header>
10
+ <main className="main">
11
+ <Routes>
12
+ <Route path="/" element={<UsersPage />} />
13
+ <Route path="/users/new" element={<UserFormPage />} />
14
+ <Route path="/users/:id/edit" element={<UserFormPage />} />
15
+ </Routes>
16
+ </main>
17
+ </div>
18
+ );
19
+ }
@@ -0,0 +1 @@
1
+ export const API_URL = import.meta.env.VITE_API_URL || "http://localhost:3000/api";
@@ -0,0 +1 @@
1
+ export * from "./env";