@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.
Files changed (31) hide show
  1. package/README.md +161 -0
  2. package/bin/cli.js +84 -0
  3. package/package.json +36 -0
  4. package/templates/react/.env.example +1 -0
  5. package/templates/react/README.md +70 -0
  6. package/templates/react/eslint.config.js +39 -0
  7. package/templates/react/index.html +13 -0
  8. package/templates/react/package.json +36 -0
  9. package/templates/react/src/app/App.jsx +11 -0
  10. package/templates/react/src/app/providers/i18n/index.js +40 -0
  11. package/templates/react/src/app/providers/router/index.jsx +25 -0
  12. package/templates/react/src/app/styles/index.css +28 -0
  13. package/templates/react/src/entities/.gitkeep +0 -0
  14. package/templates/react/src/features/.gitkeep +0 -0
  15. package/templates/react/src/main.jsx +11 -0
  16. package/templates/react/src/pages/about/AboutPage.jsx +91 -0
  17. package/templates/react/src/pages/about/index.js +1 -0
  18. package/templates/react/src/pages/home/HomePage.jsx +87 -0
  19. package/templates/react/src/pages/home/index.js +1 -0
  20. package/templates/react/src/shared/api/client.js +32 -0
  21. package/templates/react/src/shared/api/query-client.js +11 -0
  22. package/templates/react/src/shared/store/counter.js +8 -0
  23. package/templates/react/src/shared/ui/Button/Button.jsx +21 -0
  24. package/templates/react/src/shared/ui/Button/index.js +1 -0
  25. package/templates/react/src/shared/ui/Card/Card.jsx +8 -0
  26. package/templates/react/src/shared/ui/Card/index.js +1 -0
  27. package/templates/react/src/widgets/Header/Header.jsx +45 -0
  28. package/templates/react/src/widgets/Header/index.js +1 -0
  29. package/templates/react/src/widgets/layouts/BaseLayout/BaseLayout.jsx +13 -0
  30. package/templates/react/src/widgets/layouts/BaseLayout/index.js +1 -0
  31. 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,11 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+
3
+ export const queryClient = new QueryClient({
4
+ defaultOptions: {
5
+ queries: {
6
+ retry: 1,
7
+ refetchOnWindowFocus: false,
8
+ staleTime: 5 * 60 * 1000, // 5 minutes
9
+ },
10
+ },
11
+ });
@@ -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,8 @@
1
+ export function Card({ children, className = '', title }) {
2
+ return (
3
+ <div className={`bg-white rounded-lg shadow-md p-6 ${className}`}>
4
+ {title && <h3 className="text-xl font-bold mb-4">{title}</h3>}
5
+ {children}
6
+ </div>
7
+ );
8
+ }
@@ -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
+ });