create-modern-react 1.0.0 → 2.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 +270 -72
- package/bin/index.js +13 -13
- package/lib/install.js +103 -32
- package/lib/prompts.js +152 -179
- package/lib/setup.js +267 -159
- package/package.json +17 -8
- package/templates/base/.env.example +9 -0
- package/templates/base/.eslintrc.cjs +37 -0
- package/templates/base/.prettierrc +11 -0
- package/templates/base/components.json +17 -0
- package/templates/base/index.html +2 -1
- package/templates/base/package.json +33 -14
- package/templates/base/postcss.config.js +6 -0
- package/templates/base/src/App.tsx +5 -18
- package/templates/base/src/components/layout/error-boundary.tsx +60 -0
- package/templates/base/src/components/layout/index.ts +2 -0
- package/templates/base/src/components/layout/root-layout.tsx +36 -0
- package/templates/base/src/components/ui/button.tsx +55 -0
- package/templates/base/src/components/ui/card.tsx +85 -0
- package/templates/base/src/components/ui/index.ts +12 -0
- package/templates/base/src/components/ui/input.tsx +24 -0
- package/templates/base/src/components/ui/separator.tsx +29 -0
- package/templates/base/src/components/ui/skeleton.tsx +15 -0
- package/templates/base/src/hooks/index.ts +3 -0
- package/templates/base/src/hooks/use-cancel-token.ts +63 -0
- package/templates/base/src/hooks/use-debounce.ts +29 -0
- package/templates/base/src/hooks/use-loader.ts +39 -0
- package/templates/base/src/index.css +73 -60
- package/templates/base/src/lib/utils.ts +14 -0
- package/templates/base/src/main.tsx +6 -6
- package/templates/base/src/providers/index.tsx +27 -0
- package/templates/base/src/providers/theme-provider.tsx +92 -0
- package/templates/base/src/routes/index.tsx +40 -0
- package/templates/base/src/routes/routes.ts +36 -0
- package/templates/base/src/screens/home/index.tsx +132 -0
- package/templates/base/src/screens/not-found/index.tsx +29 -0
- package/templates/base/src/services/alertify-services.ts +133 -0
- package/templates/base/src/services/api/api-helpers.ts +130 -0
- package/templates/base/src/services/api/axios-instance.ts +77 -0
- package/templates/base/src/services/api/index.ts +9 -0
- package/templates/base/src/services/index.ts +2 -0
- package/templates/base/src/types/index.ts +55 -0
- package/templates/base/src/vite-env.d.ts +31 -0
- package/templates/base/tailwind.config.js +77 -0
- package/templates/base/tsconfig.json +4 -3
- package/templates/base/tsconfig.node.json +22 -0
- package/templates/base/vite.config.ts +65 -4
- package/templates/optional/antd/config-provider.tsx +33 -0
- package/templates/optional/antd/index.ts +2 -0
- package/templates/optional/antd/styles/antd-overrides.css +104 -0
- package/templates/optional/antd/theme.ts +75 -0
- package/templates/optional/husky/.husky/pre-commit +1 -0
- package/templates/optional/husky/.lintstagedrc.json +6 -0
- package/templates/optional/redux/hooks.ts +17 -0
- package/templates/optional/redux/index.ts +13 -0
- package/templates/optional/redux/provider.tsx +33 -0
- package/templates/optional/redux/store/index.ts +45 -0
- package/templates/optional/redux/store/slices/app-slice.ts +62 -0
- package/templates/base/src/App.css +0 -14
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { AxiosRequestConfig, CancelToken, AxiosError } from 'axios';
|
|
2
|
+
import axiosInstance from './axios-instance';
|
|
3
|
+
|
|
4
|
+
type Headers = Record<string, string>;
|
|
5
|
+
|
|
6
|
+
interface ApiResponse<T> {
|
|
7
|
+
data: T;
|
|
8
|
+
status: number;
|
|
9
|
+
message?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* POST request helper
|
|
14
|
+
*/
|
|
15
|
+
export async function postApi<T>(
|
|
16
|
+
path: string,
|
|
17
|
+
data?: unknown,
|
|
18
|
+
headers?: Headers,
|
|
19
|
+
cancelToken?: CancelToken,
|
|
20
|
+
config?: AxiosRequestConfig
|
|
21
|
+
): Promise<ApiResponse<T>> {
|
|
22
|
+
const response = await axiosInstance.post<T>(path, data, {
|
|
23
|
+
headers,
|
|
24
|
+
cancelToken,
|
|
25
|
+
...config,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
data: response.data,
|
|
29
|
+
status: response.status,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* GET request helper
|
|
35
|
+
*/
|
|
36
|
+
export async function getApi<T>(
|
|
37
|
+
path: string,
|
|
38
|
+
headers?: Headers,
|
|
39
|
+
cancelToken?: CancelToken,
|
|
40
|
+
config?: AxiosRequestConfig
|
|
41
|
+
): Promise<ApiResponse<T>> {
|
|
42
|
+
const response = await axiosInstance.get<T>(path, {
|
|
43
|
+
headers,
|
|
44
|
+
cancelToken,
|
|
45
|
+
...config,
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
data: response.data,
|
|
49
|
+
status: response.status,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* PATCH request helper
|
|
55
|
+
*/
|
|
56
|
+
export async function patchApi<T>(
|
|
57
|
+
path: string,
|
|
58
|
+
data?: unknown,
|
|
59
|
+
headers?: Headers,
|
|
60
|
+
config?: AxiosRequestConfig
|
|
61
|
+
): Promise<ApiResponse<T>> {
|
|
62
|
+
const response = await axiosInstance.patch<T>(path, data, {
|
|
63
|
+
headers,
|
|
64
|
+
...config,
|
|
65
|
+
});
|
|
66
|
+
return {
|
|
67
|
+
data: response.data,
|
|
68
|
+
status: response.status,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* PUT request helper
|
|
74
|
+
*/
|
|
75
|
+
export async function putApi<T>(
|
|
76
|
+
path: string,
|
|
77
|
+
data?: unknown,
|
|
78
|
+
headers?: Headers,
|
|
79
|
+
config?: AxiosRequestConfig
|
|
80
|
+
): Promise<ApiResponse<T>> {
|
|
81
|
+
const response = await axiosInstance.put<T>(path, data, {
|
|
82
|
+
headers,
|
|
83
|
+
...config,
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
data: response.data,
|
|
87
|
+
status: response.status,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* DELETE request helper
|
|
93
|
+
*/
|
|
94
|
+
export async function deleteApi<T>(
|
|
95
|
+
path: string,
|
|
96
|
+
data?: unknown,
|
|
97
|
+
headers?: Headers,
|
|
98
|
+
cancelToken?: CancelToken,
|
|
99
|
+
config?: AxiosRequestConfig
|
|
100
|
+
): Promise<ApiResponse<T>> {
|
|
101
|
+
const response = await axiosInstance.delete<T>(path, {
|
|
102
|
+
headers,
|
|
103
|
+
data,
|
|
104
|
+
cancelToken,
|
|
105
|
+
...config,
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
data: response.data,
|
|
109
|
+
status: response.status,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Error handler for Redux async thunks
|
|
115
|
+
* Extracts error message and returns it via rejectWithValue
|
|
116
|
+
*/
|
|
117
|
+
export function handleApiError(
|
|
118
|
+
error: unknown,
|
|
119
|
+
rejectWithValue: (value: string) => unknown
|
|
120
|
+
) {
|
|
121
|
+
if (error instanceof AxiosError) {
|
|
122
|
+
const message =
|
|
123
|
+
error.response?.data?.message ||
|
|
124
|
+
error.response?.data?.error ||
|
|
125
|
+
error.message ||
|
|
126
|
+
'An unexpected error occurred';
|
|
127
|
+
return rejectWithValue(message);
|
|
128
|
+
}
|
|
129
|
+
return rejectWithValue('An unexpected error occurred');
|
|
130
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000/api';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configured Axios instance with interceptors
|
|
7
|
+
* - Automatically adds auth token from localStorage
|
|
8
|
+
* - Handles 401 errors (token refresh/logout)
|
|
9
|
+
* - Supports request cancellation
|
|
10
|
+
*/
|
|
11
|
+
export const axiosInstance = axios.create({
|
|
12
|
+
baseURL: BASE_URL,
|
|
13
|
+
timeout: 30000,
|
|
14
|
+
headers: {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Request interceptor - Add auth token
|
|
20
|
+
axiosInstance.interceptors.request.use(
|
|
21
|
+
(config: InternalAxiosRequestConfig) => {
|
|
22
|
+
const token = localStorage.getItem('accessToken');
|
|
23
|
+
if (token && config.headers) {
|
|
24
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
25
|
+
}
|
|
26
|
+
return config;
|
|
27
|
+
},
|
|
28
|
+
(error: AxiosError) => {
|
|
29
|
+
return Promise.reject(error);
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Response interceptor - Handle errors
|
|
34
|
+
axiosInstance.interceptors.response.use(
|
|
35
|
+
(response) => response,
|
|
36
|
+
async (error: AxiosError) => {
|
|
37
|
+
const originalRequest = error.config as InternalAxiosRequestConfig & {
|
|
38
|
+
_retry?: boolean;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Handle 401 Unauthorized
|
|
42
|
+
if (error.response?.status === 401 && !originalRequest._retry) {
|
|
43
|
+
originalRequest._retry = true;
|
|
44
|
+
|
|
45
|
+
// Option 1: Try to refresh token
|
|
46
|
+
const refreshToken = localStorage.getItem('refreshToken');
|
|
47
|
+
if (refreshToken) {
|
|
48
|
+
try {
|
|
49
|
+
const response = await axios.post(`${BASE_URL}/auth/refresh`, {
|
|
50
|
+
refreshToken,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const { accessToken } = response.data;
|
|
54
|
+
localStorage.setItem('accessToken', accessToken);
|
|
55
|
+
|
|
56
|
+
if (originalRequest.headers) {
|
|
57
|
+
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
|
|
58
|
+
}
|
|
59
|
+
return axiosInstance(originalRequest);
|
|
60
|
+
} catch {
|
|
61
|
+
// Refresh failed - clear tokens and redirect
|
|
62
|
+
localStorage.removeItem('accessToken');
|
|
63
|
+
localStorage.removeItem('refreshToken');
|
|
64
|
+
window.location.href = '/login';
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
// No refresh token - redirect to login
|
|
68
|
+
localStorage.removeItem('accessToken');
|
|
69
|
+
window.location.href = '/login';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return Promise.reject(error);
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
export default axiosInstance;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common TypeScript type definitions
|
|
3
|
+
* Add your application-wide types here
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// API Response types
|
|
7
|
+
export interface ApiResponse<T> {
|
|
8
|
+
data: T;
|
|
9
|
+
status: number;
|
|
10
|
+
message?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PaginatedResponse<T> {
|
|
14
|
+
data: T[];
|
|
15
|
+
total: number;
|
|
16
|
+
page: number;
|
|
17
|
+
pageSize: number;
|
|
18
|
+
totalPages: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Error types
|
|
22
|
+
export interface ApiError {
|
|
23
|
+
message: string;
|
|
24
|
+
code?: string;
|
|
25
|
+
details?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// User types (example)
|
|
29
|
+
export interface User {
|
|
30
|
+
id: string;
|
|
31
|
+
email: string;
|
|
32
|
+
name: string;
|
|
33
|
+
avatar?: string;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
updatedAt: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Auth types
|
|
39
|
+
export interface AuthTokens {
|
|
40
|
+
accessToken: string;
|
|
41
|
+
refreshToken: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Utility types
|
|
45
|
+
export type Nullable<T> = T | null;
|
|
46
|
+
export type Optional<T> = T | undefined;
|
|
47
|
+
export type ValueOf<T> = T[keyof T];
|
|
48
|
+
|
|
49
|
+
// Make all properties optional recursively
|
|
50
|
+
export type DeepPartial<T> = {
|
|
51
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Make specific keys required
|
|
55
|
+
export type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
// SVG as React Component
|
|
4
|
+
declare module '*.svg?react' {
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
const ReactComponent: React.FunctionComponent<
|
|
7
|
+
React.SVGProps<SVGSVGElement> & { title?: string }
|
|
8
|
+
>;
|
|
9
|
+
export default ReactComponent;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
declare module '*.svg' {
|
|
13
|
+
import * as React from 'react';
|
|
14
|
+
export const ReactComponent: React.FunctionComponent<
|
|
15
|
+
React.SVGProps<SVGSVGElement> & { title?: string }
|
|
16
|
+
>;
|
|
17
|
+
const src: string;
|
|
18
|
+
export default src;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Environment variables
|
|
22
|
+
interface ImportMetaEnv {
|
|
23
|
+
readonly VITE_API_URL: string;
|
|
24
|
+
readonly VITE_APP_NAME: string;
|
|
25
|
+
readonly VITE_APP_VERSION: string;
|
|
26
|
+
readonly VITE_ENABLE_DEVTOOLS: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ImportMeta {
|
|
30
|
+
readonly env: ImportMetaEnv;
|
|
31
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
export default {
|
|
3
|
+
darkMode: ['class'],
|
|
4
|
+
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
|
5
|
+
theme: {
|
|
6
|
+
extend: {
|
|
7
|
+
colors: {
|
|
8
|
+
border: 'hsl(var(--border))',
|
|
9
|
+
input: 'hsl(var(--input))',
|
|
10
|
+
ring: 'hsl(var(--ring))',
|
|
11
|
+
background: 'hsl(var(--background))',
|
|
12
|
+
foreground: 'hsl(var(--foreground))',
|
|
13
|
+
primary: {
|
|
14
|
+
DEFAULT: 'hsl(var(--primary))',
|
|
15
|
+
foreground: 'hsl(var(--primary-foreground))',
|
|
16
|
+
},
|
|
17
|
+
secondary: {
|
|
18
|
+
DEFAULT: 'hsl(var(--secondary))',
|
|
19
|
+
foreground: 'hsl(var(--secondary-foreground))',
|
|
20
|
+
},
|
|
21
|
+
destructive: {
|
|
22
|
+
DEFAULT: 'hsl(var(--destructive))',
|
|
23
|
+
foreground: 'hsl(var(--destructive-foreground))',
|
|
24
|
+
},
|
|
25
|
+
muted: {
|
|
26
|
+
DEFAULT: 'hsl(var(--muted))',
|
|
27
|
+
foreground: 'hsl(var(--muted-foreground))',
|
|
28
|
+
},
|
|
29
|
+
accent: {
|
|
30
|
+
DEFAULT: 'hsl(var(--accent))',
|
|
31
|
+
foreground: 'hsl(var(--accent-foreground))',
|
|
32
|
+
},
|
|
33
|
+
popover: {
|
|
34
|
+
DEFAULT: 'hsl(var(--popover))',
|
|
35
|
+
foreground: 'hsl(var(--popover-foreground))',
|
|
36
|
+
},
|
|
37
|
+
card: {
|
|
38
|
+
DEFAULT: 'hsl(var(--card))',
|
|
39
|
+
foreground: 'hsl(var(--card-foreground))',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
borderRadius: {
|
|
43
|
+
lg: 'var(--radius)',
|
|
44
|
+
md: 'calc(var(--radius) - 2px)',
|
|
45
|
+
sm: 'calc(var(--radius) - 4px)',
|
|
46
|
+
},
|
|
47
|
+
fontFamily: {
|
|
48
|
+
sans: [
|
|
49
|
+
'Inter',
|
|
50
|
+
'system-ui',
|
|
51
|
+
'-apple-system',
|
|
52
|
+
'BlinkMacSystemFont',
|
|
53
|
+
'Segoe UI',
|
|
54
|
+
'Roboto',
|
|
55
|
+
'Helvetica Neue',
|
|
56
|
+
'Arial',
|
|
57
|
+
'sans-serif',
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
keyframes: {
|
|
61
|
+
'accordion-down': {
|
|
62
|
+
from: { height: '0' },
|
|
63
|
+
to: { height: 'var(--radix-accordion-content-height)' },
|
|
64
|
+
},
|
|
65
|
+
'accordion-up': {
|
|
66
|
+
from: { height: 'var(--radix-accordion-content-height)' },
|
|
67
|
+
to: { height: '0' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
animation: {
|
|
71
|
+
'accordion-down': 'accordion-down 0.2s ease-out',
|
|
72
|
+
'accordion-up': 'accordion-up 0.2s ease-out',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
plugins: [],
|
|
77
|
+
};
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
/* Bundler mode */
|
|
10
10
|
"moduleResolution": "bundler",
|
|
11
11
|
"allowImportingTsExtensions": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
12
|
"isolatedModules": true,
|
|
13
|
+
"moduleDetection": "force",
|
|
14
14
|
"noEmit": true,
|
|
15
15
|
"jsx": "react-jsx",
|
|
16
16
|
|
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
"noUnusedLocals": true,
|
|
20
20
|
"noUnusedParameters": true,
|
|
21
21
|
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"forceConsistentCasingInFileNames": true,
|
|
22
23
|
|
|
23
|
-
/* Path
|
|
24
|
+
/* Path aliases */
|
|
24
25
|
"baseUrl": ".",
|
|
25
26
|
"paths": {
|
|
26
|
-
"~/*": ["src/*"]
|
|
27
|
+
"~/*": ["./src/*"]
|
|
27
28
|
}
|
|
28
29
|
},
|
|
29
30
|
"include": ["src"],
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2023"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
|
|
8
|
+
/* Bundler mode */
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"moduleDetection": "force",
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
|
|
15
|
+
/* Linting */
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true
|
|
20
|
+
},
|
|
21
|
+
"include": ["vite.config.ts"]
|
|
22
|
+
}
|
|
@@ -1,12 +1,73 @@
|
|
|
1
|
-
import { defineConfig } from
|
|
2
|
-
import react from
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react-swc';
|
|
3
|
+
import svgr from 'vite-plugin-svgr';
|
|
4
|
+
import compression from 'vite-plugin-compression';
|
|
5
|
+
import path from 'path';
|
|
3
6
|
|
|
4
7
|
// https://vitejs.dev/config/
|
|
5
8
|
export default defineConfig({
|
|
6
|
-
plugins: [
|
|
9
|
+
plugins: [
|
|
10
|
+
react(),
|
|
11
|
+
// Import SVGs as React components
|
|
12
|
+
// Usage: import { ReactComponent as Logo } from './logo.svg'
|
|
13
|
+
// Or: import Logo from './logo.svg?react'
|
|
14
|
+
svgr({
|
|
15
|
+
svgrOptions: {
|
|
16
|
+
// SVGR options
|
|
17
|
+
icon: true, // Replace width/height with 1em to make SVG scale with font-size
|
|
18
|
+
svgo: true, // Optimize SVGs
|
|
19
|
+
svgoConfig: {
|
|
20
|
+
plugins: [
|
|
21
|
+
{
|
|
22
|
+
name: 'preset-default',
|
|
23
|
+
params: {
|
|
24
|
+
overrides: {
|
|
25
|
+
removeViewBox: false, // Keep viewBox for proper scaling
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
include: '**/*.svg',
|
|
33
|
+
}),
|
|
34
|
+
// Gzip compression for production builds
|
|
35
|
+
// Files larger than 1KB will be compressed
|
|
36
|
+
compression({
|
|
37
|
+
algorithm: 'gzip',
|
|
38
|
+
ext: '.gz',
|
|
39
|
+
threshold: 1024, // Only compress files larger than 1KB
|
|
40
|
+
deleteOriginFile: false, // Keep original files
|
|
41
|
+
verbose: true, // Log compression results
|
|
42
|
+
filter: /\.(js|css|html|json|svg)$/i, // File types to compress
|
|
43
|
+
}),
|
|
44
|
+
],
|
|
7
45
|
resolve: {
|
|
8
46
|
alias: {
|
|
9
|
-
|
|
47
|
+
'~': path.resolve(__dirname, './src'),
|
|
10
48
|
},
|
|
11
49
|
},
|
|
50
|
+
server: {
|
|
51
|
+
port: 3000,
|
|
52
|
+
open: true,
|
|
53
|
+
host: true,
|
|
54
|
+
},
|
|
55
|
+
build: {
|
|
56
|
+
minify: 'esbuild',
|
|
57
|
+
target: 'esnext',
|
|
58
|
+
sourcemap: false,
|
|
59
|
+
// Chunk splitting for better caching
|
|
60
|
+
rollupOptions: {
|
|
61
|
+
output: {
|
|
62
|
+
manualChunks: {
|
|
63
|
+
vendor: ['react', 'react-dom'],
|
|
64
|
+
router: ['wouter'],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
esbuild: {
|
|
70
|
+
// Remove console.log and debugger in production
|
|
71
|
+
drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
|
|
72
|
+
},
|
|
12
73
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ConfigProvider, App as AntdApp, theme as antdThemeAlgorithm } from 'antd';
|
|
2
|
+
import { useTheme } from '~/providers/theme-provider';
|
|
3
|
+
import { antdTheme, antdDarkTheme } from './theme';
|
|
4
|
+
|
|
5
|
+
interface AntdConfigProviderProps {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Ant Design ConfigProvider wrapper
|
|
11
|
+
* Provides theme configuration and integrates with the app's theme system
|
|
12
|
+
*
|
|
13
|
+
* Uses the AntdApp wrapper which provides:
|
|
14
|
+
* - Static methods for message, notification, modal
|
|
15
|
+
* - Consistent styling context
|
|
16
|
+
*/
|
|
17
|
+
export function AntdConfigProvider({ children }: AntdConfigProviderProps) {
|
|
18
|
+
const { resolvedTheme } = useTheme();
|
|
19
|
+
const isDark = resolvedTheme === 'dark';
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ConfigProvider
|
|
23
|
+
theme={{
|
|
24
|
+
...(isDark ? antdDarkTheme : antdTheme),
|
|
25
|
+
algorithm: isDark
|
|
26
|
+
? antdThemeAlgorithm.darkAlgorithm
|
|
27
|
+
: antdThemeAlgorithm.defaultAlgorithm,
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
<AntdApp>{children}</AntdApp>
|
|
31
|
+
</ConfigProvider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ant Design style overrides
|
|
3
|
+
* Use this file to customize Ant Design components with CSS
|
|
4
|
+
* Prefer using the theme config when possible
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* Make Ant Design work better with Tailwind's reset */
|
|
8
|
+
.ant-btn {
|
|
9
|
+
font-weight: 500;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Improve form spacing */
|
|
13
|
+
.ant-form-item {
|
|
14
|
+
margin-bottom: 16px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ant-form-item:last-child {
|
|
18
|
+
margin-bottom: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Better table styling */
|
|
22
|
+
.ant-table {
|
|
23
|
+
border-radius: 8px;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.ant-table-thead > tr > th {
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Card improvements */
|
|
32
|
+
.ant-card {
|
|
33
|
+
border-radius: 8px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ant-card-head {
|
|
37
|
+
min-height: 48px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Modal improvements */
|
|
41
|
+
.ant-modal-content {
|
|
42
|
+
border-radius: 12px;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.ant-modal-header {
|
|
47
|
+
border-bottom: 1px solid var(--ant-color-border-secondary);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.ant-modal-footer {
|
|
51
|
+
border-top: 1px solid var(--ant-color-border-secondary);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Dropdown improvements */
|
|
55
|
+
.ant-dropdown-menu {
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
padding: 4px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ant-dropdown-menu-item {
|
|
61
|
+
border-radius: 4px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Select improvements */
|
|
65
|
+
.ant-select-dropdown {
|
|
66
|
+
border-radius: 8px;
|
|
67
|
+
padding: 4px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.ant-select-item {
|
|
71
|
+
border-radius: 4px;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Date picker improvements */
|
|
75
|
+
.ant-picker-dropdown {
|
|
76
|
+
border-radius: 8px;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Message improvements */
|
|
80
|
+
.ant-message .ant-message-notice-content {
|
|
81
|
+
border-radius: 8px;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Notification improvements */
|
|
85
|
+
.ant-notification-notice {
|
|
86
|
+
border-radius: 8px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Tag improvements */
|
|
90
|
+
.ant-tag {
|
|
91
|
+
border-radius: 4px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Skeleton improvements for dark mode */
|
|
95
|
+
.dark .ant-skeleton-content .ant-skeleton-title,
|
|
96
|
+
.dark .ant-skeleton-content .ant-skeleton-paragraph > li {
|
|
97
|
+
background: linear-gradient(
|
|
98
|
+
90deg,
|
|
99
|
+
rgba(255, 255, 255, 0.06) 25%,
|
|
100
|
+
rgba(255, 255, 255, 0.12) 37%,
|
|
101
|
+
rgba(255, 255, 255, 0.06) 63%
|
|
102
|
+
);
|
|
103
|
+
background-size: 400% 100%;
|
|
104
|
+
}
|