create-react-zr-architecture 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.
- package/README.md +279 -0
- package/bin/cli.js +198 -0
- package/package.json +45 -0
- package/template/_env +1 -0
- package/template/_env.example +1 -0
- package/template/_gitignore +35 -0
- package/template/create-src-files.sh +785 -0
- package/template/setup-template.sh +277 -0
- package/template/src/app/App.tsx +25 -0
- package/template/src/domain/entities/User.ts +8 -0
- package/template/src/domain/repositories/UserRepository.ts +11 -0
- package/template/src/domain/types/.gitkeep +0 -0
- package/template/src/domain/usecases/users/CreateUserUseCase.ts +15 -0
- package/template/src/domain/usecases/users/GetAllUsersUseCase.ts +10 -0
- package/template/src/infrastructure/api/apiClient.ts +73 -0
- package/template/src/infrastructure/config/.gitkeep +0 -0
- package/template/src/infrastructure/data/.gitkeep +0 -0
- package/template/src/infrastructure/repositories/UserRepositoryImpl.ts +32 -0
- package/template/src/infrastructure/services/.gitkeep +0 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/main.tsx +10 -0
- package/template/src/presentation/assets/logos/.gitkeep +0 -0
- package/template/src/presentation/components/layouts/.gitkeep +0 -0
- package/template/src/presentation/components/shared/.gitkeep +0 -0
- package/template/src/presentation/components/tables/.gitkeep +0 -0
- package/template/src/presentation/context/auth/AuthContext.tsx +84 -0
- package/template/src/presentation/context/auth/useAuthContext.tsx +12 -0
- package/template/src/presentation/pages/Index.tsx +76 -0
- package/template/src/presentation/pages/auth/LoginPage.tsx +39 -0
- package/template/src/presentation/routes/config/.gitkeep +0 -0
- package/template/src/presentation/routes/guards/.gitkeep +0 -0
- package/template/src/presentation/utils/.gitkeep +0 -0
- package/template/src/presentation/viewmodels/hooks/.gitkeep +0 -0
- package/template/src/shared/config/env.ts +9 -0
- package/template/src/shared/constants/index.ts +29 -0
- package/template/src/shared/hooks/.gitkeep +0 -0
- package/template/src/shared/lib/.gitkeep +0 -0
- package/template/src/shared/types/index.ts +42 -0
- package/template/src/shared/utils/format.ts +30 -0
- package/template/src/shared/utils/validation.ts +22 -0
- package/template/src/styles/index.css +59 -0
- package/template/src/vite-env.d.ts +10 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useAuthContext } from '@/presentation/context/auth/useAuthContext';
|
|
2
|
+
|
|
3
|
+
export const LoginPage = () => {
|
|
4
|
+
const { dispatch } = useAuthContext();
|
|
5
|
+
|
|
6
|
+
const handleLogin = () => {
|
|
7
|
+
// Ejemplo de login
|
|
8
|
+
dispatch({
|
|
9
|
+
type: 'LOGIN',
|
|
10
|
+
payload: {
|
|
11
|
+
user: {
|
|
12
|
+
id: '1',
|
|
13
|
+
email: 'demo@example.com',
|
|
14
|
+
nombre: 'Usuario Demo',
|
|
15
|
+
rol: 'admin',
|
|
16
|
+
},
|
|
17
|
+
token: 'demo-token-123',
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
24
|
+
<div className="w-full max-w-md p-8 space-y-6 border rounded-lg bg-card">
|
|
25
|
+
<div className="text-center">
|
|
26
|
+
<h1 className="text-3xl font-bold">Iniciar Sesión</h1>
|
|
27
|
+
<p className="text-muted-foreground mt-2">Template de autenticación</p>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<button
|
|
31
|
+
onClick={handleLogin}
|
|
32
|
+
className="w-full p-3 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
|
|
33
|
+
>
|
|
34
|
+
Login Demo
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// ════════════════════════════════════════════════════════════
|
|
2
|
+
// Environment Variables
|
|
3
|
+
// ════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
export const env = {
|
|
6
|
+
apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:3000/api',
|
|
7
|
+
isDevelopment: import.meta.env.DEV,
|
|
8
|
+
isProduction: import.meta.env.PROD,
|
|
9
|
+
} as const;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// ════════════════════════════════════════════════════════════
|
|
2
|
+
// Application Constants
|
|
3
|
+
// ════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
export const APP_NAME = 'Clean Architecture App';
|
|
6
|
+
export const APP_VERSION = '1.0.0';
|
|
7
|
+
|
|
8
|
+
export const STORAGE_KEYS = {
|
|
9
|
+
TOKEN: 'auth_token',
|
|
10
|
+
USER: 'user_data',
|
|
11
|
+
THEME: 'app_theme',
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const ROUTES = {
|
|
15
|
+
HOME: '/',
|
|
16
|
+
LOGIN: '/login',
|
|
17
|
+
DASHBOARD: '/dashboard',
|
|
18
|
+
SETTINGS: '/settings',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export const HTTP_STATUS = {
|
|
22
|
+
OK: 200,
|
|
23
|
+
CREATED: 201,
|
|
24
|
+
BAD_REQUEST: 400,
|
|
25
|
+
UNAUTHORIZED: 401,
|
|
26
|
+
FORBIDDEN: 403,
|
|
27
|
+
NOT_FOUND: 404,
|
|
28
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
29
|
+
} as const;
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// ════════════════════════════════════════════════════════════
|
|
2
|
+
// Common Types
|
|
3
|
+
// ════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
export interface BaseEntity {
|
|
6
|
+
id: string;
|
|
7
|
+
createdAt: Date;
|
|
8
|
+
updatedAt: Date;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PaginatedResponse<T> {
|
|
12
|
+
data: T[];
|
|
13
|
+
total: number;
|
|
14
|
+
page: number;
|
|
15
|
+
pageSize: number;
|
|
16
|
+
totalPages: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ApiResponse<T> {
|
|
20
|
+
success: boolean;
|
|
21
|
+
data?: T;
|
|
22
|
+
error?: string;
|
|
23
|
+
message?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AsyncState<T> = {
|
|
27
|
+
data: T | null;
|
|
28
|
+
loading: boolean;
|
|
29
|
+
error: string | null;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export interface TableColumn<T> {
|
|
33
|
+
key: keyof T | string;
|
|
34
|
+
label: string;
|
|
35
|
+
sortable?: boolean;
|
|
36
|
+
render?: (value: any, row: T) => React.ReactNode;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SelectOption {
|
|
40
|
+
value: string;
|
|
41
|
+
label: string;
|
|
42
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// ════════════════════════════════════════════════════════════
|
|
2
|
+
// Format Utilities
|
|
3
|
+
// ════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
export const formatCurrency = (amount: number, currency = 'USD'): string => {
|
|
6
|
+
return new Intl.NumberFormat('en-US', {
|
|
7
|
+
style: 'currency',
|
|
8
|
+
currency,
|
|
9
|
+
}).format(amount);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const formatDate = (date: Date | string, format = 'short'): string => {
|
|
13
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
14
|
+
|
|
15
|
+
const options: Intl.DateTimeFormatOptions =
|
|
16
|
+
format === 'short'
|
|
17
|
+
? { year: 'numeric', month: 'short', day: 'numeric' }
|
|
18
|
+
: { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' };
|
|
19
|
+
|
|
20
|
+
return new Intl.DateTimeFormat('en-US', options).format(d);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const formatNumber = (num: number): string => {
|
|
24
|
+
return new Intl.NumberFormat('en-US').format(num);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const truncate = (str: string, length: number): string => {
|
|
28
|
+
if (str.length <= length) return str;
|
|
29
|
+
return str.substring(0, length) + '...';
|
|
30
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// ════════════════════════════════════════════════════════════
|
|
2
|
+
// Validation Utilities
|
|
3
|
+
// ════════════════════════════════════════════════════════════
|
|
4
|
+
|
|
5
|
+
export const isValidEmail = (email: string): boolean => {
|
|
6
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
7
|
+
return emailRegex.test(email);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const isValidPhone = (phone: string): boolean => {
|
|
11
|
+
const phoneRegex = /^\+?[\d\s-()]+$/;
|
|
12
|
+
return phoneRegex.test(phone);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const isValidUrl = (url: string): boolean => {
|
|
16
|
+
try {
|
|
17
|
+
new URL(url);
|
|
18
|
+
return true;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
:root {
|
|
7
|
+
--background: 0 0% 100%;
|
|
8
|
+
--foreground: 222.2 84% 4.9%;
|
|
9
|
+
--card: 0 0% 100%;
|
|
10
|
+
--card-foreground: 222.2 84% 4.9%;
|
|
11
|
+
--popover: 0 0% 100%;
|
|
12
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
13
|
+
--primary: 222.2 47.4% 11.2%;
|
|
14
|
+
--primary-foreground: 210 40% 98%;
|
|
15
|
+
--secondary: 210 40% 96.1%;
|
|
16
|
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
|
17
|
+
--muted: 210 40% 96.1%;
|
|
18
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
19
|
+
--accent: 210 40% 96.1%;
|
|
20
|
+
--accent-foreground: 222.2 47.4% 11.2%;
|
|
21
|
+
--destructive: 0 84.2% 60.2%;
|
|
22
|
+
--destructive-foreground: 210 40% 98%;
|
|
23
|
+
--border: 214.3 31.8% 91.4%;
|
|
24
|
+
--input: 214.3 31.8% 91.4%;
|
|
25
|
+
--ring: 222.2 84% 4.9%;
|
|
26
|
+
--radius: 0.5rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dark {
|
|
30
|
+
--background: 222.2 84% 4.9%;
|
|
31
|
+
--foreground: 210 40% 98%;
|
|
32
|
+
--card: 222.2 84% 4.9%;
|
|
33
|
+
--card-foreground: 210 40% 98%;
|
|
34
|
+
--popover: 222.2 84% 4.9%;
|
|
35
|
+
--popover-foreground: 210 40% 98%;
|
|
36
|
+
--primary: 210 40% 98%;
|
|
37
|
+
--primary-foreground: 222.2 47.4% 11.2%;
|
|
38
|
+
--secondary: 217.2 32.6% 17.5%;
|
|
39
|
+
--secondary-foreground: 210 40% 98%;
|
|
40
|
+
--muted: 217.2 32.6% 17.5%;
|
|
41
|
+
--muted-foreground: 215 20.2% 65.1%;
|
|
42
|
+
--accent: 217.2 32.6% 17.5%;
|
|
43
|
+
--accent-foreground: 210 40% 98%;
|
|
44
|
+
--destructive: 0 62.8% 30.6%;
|
|
45
|
+
--destructive-foreground: 210 40% 98%;
|
|
46
|
+
--border: 217.2 32.6% 17.5%;
|
|
47
|
+
--input: 217.2 32.6% 17.5%;
|
|
48
|
+
--ring: 212.7 26.8% 83.9%;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@layer base {
|
|
53
|
+
* {
|
|
54
|
+
@apply border-border;
|
|
55
|
+
}
|
|
56
|
+
body {
|
|
57
|
+
@apply bg-background text-foreground;
|
|
58
|
+
}
|
|
59
|
+
}
|