diff --git a/.claude/commands/convert-icons.md b/.claude/commands/convert-icons.md new file mode 100644 index 0000000..5e34bfc --- /dev/null +++ b/.claude/commands/convert-icons.md @@ -0,0 +1,36 @@ +# Convert Icons + +Convert raw SVG icons to React TSX components using SVGR. + +## Usage + +``` +/convert-icons +``` + +## Instructions + +1. Check that raw SVG files exist in `src/shared/assets/raw-icons/`. If the directory doesn't exist or is empty, inform the user and ask them to place SVG files there first. + +2. Run the conversion: + ```bash + bun run gicons + ``` + This executes: + ``` + npx @svgr/cli --ext tsx --typescript --no-prettier --icon --ref --no-svgo ./src/shared/assets/raw-icons/ --out-dir ./src/shared/ui/Icons/ + ``` + +3. Report which icon components were generated in `src/shared/ui/Icons/`. + +4. The generated components can be imported as: + ```tsx + import { IconName } from "@shared/ui/Icons/IconName" + ``` + +## Notes + +- Raw SVGs go in: `src/shared/assets/raw-icons/` +- Generated TSX components output to: `src/shared/ui/Icons/` +- SVGR flags: TypeScript, icon mode (scales with font-size), forwardRef support, no Prettier formatting, no SVGO optimization +- The primary icon library is `lucide-react` — custom SVG icons are for icons not available in Lucide diff --git a/.claude/commands/gc.md b/.claude/commands/gc.md new file mode 100644 index 0000000..af33557 --- /dev/null +++ b/.claude/commands/gc.md @@ -0,0 +1,55 @@ +# Generate Component (gc) + +Generate an FSD component using the project's generator script. + +## Usage + +``` +/gc +``` + +**Arguments:** +- `$ARGUMENTS` — expects ` `, e.g. `shared Button`, `entity ProjectCard`, `feature CreateProjectModal` + +## Layers + +| Alias | Path | +|-------|------| +| `shared` | `src/shared/ui/` | +| `entity` / `entities` | `src/entities/` | +| `feature` / `features` | `src/features/` | +| `widget` / `widgets` | `src/widgets/` | +| `page` / `pages` | `src/pages/` | + +## Instructions + +1. Parse `$ARGUMENTS` to extract `` and ``. If arguments are missing or unclear, ask the user. + +2. Run the generator: + ```bash + bun run gc + ``` + +3. This creates 4 files: + - `index.ts` — re-exports the component + - `.tsx` — component implementation with `FunctionComponent`, `JSX.Element`, SCSS module import, `data-testid` + - `.d.ts` — props interface `IProps` with `className?: string` + - `.module.scss` — empty `.root {}` class + +4. After generation, report the created files and the full path. + +5. If the user provides additional context about what the component should do or look like, modify the generated files accordingly: + - Add props to the `.d.ts` file + - Implement the component logic in `.tsx` + - Add styles to `.module.scss` + - Use existing project patterns: `classnames` as `cs`, typography/mixin includes from auto-injected SCSS partials, Radix UI primitives or Themes where appropriate, `lucide-react` icons + +## Project Conventions + +- Props interface: `IProps` in a `.d.ts` file +- Import types with `import type { ... }` +- Import order: types → react/libs → path aliases (`@shared/`, `@entities/`, etc.) → local +- Use `cs()` from `classnames` for combining classes +- Root element gets `className={styles.root}` and `data-testid=""` +- SCSS auto-injected namespaces: `variables`, `breakpoints`, `typography`, `mixins` +- Use `"use client"` directive only when component uses hooks or browser APIs diff --git a/.claude/commands/gen-api-types.md b/.claude/commands/gen-api-types.md new file mode 100644 index 0000000..2e93a9e --- /dev/null +++ b/.claude/commands/gen-api-types.md @@ -0,0 +1,51 @@ +# Generate API Types + +Fetch the OpenAPI schema from the backend and generate TypeScript types. + +## Usage + +``` +/gen-api-types +``` + +## Instructions + +1. Ensure the backend server is running at `http://127.0.0.1:8000`. If the command fails with a connection error, inform the user that the backend must be running first. + +2. Run the type generation: + ```bash + bun run gen:api-types + ``` + This executes: + ``` + openapi-typescript http://127.0.0.1:8000/api/schema/ --output src/shared/api/__generated__/openapi.types.ts + ``` + +3. Report success and note that the generated types are at `src/shared/api/__generated__/openapi.types.ts`. + +4. The generated file exports: + - `paths` — all API endpoints with request/response types + - `components` — schema definitions (used as `components["schemas"]["ModelName"]`) + - `operations` — operation-level types + +## Usage in Code + +```tsx +// Import schema types +import type { components } from "@shared/api/__generated__/openapi.types" + +type ProjectRead = components["schemas"]["ProjectRead"] + +// Use with the API client +import api from "@shared/api" + +api.useQuery("get", "/api/projects/") +api.useMutation("post", "/api/projects/", { onSuccess, onError }) +``` + +## Notes + +- Requires the backend running at `http://127.0.0.1:8000` +- Uses `openapi-typescript` to generate types from the `/api/schema/` endpoint +- The API client (`src/shared/api/index.ts`) uses `openapi-fetch` + `openapi-react-query` with these generated types +- After regeneration, check for any TypeScript errors in components that use the API types, as schema changes may break existing code diff --git a/.env b/.env index fefb75e..7c4ece2 100644 --- a/.env +++ b/.env @@ -1 +1,3 @@ -NEXT_PUBLIC_API_URL=http://localhost:8000/ \ No newline at end of file +NEXT_PUBLIC_API_URL=http://localhost:8000 +NEXT_PUBLIC_WS_URL=ws://localhost:8000 +NEXT_PUBLIC_MOCK_WS=false \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index abf49c0..8a846aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ Next.js 16 application using **Feature-Sliced Design (FSD)** architecture, power | Styling | SCSS Modules, normalize.css | | State/Fetch | TanStack React Query 5, Axios, Xior | | Animation | Framer Motion | -| Utilities | Lodash, Moment.js, classnames, usehooks-ts | +| Utilities | Lodash, date-fns, classnames, usehooks-ts | | Icons | Lucide React, SVGR (custom icons) | | Notifications | React Toastify | | File Upload | React Dropzone | @@ -174,10 +174,12 @@ export const Button: FC = ({ variant, onClick }): JSX.Element => { 2. **Public API** — export only through `index.ts` 3. **No Cross-Slice Imports** — features cannot import from other features 4. **Shared is Agnostic** — no business logic in shared layer +5. **Features are module-aware** — group features by domain inside module folders (see below) ### When to Split Files Split into separate files **only when**: + - Hook/API is reused by multiple components - File exceeds ~200 lines - Props interface is shared across 3+ components @@ -208,6 +210,72 @@ Split into separate files **only when**: --- +## Features Layer — Module-Aware Structure + +Features **must be grouped by domain module**. Never place feature folders flat at the top of `src/features/`. + +``` +src/features/ +├── profile/ # Profile domain module +│ ├── index.ts # Barrel export for all features in this module +│ ├── AvatarUpload/ +│ ├── EditProfileForm/ +│ └── LogoutButton/ +└── project/ # Project domain module + ├── index.ts + ├── CreateProjectModal/ + └── ... +``` + +**Rules:** +- Each module folder has an `index.ts` barrel that re-exports all its features +- Import via the module barrel: `import { AvatarUpload } from "@features/profile"` +- When creating a new feature, place it inside the relevant domain folder +- After running `bun run gc feature `, move the generated folder into the correct module +- Create a new module folder + barrel if the domain doesn't exist yet + +--- + +## Shared Utilities + +Reusable operations should live in `src/shared/` — **do not inline shared logic inside feature components**. + +### File Upload + +Use `uploadFile()` from `@shared/api/uploadFile` for any file upload: + +```ts +import { uploadFile } from "@shared/api/uploadFile" + +const result = await uploadFile(file, "avatars") +// result.file_url — URL of the uploaded file +// result.file_path — storage path +``` + +This handles FormData construction, Content-Type header override, and JWT auth automatically. + +### Date Formatting + +Use `date-fns` with Russian locale via shared utilities in `src/shared/lib/dates.ts`. **Never use `moment.js` or inline `Date` formatting in components.** + +```ts +import { formatDate, formatRelativeTime } from "@shared/lib/dates" + +formatDate(user.date_joined) // "21.02.2026" (default: "dd.MM.yyyy") +formatDate(date, "dd MMM yyyy") // "21 февр. 2026" +formatRelativeTime(project.updated_at) // "2 дня назад" +``` + +Add new date helpers to `src/shared/lib/dates.ts`, not to individual components. + +### API Client + +- **In React components**: use `api.useQuery()` / `api.useMutation()` from `@shared/api` +- **Outside React** (utilities, event handlers): use `fetchClient` from `@shared/api` +- **File uploads**: use `uploadFile()` from `@shared/api/uploadFile` + +--- + ## Icons Workflow 1. Place raw SVG in `src/shared/assets/raw-icons/` @@ -236,6 +304,10 @@ Split into separate files **only when**: | API client | Use Axios/Xior with React Query | | State management | TanStack Query (server state) | +## Localization + +All user-facing UI text **must be in Russian**. This includes: labels, headings, buttons, placeholders, tooltips, aria-labels, error messages, breadcrumbs, and any other text visible to the user. The only exception is the brand name "Coffee Project" / "Cofee Project" — it stays in English. + ## Implementation sentiments Write less complicated code, simple but readable code @@ -245,3 +317,17 @@ To import classNames lib use `import cs from 'classnames'` Always install packages using `bun install ` +To test is project have no errors use +`bunx tsc --noEmit` + +--- + +## Common Mistakes to Avoid + +1. **Flat features folder** — never place feature component folders directly in `src/features/`. Always group them inside a domain module folder (`profile/`, `project/`, etc.). +2. **Inlining reusable logic** — if an operation (file upload, date formatting, etc.) could be used by multiple features, extract it to `src/shared/`. Features should be thin wrappers around shared utilities. +3. **Wrong StaticLoader import** — it lives at `@shared/ui/Loader`, not `@shared/ui/Loader/StaticLoader`. There is no subdirectory. +4. **multipart/form-data with fetchClient** — the default `fetchClient` sets `Content-Type: application/json`. For file uploads you must override headers and body serializer. Use the shared `uploadFile()` utility instead. +5. **Broken lint scripts** — `bun run lint` calls `lint:es` and `lint:prettier` which are not defined in `package.json`. Use `bunx tsc --noEmit` for type checking until lint is fixed. +6. **Generator output needs moving** — `bun run gc feature ` creates the folder flat in `src/features/`. You must manually move it into the correct domain module folder afterward. +7. **Raw `fetch` / `useEffect` for API calls** — never use plain `fetch` or `useEffect`-based polling for API requests. Always use `api.useQuery()` / `api.useMutation()` from `@shared/api` which wraps TanStack Query + openapi-fetch. For polling, use `refetchInterval`. Raw `fetch` bypasses typed routes, auth middleware, and query caching. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..54c65fe --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,126 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +See also the monorepo-level `../CLAUDE.md` for full architecture overview and backend docs. + +## Commands + +```bash +bun dev # Dev server (localhost:3000) +bun run build # Production build +bun run lint # ESLint + Prettier (concurrent) +bunx tsc --noEmit # Type-check without emitting +bun run gc # Generate FSD component (e.g. bun run gc shared Button) +bun run gicons # Convert raw SVGs → React icon components +bun run gen:api-types # Regenerate API types from OpenAPI schema (backend must be running) +``` + +## Architecture + +Next.js 16 App Router with Feature-Sliced Design. Strict unidirectional imports: `pages → widgets → features → entities → shared`. + +- **App directory** is at `app/` (project root), not `src/app/`. The `src/app/` layer holds global styles and providers. +- **`app/template.tsx`** wraps all routes with Header (conditionally hidden on auth routes). +- **All components are `"use client"`** unless explicitly marked otherwise. + +## API & Data Layer + +- **`fetchClient`** (`openapi-fetch`) — typed HTTP client with JWT middleware that reads `access_token` from cookies. Defined in `src/shared/api/index.ts`. +- **`api`** (`openapi-react-query`) — wraps `fetchClient` for use with TanStack Query hooks in components. Import as `import api from "@shared/api"`. +- **Generated types** live in `src/shared/api/__generated__/openapi.types.ts` — never edit manually. +- **Server actions** in `src/shared/api/server.ts` — used for server-side API calls (ping, token verification). + +## Styling + +- **SCSS Modules** (`.module.scss`) for all component styles. +- **SCSS partials auto-injected** via `next.config.mjs` using `@use`: `_variables.scss`, `_breakpoints.scss`, `_typography.scss`, `_mixins.scss`. No need to import them manually in `.module.scss` files. +- **Radix UI Themes** wraps the app (`accentColor="iris"`, `grayColor="slate"`). Some components use Radix primitives directly (e.g., Dropdown uses `@radix-ui/react-dropdown-menu`, not Radix Themes). +- **Class composition**: `import cs from "classnames"`. +- **Design tokens** defined as CSS custom properties in `src/shared/styles/global.scss`, mirrored as SCSS vars in `_variables.scss`. + +## State Management + +- **Server state**: TanStack React Query (primary for all API data). +- **Client state**: Redux Toolkit with two slices: `appState` and `user` (in `src/shared/store/`). +- **Provider hierarchy** (in `src/shared/context/AppProviders.tsx`): Redux → QueryClient → UserSync → Radix Theme. + +## Component Convention + +Generate new components with `bun run gc ` — never create component files manually. Each component folder contains: +- `index.ts` — public re-export only +- `ComponentName.tsx` — implementation +- `ComponentName.module.scss` — scoped styles +- `ComponentName.d.ts` — props interface (`IComponentNameProps`) + +## Code Style + +- **Prettier**: tabs (width 2), no semicolons, double quotes, sorted imports. +- **Imports**: use path aliases (`@shared/*`, `@entities/*`, etc.), never relative paths across layers. +- **Forms**: `react-hook-form` for form state management. +- **Icons**: Lucide React for standard icons. Custom icons: place SVG in `src/shared/assets/raw-icons/`, run `bun run gicons`, import from `@shared/ui/Icons/IconName`. + +## Features Layer — Module-Aware Structure + +Features are **grouped by domain module**, not placed flat at the top level. Each module folder has a barrel `index.ts`: + +``` +src/features/ +├── profile/ # Profile domain +│ ├── index.ts # Barrel: re-exports all features in module +│ ├── AvatarUpload/ +│ ├── EditProfileForm/ +│ └── LogoutButton/ +└── project/ # Project domain + ├── index.ts + ├── CreateProjectModal/ + ├── DeleteProjectModal/ + ├── EditProjectModal/ + └── RenameProjectModal/ +``` + +Import via module barrel: `import { AvatarUpload, EditProfileForm } from "@features/profile"`. + +When adding a new feature, place it inside the relevant domain module folder (create one if needed). + +## File Uploads + +Use the shared `uploadFile` utility for any file upload — do not inline FormData logic in components: + +```ts +import { uploadFile } from "@shared/api/uploadFile" +const result = await uploadFile(file, "avatars") +// result.file_url, result.file_path +``` + +The utility handles FormData construction, Content-Type override, and auth middleware automatically. + +## Date Formatting + +Use `date-fns` with Russian locale for all date formatting — never use `moment.js` or inline `Date` logic: + +```ts +import { formatDate, formatRelativeTime } from "@shared/lib/dates" + +formatDate(user.date_joined) // "21.02.2026" +formatDate(date, "dd MMM yyyy") // "21 февр. 2026" +formatRelativeTime(project.updated_at) // "2 дня назад" +``` + +Utilities live in `src/shared/lib/dates.ts`. Add new date helpers there, not in components. + +## Localization + +All user-facing UI text **must be in Russian** — labels, headings, buttons, placeholders, tooltips, aria-labels, error messages, breadcrumbs. The brand name "Coffee Project" / "Cofee Project" stays in English. + +## Gotchas + +- The `pages/` directory in the project root must exist (even if empty) — removing it causes Next.js build errors. +- Dropdown component's `asChild` trigger applies both the Dropdown's trigger class AND the child's class. Avoid `all: unset` on Dropdown triggers — it strips child flex/display styles. +- SCSS auto-import uses `@use` (not `@import`), so variables/mixins are namespaced (e.g., `variables.$color`). The files are injected as `additionalData` in `next.config.mjs`. +- **openapi-fetch + multipart**: `fetchClient` defaults to `Content-Type: application/json`. For file uploads, you must override the header and body serializer. Use the shared `uploadFile()` utility instead of doing this manually. +- **StaticLoader** is exported from `@shared/ui/Loader` (file is `Loader.tsx`), not from `@shared/ui/Loader/StaticLoader` — there is no subdirectory. +- **`lint:es` / `lint:prettier` scripts** are referenced by `bun run lint` but not defined in `package.json`. Linting is currently broken — use `bunx tsc --noEmit` for type checking. +- **`next/image` remote hosts**: External image hostnames must be listed in `next.config.mjs` `images.remotePatterns`. MinIO (`localhost:9000`) is already configured. If you add another storage backend, add its hostname there too. +- **Stale OpenAPI types**: Always run `bun run gen:api-types` before implementing against the API if the backend has changed. Stale types cause silent 404s at runtime. +- **Never use raw `fetch`/`useEffect` for API calls** — always use `api.useQuery()`/`api.useMutation()` from `@shared/api` (TanStack Query + openapi-fetch wrapper). For polling, use the `refetchInterval` option. Raw `fetch` bypasses typed routes, auth middleware, and query caching. diff --git a/app/(protected)/profile/page.tsx b/app/(protected)/profile/page.tsx new file mode 100644 index 0000000..3ccae91 --- /dev/null +++ b/app/(protected)/profile/page.tsx @@ -0,0 +1,11 @@ +import { JSX } from "react" + +import { ProfilePage } from "@pages/ProfilePage" + +export default function Profile(): JSX.Element { + return ( +
+ +
+ ) +} diff --git a/app/layout.tsx b/app/layout.tsx index 9bfba6a..594d114 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -24,7 +24,14 @@ export default function RootLayout({ children: ReactNode }>) { return ( - + + +