@t1ep1l0t/create-fsd 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 +161 -0
- package/bin/cli.js +84 -0
- package/package.json +36 -0
- package/templates/react/.env.example +1 -0
- package/templates/react/README.md +70 -0
- package/templates/react/eslint.config.js +39 -0
- package/templates/react/index.html +13 -0
- package/templates/react/package.json +36 -0
- package/templates/react/src/app/App.jsx +11 -0
- package/templates/react/src/app/providers/i18n/index.js +40 -0
- package/templates/react/src/app/providers/router/index.jsx +25 -0
- package/templates/react/src/app/styles/index.css +28 -0
- package/templates/react/src/entities/.gitkeep +0 -0
- package/templates/react/src/features/.gitkeep +0 -0
- package/templates/react/src/main.jsx +11 -0
- package/templates/react/src/pages/about/AboutPage.jsx +91 -0
- package/templates/react/src/pages/about/index.js +1 -0
- package/templates/react/src/pages/home/HomePage.jsx +87 -0
- package/templates/react/src/pages/home/index.js +1 -0
- package/templates/react/src/shared/api/client.js +32 -0
- package/templates/react/src/shared/api/query-client.js +11 -0
- package/templates/react/src/shared/store/counter.js +8 -0
- package/templates/react/src/shared/ui/Button/Button.jsx +21 -0
- package/templates/react/src/shared/ui/Button/index.js +1 -0
- package/templates/react/src/shared/ui/Card/Card.jsx +8 -0
- package/templates/react/src/shared/ui/Card/index.js +1 -0
- package/templates/react/src/widgets/Header/Header.jsx +45 -0
- package/templates/react/src/widgets/Header/index.js +1 -0
- package/templates/react/src/widgets/layouts/BaseLayout/BaseLayout.jsx +13 -0
- package/templates/react/src/widgets/layouts/BaseLayout/index.js +1 -0
- package/templates/react/vite.config.js +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# create-fsd
|
|
2
|
+
|
|
3
|
+
CLI tool to create React projects with Feature-Sliced Design (FSD) architecture.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Feature-Sliced Design architecture
|
|
8
|
+
- React 18 with Vite
|
|
9
|
+
- Pre-configured libraries:
|
|
10
|
+
- React Router DOM (routing)
|
|
11
|
+
- Zustand (state management)
|
|
12
|
+
- Axios (HTTP client with interceptors)
|
|
13
|
+
- React Query (server state management)
|
|
14
|
+
- i18next (internationalization)
|
|
15
|
+
- TailwindCSS v4 (styling)
|
|
16
|
+
- Example code for all libraries
|
|
17
|
+
- Path aliases configured (@app, @pages, @widgets, @features, @entities, @shared)
|
|
18
|
+
- ESLint configured
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Using npm
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm create fsd my-app -- --template react
|
|
26
|
+
cd my-app
|
|
27
|
+
npm run dev
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Using npx
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx create-fsd my-app --template react
|
|
34
|
+
cd my-app
|
|
35
|
+
npm run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Local testing
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
node bin/cli.js my-app --template react
|
|
42
|
+
cd my-app
|
|
43
|
+
npm run dev
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Command Line Options
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
create-fsd <project-name> [options]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Options
|
|
53
|
+
|
|
54
|
+
- `-t, --template <template>` - Template to use (default: "react")
|
|
55
|
+
|
|
56
|
+
### Examples
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Create a new React project with FSD architecture
|
|
60
|
+
create-fsd my-awesome-app --template react
|
|
61
|
+
|
|
62
|
+
# Short form
|
|
63
|
+
create-fsd my-awesome-app -t react
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Project Structure
|
|
67
|
+
|
|
68
|
+
The generated project follows FSD architecture:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
src/
|
|
72
|
+
├── app/ # Application initialization
|
|
73
|
+
│ ├── providers/ # Providers (router, i18n, etc.)
|
|
74
|
+
│ ├── styles/ # Global styles
|
|
75
|
+
│ └── App.jsx # Root component
|
|
76
|
+
├── pages/ # Application pages
|
|
77
|
+
│ ├── home/
|
|
78
|
+
│ └── about/
|
|
79
|
+
├── widgets/ # Composite UI components
|
|
80
|
+
│ ├── Header/
|
|
81
|
+
│ └── layouts/
|
|
82
|
+
├── features/ # Business features (empty, ready for your code)
|
|
83
|
+
├── entities/ # Business entities (empty, ready for your code)
|
|
84
|
+
└── shared/ # Shared code
|
|
85
|
+
├── api/ # API client & query configuration
|
|
86
|
+
├── store/ # Zustand stores
|
|
87
|
+
└── ui/ # Reusable UI components
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Publishing to npm
|
|
91
|
+
|
|
92
|
+
### 1. Login to npm
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm login
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 2. Update package.json
|
|
99
|
+
|
|
100
|
+
Make sure your package.json has:
|
|
101
|
+
- Unique package name
|
|
102
|
+
- Correct version
|
|
103
|
+
- Valid author information
|
|
104
|
+
|
|
105
|
+
### 3. Publish
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm publish
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 4. Test published package
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm create fsd test-app -- --template react
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
### Install dependencies
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm install
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Test locally
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm test
|
|
129
|
+
# or
|
|
130
|
+
node bin/cli.js test-project --template react
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Add new templates
|
|
134
|
+
|
|
135
|
+
1. Create a new folder in `templates/` directory
|
|
136
|
+
2. Add all necessary files for the template
|
|
137
|
+
3. Update CLI help text if needed
|
|
138
|
+
|
|
139
|
+
## Adding More Libraries
|
|
140
|
+
|
|
141
|
+
To add more libraries to the template:
|
|
142
|
+
|
|
143
|
+
1. Add the library to `templates/react/package.json`
|
|
144
|
+
2. Create example usage in the appropriate FSD layer
|
|
145
|
+
3. Add configuration if needed (e.g., in `src/app/providers/`)
|
|
146
|
+
4. Update the template README with the new library
|
|
147
|
+
|
|
148
|
+
### Example: Adding React Hook Form
|
|
149
|
+
|
|
150
|
+
1. Add to dependencies:
|
|
151
|
+
```json
|
|
152
|
+
"react-hook-form": "^7.50.0"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
2. Create example in `src/features/auth/LoginForm.jsx`
|
|
156
|
+
|
|
157
|
+
3. Update documentation
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = path.dirname(__filename);
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name('create-fsd')
|
|
18
|
+
.description('CLI to create React projects with FSD architecture')
|
|
19
|
+
.version('1.0.0')
|
|
20
|
+
.argument('<project-name>', 'name of the project')
|
|
21
|
+
.option('-t, --template <template>', 'template to use', 'react')
|
|
22
|
+
.action(async (projectName, options) => {
|
|
23
|
+
const { template } = options;
|
|
24
|
+
|
|
25
|
+
console.log(chalk.blue.bold(`\n🚀 Creating ${projectName} with ${template} template...\n`));
|
|
26
|
+
|
|
27
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
28
|
+
|
|
29
|
+
// Check if directory exists
|
|
30
|
+
if (fs.existsSync(targetDir)) {
|
|
31
|
+
console.log(chalk.red(`❌ Directory ${projectName} already exists!`));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create project directory
|
|
36
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
37
|
+
|
|
38
|
+
const templateDir = path.join(__dirname, '..', 'templates', template);
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(templateDir)) {
|
|
41
|
+
console.log(chalk.red(`❌ Template ${template} not found!`));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Copy template files
|
|
46
|
+
const spinner = ora('Copying template files...').start();
|
|
47
|
+
try {
|
|
48
|
+
fs.copySync(templateDir, targetDir);
|
|
49
|
+
spinner.succeed('Template files copied!');
|
|
50
|
+
} catch (error) {
|
|
51
|
+
spinner.fail('Failed to copy template files');
|
|
52
|
+
console.error(error);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Update package.json with project name
|
|
57
|
+
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
58
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
59
|
+
const packageJson = fs.readJsonSync(packageJsonPath);
|
|
60
|
+
packageJson.name = projectName;
|
|
61
|
+
fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Install dependencies
|
|
65
|
+
const installSpinner = ora('Installing dependencies...').start();
|
|
66
|
+
try {
|
|
67
|
+
process.chdir(targetDir);
|
|
68
|
+
execSync('npm install', { stdio: 'inherit' });
|
|
69
|
+
installSpinner.succeed('Dependencies installed!');
|
|
70
|
+
} catch (error) {
|
|
71
|
+
installSpinner.fail('Failed to install dependencies');
|
|
72
|
+
console.error(error);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Success message
|
|
77
|
+
console.log(chalk.green.bold('\n✅ Project created successfully!\n'));
|
|
78
|
+
console.log(chalk.cyan('Next steps:'));
|
|
79
|
+
console.log(chalk.white(` cd ${projectName}`));
|
|
80
|
+
console.log(chalk.white(' npm run dev'));
|
|
81
|
+
console.log();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@t1ep1l0t/create-fsd",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to create React projects with FSD architecture",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-fsd": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"templates",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node bin/cli.js test-project --template react"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"cli",
|
|
19
|
+
"fsd",
|
|
20
|
+
"feature-sliced-design",
|
|
21
|
+
"react",
|
|
22
|
+
"vite",
|
|
23
|
+
"template"
|
|
24
|
+
],
|
|
25
|
+
"author": "t1ep1l0t",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=16.0.0"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"commander": "^11.1.0",
|
|
32
|
+
"chalk": "^5.3.0",
|
|
33
|
+
"ora": "^8.0.1",
|
|
34
|
+
"fs-extra": "^11.2.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VITE_API_URL=https://api.example.com
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# FSD React Application
|
|
2
|
+
|
|
3
|
+
A modern React application built with Feature-Sliced Design architecture.
|
|
4
|
+
|
|
5
|
+
## Technologies
|
|
6
|
+
|
|
7
|
+
- **React 18** - UI library
|
|
8
|
+
- **Vite** - Build tool and dev server
|
|
9
|
+
- **React Router DOM** - Client-side routing
|
|
10
|
+
- **Zustand** - State management
|
|
11
|
+
- **Axios** - HTTP client
|
|
12
|
+
- **React Query** - Server state management
|
|
13
|
+
- **i18next** - Internationalization
|
|
14
|
+
- **TailwindCSS v4** - Styling
|
|
15
|
+
|
|
16
|
+
## Project Structure
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
src/
|
|
20
|
+
├── app/ # Application initialization
|
|
21
|
+
│ ├── providers/ # Context providers (router, i18n)
|
|
22
|
+
│ └── styles/ # Global styles
|
|
23
|
+
├── pages/ # Page components
|
|
24
|
+
├── widgets/ # Composite UI components
|
|
25
|
+
├── features/ # Business features
|
|
26
|
+
├── entities/ # Business entities
|
|
27
|
+
└── shared/ # Shared code
|
|
28
|
+
├── api/ # API client configuration
|
|
29
|
+
├── store/ # Global stores
|
|
30
|
+
└── ui/ # Reusable UI components
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Getting Started
|
|
34
|
+
|
|
35
|
+
### Install dependencies
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Development
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm run dev
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Build for production
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm run build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Preview production build
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run preview
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
- **API URL**: Set `VITE_API_URL` in `.env` file
|
|
62
|
+
- **Languages**: Configure in `src/app/providers/i18n/index.js`
|
|
63
|
+
- **Routes**: Define in `src/app/providers/router/index.jsx`
|
|
64
|
+
- **Tailwind**: Customize in `src/app/styles/index.css`
|
|
65
|
+
|
|
66
|
+
## Learn More
|
|
67
|
+
|
|
68
|
+
- [Feature-Sliced Design](https://feature-sliced.design/)
|
|
69
|
+
- [React Documentation](https://react.dev/)
|
|
70
|
+
- [Vite Guide](https://vitejs.dev/guide/)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import js from '@eslint/js'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
import react from 'eslint-plugin-react'
|
|
4
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
5
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
6
|
+
|
|
7
|
+
export default [
|
|
8
|
+
{ ignores: ['dist'] },
|
|
9
|
+
{
|
|
10
|
+
files: ['**/*.{js,jsx}'],
|
|
11
|
+
languageOptions: {
|
|
12
|
+
ecmaVersion: 2020,
|
|
13
|
+
globals: globals.browser,
|
|
14
|
+
parserOptions: {
|
|
15
|
+
ecmaVersion: 'latest',
|
|
16
|
+
ecmaFeatures: { jsx: true },
|
|
17
|
+
sourceType: 'module',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
settings: { react: { version: '18.3' } },
|
|
21
|
+
plugins: {
|
|
22
|
+
react,
|
|
23
|
+
'react-hooks': reactHooks,
|
|
24
|
+
'react-refresh': reactRefresh,
|
|
25
|
+
},
|
|
26
|
+
rules: {
|
|
27
|
+
...js.configs.recommended.rules,
|
|
28
|
+
...react.configs.recommended.rules,
|
|
29
|
+
...react.configs['jsx-runtime'].rules,
|
|
30
|
+
...reactHooks.configs.recommended.rules,
|
|
31
|
+
'react/jsx-no-target-blank': 'off',
|
|
32
|
+
'react-refresh/only-export-components': [
|
|
33
|
+
'warn',
|
|
34
|
+
{ allowConstantExport: true },
|
|
35
|
+
],
|
|
36
|
+
'react/prop-types': 'off',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
]
|
|
@@ -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>FSD React App</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,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fsd-react-app",
|
|
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
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"react": "^18.3.1",
|
|
14
|
+
"react-dom": "^18.3.1",
|
|
15
|
+
"react-router-dom": "^6.22.0",
|
|
16
|
+
"zustand": "^4.5.0",
|
|
17
|
+
"axios": "^1.6.7",
|
|
18
|
+
"i18next": "^23.8.2",
|
|
19
|
+
"react-i18next": "^14.0.5",
|
|
20
|
+
"@tanstack/react-query": "^5.22.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/react": "^18.3.12",
|
|
24
|
+
"@types/react-dom": "^18.3.1",
|
|
25
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
26
|
+
"vite": "^6.0.3",
|
|
27
|
+
"@eslint/js": "^9.17.0",
|
|
28
|
+
"eslint": "^9.17.0",
|
|
29
|
+
"globals": "^15.14.0",
|
|
30
|
+
"eslint-plugin-react": "^7.37.2",
|
|
31
|
+
"eslint-plugin-react-hooks": "^5.0.0",
|
|
32
|
+
"eslint-plugin-react-refresh": "^0.4.16",
|
|
33
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
34
|
+
"tailwindcss": "^4.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
2
|
+
import { RouterProvider } from '@app/providers/router';
|
|
3
|
+
import { queryClient } from '@shared/api/query-client';
|
|
4
|
+
|
|
5
|
+
export function App() {
|
|
6
|
+
return (
|
|
7
|
+
<QueryClientProvider client={queryClient}>
|
|
8
|
+
<RouterProvider />
|
|
9
|
+
</QueryClientProvider>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import i18n from 'i18next';
|
|
2
|
+
import { initReactI18next } from 'react-i18next';
|
|
3
|
+
|
|
4
|
+
const resources = {
|
|
5
|
+
en: {
|
|
6
|
+
translation: {
|
|
7
|
+
welcome: 'Welcome to FSD React App',
|
|
8
|
+
home: 'Home',
|
|
9
|
+
about: 'About',
|
|
10
|
+
counter: 'Counter',
|
|
11
|
+
increment: 'Increment',
|
|
12
|
+
decrement: 'Decrement',
|
|
13
|
+
reset: 'Reset',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
ru: {
|
|
17
|
+
translation: {
|
|
18
|
+
welcome: 'Добро пожаловать в FSD React приложение',
|
|
19
|
+
home: 'Главная',
|
|
20
|
+
about: 'О нас',
|
|
21
|
+
counter: 'Счетчик',
|
|
22
|
+
increment: 'Увеличить',
|
|
23
|
+
decrement: 'Уменьшить',
|
|
24
|
+
reset: 'Сбросить',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
i18n
|
|
30
|
+
.use(initReactI18next)
|
|
31
|
+
.init({
|
|
32
|
+
resources,
|
|
33
|
+
lng: 'en',
|
|
34
|
+
fallbackLng: 'en',
|
|
35
|
+
interpolation: {
|
|
36
|
+
escapeValue: false,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export default i18n;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createBrowserRouter, RouterProvider as RRDRouterProvider } from 'react-router-dom';
|
|
2
|
+
import { HomePage } from '@pages/home';
|
|
3
|
+
import { AboutPage } from '@pages/about';
|
|
4
|
+
import { BaseLayout } from '@widgets/layouts/BaseLayout';
|
|
5
|
+
|
|
6
|
+
const router = createBrowserRouter([
|
|
7
|
+
{
|
|
8
|
+
path: '/',
|
|
9
|
+
element: <BaseLayout />,
|
|
10
|
+
children: [
|
|
11
|
+
{
|
|
12
|
+
index: true,
|
|
13
|
+
element: <HomePage />,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
path: 'about',
|
|
17
|
+
element: <AboutPage />,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export function RouterProvider() {
|
|
24
|
+
return <RRDRouterProvider router={router} />;
|
|
25
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@theme {
|
|
4
|
+
--color-primary: #3b82f6;
|
|
5
|
+
--color-secondary: #8b5cf6;
|
|
6
|
+
--color-success: #10b981;
|
|
7
|
+
--color-danger: #ef4444;
|
|
8
|
+
--color-warning: #f59e0b;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
* {
|
|
12
|
+
margin: 0;
|
|
13
|
+
padding: 0;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
body {
|
|
18
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
19
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
20
|
+
sans-serif;
|
|
21
|
+
-webkit-font-smoothing: antialiased;
|
|
22
|
+
-moz-osx-font-smoothing: grayscale;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
code {
|
|
26
|
+
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
27
|
+
monospace;
|
|
28
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { App } from '@app/App';
|
|
4
|
+
import '@app/styles/index.css';
|
|
5
|
+
import '@app/providers/i18n';
|
|
6
|
+
|
|
7
|
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
8
|
+
<React.StrictMode>
|
|
9
|
+
<App />
|
|
10
|
+
</React.StrictMode>
|
|
11
|
+
);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Card } from '@shared/ui/Card';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
|
|
4
|
+
export function AboutPage() {
|
|
5
|
+
const { t } = useTranslation();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div className="max-w-3xl mx-auto space-y-6">
|
|
9
|
+
<h1 className="text-4xl font-bold text-gray-900">{t('about')}</h1>
|
|
10
|
+
|
|
11
|
+
<Card title="FSD Architecture">
|
|
12
|
+
<p className="text-gray-700 leading-relaxed mb-4">
|
|
13
|
+
This project follows Feature-Sliced Design (FSD) architecture, which provides
|
|
14
|
+
a clear and scalable folder structure for your React applications.
|
|
15
|
+
</p>
|
|
16
|
+
<div className="space-y-2">
|
|
17
|
+
<div className="flex gap-2">
|
|
18
|
+
<span className="font-mono bg-gray-100 px-2 py-1 rounded">app/</span>
|
|
19
|
+
<span className="text-gray-600">- Application initialization and providers</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div className="flex gap-2">
|
|
22
|
+
<span className="font-mono bg-gray-100 px-2 py-1 rounded">pages/</span>
|
|
23
|
+
<span className="text-gray-600">- Application pages</span>
|
|
24
|
+
</div>
|
|
25
|
+
<div className="flex gap-2">
|
|
26
|
+
<span className="font-mono bg-gray-100 px-2 py-1 rounded">widgets/</span>
|
|
27
|
+
<span className="text-gray-600">- Complex UI components</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div className="flex gap-2">
|
|
30
|
+
<span className="font-mono bg-gray-100 px-2 py-1 rounded">features/</span>
|
|
31
|
+
<span className="text-gray-600">- Business features</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div className="flex gap-2">
|
|
34
|
+
<span className="font-mono bg-gray-100 px-2 py-1 rounded">entities/</span>
|
|
35
|
+
<span className="text-gray-600">- Business entities</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div className="flex gap-2">
|
|
38
|
+
<span className="font-mono bg-gray-100 px-2 py-1 rounded">shared/</span>
|
|
39
|
+
<span className="text-gray-600">- Reusable code and UI components</span>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</Card>
|
|
43
|
+
|
|
44
|
+
<Card title="Configured Libraries">
|
|
45
|
+
<ul className="space-y-3">
|
|
46
|
+
<li>
|
|
47
|
+
<strong className="text-blue-600">React Router DOM</strong>
|
|
48
|
+
<p className="text-gray-600 text-sm">Client-side routing with nested routes</p>
|
|
49
|
+
</li>
|
|
50
|
+
<li>
|
|
51
|
+
<strong className="text-blue-600">Zustand</strong>
|
|
52
|
+
<p className="text-gray-600 text-sm">Lightweight state management</p>
|
|
53
|
+
</li>
|
|
54
|
+
<li>
|
|
55
|
+
<strong className="text-blue-600">Axios</strong>
|
|
56
|
+
<p className="text-gray-600 text-sm">HTTP client with interceptors configured</p>
|
|
57
|
+
</li>
|
|
58
|
+
<li>
|
|
59
|
+
<strong className="text-blue-600">React Query</strong>
|
|
60
|
+
<p className="text-gray-600 text-sm">Server state management and caching</p>
|
|
61
|
+
</li>
|
|
62
|
+
<li>
|
|
63
|
+
<strong className="text-blue-600">i18next</strong>
|
|
64
|
+
<p className="text-gray-600 text-sm">Internationalization (EN/RU)</p>
|
|
65
|
+
</li>
|
|
66
|
+
<li>
|
|
67
|
+
<strong className="text-blue-600">TailwindCSS v4</strong>
|
|
68
|
+
<p className="text-gray-600 text-sm">Utility-first CSS framework</p>
|
|
69
|
+
</li>
|
|
70
|
+
</ul>
|
|
71
|
+
</Card>
|
|
72
|
+
|
|
73
|
+
<Card title="Get Started">
|
|
74
|
+
<div className="space-y-4">
|
|
75
|
+
<div>
|
|
76
|
+
<h4 className="font-semibold mb-2">Development</h4>
|
|
77
|
+
<code className="block bg-gray-900 text-green-400 p-3 rounded">
|
|
78
|
+
npm run dev
|
|
79
|
+
</code>
|
|
80
|
+
</div>
|
|
81
|
+
<div>
|
|
82
|
+
<h4 className="font-semibold mb-2">Build for production</h4>
|
|
83
|
+
<code className="block bg-gray-900 text-green-400 p-3 rounded">
|
|
84
|
+
npm run build
|
|
85
|
+
</code>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</Card>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { AboutPage } from './AboutPage';
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useTranslation } from 'react-i18next';
|
|
2
|
+
import { Card } from '@shared/ui/Card';
|
|
3
|
+
import { Button } from '@shared/ui/Button';
|
|
4
|
+
import { useCounterStore } from '@shared/store/counter';
|
|
5
|
+
import { useQuery } from '@tanstack/react-query';
|
|
6
|
+
import { apiClient } from '@shared/api/client';
|
|
7
|
+
|
|
8
|
+
export function HomePage() {
|
|
9
|
+
const { t } = useTranslation();
|
|
10
|
+
const { count, increment, decrement, reset } = useCounterStore();
|
|
11
|
+
|
|
12
|
+
const { data: posts, isLoading } = useQuery({
|
|
13
|
+
queryKey: ['posts'],
|
|
14
|
+
queryFn: async () => {
|
|
15
|
+
const response = await apiClient.get('/posts?_limit=5');
|
|
16
|
+
return response.data;
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="space-y-8">
|
|
22
|
+
<div className="text-center">
|
|
23
|
+
<h1 className="text-4xl font-bold text-gray-900 mb-4">{t('welcome')}</h1>
|
|
24
|
+
<p className="text-lg text-gray-600">
|
|
25
|
+
React + Vite + FSD Architecture + TailwindCSS v4
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div className="grid md:grid-cols-2 gap-6">
|
|
30
|
+
<Card title={t('counter')}>
|
|
31
|
+
<div className="text-center space-y-4">
|
|
32
|
+
<div className="text-6xl font-bold text-blue-600">{count}</div>
|
|
33
|
+
<div className="flex gap-2 justify-center">
|
|
34
|
+
<Button onClick={decrement} variant="danger">
|
|
35
|
+
{t('decrement')}
|
|
36
|
+
</Button>
|
|
37
|
+
<Button onClick={reset} variant="secondary">
|
|
38
|
+
{t('reset')}
|
|
39
|
+
</Button>
|
|
40
|
+
<Button onClick={increment} variant="success">
|
|
41
|
+
{t('increment')}
|
|
42
|
+
</Button>
|
|
43
|
+
</div>
|
|
44
|
+
<p className="text-sm text-gray-500">Powered by Zustand</p>
|
|
45
|
+
</div>
|
|
46
|
+
</Card>
|
|
47
|
+
|
|
48
|
+
<Card title="React Query Example">
|
|
49
|
+
{isLoading ? (
|
|
50
|
+
<div className="text-center text-gray-500">Loading posts...</div>
|
|
51
|
+
) : (
|
|
52
|
+
<ul className="space-y-2">
|
|
53
|
+
{posts?.map((post) => (
|
|
54
|
+
<li key={post.id} className="p-2 bg-gray-50 rounded">
|
|
55
|
+
<div className="font-medium text-sm">{post.title}</div>
|
|
56
|
+
</li>
|
|
57
|
+
))}
|
|
58
|
+
</ul>
|
|
59
|
+
)}
|
|
60
|
+
<p className="text-sm text-gray-500 mt-4">Powered by React Query + Axios</p>
|
|
61
|
+
</Card>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<Card title="Technologies Used">
|
|
65
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
66
|
+
{[
|
|
67
|
+
'React Router',
|
|
68
|
+
'Zustand',
|
|
69
|
+
'Axios',
|
|
70
|
+
'i18next',
|
|
71
|
+
'React Query',
|
|
72
|
+
'TailwindCSS v4',
|
|
73
|
+
'Vite',
|
|
74
|
+
'FSD Architecture',
|
|
75
|
+
].map((tech) => (
|
|
76
|
+
<div
|
|
77
|
+
key={tech}
|
|
78
|
+
className="p-3 bg-blue-50 text-blue-700 rounded-lg text-center font-medium"
|
|
79
|
+
>
|
|
80
|
+
{tech}
|
|
81
|
+
</div>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
</Card>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { HomePage } from './HomePage';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
|
|
3
|
+
export const apiClient = axios.create({
|
|
4
|
+
baseURL: import.meta.env.VITE_API_URL || 'https://jsonplaceholder.typicode.com',
|
|
5
|
+
timeout: 10000,
|
|
6
|
+
headers: {
|
|
7
|
+
'Content-Type': 'application/json',
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
apiClient.interceptors.request.use(
|
|
12
|
+
(config) => {
|
|
13
|
+
const token = localStorage.getItem('token');
|
|
14
|
+
if (token) {
|
|
15
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
16
|
+
}
|
|
17
|
+
return config;
|
|
18
|
+
},
|
|
19
|
+
(error) => {
|
|
20
|
+
return Promise.reject(error);
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
apiClient.interceptors.response.use(
|
|
25
|
+
(response) => response,
|
|
26
|
+
(error) => {
|
|
27
|
+
if (error.response?.status === 401) {
|
|
28
|
+
localStorage.removeItem('token');
|
|
29
|
+
}
|
|
30
|
+
return Promise.reject(error);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
|
|
3
|
+
export const useCounterStore = create((set) => ({
|
|
4
|
+
count: 0,
|
|
5
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
6
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
7
|
+
reset: () => set({ count: 0 }),
|
|
8
|
+
}));
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function Button({ children, variant = 'primary', onClick, className = '', ...props }) {
|
|
2
|
+
const baseStyles = 'px-4 py-2 rounded-lg font-medium transition-colors duration-200';
|
|
3
|
+
|
|
4
|
+
const variantStyles = {
|
|
5
|
+
primary: 'bg-blue-600 hover:bg-blue-700 text-white',
|
|
6
|
+
secondary: 'bg-gray-600 hover:bg-gray-700 text-white',
|
|
7
|
+
success: 'bg-green-600 hover:bg-green-700 text-white',
|
|
8
|
+
danger: 'bg-red-600 hover:bg-red-700 text-white',
|
|
9
|
+
outline: 'border-2 border-blue-600 text-blue-600 hover:bg-blue-50',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<button
|
|
14
|
+
className={`${baseStyles} ${variantStyles[variant]} ${className}`}
|
|
15
|
+
onClick={onClick}
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
{children}
|
|
19
|
+
</button>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Button } from './Button';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Card } from './Card';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Link } from 'react-router-dom';
|
|
2
|
+
import { useTranslation } from 'react-i18next';
|
|
3
|
+
|
|
4
|
+
export function Header() {
|
|
5
|
+
const { t, i18n } = useTranslation();
|
|
6
|
+
|
|
7
|
+
const toggleLanguage = () => {
|
|
8
|
+
const newLang = i18n.language === 'en' ? 'ru' : 'en';
|
|
9
|
+
i18n.changeLanguage(newLang);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<header className="bg-white shadow-sm">
|
|
14
|
+
<div className="container mx-auto px-4 py-4">
|
|
15
|
+
<nav className="flex items-center justify-between">
|
|
16
|
+
<div className="flex items-center gap-8">
|
|
17
|
+
<Link to="/" className="text-xl font-bold text-blue-600">
|
|
18
|
+
FSD App
|
|
19
|
+
</Link>
|
|
20
|
+
<div className="flex gap-4">
|
|
21
|
+
<Link
|
|
22
|
+
to="/"
|
|
23
|
+
className="text-gray-700 hover:text-blue-600 transition-colors"
|
|
24
|
+
>
|
|
25
|
+
{t('home')}
|
|
26
|
+
</Link>
|
|
27
|
+
<Link
|
|
28
|
+
to="/about"
|
|
29
|
+
className="text-gray-700 hover:text-blue-600 transition-colors"
|
|
30
|
+
>
|
|
31
|
+
{t('about')}
|
|
32
|
+
</Link>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<button
|
|
36
|
+
onClick={toggleLanguage}
|
|
37
|
+
className="px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded-md text-sm transition-colors"
|
|
38
|
+
>
|
|
39
|
+
{i18n.language === 'en' ? 'RU' : 'EN'}
|
|
40
|
+
</button>
|
|
41
|
+
</nav>
|
|
42
|
+
</div>
|
|
43
|
+
</header>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Header } from './Header';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Outlet } from 'react-router-dom';
|
|
2
|
+
import { Header } from '@widgets/Header';
|
|
3
|
+
|
|
4
|
+
export function BaseLayout() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="min-h-screen bg-gray-50">
|
|
7
|
+
<Header />
|
|
8
|
+
<main className="container mx-auto px-4 py-8">
|
|
9
|
+
<Outlet />
|
|
10
|
+
</main>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BaseLayout } from './BaseLayout';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [react(), tailwindcss()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: {
|
|
10
|
+
'@': path.resolve(__dirname, './src'),
|
|
11
|
+
'@app': path.resolve(__dirname, './src/app'),
|
|
12
|
+
'@pages': path.resolve(__dirname, './src/pages'),
|
|
13
|
+
'@widgets': path.resolve(__dirname, './src/widgets'),
|
|
14
|
+
'@features': path.resolve(__dirname, './src/features'),
|
|
15
|
+
'@entities': path.resolve(__dirname, './src/entities'),
|
|
16
|
+
'@shared': path.resolve(__dirname, './src/shared'),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|