264 lines
12 KiB
Markdown
264 lines
12 KiB
Markdown
# pulse-web — Архитектура
|
||
|
||
**Репозиторий:** `https://git.digital-home.site/daniil/pulse-web`
|
||
**URL:** `https://pulse.digital-home.site`
|
||
**Dev:** `http://192.168.31.60:5174`
|
||
**Storybook:** `http://192.168.31.60:6006`
|
||
|
||
## Общая архитектура
|
||
|
||
React SPA (Single Page Application):
|
||
|
||
```
|
||
src/
|
||
api/ ← API-функции (axios, по доменам)
|
||
store/ ← State management (Zustand)
|
||
contexts/ ← React Context (тема)
|
||
pages/ ← Страницы (маршруты)
|
||
components/ ← Переиспользуемые компоненты
|
||
main.jsx ← Точка входа (BrowserRouter + ThemeContext)
|
||
App.jsx ← Роутер (Routes/Route)
|
||
index.css ← Глобальные стили (Tailwind)
|
||
```
|
||
|
||
**Стек:**
|
||
- Framework: React 18
|
||
- Bundler: Vite 5
|
||
- Роутинг: React Router DOM 6
|
||
- State: Zustand 4
|
||
- HTTP: Axios (с interceptors для JWT refresh)
|
||
- UI: Tailwind CSS 3
|
||
- Анимации: Framer Motion 11
|
||
- Иконки: Lucide React
|
||
- Графики: Recharts 2
|
||
- Утилиты дат: date-fns 3
|
||
- Компонент-либа: Storybook 8
|
||
- Тесты: Vitest + Testing Library
|
||
|
||
## Структура папок
|
||
|
||
```
|
||
pulse-web/
|
||
├── src/
|
||
│ ├── api/
|
||
│ │ ├── client.js # Axios instance + JWT interceptor
|
||
│ │ ├── auth.js # (в store/auth.js)
|
||
│ │ ├── tasks.js # tasksApi: list, today, create, complete...
|
||
│ │ ├── habits.js # habitsApi: list, create, log, stats...
|
||
│ │ ├── finance.js # financeApi: categories, transactions, summary
|
||
│ │ ├── savings.js # savingsApi: categories, transactions, stats
|
||
│ │ └── profile.js # profileApi: get, update
|
||
│ ├── store/
|
||
│ │ └── auth.js # useAuthStore (Zustand): user, login, logout
|
||
│ ├── contexts/
|
||
│ │ └── ThemeContext.jsx # ThemeProvider: light/dark, localStorage
|
||
│ ├── pages/
|
||
│ │ ├── Home.jsx # Главная страница (дашборд)
|
||
│ │ ├── Tracker.jsx # Трекер (таб: привычки/задачи/статистика)
|
||
│ │ ├── Habits.jsx # Страница привычек (встраивается в Tracker)
|
||
│ │ ├── Tasks.jsx # Страница задач (встраивается в Tracker)
|
||
│ │ ├── Stats.jsx # Статистика (встраивается в Tracker)
|
||
│ │ ├── Finance.jsx # Финансы (таб: обзор/транзакции/аналитика/категории)
|
||
│ │ ├── Savings.jsx # Накопления
|
||
│ │ ├── Settings.jsx # Настройки пользователя
|
||
│ │ ├── Login.jsx # Вход
|
||
│ │ ├── Register.jsx # Регистрация
|
||
│ │ ├── ForgotPassword.jsx # Сброс пароля
|
||
│ │ ├── ResetPassword.jsx # Новый пароль (по токену из email)
|
||
│ │ └── VerifyEmail.jsx # Подтверждение email
|
||
│ ├── components/
|
||
│ │ ├── Navigation.jsx # Нижняя навигация (fixed bottom)
|
||
│ │ ├── CreateTaskModal.jsx
|
||
│ │ ├── EditTaskModal.jsx
|
||
│ │ ├── CreateHabitModal.jsx
|
||
│ │ ├── EditHabitModal.jsx
|
||
│ │ ├── LogHabitModal.jsx
|
||
│ │ └── finance/
|
||
│ │ ├── FinanceDashboard.jsx # Обзор месяца
|
||
│ │ ├── TransactionList.jsx # Список транзакций
|
||
│ │ ├── FinanceAnalytics.jsx # Графики трендов
|
||
│ │ ├── CategoriesManager.jsx # Управление категориями
|
||
│ │ └── AddTransactionModal.jsx
|
||
│ ├── App.jsx # Routes + ProtectedRoute/PublicRoute
|
||
│ ├── main.jsx # ReactDOM.render + Providers
|
||
│ └── index.css # Tailwind + кастомные стили
|
||
├── public/
|
||
├── package.json
|
||
├── vite.config.js
|
||
├── tailwind.config.js
|
||
├── nginx.conf
|
||
└── Dockerfile
|
||
```
|
||
|
||
## Страницы / Роуты
|
||
|
||
| Путь | Компонент | Защита | Описание |
|
||
|------|-----------|--------|----------|
|
||
| `/login` | `Login` | Public only | Форма входа |
|
||
| `/register` | `Register` | Public only | Форма регистрации |
|
||
| `/forgot-password` | `ForgotPassword` | Public only | Запрос сброса пароля |
|
||
| `/verify-email` | `VerifyEmail` | Нет | Подтверждение email по токену |
|
||
| `/reset-password` | `ResetPassword` | Нет | Установка нового пароля |
|
||
| `/` | `Home` | Protected | Главная: дашборд |
|
||
| `/tracker` | `Tracker` | Protected | Трекер привычек/задач/статистики |
|
||
| `/habits` | → `/tracker` | Protected | Редирект на трекер |
|
||
| `/tasks` | → `/tracker` | Protected | Редирект на трекер |
|
||
| `/stats` | → `/tracker` | Protected | Редирект на трекер |
|
||
| `/savings` | `Savings` | Protected | Накопления |
|
||
| `/settings` | `Settings` | Protected | Настройки аккаунта |
|
||
| `*` | → `/` | — | Любой неизвестный → главная |
|
||
|
||
### Tracker (вкладки)
|
||
`/tracker` содержит 3 вкладки:
|
||
1. **Привычки** — `Habits` компонент
|
||
2. **Задачи** — `Tasks` компонент
|
||
3. **Статистика** — `Stats` компонент
|
||
|
||
### Finance (вкладки)
|
||
`/finance` (доступна через роутер если добавить) содержит 4 вкладки:
|
||
1. **Обзор** — `FinanceDashboard`
|
||
2. **Транзакции** — `TransactionList`
|
||
3. **Аналитика** — `FinanceAnalytics`
|
||
4. **Категории** — `CategoriesManager`
|
||
|
||
> ⚠️ Страница Finance рендерится, но **нет роута** в App.jsx. Доступ только если добавить `<Route path="/finance">`.
|
||
|
||
## Основные компоненты
|
||
|
||
| Компонент | Файл | Назначение |
|
||
|-----------|------|------------|
|
||
| `Navigation` | components/Navigation.jsx | Нижняя навбар (Главная, Трекер, Накопления, Настройки). Показывает Finance только для `user.id === 1` (owner) |
|
||
| `ProtectedRoute` | App.jsx | Редиректит на `/login` если не авторизован |
|
||
| `PublicRoute` | App.jsx | Редиректит на `/` если уже авторизован |
|
||
| `FinanceDashboard` | finance/ | Обзор месяца: баланс, доходы/расходы, по категориям, дневной график |
|
||
| `TransactionList` | finance/ | Список транзакций с фильтрацией |
|
||
| `FinanceAnalytics` | finance/ | Recharts: тренды по месяцам |
|
||
| `CategoriesManager` | finance/ | CRUD категорий |
|
||
| `AddTransactionModal` | finance/ | Модал добавления транзакции |
|
||
| `CreateTaskModal` | components/ | Создание задачи |
|
||
| `EditTaskModal` | components/ | Редактирование задачи |
|
||
| `CreateHabitModal` | components/ | Создание привычки |
|
||
| `LogHabitModal` | components/ | Отметка привычки |
|
||
|
||
## API вызовы
|
||
|
||
### Axios Client (`src/api/client.js`)
|
||
|
||
```js
|
||
const api = axios.create({ baseURL: VITE_API_URL })
|
||
|
||
// Request interceptor: добавляет Bearer токен из localStorage
|
||
api.interceptors.request → Authorization: Bearer <access_token>
|
||
|
||
// Response interceptor: при 401 делает refresh и повторяет запрос
|
||
api.interceptors.response → POST /auth/refresh → обновляет tokens в localStorage
|
||
```
|
||
|
||
### API модули
|
||
|
||
```js
|
||
// tasks.js
|
||
tasksApi.list(completed?) → GET /tasks
|
||
tasksApi.today() → GET /tasks/today
|
||
tasksApi.create(data) → POST /tasks
|
||
tasksApi.complete(id) → POST /tasks/{id}/complete
|
||
tasksApi.uncomplete(id) → POST /tasks/{id}/uncomplete
|
||
tasksApi.update(id, data) → PUT /tasks/{id}
|
||
tasksApi.delete(id) → DELETE /tasks/{id}
|
||
|
||
// habits.js
|
||
habitsApi.list() → GET /habits
|
||
habitsApi.create(data) → POST /habits
|
||
habitsApi.log(id, data) → POST /habits/{id}/log
|
||
habitsApi.stats() → GET /habits/stats
|
||
habitsApi.habitStats(id) → GET /habits/{id}/stats
|
||
|
||
// finance.js
|
||
financeApi.getCategories() → GET /finance/categories
|
||
financeApi.createCategory(data) → POST /finance/categories
|
||
financeApi.getTransactions(m, y) → GET /finance/transactions?month=&year=
|
||
financeApi.createTransaction(data) → POST /finance/transactions
|
||
financeApi.getSummary(m, y) → GET /finance/summary
|
||
financeApi.getAnalytics() → GET /finance/analytics
|
||
|
||
// savings.js
|
||
savingsApi.getCategories() → GET /savings/categories
|
||
savingsApi.createCategory(data) → POST /savings/categories
|
||
savingsApi.getTransactions() → GET /savings/transactions
|
||
savingsApi.getStats() → GET /savings/stats
|
||
|
||
// profile.js
|
||
profileApi.get() → GET /profile
|
||
profileApi.update(data) → PUT /profile
|
||
```
|
||
|
||
## State Management
|
||
|
||
**Zustand** — единственный store: `src/store/auth.js`
|
||
|
||
```js
|
||
useAuthStore = {
|
||
user: null | User,
|
||
isLoading: boolean,
|
||
isAuthenticated: boolean,
|
||
|
||
initialize() // Вызывается в App.jsx useEffect: GET /auth/me
|
||
login(email, password) // POST /auth/login, сохраняет tokens
|
||
register(email, username, password)
|
||
logout() // Чистит localStorage + state
|
||
}
|
||
```
|
||
|
||
**React Context:**
|
||
- `ThemeContext` (`contexts/ThemeContext.jsx`) — dark/light mode, сохраняется в localStorage
|
||
|
||
**Нет Redux / React Query** — данные загружаются локально в компонентах через `useState + useEffect`.
|
||
|
||
## Основные зависимости
|
||
|
||
| Пакет | Версия | Назначение |
|
||
|-------|--------|------------|
|
||
| react | 18.2 | UI framework |
|
||
| react-router-dom | 6.22 | Роутинг |
|
||
| zustand | 4.5 | State management |
|
||
| axios | 1.6 | HTTP клиент |
|
||
| tailwindcss | 3.4 | CSS utility framework |
|
||
| framer-motion | 11 | Анимации |
|
||
| lucide-react | 0.312 | Иконки |
|
||
| recharts | 2.12 | Графики |
|
||
| date-fns | 3.3 | Утилиты дат |
|
||
| clsx | 2.1 | Условные классы |
|
||
| storybook | 8.5 | UI компонент-браузер (dev) |
|
||
| vitest | 4 | Тесты |
|
||
|
||
## Конфигурация
|
||
|
||
| Env переменная | Описание | Default |
|
||
|---------------|----------|---------|
|
||
| `VITE_API_URL` | URL backend API | `https://api.digital-home.site` |
|
||
|
||
## Где искать что
|
||
|
||
| Задача | Файл |
|
||
|--------|------|
|
||
| **Новая страница** | `src/pages/NewPage.jsx` + роут в `App.jsx` + ссылка в `Navigation.jsx` |
|
||
| **Новый компонент** | `src/components/NewComponent.jsx` |
|
||
| **Новый API вызов** | `src/api/<domain>.js` (добавить метод) |
|
||
| **Финансы: UI** | `src/pages/Finance.jsx`, `src/components/finance/` |
|
||
| **Финансы: API** | `src/api/finance.js` |
|
||
| **Привычки: UI** | `src/pages/Habits.jsx`, `CreateHabitModal`, `LogHabitModal` |
|
||
| **Привычки: API** | `src/api/habits.js` |
|
||
| **Задачи: UI** | `src/pages/Tasks.jsx`, `CreateTaskModal`, `EditTaskModal` |
|
||
| **Задачи: API** | `src/api/tasks.js` |
|
||
| **Авторизация** | `src/store/auth.js`, `src/pages/Login.jsx` |
|
||
| **Глобальные стили** | `src/index.css` + `tailwind.config.js` |
|
||
| **Темная тема** | `src/contexts/ThemeContext.jsx` |
|
||
| **Нижняя навигация** | `src/components/Navigation.jsx` |
|
||
|
||
## Заметки
|
||
|
||
- `Finance` страница не добавлена в роутер и навигацию для обычных пользователей — только для owner (id=1)
|
||
- Legacy роуты `/habits`, `/tasks`, `/stats` → редиректят на `/tracker`
|
||
- Токены хранятся в `localStorage`: `access_token`, `refresh_token`
|
||
- Auto-refresh токена при 401 ответе (в axios interceptor)
|