# Frontend guidelines (React + TypeScript)
This document contains coding standards for the Databasus frontend (React 19 + TypeScript + Vite + Ant Design + TailwindCSS).
For project-wide engineering philosophy, see the root `CLAUDE.md`.
---
## Table of Contents
- [UI kit and icons](#ui-kit-and-icons)
- [React component structure](#react-component-structure)
- [Vertical spacing](#vertical-spacing)
- [Clipboard operations](#clipboard-operations)
- [Forms](#forms)
- [User-facing copy](#user-facing-copy)
- [FSD (Feature-Sliced Design)](#fsd-feature-sliced-design)
- [Refactoring](#refactoring)
---
## UI kit and icons
- **AntD 5 only** for components. Don't pull in Mantine, MUI, Chakra, shadcn, or Radix directly. Use AntD primitives (`Button`, `Input`, `Modal`, `Form`, `Table`, `Menu`, `Tabs`, etc.) plus Tailwind utility classes for layout and spacing.
- **`@ant-design/icons` only** for icons. Don't add `lucide-react`, `@heroicons/react`, `react-icons`, or FontAwesome.
---
## React component structure
Write React components with the following structure:
```typescript
interface Props {
someValue: SomeValue;
}
const someHelperFunction = () => {
...
}
export const ReactComponent = ({ someValue }: Props): JSX.Element => {
// First put states
const [someState, setSomeState] = useState<...>(...)
// Then place functions
const loadSomeData = async () => {
...
}
// Then hooks
useEffect(() => {
loadSomeData();
});
// Then calculated values
const calculatedValue = someValue.calculate();
return
...
}
```
### Structure order
1. **Props interface** — Define component props
2. **Helper functions** (outside component) — Pure utility functions
3. **Component declaration**
- **States** — `useState` declarations
- **Plain functions** — Event handlers, async operations, in-component formatters. Anything that does _not_ call a React hook.
- **Hooks** — `useRef` + ref mutation, `useCallback`, `useMemo`, `useEffect`. A function wrapped in `useCallback` is a hook — it lives here, not in the functions section.
- **Calculated values** — Derived data computed inline (e.g. an AntD `columns` array).
- **Return** — JSX markup
**All hooks (including every `useEffect`) come below every plain function definition.** If a `useEffect` reads a handler, the handler must be defined above it — don't reorder by putting handlers below the effects.
---
## Vertical spacing
- Structure function bodies with vertical rhythm. Put a blank line between logically distinct steps (setup / main work / return, independent branches, before and after a guard). Do not leave blank lines at the start or end of a body, never stack two in a row. Do not insert blanks inside a tight expression, a single statement split across lines, or a short (≤ 5-line) function. If blank lines alone aren't enough to navigate a body, extract — don't add comments.
---
## Clipboard operations
Always use `ClipboardHelper` (`shared/lib/ClipboardHelper.ts`) for clipboard operations — never call `navigator.clipboard` directly.
- **Copy:** `ClipboardHelper.copyToClipboard(text)` — uses `navigator.clipboard` with `execCommand('copy')` fallback for non-secure contexts (HTTP).
- **Paste:** Check `ClipboardHelper.isClipboardApiAvailable()` first. If available, use `ClipboardHelper.readFromClipboard()`. If not, show `ClipboardPasteModalComponent` (`shared/ui`) which lets the user paste manually via a text input modal.
---
## Forms
### Progressive disclosure
- Submit buttons (`Save`, `Update password`, etc.) render **only when the form is dirty**.
- Dependent fields (e.g. `Confirm password`) render **only after the field they depend on has a value**.
---
## User-facing copy
Use a plain hyphen `-` in any string the user will see — labels, descriptions, notifications, modal bodies, error messages. Reserve em dashes (`—`) and en dashes (`–`) for markdown docs and code comments only.
---
## FSD (Feature-Sliced Design)
The project follows FSD v2.1. Layers in use: `app/`, `pages/`, `widgets/`, `features/`, `entities/`, `shared/`.
### Import direction
`app → pages → widgets → features → entities → shared`
A module may only import from layers strictly **below** it. Cross-imports between slices on the **same** layer are forbidden.
```tsx
// ✅ Allowed
import { useUser } from '@/entities/user';
import { LoginForm } from '@/features/auth';
// ❌ Violations
import { loginUser } from '@/features/auth';
// inside entities/
import { likePost } from '@/features/like-post';
// inside another feature
import { ProfilePage } from '@/pages/profile';
import { Button } from '@/shared/ui/Button';
// inside a feature
```
### Where new code goes
- Used in only one page → keep it in that `pages/` slice.
- Reusable infrastructure, **no business logic** → `shared/` (UI kit, utils, API client, route constants, auth tokens, CRUD helpers).
- User interaction reused in 2+ pages → `features/`.
- Domain model reused in 2+ pages/features → `entities/`.
- App-wide providers, router, theme → `app/`.
**When in doubt — keep it in `pages/`.** Extract only when a second real consumer appears.
### Quick placement table
| Scenario | Single use | Reused in 2+ places |
| ------------------------ | ------------------------------------- | --------------------------------------- |
| Profile form | `pages/profile/ui/ProfileForm.tsx` | `features/profile-form/` |
| Database card | `pages/databases/ui/DatabaseCard.tsx` | `entities/database/ui/DatabaseCard.tsx` |
| Data fetching for backup | `pages/backup/api/fetch-backup.ts` | `entities/backup/api/` |
| Auth token / session | `shared/auth/` (always) | `shared/auth/` (always) |
| Login form | `pages/login/ui/LoginForm.tsx` | `features/auth/` |
| CRUD helpers | `shared/api/` (always) | `shared/api/` (always) |
| Date formatting util | — | `shared/lib/format-date.ts` |
| Modal content | `pages/[page]/ui/SomeModal.tsx` | — |
### MUST rules
1. **Downward-only imports.** No upward imports, no same-layer cross-imports.
2. **Public API via `index.ts`.** External consumers import only from a slice's `index.ts`, never its internal files.
```tsx
// ✅
import { LoginForm } from '@/features/auth'
// ❌
import { LoginForm } from '@/features/auth/ui/LoginForm'
```
3. **Domain-based file names.** Name files by the domain they represent, not their technical role.
```text
// ❌ model/types.ts, model/utils.ts, lib/helpers.ts
// ✅ model/user.ts, model/backup.ts, api/fetch-database.ts
```
4. **No business logic in `shared/`.** Shared holds only infrastructure. Domain calculations live in `entities/` or higher.
5. **One type per file in `model/` / DTOs.** Each `interface`, `class`, or `enum` that represents a domain entity, DTO, request body, or response shape gets its own file in `model/` (or `models/`), named after the type. Don't co-locate sibling types like `Foo` + `FooStatus` + `FooResponse` in a single `Foo.ts` — split them. Re-export each from the slice `index.ts` so consumers still import from the slice's public API.
```text
// ❌ model/RestoreVerification.ts — enums + table-stat + main interface all in one file
// ✅ model/RestoreVerification.ts — interface RestoreVerification
// ✅ model/RestoreVerificationTableStat.ts — interface RestoreVerificationTableStat
// ✅ model/VerificationStatus.ts — enum VerificationStatus
// ✅ model/VerificationTrigger.ts — enum VerificationTrigger
```
Tiny shape-only helper types tightly coupled to a parent type (a literal-union alias, a `Pick<>`) may stay in the parent's file. Splitting applies to anything with its own identity — anything you'd reasonably import on its own elsewhere.
### Segments inside a slice
- `ui/` — components, styles
- `model/` — state, types, domain logic, validation
- `api/` — backend calls, request functions, API-specific types
- `lib/` — internal helpers for this slice only
- `config/` — slice-level config / feature flags
`app/` and `shared/` have **segments only, no slices**. Segments within them may import from each other.
### AVOID
- Creating an entity prematurely (single consumer → keep it in the page).
- Putting CRUD inside `entities/` — CRUD is infrastructure, goes to `shared/api/`.
- Creating a `user` entity just for auth tokens/DTOs — those belong in `shared/auth/` or `shared/api/`.
- Extracting single-use code "for future reuse."
- God slices (`user-management/` covering auth + profile + password) — split by focused responsibility.
- Importing UI segments of one entity from another entity. Entity UI may be imported only from features/widgets/pages.
- Abusing the `@x` cross-import pattern — it is a last resort, not a tool.
### Cross-imports between same-layer slices
When two slices on the same layer need to share code, try **in order**:
1. **Merge** — if they always change together, they are one slice.
2. **Extract shared logic down to `entities/`** — keep UI in the features/widgets.
3. **Compose in a higher layer (IoC)** — the parent page/widget imports both and wires them together via props/slots.
4. **`@x` notation** — explicit, documented cross-import between entities only. Last resort.
---
## Refactoring
When applying changes, **do not forget to refactor old code**. You can shortify, make more readable, improve code quality, etc. Common logic can be extracted to functions, constants, files, etc.
**After each large change with more than ~50-100 lines of code:**
- Run `pnpm format` (from `frontend/` root folder)
- Run `pnpm lint` to verify the change