Reorganize app workspaces and align Admin architecture

This commit is contained in:
Igor Martyniuk
2026-07-17 13:46:21 +00:00
parent ba691d3a0c
commit 0ba01cd111
183 changed files with 3745 additions and 4354 deletions
+4 -74
View File
@@ -1,79 +1,9 @@
# Admin App Documentation
The admin app is a reusable React admin panel template. It includes the app shell, layouts, shared infrastructure, built-in core modules, development tools, and generated project modules.
Source root:
```txt
apps/admin/
```
Frontend architecture guide:
```txt
docs/apps-architecture/react-frontend/Readme.md
```
## Current Purpose
This page is a documentation stub. The admin app will be documented in more detail while its modules are migrated or aligned to the shared React frontend architecture.
For now, use this page to define ownership and the target documentation structure.
The admin app is a reusable React admin panel template.
It includes the app shell, layouts, shared infrastructure,
built-in core modules, development tools, and generated project modules.
## Architecture Direction
`apps/admin` follows the general React architecture described in [React Frontend Architecture](../../apps-architecture/react-frontend/Readme.md).
Because the admin app is a template and module host, the architecture applies at multiple levels:
1. App shell architecture under `apps/admin/src/app` and `apps/admin/src/shared`.
2. Core module architecture under `apps/admin/src/core/*`.
3. Generated or project module architecture under `apps/admin/src/modules/*`.
Large modules should be structured like small apps inside their own folder:
```txt
ModuleName/
config/
data/
entities/
providers/
pages/
shared/
view/
index.ts
```
Small modules may start with fewer folders. Add layers when the module actually owns API calls, entities, providers, pages, UI blocks, or module-only helpers.
## Source Areas
| Area | Path | Purpose |
| ----------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| App shell | `apps/admin/src/app/` | App routing, providers, layouts, i18n, styles, and global configuration. |
| Infrastructure | `apps/admin/src/infrastructure/` | HTTP, React Query helpers, storage adapters, and technical error handling shared by modules. |
| Reference data | `apps/admin/src/refdata/` | Common API-backed datasets and option lists that are not owned by a single feature module. |
| Core modules | `apps/admin/src/core/` | Built-in admin template modules such as File Manager, auth, account, permissions, dashboard, and admin management. |
| Generated modules | `apps/admin/src/modules/` | Project-specific or generated business modules. |
| Devtools | `apps/admin/src/devtools/` | Developer tools documented in the [Devtools guide](../../devtools/Readme.md). |
| Shared frontend | `apps/admin/src/shared/` | Admin-wide reusable UI, widgets, forms, editors, hooks, and pure utilities. |
## Module Documentation Plan
Each core module or generated module should eventually have documentation that covers:
1. Module purpose.
2. Source path.
3. Routes and pages.
4. Data layer API and query ownership.
5. Entities and DTO mapping.
6. Providers, stores, and local state ownership.
7. View widgets, forms, and reusable components.
8. Backend integration, if the module has backend routes.
9. Known migration gaps from the target architecture.
## Current Module Docs
| Module | Source | Docs |
| ------------ | ---------------------------------- | -------------------------------------------------------------------- |
| File Manager | `apps/admin/src/core/FileManager/` | [File Manager frontend](../../core-modules/file-manager/frontend.md) |
`apps/admin` follows the general React architecture described in [React Frontend Architecture](./architecture/Readme.md).
+126
View File
@@ -0,0 +1,126 @@
# CRUX React Admin Panel Architecture
## Table of Contents
- [Disclaimer](#disclaimer)
- [Core Principles](#core-principles)
- [Recommended Layering](#recommended-layering)
- [Architecture Scope](#architecture-scope)
- [Entities Layer](#1-entities-layer)
- [Data Layer (Data Access Layer)](#2-data-layer-data-access-layer)
- [Purpose](#purpose)
- [Responsibilities](#responsibilities-2)
- [Rules and Guidelines](#rules-and-guidelines)
- [Infrastructure Layer](#infrastructure-layer)
- [Reference Data Layer](#reference-data-layer)
- [App Layer](#3-app-layer)
- [Pages Layer](#4-pages-layer)
- [View Layer](#5-view-layer)
- [view/components/](#viewcomponents)
- [view/forms/](#viewforms)
- [view/widgets/](#viewwidgets)
- [Shared Layer](#6-shared-layer)
- [Responsibilities](#responsibilities-4)
- [Rules](#rules-2)
- [Full documentation](#full-documentation)
## Disclaimer
This architecture guide is intended as a **recommendation**, not a strict rulebook. **Feel free to adapt and modify it based on your team’s needs and project context**.
Key points to keep in mind:
1. **Maintainability comes first**.
The main goal of this structure is to help someone new to the project quickly understand where to make changes.
It’s about finding the right place to touch the code now, rather than strictly following a pattern for its own sake.
**Ultimately, the main goal is to make development easier for everyone**
2. **Naming debates are inevitable**.
Any architecture guide may spark discussions about file, folder, or function naming. Use this guide as a reference, but remember that practicality and clarity are more important than strict adherence.
3. **Flexibility is allowed**.
- You can adjust layers, module structure, or naming conventions if it improves readability or reduces complexity.
- New patterns or exceptions can be added as the project evolves.
In short: this guide exists to make the project easier to understand and maintain, not to create unnecessary friction. Use judgment and prioritize clarity over rigid consistency.
---
Цей посібник з архітектури є **рекомендацією**, а не суворим зводом правил. **Не соромтеся адаптувати та змінювати його відповідно до потреб вашої команди та контексту проєкту**.
Ключові моменти, які варто мати на увазі:
1. **Насамперед — зручність підтримки**.
Головна мета цієї структури — допомогти новому учаснику проєкту швидко зрозуміти, де вносити зміни.
Йдеться про те, щоб знайти правильне місце в коді просто зараз, а не сліпо дотримуватися патерну заради самого патерну.
**Зрештою, головна мета — зробити розробку простішою для всіх.**
2. **Дискусії щодо назв неминучі**.
Будь-який посібник з архітектури може спровокувати суперечки щодо назв файлів, папок або функцій. Використовуйте цей посібник як орієнтир, але пам'ятайте, що практичність і зрозумілість важливіші за суворе дотримання правил.
3. **Гнучкість допускається**.
- Ви можете коригувати шари, структуру модулів або угоди щодо найменування, якщо це покращує читабельність або зменшує складність.
- У міру розвитку проєкту можна додавати нові патерни або винятки.
Коротко: цей посібник існує для того, щоб проєкт був зрозумілішим і простішим у підтримці, а не для того, щоб створювати зайві труднощі. Керуйтеся здоровим глуздом і надавайте перевагу ясності, а не жорсткій послідовності.
## Core Principles
1. Blocks are composable and isolated
- Each block (component, form, widget) has a single responsibility.
- Higher-level blocks (widgets, pages) compose lower-level blocks.
2. Data vs. UI separation
- `data` handles API, state, and business data.
- `view` handles UI and interaction.
- No API logic in UI components — UI calls hooks from `data`.
3. Entities are global types/interfaces
- Placing them in `entities`.
- Any block (data hook, form, widget, page) can use these.
4. Shared
- shared/ = globally reusable components, hooks, utilities.
5. Infrastructure
- infrastructure/ = app-wide technical adapters such as HTTP clients, React Query, storage adapters, and formatting service wrappers.
6. Reference data
- refdata/ = app-wide API-backed lookup datasets shared by multiple modules.
## Recommended Layering
```plaintext
src/
├── app/ # composition root: providers, routing, config, i18n, layouts
├── infrastructure/ # technical adapters: HTTP, query, storage, error handling
├── refdata/ # API-backed common reference datasets
├── shared/ # reusable app-wide UI, hooks, utilities, and widgets
├── core/ # built-in product modules
├── modules/ # project-specific modules
└── devtools/ # development-only modules and tools
```
## Architecture Scope
The layer structure can be used at three levels:
| Scope | Example | Meaning |
| --------------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| App-level architecture | `apps/admin/src/app`, `apps/admin/src/shared` | Global app shell, providers, routing, layout, infrastructure, reusable app-wide building blocks. |
| Core module architecture | `apps/admin/src/core/FileManager` | Built-in product modules shipped with the admin template. Each large core module can own its own `data`, `entities`, `pages`, `view`, and `shared`, plus feature-scoped context when needed. |
| Project module architecture | `apps/admin/src/modules/*` | Generated or project-specific modules. Each module should follow the same layer rules inside its own folder when it becomes more than a simple page. |
For small modules, do not create empty layers only to match the template. Add a layer when the module has real ownership for that layer.
## Full documentation
- [Entities Layer](./layers/entities.md)
- [Data Layer](./layers/data.md)
- [App Layer](./layers/app.md)
- [Infrastructure Layer](./layers/infrastructure.md)
- [Reference Data Layer](./layers/refdata.md)
- [View Layer](./layers/view.md)
- [Shared Layer](./layers/shared.md)
- [File & Folder Naming Convention](./naming.md)
@@ -0,0 +1,83 @@
# App Layer
## Purpose
The `app` layer is the composition root of the Admin application. It owns the concerns
that exist once for the whole app and coordinates lower-level layers and feature
modules without containing feature business logic.
Global React providers belong inside `app/providers/`. They are an implementation
detail of the app layer, not a separate architectural layer.
## Responsibilities
- Compose global React providers in one root component.
- Configure application-wide settings and environment-derived values.
- Initialize internationalization and global styles.
- Define top-level routing and connect routes registered by modules.
- Provide application layouts, navigation, and menu composition.
- Register core, project, and development modules with the app shell.
## Current Structure
```plaintext
app/
├── config/ # global settings, environment values, and config types
├── css/ # global styles and app-level third-party overrides
├── i18n/ # languages, messages, and internationalization config
├── layouts/ # top-level Admin layout variants
├── providers/ # providers composed once for the entire application
│ ├── AppProviders.tsx
│ ├── I18nProvider.tsx
│ ├── QueryProvider.tsx
│ ├── SettingsProvider.tsx
│ └── ThemeProvider.tsx
├── routing/
│ ├── AppRouting.tsx
│ └── AppRoutingSetup.tsx
├── services/
│ └── modules/ # module registry, routes, menus, and messages
└── menu.tsx # app-level menu composition
```
The exact files may evolve, but their ownership should remain app-wide. A concern
used by only one feature belongs to that feature module instead.
## Global Providers
`app/providers/AppProviders.tsx` is the single provider composition point. The
current provider tree supplies:
- TanStack Query configuration and error handling
- authentication context from the Auth core module
- application settings
- shared tooltip and notification UI
- theme state
- internationalization and text direction
- React Router with the configured base URL
## Routing
`app/routing/` owns top-level route composition. It connects the application shell
to routes contributed by registered modules. Feature pages and route-specific logic
remain inside their owning modules.
## Configuration and Internationalization
- `app/config/` owns global settings, application metadata, and configuration types.
- `app/i18n/` owns supported languages, translated messages, and i18n configuration.
- Environment access should be centralized through app configuration instead of
being repeated throughout feature code.
## Layouts and Global CSS
`app/layouts/` contains the Admin shell variants. `app/css/` contains global styles,
layout styles, and app-wide overrides for third-party components. Feature-specific
styles should stay with the feature that owns them.
## Module Registration
`app/services/modules/` integrates modules into the application through their
routes, menus, and translated messages. It is integration code owned by the app
shell; module implementation details remain under `core/`, `modules/`, or
`devtools/`.
+260
View File
@@ -0,0 +1,260 @@
# Data Layer
## Table of Contents
- [Data Layer](#data-layer)
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Data Layer Organization Approaches](#data-layer-organization-approaches)
- [By Module](#by-module)
- [By Entity](#by-entity)
- [Data Flow](#data-flow)
- [DTO Types](#1-dto-types)
- [API Layer](#2-api-layer)
- [Mapper](#3-mapper)
- [Mapper Testing](#mapper-testing)
- [Query Hooks](#4-query-hooks)
- [Pagination](#pagination)
## Purpose
The `data` layer is responsible for **all data access and data transformation** in the application.
It is the only layer that knows:
- how to call APIs
- how to map API responses (DTOs) to application entities
- how to handle caching, queries, and mutations
Higher layers (`forms`, `widgets`, `pages`) **do not interact with APIs directly**.
Instead, they consume **hooks provided by the data layer**.
## Responsibilities
The `data/` layer is responsible for:
- performing **API calls**
- defining **DTO types (Data Transfer Objects)**
- converting DTOs → **entities** using **mappers**
- providing **React Query hooks**
- handling **side effects** (requests, caching, errors)
- defining **API-related types**
## Data Layer Organization Approaches
```plaintext
data/
| --- user/
| | --- api.ts # user-related API functions (getUsers, createUser, etc.)
| | --- hooks.ts # React Query hooks (useUsersQuery, useCreateUserMutation)
| | --- dto.ts # API DTO types for user
| | --- mapper.ts # map user DTO → entity
| | --- types.ts # API types/interfaces
|
| --- pagination/
| | --- dto.ts
| | --- mapper.ts
| | --- types.ts
```
There are two main ways to structure the `data` layer:
- By Module — grouped by type of file (API, hooks, DTOs, mappers, types)
- By Entity — grouped by business feature/entity (user, product, message)
| Approach | Best For | Pros | Cons |
| --------- | ------------------------------- | -------------------------------------------- | ---------------------------------------------- |
| By Module | Small apps, simple data layers | Easy to find all API/hooks/DTOs in one place | Files for one feature scattered; hard to scale |
| By Entity | Medium/large apps, scalable CRM | Encapsulated, modular, easier to maintain | Harder to see all APIs/hooks globally |
### By Module
```plaintext
data/
| --- api/ # API request functions
| | --- userAPI.ts
| | --- productAPI.ts
| | --- messageAPI.ts
|
| --- hooks/ # React Query hooks or other data hooks
| | --- useUserQuery.ts
| | --- useCreateProductMutation.ts
| | --- useMessages.ts
|
| --- dto/ # API DTO types
| | --- userDTO.ts
| | --- productDTO.ts
| | --- messageDTO.ts
|
├ --- mappers/ # DTO → entity mappers
| | --- userMapper.ts
| | --- productMapper.ts
|
| --- types/ # API types/interfaces
```
### By Entity
```plaintext
data/
| --- user/
| | --- api.ts # user-related API functions (getUsers, createUser, etc.)
| | --- hooks.ts # React Query hooks (useUsersQuery, useCreateUserMutation)
| | --- dto.ts # API DTO types for user
| | --- mapper.ts # map user DTO → entity
| | --- types.ts # API types/interfaces (optional)
|
| --- product/
| | --- api.ts
| | --- hooks.ts
| | --- dto.ts
| | --- mapper.ts
| | --- types.ts
```
## Data Flow
### 1. DTO Types
```ts
export type UserDTO = {
id: number;
full_name: string;
email_address: string;
};
export type CreateUserDTO = {
full_name: string;
email_address: string;
};
```
### 2. API Layer
API functions or class methods perform HTTP requests. API always returns DTO objects.
```ts
export class UserApi {
private http: HttpClient;
async getDetailed(id: number): Promise<UserDTO> {
return this.http.get(`/users/${id}`);
}
async getList(): Promise<UserDTO[]> {
return this.http.get(`/users`);
}
async create(dto: CreateUserDTO): Promise<UserDTO> {
return this.http.post(`/users`, dto);
}
}
```
### 3. Mapper
Mappers convert `DTO` to `Entity`
```ts
import { User } from "@/entities/user";
export class UserMapper {
toEntity(dto: UserDTO): User {
return {
id: dto.id,
name: dto.full_name,
email: dto.email_address,
};
}
toEntities(dtos: UserDTO[]): User[] {
return dtos.map((dto) => this.toEntity(dto));
}
toPaginated(dto: PaginatedDataDTO<UserDTO[]>): PaginatedResult<User[]> => {
return {
meta: this.paginationMetaMapper.toMeta(dto.meta),
data: this.toEntities(dto.data),
};
};
}
```
### Mapper Testing
Mappers are part of the data layer contract and should have unit tests.
Mapper tests are required when a mapper:
- renames fields from API DTOs to app entities
- normalizes values, paths, dates, sizes, statuses, enums, or defaults
- maps nested DTOs through other mappers
- maps arrays, paginated data, or optional API fields
- protects the app from backend response shape changes
These tests should focus only on transformation behavior. Do not mock React, render components, or call real APIs in mapper tests.
Recommended test coverage:
- `toEntity` maps a representative DTO to the expected entity
- `toEntities` maps arrays consistently
- optional or missing DTO fields use the correct defaults
- nested mappers preserve the expected entity shape
- enum/status casts are covered with expected values
For apps that keep tests outside production source, use a test folder at the app root:
```plaintext
apps/admin/
├── src/
│ └── core/FileManager/data/mappers/entryMapper.ts
└── tests/
└── unit/core/FileManager/data/mappers/entryMapper.test.ts
```
### 4. Query Hooks
Hooks combine API + mapper + React Query.
```ts
export const useUserDetailed = (id: number) => {
const api = new UserApi(http);
const mapper = new UserMapper();
return useQuery({
queryKey: ["user", id],
queryFn: async () => {
const dto = await api.getDetailed(id);
return mapper.toEntity(dto);
},
});
};
```
### Pagination
Pagination is not **entity-specific**, so it should live in a shared data module.
```ts
export type PaginatedMetaDTO = {
total: number;
perPage: number;
currentPage: number;
lastPage: number;
};
export type PaginatedDTO<T> = {
data: T;
meta: PaginatedMetaDTO;
};
export class PaginationMapper {
toMeta(dto: PaginatedMetaDTO) {
return {
total: dto.total,
perPage: dto.perPage,
currentPage: dto.currentPage,
lastPage: dto.lastPage,
};
}
}
```
@@ -0,0 +1,53 @@
# Entities
## Purpose
The `entities` layer defines the **core domain models of the application**.
Entities represent **business objects** used throughout the system, such as `User`, `Product`, `Project`, or `Message`.
This layer contains **pure type definitions** that describe how data is structured inside the application.
Entities are **stable, reusable models** that can be used across any layer.
```plaintext
entities/
| --- user.ts # { id: string; name: string; email: string }
| --- product.ts # { id: string; name: string; price: number }
| --- message.ts # { id: string; content: string; createdAt: string }
| --- enums.ts # { UserRole, ProductCategory }
```
or
```plaintext
entities/
| --- user/
| | --- interfaces.ts # { id: string; name: string; email: string }
| | --- types.ts # { id: string; name: string; email: string }
| | --- enums.ts # { UserRole }
| | --- rules.ts # MIN_ALLOWED_AGE = 18
| | --- index.ts # export all entity types
| --- product.ts # { id: string; name: string; price: number }
| --- message.ts # { id: string; content: string; createdAt: string }
```
## Responsibilities
The entities layer is responsible for:
- defining **domain models**
- defining **entity-related enums or constants**
- providing **shared types used across the application**
```ts
export type User = {
id: number;
name: string;
email: string;
};
export enum UserRole {
ADMIN = "admin",
SIMLPE_JACK = "simple_jack",
}
```
@@ -0,0 +1,106 @@
# Infrastructure Layer
## Table of Contents
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Recommended Structure](#recommended-structure)
- [Rules](#rules)
- [Examples](#examples)
## Purpose
The `infrastructure/` layer contains app-wide technical adapters and integration helpers.
Use this layer for code that connects the frontend application to technical systems or shared runtime services, not for feature-specific business logic.
## Responsibilities
The `infrastructure/` layer is responsible for:
- HTTP clients and request/response interceptors
- React Query shared helpers, response types, query key helpers, and error normalization
- storage, browser, or runtime adapters
- shared formatting/service adapters such as date or name formatting services
- app-wide technical configuration wrappers
## Recommended Structure
```plaintext
infrastructure/
├── axios/
│ ├── index.ts # configured axios client
│ ├── interceptors.ts # request/response interceptors
│ └── axios.d.ts # axios type augmentation
│
├── reactQuery/
│ ├── index.ts
│ ├── errors.ts # query error handling helpers
│ ├── helpers.ts # query key and pagination helpers
│ ├── queryParams.ts # table/query-param mapping helpers
│ └── types.ts # shared API response/query types
│
├── IntlDate.ts # date formatting service adapter
└── IntlName.ts # name formatting service adapter
```
## Rules
1. Keep infrastructure feature-agnostic.
- No module-specific business rules
- No page or component composition
- No entity mapping that belongs in a module `data/mapper`
2. API modules should use infrastructure clients instead of creating their own clients.
```ts
import { axiosClient } from "#infrastructure/axios";
```
3. React Query helpers that are shared across modules belong here.
Examples: query key builders, list response types, pagination helpers, error extraction helpers.
4. UI layers must not call raw HTTP clients directly.
View and page code should call data-layer hooks. Direct `#infrastructure/axios` usage belongs in API/data modules, not in components.
5. Generic formatting adapters may be consumed by UI when they are not tied to a feature.
Examples: `IntlDate`, `IntlName`.
6. Prefer dependency injection for app-state coupling.
If an interceptor needs auth or query-client behavior, keep that coupling narrow and documented. Avoid importing random feature modules into infrastructure.
## Examples
API modules use the configured HTTP client:
```ts
import { axiosClient } from "#infrastructure/axios";
export class UserAPI {
getDetailed(id: number) {
return axiosClient.get(`/users/${id}`).then((response) => response.data);
}
}
```
Data/query modules use shared query helpers:
```ts
import {
createKeyBuilder,
getPaginationInfo,
} from "#infrastructure/reactQuery";
export const usersCacheKey = createKeyBuilder("USERS", "LIST");
```
UI can consume generic app-wide formatters:
```ts
import { IntlDate } from "#infrastructure/IntlDate";
```
@@ -0,0 +1,109 @@
# Reference Data Layer
## Table of Contents
- [Purpose](#purpose)
- [Responsibilities](#responsibilities)
- [Recommended Structure](#recommended-structure)
- [Rules](#rules)
- [Examples](#examples)
## Purpose
The `refdata/` layer contains app-wide reference datasets that are reused across modules.
Reference data is data that usually comes from the backend, but is not owned by one feature module.
Examples:
- countries
- languages
- currencies
- time zones
- common option dictionaries
- shared lookup datasets
## Responsibilities
The `refdata/` layer is responsible for:
- fetching common reference datasets
- exposing reusable query hooks for those datasets
- defining cache keys for reference data
- keeping app-wide lookup data out of feature modules
## Recommended Structure
For small datasets, a single file per dataset is enough:
```plaintext
refdata/
├── countries.ts
├── languages.ts
└── currencies.ts
```
For larger datasets, use a folder:
```plaintext
refdata/
└── countries/
├── api.ts
├── dto.ts
├── mapper.ts
├── queries.ts
└── keys.ts
```
## Rules
1. Use `refdata/` only for app-wide datasets.
If a dataset belongs to one feature module, keep it in that module's `data/` layer.
2. `refdata/` may use infrastructure.
Reference data hooks can use `#infrastructure/axios` and `#infrastructure/reactQuery`.
3. `refdata/` should not import from `view/`, `pages/`, or feature modules.
It should remain reusable across the app.
4. Expose hooks or small service functions.
Consumers should not need to know backend endpoints or cache key details.
5. Add mappers when backend DTOs differ from app-facing reference data.
If a reference dataset performs DTO transformation, test the mapper the same way module data mappers are tested.
## Examples
```ts
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { axiosClient } from "#infrastructure/axios";
import { createKeyBuilder } from "#infrastructure/reactQuery";
export const countriesCacheKey = createKeyBuilder(
"REF_DATA",
"COUNTRIES",
"LIST",
);
interface CountryData {
code: string;
name: string;
}
export const useCountries = () => {
return useQuery({
queryKey: countriesCacheKey.store(),
placeholderData: keepPreviousData,
queryFn: async () => {
return axiosClient
.get<Array<CountryData>>("/datasets/countries")
.then((data) => data.data);
},
});
};
```
@@ -0,0 +1,252 @@
# Shared Layer
## Table of Contents
- [Purpose](#purpose)
- [Recommended Structure](#recommended-structure)
- [UI Components](#ui-components)
- [Purpose](#ui-components-purpose)
- [Core Principles](#ui-components-core-principles)
1. [Simple Components](#ui-components-core-principles-1)
2. [Composable Design](#ui-components-core-principles-2)
3. [Easily Customizable](#ui-components-core-principles-3)
4. [No Business Logic](#ui-components-core-principles-4)
- [Styling Strategy](#ui-components-styling-strategy)
- [Styling Philosophy](#ui-components-styling-strategy-philosophy)
- [Technology Stack](#ui-components-styling-strategy-tech-stack)
- [Hooks](#hooks)
- [Utilities](#utilities)
- [Libraries](#libraries)
- [Assets](#assets)
- [Styles](#styles)
## Purpose
The `shared` layer contains **global reusable building blocks used across the entire application**.
The goal is to keep these utilities **framework-level and reusable across the entire application**.
```plaintext
shared/
├── ui/ # dumb or basic UI components
│ ├── Button.tsx
│ ├── Modal.tsx
│ └── Card.tsx
│
├── widgets/ # reusable composed UI blocks
│ ├── DataGrid/
│ └── screens/
│
├── forms/ # reusable form composition helpers
│ └── composed/
│
├── editors/ # reusable editor integrations
│ ├── tinymce/
│ └── image_input/
│
├── hooks/ # reusable hooks
│ ├── useDebounce.ts
│ ├── useToggle.ts
│ └── useLocalStorage.ts
│
├── lib/ # pure helpers & utilities
│ └── formatDate.ts
│
├── assets/ # images, icons, fonts
│ ├── icons/
│ └── fonts/
│
└── styles/ # global CSS, variables, Tailwind overrides
├── globals.css
└── variables.css
```
## Recommended Structure
```plaintext
shared/
|
| --- ui/ # primitive UI components
|
| --- widgets/ # reusable composed UI blocks
|
| --- forms/ # reusable form composition
|
| --- editors/ # reusable editor wrappers
|
| --- hooks/ # generic reusable hooks
|
| --- utils/ # pure helper functions
|
| --- lib/ # pure helpers and utility services
|
| --- assets/ # images, icons, fonts
|
| --- styles/ # global styling
|
| --- constants/ # global constants
```
### UI Components
#### Purpose {#ui-components-purpose}
The goal of the UI layer is to provide simple, composable, and customizable components that can be reused across the application.
- components are simple building blocks
- components are composable
- components are fully customizable
- components are not tied to business logic
UI components should behave like Lego blocks that can be combined into larger UI structures.
#### Core Principles {#ui-components-core-principles}
##### 1. Simple Components {#ui-components-core-principles-1}
Examples:
- Button
- Input
- Card
- Modal
- Dropdown
##### 2. Composable Design {#ui-components-core-principles-2}
Components must support composition.
Composition allows developers to build complex UI without modifying base components.
Example:
```tsx
<Card>
<CardHeader />
<CardContent />
<CardFooter />
</Card>
```
```tsx
<Button>
<Icon />
Save
</Button>
```
##### 3. Easily Customizable {#ui-components-core-principles-3}
UI components should allow customization through:
- className
- variants
- props
- composition
```tsx
<Button variant="outline" size="lg">
Save
</Button>
```
##### 4. No Business Logic {#ui-components-core-principles-4}
UI components must remain presentation-only. May include local state.
They should NOT:
- call API
- use React Query
- contain form submission logic
- contain domain-specific logic
#### Styling Strategy {#ui-components-styling-strategy}
##### Styling Philosophy {#ui-components-styling-strategy-philosophy}
Styling follows these principles:
- Utility-first styling
- Design tokens for consistency
- Component-level styling
- Variants instead of style duplication
- Minimal custom CSS
##### Technology Stack {#ui-components-styling-strategy-tech-stack}
- **TailwindCSS** - primary styling system
- **class-variance-authority (CVA)** - component variants
- **CSS variables** - design tokens
- **Radix primitives** - accessible UI building blocks
Custom CSS should be minimal and centralized.
### Hooks
The `shared/hooks/` folder contains generic reusable hooks.
These hooks must not depend on entities or business logic.
```plaintext
shared/hooks/
| --- useDebounce.ts
| --- useToggle.ts
| --- usePrevious.ts
| --- useLocalStorage.ts
```
### Utilities
The `shared/utils/` folder contains pure helper functions.
These functions should be stateless and reusable
```plaintext
shared/utils/
| --- formatDate.ts
| --- formatCurrency.ts
| --- generateId.ts
| --- sleep.ts
```
### Libraries
The `shared/lib/` folder contains pure helpers and utility services.
Examples include:
- date formatting helpers
- name formatting helpers
- URL/file path helpers
- phone-number formatting helpers
```plaintext
shared/lib/
| --- time/
| --- phone_number/
| --- resolveFileURL.ts
```
### Assets
The `shared/assets/` folder stores static resources used across the application.
```plaintext
shared/assets/
| --- icons/
| --- fonts/
```
### Styles
The `shared/styles/` folder contains global styling resources.
Typical usage:
- CSS variables
- Tailwind configuration extensions
- global resets
```plaintext
shared/styles/
| --- globals.css
| --- variables.css
| --- tailwind.css
```
+345
View File
@@ -0,0 +1,345 @@
# Views Layer
## Table of Contents
- [Views Layer](#views-layer)
- [Components](#components)
- [Components Purpose](#components-purpose)
- [Components Responsibilities](#components-responsibilities)
- [What Components should NOT do](#what-components-should-not-do)
- [Widgets](#widgets)
- [Widget Purpose](#widget-purpose)
- [Widget Characteristics](#widget-characteristics)
- [Widget Responsibilities](#widget-responsibilities)
- [Forms](#forms)
- [Form Architecture](#form-architecture)
- [Form Logic](#form-logic)
- [Form Validation](#form-validation)
- [Form Values](#form-values)
- [Form Types](#form-types)
- [Submit Functions](#submit-functions)
- [Purpose](#submit-functions-purpose)
- [API Example](#api-example)
- [Responsibilities](#responsibilities)
- [Composed Form Elements](#composed-form-elements)
- [Example: InputSwitch](#example-inputswitch)
- [Usage in a Form](#usage-in-a-form)
## Components
### Components Purpose
Components are reusable UI blocks used to build user interfaces.
They encapsulate presentation and simple UI behavior, but do not contain business logic or data fetching. `UserAvatar`, `ProductBadge`
Components are designed to be small, composable, and reusable across multiple parts of the application.
### Components Responsibilities:
- render UI
- accept props
- handle simple UI state (hover, toggle, open/close)
- compose other components
### What **Components** should NOT do
Components should not contain logic such as:
- API calls
- mutations
- complex state management
- business workflows
## Widgets
### Widget Purpose
Widgets are **self-contained UI blocks that implement a specific piece of functionality**.
Unlike simple components, widgets combine **UI, logic, and interactions** to deliver a complete user-facing feature.
Widgets are typically used to **compose pages** and represent meaningful parts of the interface such as panels, sections, or complex interactive blocks. Widgets should remain modular building blocks.
### Widget Characteristics
A widget usually:
- encapsulates **UI and feature logic**
- composes **multiple components**
- may include **forms, dialogs, or actions**
- can interact with **data**
- exposes a **simple API for pages**
Widgets should hide their internal implementation and provide a **clean interface** to the outside.
### Widget Responsibilities
Widgets may:
- orchestrate UI interactions
- manage local UI state
- connect to features or data hooks
- combine multiple components/forms into a functional block
- may fetch data or rely on data passed in props
- A **widget** can use another **widget** if:
- the nested widget represents a clear functional sub-block
- the parent widget is grouping or orchestrating UI
- the dependency does not create tight coupling
```ts
export const ProjectOverviewWidget = ({ projectId }: Props) => {
return (
<div>
<ProjectStatsWidget projectId={projectId} />
<ProjectActivityWidget projectId={projectId} />
<ProjectMembersWidget projectId={projectId} />
</div>
);
};
```
## Forms
### Form Architecture
```plaintext
view/forms/SomeForm/
| --- form.ts
| --- Component.tsx
| --- submit.ts
| --- constants.ts # Optional
| --- schema.ts # Optional validation schema. Good for large forms or multistep forms
| --- index.ts
```
| File | Responsibility |
| --------------- | ---------------------------------- |
| `form.ts` | validation schema, types, defaults |
| `Component.tsx` | UI + react-hook-form integration |
| `submit.ts` | Submission logic |
| `constants.ts` | form IDs, UI constants |
| `index.ts` | public exports |
### Form Logic
`form.ts` contains pure form logic.
It should include:
- validation schema
- form types
- default values
- helper templates
#### Form Validation
```ts
export const validationSchema = z.object({
first_name: z.string().min(3),
last_name: z.string().min(3),
});
```
If you use `i18n` you can create function that returns validation schema.
```ts
function createValidationSchema(
translationFn: TFunction<"translation", undefined>, // Depend on libraty you use
) {
return z.object({
first_name: z.string({ message: translationFn("required") }),
last_name: z.string({ message: translationFn("required") }),
});
}
```
> [! WARNING]
> Dont include `i18n` singleton last_name: z.string().min(1, i18n.t('required'))
> It will not translate errors when language state change
#### Form Values
Default values should be defined using factory functions.
Avoid inline default objects.
```ts
export function createFormValues(): FormValues {
return {
first_name: "",
last_name: "",
};
}
```
```ts
export function toFormValues(data: SomeData): FormValues {
return {
first_name: data?.first_name ?? "",
last_name: data?.last_name ?? "",
};
}
```
#### Form Types
Define types for form values and submit functiuons
```ts
export type FormValues = z.infer<typeof validationSchema>;
export type SubmitFunc = (
values: FormValues,
handlers: UseFormReturn<FormValues>,
) => void;
```
#### Submit Functions
##### Purpose {#submit-functions-purpose}
The `useSubmit` hook standardizes form submission logic:
- Handles create and update operations
- Integrates with `data/` layer hooks (`useCreate`, `useUpdate`)
- Automatically handles validation errors from the API
- Invalidates lists or detailed queries to refresh UI
- Exposes a unified API for forms
This pattern allows keep submission logic centralized.
##### API Example
```ts
interface Params {
onSuccess?: (id?: number) => void;
}
export const useSubmit = (params?: Params) => {
const onSuccess = params?.onSuccess;
const { mutate: createMutation, isPending: isCreatePending } = useCreate();
const { mutate: updateMutation, isPending: isUpdateending } = useUpdate();
const handleSubmit: SubmitFunc = (values, handlers) => {
if (values.id) {
handleUpdate(values, handlers);
} else {
handleCreate(values, handlers);
}
};
const handleCreate: SubmitFunc = (values, handlers) => {
createMutation(values, {
onSuccess() {
invalidateList();
if (typeof onSuccess === "function") onSuccess();
},
onError(error) {
extractValidationErrors(error).forEach((e) => {
handlers.setError(e.field as keyof typeof values, {
type: "apiError",
message: e.message,
});
});
},
});
};
const handleUpdate: SubmitFunc = (values, handlers) => {
if (!values.id) return;
updateMutation(
{ ...values, id: values.id },
{
onSuccess() {
invalidateList();
if (values.id) invalidateDetailed(values.id);
if (typeof onSuccess === "function") {
onSuccess(values.id ?? undefined);
}
},
onError(error) {
extractValidationErrors(error).forEach((e) => {
handlers.setError(e.field as keyof typeof values, {
type: "apiError",
message: e.message,
});
});
},
},
);
};
return {
submit: handleSubmit,
isSubmitting: isCreatePending || isUpdateending,
};
};
```
##### Responsibilities
1. Determine operation type
- If values.id exists -> update
- Otherwise -> create
2. Call the appropriate mutation
- `createMutation` or `updateMutation` from `data` layer
- Hooks handle API calls internally
3. Handle success
- Invalidate queries (list/detail)
- Trigger optional `onSuccess` callback
4. Handle errors
- Extract validation errors from API
- Set errors on form fields using `handlers.setError`
5. Expose state
- `submit` -> function to call from form
- `isSubmitting` -> combines pending state from both mutations
#### Composed Form Elements
To avoid repetitive boilerplate code when building forms, we encourages creating composed form elements that integrate React Hook Form with shared UI components.
- Combines label, input, validation message, and layout in one reusable component.
- Can encapsulate any input type: text, switch, select, date, textarea, etc.
- Works seamlessly with FormProvider for context propagation.
##### Example: InputSwitch
InputSwitch wraps a Switch component into a reusable form field:
```tsx
<InputSwitch
name="notificationsEnabled"
label="Enable notifications"
description="Toggle notifications on/off"
required
/>
```
Key points:
- Uses `useFormContext` to access form control.
- Wraps `FormField`, `FormItem`, `FormLabel`, `FormMessage`, and `FormControl`.
- Handles required and disabled states.
- This allows you to reuse the same input type across forms without writing repetitive boilerplate.
##### Usage in a Form
```tsx
<FormProvider {...form}>
<Component.FormElement
id={MAIN_FORM_ID}
onSubmit={form.handleSubmit((values) => onSubmit(values, form))}
>
<Component.FormGrid columns={2}>
<Component.GridElement colSpan={1}>
<Component.InputSwitch
name="notificationsEnabled"
required
label="Enable notifications"
/>
</Component.GridElement>
</Component.FormGrid>
</Component.FormElement>
</FormProvider>
```
+251
View File
@@ -0,0 +1,251 @@
# File & Folder Naming
## Table of Contents
- [Naming Convention](#naming-convention)
- [Folders](#folders)
- [Component Files](#component-files)
- [Hook Files](#hook-files)
- [Utility & Helper Files](#utility--helper-files)
- [Other TypeScript Files](#other-typescript-files)
- [Examples by Layer](#examples-by-layer)
- [Case-Only Rename Problem (macOS + Git)](#case-only-rename-problem-macos--git)
- [Required: Correct Rename Procedure](#required-correct-rename-procedure)
- [Making Git Case-Sensitive on macOS](#making-git-case-sensitive-on-macos)
- [Important Reality Check](#important-reality-check)
## Naming Convention
We follow **standard React community naming conventions**:
| Type | Convention | Example |
| ---------------------- | -------------------------------------------- | ------------------------------------- |
| Folders | lowercase (PascalCase for component folders) | `user/`, `shared/`, `CreateUserForm/` |
| React component files | PascalCase | `UserProfile.tsx`, `Button.tsx` |
| Hook files | camelCase (`use` prefix) | `useDebounce.ts`, `useToggle.ts` |
| Utility / helper files | camelCase | `formatDate.ts`, `generateId.ts` |
| API / service files | camelCase (acronyms uppercase) | `userAPI.ts`, `productAPI.ts` |
| DTO / mapper files | camelCase (acronyms uppercase) | `userDTO.ts`, `userMapper.ts` |
| Type / interface files | camelCase | `types.ts`, `dto.ts`, `interfaces.ts` |
| Constants files | camelCase | `constants.ts`, `formConstants.ts` |
| Config / entry files | camelCase | `index.ts`, `queryClient.ts` |
### Folders
Layer and infrastructure folders use **lowercase**.
Component, form, and widget folders use **PascalCase** — matching the component they contain.
```
src/
data/ # lowercase — layer folder
user/ # lowercase — entity folder
product/
view/ # lowercase — layer folder
components/
forms/
CreateUserForm/ # PascalCase — component folder
widgets/
UserProfileWidget/
shared/
ui/
hooks/
lib/
```
### Component Files
React component files use **PascalCase** — matching the name of the exported component.
```
UserProfile.tsx
ProductCard.tsx
LoginButton.tsx
UserProfileWidget.tsx
CreateProductForm.tsx
```
### Hook Files
Hook files use **camelCase** and always start with `use`.
```
useDebounce.ts
useToggle.ts
useLocalStorage.ts
useUserQuery.ts
useCreateProductMutation.ts
```
### Utility & Helper Files
Utility and helper files use **camelCase**.
```
formatDate.ts
formatCurrency.ts
generateId.ts
sleep.ts
```
### Other TypeScript Files
All other `.ts` files (APIs, DTOs, mappers, types, config) use **camelCase**, with acronyms kept uppercase.
```
userAPI.ts
userDTO.ts
userMapper.ts
types.ts
constants.ts
queryClient.ts
axiosInstance.ts
```
### Examples by Layer
#### `shared/`
```plaintext
shared/
├── ui/
│ ├── Button.tsx
│ ├── Modal.tsx
│ └── Card.tsx
│
├── hooks/
│ ├── useDebounce.ts
│ ├── useToggle.ts
│ └── useLocalStorage.ts
│
├── lib/
│ ├── axios/
│ │ └── axiosInstance.ts
│ └── query/
│ └── queryClient.ts
│
└── utils/
├── formatDate.ts
└── formatCurrency.ts
```
#### `data/`
```plaintext
data/
├── user/
│ ├── userAPI.ts
│ ├── hooks.ts # or useUserQuery.ts, useCreateUserMutation.ts
│ ├── userDTO.ts
│ └── userMapper.ts
│
└── product/
├── productAPI.ts
├── hooks.ts
├── productDTO.ts
└── productMapper.ts
```
#### `app/`
```plaintext
app/
├── config/
├── i18n/
├── layouts/
├── providers/
│ ├── AppProviders.tsx
│ ├── I18nProvider.tsx
│ ├── QueryProvider.tsx
│ ├── SettingsProvider.tsx
│ └── ThemeProvider.tsx
├── routing/
└── services/
```
#### `view/`
```plaintext
view/
├── components/
│ ├── UserAvatar.tsx
│ └── ProductCard.tsx
│
├── widgets/
│ └── UserProfileWidget/
│ ├── index.ts
│ └── UserProfileWidget.tsx
│
└── forms/
└── CreateUserForm/
├── form.ts
├── Component.tsx
├── submit.ts
└── index.ts
```
## Case-Only Rename Problem (macOS + Git)
macOS file systems are **case-insensitive by default**, while Git is **case-sensitive internally**.
Renaming a file like `api.ts` → `Api.ts` may:
- Not be detected by Git
- Break CI (Linux environments are case-sensitive)
- Cause inconsistent repository state
### REQUIRED: Correct Rename Procedure
If you **must rename a file or folder by case only**, you are required to perform a two-step rename.
#### Step 1 — Rename to Temporary Name
```bash
git mv userApi.ts userApi-temp.ts
git commit -m "chore: temporary rename userApi.ts to userApi-temp.ts"
```
#### Step 2 — Rename to Final Name
```bash
git mv userApi-temp.ts UserApi.ts
git commit -m "chore: rename userApi-temp.ts to UserApi.ts (case change)"
```
### Rules
- Never rename by case only in one step
- Always use an intermediate name
- Always commit both steps
- Applies to files and folders
- Do not rely only on IDE rename
- Always verify with `git status`
### Making Git Case-Sensitive on macOS
By default, Git on macOS usually has:
`core.ignorecase=true`
This makes Git ignore case-only changes.
#### Set Case Sensitivity for This Repository
```bash
git config core.ignorecase false
```
#### Verify:
```bash
git config core.ignorecase
```
It should return: `false`
### Important Reality Check
Even if `core.ignorecase=false`:
- macOS file system is still case-insensitive
- Case-only renames can still behave unexpectedly
- The two-step rename process is still the safest approach
+61
View File
@@ -0,0 +1,61 @@
# Backend Architecture
This guide is a short backend architecture reference for CRUX backend apps.
The current backend is based on AdonisJS v6. Use the official AdonisJS folder structure as the default baseline:
https://v6-docs.adonisjs.com/guides/getting-started/folder-structure
## Main Idea
Keep the backend close to the standard AdonisJS structure. Add project-specific folders only when they make ownership clearer.
## Default Folders
| Folder | Purpose |
| ----------- | -------------------------------------------------------------------------------------------- |
| `app/` | Application code: controllers, services, middleware, policies, validators, and domain logic. |
| `start/` | Boot lifecycle files such as routes, kernel setup, env loading, and event registration. |
| `config/` | Runtime configuration for AdonisJS and installed packages. |
| `database/` | Migrations and seeders. |
| `bin/` | Entrypoints for server, console, and tests. |
| `public/` | Static files and generated public assets. |
| `tests/` | Backend tests. |
## Basic Flow
```txt
start/routes.ts
-> controller
-> validator / policy
-> service
-> model / database / external service
```
Controllers should handle HTTP concerns. Services should hold business logic. Validators should own request validation. Policies and abilities should own authorization.
## Module Folders
If a backend app has reusable or generated modules, keep each module's files together when that improves maintainability.
Example:
```txt
app/base_modules/ModuleName/
controller/
services/
validator/
policy/
routes.ts
```
This is an app convention, not an AdonisJS requirement. Prefer the default AdonisJS structure unless a module boundary is useful.
## Rules
- Follow AdonisJS conventions first.
- Keep HTTP logic out of services.
- Keep business logic out of controllers.
- Validate request input before calling services.
- Use policies or abilities for protected operations.
- Document module-specific architecture in module docs, not in this general guide.