create-react-scaffold-cli 0.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/bin/index.js +34 -0
- package/package.json +15 -0
- package/scripts/createProject.js +30 -0
- package/templates/base/.env +1 -0
- package/templates/base/.husky/pre-commit +1 -0
- package/templates/base/.husky/pre-push +0 -0
- package/templates/base/.prettierrc +8 -0
- package/templates/base/.vscode/extensions.json +8 -0
- package/templates/base/.vscode/settings.json +16 -0
- package/templates/base/eslint.config.js +48 -0
- package/templates/base/index.html +16 -0
- package/templates/base/jsconfig.json +7 -0
- package/templates/base/package.json +64 -0
- package/templates/base/postcss.config.mjs +7 -0
- package/templates/base/readme.md +97 -0
- package/templates/base/src/app/App.jsx +13 -0
- package/templates/base/src/app/Router.jsx +4 -0
- package/templates/base/src/app/app_readme.md +74 -0
- package/templates/base/src/app/index.css +1 -0
- package/templates/base/src/app/main.jsx +10 -0
- package/templates/base/src/app/middlewares/index.js +0 -0
- package/templates/base/src/app/providers/QueryProvider.jsx +75 -0
- package/templates/base/src/app/providers/index.js +1 -0
- package/templates/base/src/features/features_readme.md +102 -0
- package/templates/base/src/features/index.js +0 -0
- package/templates/base/src/features/sample/components/index.js +0 -0
- package/templates/base/src/features/sample/constants/index.js +0 -0
- package/templates/base/src/features/sample/constants/sample.constants.js +0 -0
- package/templates/base/src/features/sample/hooks/index.js +0 -0
- package/templates/base/src/features/sample/pages/index.js +0 -0
- package/templates/base/src/features/sample/sample.assets.js +0 -0
- package/templates/base/src/features/sample/sample.context.js +0 -0
- package/templates/base/src/features/sample/sample.navigations.js +0 -0
- package/templates/base/src/features/sample/sample.queryKeys.js +0 -0
- package/templates/base/src/features/sample/sample.routes.jsx +0 -0
- package/templates/base/src/shared/constants/app.constants.js +4 -0
- package/templates/base/src/shared/constants/assets.constants.js +0 -0
- package/templates/base/src/shared/constants/index.js +0 -0
- package/templates/base/src/shared/contexts/index.js +0 -0
- package/templates/base/src/shared/hooks/index.js +3 -0
- package/templates/base/src/shared/hooks/useBooleanState.js +19 -0
- package/templates/base/src/shared/hooks/useDebounce.js +17 -0
- package/templates/base/src/shared/hooks/useToggleState.js +11 -0
- package/templates/base/src/shared/layouts/index.js +0 -0
- package/templates/base/src/shared/libs/axios.js +6 -0
- package/templates/base/src/shared/libs/cn.js +7 -0
- package/templates/base/src/shared/libs/index.js +2 -0
- package/templates/base/src/shared/shared_readme.md +98 -0
- package/templates/base/src/shared/theme/index.js +1 -0
- package/templates/base/src/shared/theme/theme.js +2138 -0
- package/templates/base/src/shared/ui/Box.jsx +200 -0
- package/templates/base/src/shared/ui/Button.jsx +150 -0
- package/templates/base/src/shared/ui/Checkbox.jsx +112 -0
- package/templates/base/src/shared/ui/DropdownMenu.jsx +152 -0
- package/templates/base/src/shared/ui/Flex.jsx +151 -0
- package/templates/base/src/shared/ui/FlexItem.jsx +96 -0
- package/templates/base/src/shared/ui/FormField.jsx +184 -0
- package/templates/base/src/shared/ui/Grid.jsx +151 -0
- package/templates/base/src/shared/ui/GridItem.jsx +95 -0
- package/templates/base/src/shared/ui/Modal.jsx +43 -0
- package/templates/base/src/shared/ui/Scrollable.jsx +47 -0
- package/templates/base/src/shared/ui/Select.jsx +207 -0
- package/templates/base/src/shared/ui/Sheet.jsx +112 -0
- package/templates/base/src/shared/ui/Text.jsx +122 -0
- package/templates/base/src/shared/ui/Toaster.jsx +31 -0
- package/templates/base/src/shared/ui/index.js +1 -0
- package/templates/base/src/shared/utils/getClassName.js +5 -0
- package/templates/base/src/shared/utils/index.js +4 -0
- package/templates/base/src/shared/utils/memo.js +3 -0
- package/templates/base/src/shared/utils/parser.js +41 -0
- package/templates/base/src/shared/utils/tryCatch.js +13 -0
- package/templates/base/vite.config.js +19 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import { createProject } from '../scripts/createProject.js';
|
|
5
|
+
|
|
6
|
+
async function init() {
|
|
7
|
+
const responses = await prompts([
|
|
8
|
+
{
|
|
9
|
+
type: 'text',
|
|
10
|
+
name: 'projectName',
|
|
11
|
+
message: 'What is your project name?',
|
|
12
|
+
validate: (value) => (value ? true : 'Project name is required'),
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
type: 'toggle',
|
|
16
|
+
name: 'installDeps',
|
|
17
|
+
message: 'Do you want to install dependencies now?',
|
|
18
|
+
initial: true,
|
|
19
|
+
active: 'yes',
|
|
20
|
+
inactive: 'no',
|
|
21
|
+
},
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
const { projectName, installDeps } = responses;
|
|
25
|
+
|
|
26
|
+
if (!projectName) {
|
|
27
|
+
console.log('❌ Project creation cancelled');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await createProject(projectName, { installDeps });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
init();
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-react-scaffold-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-react-scaffold": "bin/index.js"
|
|
7
|
+
},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"commander": "^14.0.2",
|
|
10
|
+
"execa": "^9.6.1",
|
|
11
|
+
"fs-extra": "^11.3.3",
|
|
12
|
+
"prompts": "^2.4.2",
|
|
13
|
+
"react-scaffold": "^0.1.3"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export async function createProject(name, options) {
|
|
10
|
+
const { installDeps } = options;
|
|
11
|
+
|
|
12
|
+
const targetDir = path.resolve(process.cwd(), name);
|
|
13
|
+
const templateDir = path.resolve(__dirname, '../templates/base');
|
|
14
|
+
|
|
15
|
+
console.log(`📁 Creating project: ${name}`);
|
|
16
|
+
await fs.copy(templateDir, targetDir);
|
|
17
|
+
|
|
18
|
+
if (installDeps) {
|
|
19
|
+
console.log('📦 Installing dependencies...');
|
|
20
|
+
await execa('npm', ['install'], {
|
|
21
|
+
cwd: targetDir,
|
|
22
|
+
stdio: 'inherit',
|
|
23
|
+
});
|
|
24
|
+
} else {
|
|
25
|
+
console.log('⏭ Skipped dependency installation');
|
|
26
|
+
console.log(`➡ Run 'npm install' inside ${name} when ready`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log('✅ Project ready!');
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VITE_BACKEND_URL=""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npx lint-staged
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"typescript.updateImportsOnFileMove.enabled": "always",
|
|
3
|
+
"javascript.updateImportsOnFileMove.enabled": "always",
|
|
4
|
+
"editor.formatOnPaste": true,
|
|
5
|
+
"editor.formatOnSave": true,
|
|
6
|
+
"editor.cursorSmoothCaretAnimation": "on",
|
|
7
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
8
|
+
"editor.codeActionsOnSave": {
|
|
9
|
+
"source.fixAll.eslint": "explicit"
|
|
10
|
+
},
|
|
11
|
+
"cSpell.words": ["premiumlogo"],
|
|
12
|
+
"[javascriptreact]": {
|
|
13
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
|
14
|
+
},
|
|
15
|
+
"liveServer.settings.port": 5501
|
|
16
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import react from 'eslint-plugin-react';
|
|
3
|
+
import reactHooks from 'eslint-plugin-react-hooks';
|
|
4
|
+
import reactRefresh from 'eslint-plugin-react-refresh';
|
|
5
|
+
import globals from 'globals';
|
|
6
|
+
|
|
7
|
+
export default [
|
|
8
|
+
js.configs.recommended,
|
|
9
|
+
{
|
|
10
|
+
files: ['**/*.{js,jsx}'],
|
|
11
|
+
languageOptions: {
|
|
12
|
+
ecmaVersion: 2020,
|
|
13
|
+
sourceType: 'module',
|
|
14
|
+
parseOptions: {
|
|
15
|
+
ecmaVersion: {
|
|
16
|
+
jsx: true,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
globals: {
|
|
20
|
+
...globals.browser,
|
|
21
|
+
window: 'readonly',
|
|
22
|
+
document: 'readonly',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
plugins: {
|
|
26
|
+
react,
|
|
27
|
+
'react-hooks': reactHooks,
|
|
28
|
+
'react-refresh': reactRefresh,
|
|
29
|
+
},
|
|
30
|
+
settings: {
|
|
31
|
+
react: {
|
|
32
|
+
version: 'detect',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
rules: {
|
|
36
|
+
'no-console': 'error',
|
|
37
|
+
'prefer-const': 'error',
|
|
38
|
+
'no-fallthrough': 'error',
|
|
39
|
+
'no-duplicate-imports': 'error',
|
|
40
|
+
'react/react-in-jsx-scope': 'off',
|
|
41
|
+
'react/prop-types': 'off',
|
|
42
|
+
'react-hooks/rules-of-hooks': 'error',
|
|
43
|
+
'react-hooks/exhaustive-deps': 'warn', // important for hooks
|
|
44
|
+
'no-undef': 'error', // ⚠ this one detects undefined variables
|
|
45
|
+
'no-unused-vars': ['warn', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
];
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
|
7
|
+
name="viewport"
|
|
8
|
+
content="width=device-width, initial-scale=1, viewport-fit=cover, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
9
|
+
/>
|
|
10
|
+
<title>vite-project</title>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-project",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"prepare": "husky"
|
|
12
|
+
},
|
|
13
|
+
"lint-staged": {
|
|
14
|
+
"src/**/*.{js,jsx}": [
|
|
15
|
+
"eslint --fix",
|
|
16
|
+
"prettier --write"
|
|
17
|
+
],
|
|
18
|
+
"src/**/*.css": [
|
|
19
|
+
"prettier --write"
|
|
20
|
+
],
|
|
21
|
+
"package.json": [
|
|
22
|
+
"prettier --write"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
27
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
28
|
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
29
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
30
|
+
"@tailwindcss/postcss": "^4.1.18",
|
|
31
|
+
"@tanstack/react-query": "^5.90.16",
|
|
32
|
+
"@tanstack/react-query-devtools": "^5.91.2",
|
|
33
|
+
"axios": "^1.13.2",
|
|
34
|
+
"clsx": "^2.1.1",
|
|
35
|
+
"nuqs": "^2.8.6",
|
|
36
|
+
"postcss": "^8.5.6",
|
|
37
|
+
"react": "^19.2.0",
|
|
38
|
+
"react-dom": "^19.2.0",
|
|
39
|
+
"react-hot-toast": "^2.6.0",
|
|
40
|
+
"react-icons": "^5.5.0",
|
|
41
|
+
"tailwind-merge": "^3.4.0",
|
|
42
|
+
"tailwindcss": "^4.1.18"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@eslint/js": "^9.39.2",
|
|
46
|
+
"@types/react": "^19.2.5",
|
|
47
|
+
"@types/react-dom": "^19.2.3",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^8.51.0",
|
|
49
|
+
"@typescript-eslint/parser": "^8.51.0",
|
|
50
|
+
"@vitejs/plugin-react": "^5.1.1",
|
|
51
|
+
"eslint": "^9.39.2",
|
|
52
|
+
"eslint-config-prettier": "^10.1.8",
|
|
53
|
+
"eslint-plugin-react": "^7.37.5",
|
|
54
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
55
|
+
"eslint-plugin-react-refresh": "^0.4.26",
|
|
56
|
+
"eslint-plugin-unused-imports": "^4.3.0",
|
|
57
|
+
"globals": "^16.5.0",
|
|
58
|
+
"husky": "^9.1.7",
|
|
59
|
+
"lint-staged": "^16.2.7",
|
|
60
|
+
"prettier": "3.7.4",
|
|
61
|
+
"prettier-plugin-jsdoc": "^1.8.0",
|
|
62
|
+
"vite": "^7.2.4"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# React Feature‑First Scaffold
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This repository is an **internal, opinionated React scaffold** designed for large-scale frontend applications. It enforces a **feature‑first architecture**, clear ownership boundaries, and predictable patterns so teams can scale safely without architectural drift.
|
|
6
|
+
|
|
7
|
+
This scaffold will later be distributed via an internal **NPM CLI** (similar to `create-react-app`) to bootstrap projects with best practices preconfigured.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Core Principles
|
|
12
|
+
|
|
13
|
+
- **Feature-first, not layer-first**
|
|
14
|
+
- **Explicit boundaries** between app / features / shared
|
|
15
|
+
- **Locality of logic** (keep things close to where they are used)
|
|
16
|
+
- **Predictable imports** and naming conventions
|
|
17
|
+
- **Low cognitive load** for new developers
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Tech Stack
|
|
22
|
+
|
|
23
|
+
### Runtime
|
|
24
|
+
|
|
25
|
+
- **React 19** – UI layer
|
|
26
|
+
- **Vite** – fast dev & build tooling
|
|
27
|
+
|
|
28
|
+
### Styling
|
|
29
|
+
|
|
30
|
+
- **Tailwind CSS** – utility-first styling
|
|
31
|
+
- **tailwind-merge** – class conflict resolution
|
|
32
|
+
- **clsx** – conditional class composition
|
|
33
|
+
|
|
34
|
+
### Data & State
|
|
35
|
+
|
|
36
|
+
- **@tanstack/react-query** – server-state management
|
|
37
|
+
- **Axios** – HTTP client
|
|
38
|
+
|
|
39
|
+
### UI Utilities
|
|
40
|
+
|
|
41
|
+
- **Radix UI** – accessible headless components
|
|
42
|
+
- **react-hot-toast** – notifications
|
|
43
|
+
- **react-icons** – icon system
|
|
44
|
+
|
|
45
|
+
### Code Quality
|
|
46
|
+
|
|
47
|
+
- **ESLint** – linting with strict rules
|
|
48
|
+
- **Prettier** – formatting
|
|
49
|
+
- **Husky + lint-staged** – pre-commit enforcement
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## High-Level Folder Structure
|
|
54
|
+
|
|
55
|
+
```txt
|
|
56
|
+
src/
|
|
57
|
+
app/ # Application bootstrap & global wiring
|
|
58
|
+
features/ # Business features (isolated, self-contained)
|
|
59
|
+
shared/ # Cross-feature reusable modules
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Each top-level folder has **its own README** explaining rules and responsibilities.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Installation (WIP)
|
|
67
|
+
|
|
68
|
+
> CLI installation instructions will be added once the NPM package is published.
|
|
69
|
+
|
|
70
|
+
For now:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npm install
|
|
74
|
+
npm run dev
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Documentation Index
|
|
80
|
+
|
|
81
|
+
- `src/app/README.md` – Application bootstrap & providers
|
|
82
|
+
- `src/features/README.md` – Feature architecture rules
|
|
83
|
+
- `src/shared/README.md` – Shared modules & reuse policy
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Philosophy
|
|
88
|
+
|
|
89
|
+
> **Architecture is a product feature.**
|
|
90
|
+
|
|
91
|
+
This scaffold exists to reduce decision fatigue, prevent architectural erosion, and help teams move faster with confidence.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Ownership
|
|
96
|
+
|
|
97
|
+
This scaffold is maintained internally and should be treated as a **living standard**. Changes must be intentional, documented, and agreed upon by the frontend team.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# App Module
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
The `app` layer is responsible for **bootstrapping the application**. It wires together global providers, routing, and application-level configuration.
|
|
5
|
+
|
|
6
|
+
This folder should remain **small, boring, and stable**.
|
|
7
|
+
|
|
8
|
+
> If something changes frequently, it probably does NOT belong here.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Responsibilities
|
|
13
|
+
|
|
14
|
+
The `app` module may contain:
|
|
15
|
+
|
|
16
|
+
- Application entry point (`main.jsx`)
|
|
17
|
+
- Root component (`App.jsx`)
|
|
18
|
+
- Global providers (React Query, Theme, etc.)
|
|
19
|
+
- Application router
|
|
20
|
+
- Global middlewares
|
|
21
|
+
- One-time initialization logic
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Folder Structure
|
|
26
|
+
|
|
27
|
+
```txt
|
|
28
|
+
app/
|
|
29
|
+
middlewares/ # Global middlewares (logging, guards, etc.)
|
|
30
|
+
providers/ # Global providers (QueryProvider, ThemeProvider)
|
|
31
|
+
App.jsx # Root component
|
|
32
|
+
Router.jsx # App-level router
|
|
33
|
+
main.jsx # Application entry
|
|
34
|
+
index.css # Global styles
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Rules
|
|
40
|
+
|
|
41
|
+
### ✅ Allowed
|
|
42
|
+
- Global providers
|
|
43
|
+
- App-wide configuration
|
|
44
|
+
- Router composition
|
|
45
|
+
|
|
46
|
+
### ❌ Not Allowed
|
|
47
|
+
- Feature-specific logic
|
|
48
|
+
- Business rules
|
|
49
|
+
- UI components
|
|
50
|
+
- API calls
|
|
51
|
+
- Feature contexts
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Import Rules
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
// ✅ Allowed
|
|
59
|
+
import { QueryProvider } from './providers';
|
|
60
|
+
import { AppRouter } from './Router';
|
|
61
|
+
|
|
62
|
+
// ❌ Not Allowed
|
|
63
|
+
import { LoginForm } from '@/features/auth/components';
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Philosophy
|
|
69
|
+
|
|
70
|
+
The `app` layer is a **composition root**, not a feature owner.
|
|
71
|
+
|
|
72
|
+
Keep it minimal.
|
|
73
|
+
Keep it predictable.
|
|
74
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import 'tailwindcss';
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { MutationCache, QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
|
+
import { AxiosError } from 'axios';
|
|
4
|
+
import toast from 'react-hot-toast';
|
|
5
|
+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
6
|
+
|
|
7
|
+
export const QueryProvider = ({ children }) => {
|
|
8
|
+
const [queryCache] = React.useState(
|
|
9
|
+
new QueryCache({
|
|
10
|
+
onError: () => {},
|
|
11
|
+
})
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const [mutationCache] = React.useState(
|
|
15
|
+
new MutationCache({
|
|
16
|
+
onError: (error) => {
|
|
17
|
+
const isAxiosError = error instanceof AxiosError;
|
|
18
|
+
|
|
19
|
+
if (!isAxiosError) {
|
|
20
|
+
return toast.error('Something went wrong.');
|
|
21
|
+
}
|
|
22
|
+
if (error.code === AxiosError.ERR_NETWORK) {
|
|
23
|
+
return toast.error('Network Error');
|
|
24
|
+
}
|
|
25
|
+
if (error.status === 500) {
|
|
26
|
+
return toast.error('Network Error');
|
|
27
|
+
}
|
|
28
|
+
const { response } = error;
|
|
29
|
+
if (!response) {
|
|
30
|
+
return toast.error('Something went wrong');
|
|
31
|
+
}
|
|
32
|
+
const { data } = response;
|
|
33
|
+
if (!data) {
|
|
34
|
+
return toast.error('Something went wrong');
|
|
35
|
+
}
|
|
36
|
+
const { message } = data;
|
|
37
|
+
if (!message) {
|
|
38
|
+
return toast.error('Something went wrong');
|
|
39
|
+
}
|
|
40
|
+
const isIterable = Array.isArray(message);
|
|
41
|
+
if (!isIterable) {
|
|
42
|
+
return toast.error(message);
|
|
43
|
+
}
|
|
44
|
+
for (let index = 0; index < message.length; index++) {
|
|
45
|
+
const error = message[index];
|
|
46
|
+
toast.error(error);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const [client] = React.useState(
|
|
53
|
+
new QueryClient({
|
|
54
|
+
queryCache: queryCache,
|
|
55
|
+
mutationCache: mutationCache,
|
|
56
|
+
defaultOptions: {
|
|
57
|
+
queries: {
|
|
58
|
+
retry: false,
|
|
59
|
+
refetchOnWindowFocus: false,
|
|
60
|
+
refetchOnReconnect: false,
|
|
61
|
+
},
|
|
62
|
+
mutations: {
|
|
63
|
+
retry: false,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<QueryClientProvider client={client}>
|
|
71
|
+
{children}
|
|
72
|
+
<ReactQueryDevtools />
|
|
73
|
+
</QueryClientProvider>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { QueryProvider } from './QueryProvider';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Features Module
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
The `features` folder is the **core of the application**.
|
|
6
|
+
|
|
7
|
+
Each feature represents a **self-contained unit of business functionality** with clear ownership and boundaries.
|
|
8
|
+
|
|
9
|
+
> If a user-facing requirement exists, it belongs to a feature.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Feature-First Architecture
|
|
14
|
+
|
|
15
|
+
Instead of grouping by technical layers (components, hooks, services), we group by **business capability**.
|
|
16
|
+
|
|
17
|
+
This improves:
|
|
18
|
+
|
|
19
|
+
- Scalability
|
|
20
|
+
- Ownership
|
|
21
|
+
- Discoverability
|
|
22
|
+
- Refactor safety
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Feature Structure
|
|
27
|
+
|
|
28
|
+
```txt
|
|
29
|
+
features/<feature-name>/
|
|
30
|
+
assets/ # Feature-specific images/icons
|
|
31
|
+
components/ # Reusable UI within the feature
|
|
32
|
+
constants/ # Feature constants
|
|
33
|
+
hooks/ # Feature-specific hooks
|
|
34
|
+
pages/ # Route-level components
|
|
35
|
+
|
|
36
|
+
<feature>.assets.js
|
|
37
|
+
<feature>.context.js
|
|
38
|
+
<feature>.navigations.js
|
|
39
|
+
<feature>.queryKeys.js
|
|
40
|
+
<feature>.routes.jsx
|
|
41
|
+
|
|
42
|
+
index.js # Public API of the feature
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Rules
|
|
48
|
+
|
|
49
|
+
### Feature Ownership
|
|
50
|
+
|
|
51
|
+
- A feature **owns its logic, UI, assets, and state**
|
|
52
|
+
- Features should not depend on other features
|
|
53
|
+
|
|
54
|
+
### Imports
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
// ✅ Allowed
|
|
58
|
+
import { Button } from '@/shared/ui';
|
|
59
|
+
import { useAuth } from '@/features/auth';
|
|
60
|
+
|
|
61
|
+
// ❌ Not Allowed
|
|
62
|
+
import { useProfile } from '@/features/profile';
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Constants Policy
|
|
68
|
+
|
|
69
|
+
Feature constants include:
|
|
70
|
+
|
|
71
|
+
- Query keys
|
|
72
|
+
- Navigation definitions
|
|
73
|
+
- Route names
|
|
74
|
+
- Feature-level config
|
|
75
|
+
|
|
76
|
+
> Constants should stay **inside the feature** unless reused across multiple features.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Contexts
|
|
81
|
+
|
|
82
|
+
- Contexts should be **feature-scoped by default**
|
|
83
|
+
- Global contexts require strong justification
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Deletion Rule
|
|
88
|
+
|
|
89
|
+
If a feature is removed:
|
|
90
|
+
|
|
91
|
+
- Delete the entire folder
|
|
92
|
+
- No side effects elsewhere
|
|
93
|
+
|
|
94
|
+
If deletion breaks other features, boundaries are violated.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Example Feature
|
|
99
|
+
|
|
100
|
+
`sample/` is a reference feature and can be safely removed.
|
|
101
|
+
|
|
102
|
+
Use it as a blueprint for new features.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|