denwa-react-shared 1.0.88 → 1.0.90
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.
- package/package.json +15 -2
- package/skills/admin-forms/SKILL.md +121 -0
- package/skills/admin-table/SKILL.md +153 -0
- package/skills/material-map/SKILL.md +65 -0
- package/skills/media-management/SKILL.md +107 -0
- package/skills/session-auth/SKILL.md +100 -0
- package/skills/text-editor/SKILL.md +100 -0
- package/skills/utility-hooks/SKILL.md +97 -0
package/package.json
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "denwa-react-shared",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.90",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Denwa",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/Denwa799/react-shared.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"react",
|
|
13
|
+
"shared",
|
|
14
|
+
"admin",
|
|
15
|
+
"tanstack-intent"
|
|
16
|
+
],
|
|
7
17
|
"main": "dist/denwa-react-shared.umd.js",
|
|
8
18
|
"module": "dist/denwa-react-shared.es.js",
|
|
9
19
|
"files": [
|
|
10
|
-
"dist"
|
|
20
|
+
"dist",
|
|
21
|
+
"skills",
|
|
22
|
+
"!skills/_artifacts"
|
|
11
23
|
],
|
|
12
24
|
"types": "dist/index.d.ts",
|
|
13
25
|
"exports": {
|
|
@@ -63,6 +75,7 @@
|
|
|
63
75
|
"@types/react": "^19.1.6",
|
|
64
76
|
"@types/react-dom": "^19.1.6",
|
|
65
77
|
"@types/validator": "^13.15.1",
|
|
78
|
+
"@tanstack/intent": "latest",
|
|
66
79
|
"@vitejs/plugin-react-swc": "^3.10.1",
|
|
67
80
|
"autoprefixer": "^10.4.21",
|
|
68
81
|
"eslint": "^9.28.0",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: admin-forms
|
|
3
|
+
description: >
|
|
4
|
+
Implement entity editor forms using DrawerForm and specialized inputs.
|
|
5
|
+
Covers form submission, language switching, and integration with
|
|
6
|
+
AdminTable actions.
|
|
7
|
+
type: framework
|
|
8
|
+
library: denwa-react-shared
|
|
9
|
+
framework: react
|
|
10
|
+
library_version: "1.0.88"
|
|
11
|
+
requires:
|
|
12
|
+
- session-auth
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Drawer Forms & Inputs
|
|
16
|
+
|
|
17
|
+
Most entity management in the admin panel happens within `Drawer` panels using `BaseDrawerForm`. This component standardizes the appearance of save buttons and multi-language controls.
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { BaseDrawerForm, AdminDrawerFormProps } from 'denwa-react-shared';
|
|
23
|
+
|
|
24
|
+
const UserForm = ({ id, action, onClose, onRefetch }: AdminDrawerFormProps<'create' | 'update' | 'read'>) => {
|
|
25
|
+
const { control, handleSubmit } = useForm();
|
|
26
|
+
|
|
27
|
+
const isReadOnly = action === 'read';
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<BaseDrawerForm
|
|
31
|
+
saveText="Сохранить"
|
|
32
|
+
submitButtonText={action === 'create' ? 'Создать' : 'Обновить'}
|
|
33
|
+
isVisibleSubmit={!isReadOnly}
|
|
34
|
+
onSubmitClick={handleSubmit(onSave)}
|
|
35
|
+
>
|
|
36
|
+
<Controller
|
|
37
|
+
name="name"
|
|
38
|
+
control={control}
|
|
39
|
+
render={({ field }) => <Input {...field} disabled={isReadOnly} />}
|
|
40
|
+
/>
|
|
41
|
+
{/* ... form content */}
|
|
42
|
+
</BaseDrawerForm>
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Core Patterns
|
|
48
|
+
|
|
49
|
+
### Language Switching
|
|
50
|
+
If the entity supports multiple languages, enable `isVisibleLanguage` and provide `languagesData`.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<BaseDrawerForm
|
|
54
|
+
isVisibleLanguage
|
|
55
|
+
language={currentLang}
|
|
56
|
+
languagesData={[
|
|
57
|
+
{ label: 'Русский', value: 'ru' },
|
|
58
|
+
{ label: 'English', value: 'en' },
|
|
59
|
+
]}
|
|
60
|
+
onChangeLang={(lang) => setLang(lang)}
|
|
61
|
+
// Optional: Bulk translation button
|
|
62
|
+
isVisibleLanguageButton
|
|
63
|
+
translateAllText="Перевести все"
|
|
64
|
+
onTranslateAllClick={handleTranslate}
|
|
65
|
+
>
|
|
66
|
+
{/* ... fields */}
|
|
67
|
+
</BaseDrawerForm>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Integration with AdminTable
|
|
71
|
+
The `AdminTable` orchestration passes `id`, `action`, `onClose`, and `onRefetch` to the `drawerContent` component.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// In AdminTable:
|
|
75
|
+
<AdminTable
|
|
76
|
+
drawerContent={UserForm}
|
|
77
|
+
readAction="read"
|
|
78
|
+
createAction="create"
|
|
79
|
+
updateAction="update"
|
|
80
|
+
// ...
|
|
81
|
+
/>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Common Mistakes
|
|
85
|
+
|
|
86
|
+
### MEDIUM Using raw antd Form
|
|
87
|
+
Wrong:
|
|
88
|
+
```tsx
|
|
89
|
+
import { Form } from 'antd';
|
|
90
|
+
return <Form>{children}</Form>;
|
|
91
|
+
```
|
|
92
|
+
Correct:
|
|
93
|
+
```tsx
|
|
94
|
+
import { BaseDrawerForm } from 'denwa-react-shared';
|
|
95
|
+
return <BaseDrawerForm>{children}</BaseDrawerForm>;
|
|
96
|
+
```
|
|
97
|
+
`BaseDrawerForm` wraps `antd` Form but adds standardized footer buttons and integrated logic for language switching and submission loading states.
|
|
98
|
+
|
|
99
|
+
Source: maintainer interview
|
|
100
|
+
|
|
101
|
+
### HIGH Mismanaging "read" action state
|
|
102
|
+
Wrong:
|
|
103
|
+
```tsx
|
|
104
|
+
const UserForm = ({ action }) => {
|
|
105
|
+
return <BaseDrawerForm>{/* inputs are always enabled */}</BaseDrawerForm>;
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
Correct:
|
|
109
|
+
```tsx
|
|
110
|
+
const UserForm = ({ action }) => {
|
|
111
|
+
const isReadOnly = action === 'read';
|
|
112
|
+
return (
|
|
113
|
+
<BaseDrawerForm isVisibleSubmit={!isReadOnly}>
|
|
114
|
+
<Input disabled={isReadOnly} />
|
|
115
|
+
</BaseDrawerForm>
|
|
116
|
+
);
|
|
117
|
+
};
|
|
118
|
+
```
|
|
119
|
+
When `action` is 'read', the form should visually hide the submit button and disable all input fields.
|
|
120
|
+
|
|
121
|
+
Source: src/shared/ui/admin-table/index.tsx
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: admin-table
|
|
3
|
+
description: >
|
|
4
|
+
Implement complex data tables using AdminTable and useFetchTableData.
|
|
5
|
+
Covers columns configuration, server-side filtering, sorting,
|
|
6
|
+
and search state mapping.
|
|
7
|
+
type: framework
|
|
8
|
+
library: denwa-react-shared
|
|
9
|
+
framework: react
|
|
10
|
+
library_version: "1.0.88"
|
|
11
|
+
requires:
|
|
12
|
+
- session-auth
|
|
13
|
+
sources:
|
|
14
|
+
- "Denwa799/react-shared:src/shared/ui/admin-table/index.tsx"
|
|
15
|
+
- "Denwa799/react-shared:src/shared/lib/hooks/use-fetch-table-data.ts"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Admin Table & Data Fetching
|
|
19
|
+
|
|
20
|
+
The `AdminTable` component is the centerpiece for building resource lists. It works in conjunction with `useFetchTableData` to map UI states (search, sort, page) to API filters.
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { AdminTable, useFetchTableData } from 'denwa-react-shared';
|
|
26
|
+
import { ColumnsType } from 'antd/es/table';
|
|
27
|
+
|
|
28
|
+
const UsersPage = () => {
|
|
29
|
+
// 1. Setup state setters (usually from nuqs or local state)
|
|
30
|
+
const [order, setOrder] = useState('createdAtDesc');
|
|
31
|
+
const [search, setSearch] = useState('');
|
|
32
|
+
|
|
33
|
+
// 2. Use hook to get mapped filters/sort for API
|
|
34
|
+
const { filter, sort, onChangeOrder, ...searchHandlers } = useFetchTableData({
|
|
35
|
+
order,
|
|
36
|
+
search,
|
|
37
|
+
onSetOrder: setOrder,
|
|
38
|
+
onSetSearch: setSearch,
|
|
39
|
+
// ... other setters for pagination, radio, date search
|
|
40
|
+
sortFields: [{ key: 'createdAtDesc', field: 'createdAt', order: 'desc' }],
|
|
41
|
+
searchFields: ['name', 'email'],
|
|
42
|
+
// ... field configuration
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 3. Render table
|
|
46
|
+
return (
|
|
47
|
+
<AdminTable
|
|
48
|
+
tableData={data}
|
|
49
|
+
columns={columns}
|
|
50
|
+
order={order}
|
|
51
|
+
onChangeOrder={onChangeOrder}
|
|
52
|
+
searchProps={{
|
|
53
|
+
...searchHandlers,
|
|
54
|
+
// ... text labels
|
|
55
|
+
}}
|
|
56
|
+
// ... actions and text props
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Core Patterns
|
|
63
|
+
|
|
64
|
+
### Configuring Search and Filters
|
|
65
|
+
`searchProps` expects a `TableSearchProps` object. Ensure you provide the `datePickerComponent` and all required handlers from `useFetchTableData`.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<AdminTable
|
|
69
|
+
searchProps={{
|
|
70
|
+
datePickerComponent: BaseDatePicker,
|
|
71
|
+
searchText: 'Поиск',
|
|
72
|
+
noDateText: 'Нет даты',
|
|
73
|
+
emptyText: 'Ничего не найдено',
|
|
74
|
+
searchSelect: currentSearchField,
|
|
75
|
+
radioOptions: [{ label: 'Все', value: 'all' }],
|
|
76
|
+
searchOptions: [{ label: 'Имя', value: 'name' }],
|
|
77
|
+
searchType: 'text',
|
|
78
|
+
onChangeSearch: handleFieldChange,
|
|
79
|
+
onChangeTextSearch: handleTextChange,
|
|
80
|
+
}}
|
|
81
|
+
// ...
|
|
82
|
+
/>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Server-Side Pagination
|
|
86
|
+
The table expects `serverPagination` prop of type `IPaginate`.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<AdminTable
|
|
90
|
+
serverPagination={{
|
|
91
|
+
total: totalItems,
|
|
92
|
+
page: currentPage,
|
|
93
|
+
limit: pageSize,
|
|
94
|
+
}}
|
|
95
|
+
onSetPaginate={(page, limit) => {
|
|
96
|
+
setPage(page);
|
|
97
|
+
setLimit(limit);
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Common Mistakes
|
|
103
|
+
|
|
104
|
+
### HIGH Hallucinating internal state management
|
|
105
|
+
Wrong:
|
|
106
|
+
```tsx
|
|
107
|
+
// Trying to use useFetchTableData without passing external setters
|
|
108
|
+
const tableInfo = useFetchTableData({
|
|
109
|
+
order: 'desc', // Static value
|
|
110
|
+
// Missing onSetOrder, onSetSearch, etc.
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
Correct:
|
|
114
|
+
```tsx
|
|
115
|
+
const [order, setOrder] = useState('desc');
|
|
116
|
+
const tableInfo = useFetchTableData({
|
|
117
|
+
order,
|
|
118
|
+
onSetOrder: setOrder,
|
|
119
|
+
// Must provide setters to allow the hook to update external state
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
The hook acts as a logic mapper and relies on external state (like `nuqs` or `useState`) to persist changes.
|
|
123
|
+
|
|
124
|
+
Source: maintainer interview
|
|
125
|
+
|
|
126
|
+
### HIGH Passings wrong pagination format
|
|
127
|
+
Wrong:
|
|
128
|
+
```tsx
|
|
129
|
+
// Using antd internal pagination object
|
|
130
|
+
<AdminTable serverPagination={{ current: 1, pageSize: 10 }} />
|
|
131
|
+
```
|
|
132
|
+
Correct:
|
|
133
|
+
```tsx
|
|
134
|
+
<AdminTable serverPagination={{ page: 1, limit: 10, total: 100 }} />
|
|
135
|
+
```
|
|
136
|
+
`AdminTable` expects a custom `IPaginate` shape, not the standard Ant Design pagination object.
|
|
137
|
+
|
|
138
|
+
Source: src/shared/ui/admin-table/types.ts
|
|
139
|
+
|
|
140
|
+
### MEDIUM Using raw antd Table
|
|
141
|
+
Wrong:
|
|
142
|
+
```tsx
|
|
143
|
+
import { Table } from 'antd';
|
|
144
|
+
return <Table dataSource={data} columns={columns} />;
|
|
145
|
+
```
|
|
146
|
+
Correct:
|
|
147
|
+
```tsx
|
|
148
|
+
import { AdminTable } from 'denwa-react-shared';
|
|
149
|
+
return <AdminTable tableData={data} columns={columns} ... />;
|
|
150
|
+
```
|
|
151
|
+
Using raw `Table` loses integrated search UI, standardized skeletons, and the "Drawer Form" orchestration.
|
|
152
|
+
|
|
153
|
+
Source: maintainer interview
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: material-map
|
|
3
|
+
description: >
|
|
4
|
+
Integrate Yandex Maps for coordinate selection. Covers map initialization,
|
|
5
|
+
setting markers, and coordinate synchronization.
|
|
6
|
+
type: framework
|
|
7
|
+
library: denwa-react-shared
|
|
8
|
+
framework: react
|
|
9
|
+
library_version: "1.0.88"
|
|
10
|
+
requires:
|
|
11
|
+
- session-auth
|
|
12
|
+
sources:
|
|
13
|
+
- "Denwa799/react-shared:src/shared/ui/material-map/index.tsx"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Yandex Maps Integration
|
|
17
|
+
|
|
18
|
+
`BaseMaterialMap` uses Yandex Maps to allow users to select geographic coordinates. It requires a Yandex Maps API key and provides a simple interface for single-point selection.
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { BaseMaterialMap } from 'denwa-react-shared';
|
|
24
|
+
|
|
25
|
+
const LocationPicker = () => {
|
|
26
|
+
return (
|
|
27
|
+
<BaseMaterialMap
|
|
28
|
+
apiKey="your-yandex-api-key"
|
|
29
|
+
value={{ lat: 55.751244, lng: 37.618423 }}
|
|
30
|
+
onChange={(coords) => setLocation(coords)}
|
|
31
|
+
height={400}
|
|
32
|
+
zoom={10}
|
|
33
|
+
noDataText="Координаты не выбраны"
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Core Patterns
|
|
40
|
+
|
|
41
|
+
### Manual Coordinate Entry
|
|
42
|
+
The component displays the current coordinates in inputs. Users can either click on the map or type values manually. Always ensure `onChange` is provided to capture changes from both sources.
|
|
43
|
+
|
|
44
|
+
### Custom Height and Styling
|
|
45
|
+
The map container should be constrained by the `height` prop. It defaults to `100%` width of the parent container.
|
|
46
|
+
|
|
47
|
+
## Common Mistakes
|
|
48
|
+
|
|
49
|
+
### CRITICAL Missing API Key
|
|
50
|
+
Wrong:
|
|
51
|
+
```tsx
|
|
52
|
+
<BaseMaterialMap value={coords} />
|
|
53
|
+
```
|
|
54
|
+
Correct:
|
|
55
|
+
```tsx
|
|
56
|
+
<BaseMaterialMap apiKey={import.meta.env.VITE_YAMAP_KEY} value={coords} />
|
|
57
|
+
```
|
|
58
|
+
The map uses `@iminside/react-yandex-maps` internally. If the `apiKey` is missing or invalid, the map will fail to load or will show a "Demo mode" watermark.
|
|
59
|
+
|
|
60
|
+
Source: src/shared/ui/material-map/index.tsx
|
|
61
|
+
|
|
62
|
+
### HIGH Forgetting default center for empty values
|
|
63
|
+
If the `value` is undefined, the map might center on a default (often 0,0) or fail. Always provide a fallback `defaultCenter` or check for existence.
|
|
64
|
+
|
|
65
|
+
Source: maintainer interview
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: media-management
|
|
3
|
+
description: >
|
|
4
|
+
Handle single/multiple image uploads and file attachments.
|
|
5
|
+
Covers drag-and-drop sorting, temporary upload patterns,
|
|
6
|
+
and media deletion.
|
|
7
|
+
type: framework
|
|
8
|
+
library: denwa-react-shared
|
|
9
|
+
framework: react
|
|
10
|
+
library_version: "1.0.88"
|
|
11
|
+
requires:
|
|
12
|
+
- session-auth
|
|
13
|
+
sources:
|
|
14
|
+
- "Denwa799/react-shared:src/shared/ui/image-upload/base-image-upload.tsx"
|
|
15
|
+
- "Denwa799/react-shared:src/shared/ui/file-upload/file-upload.tsx"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Media & File Management
|
|
19
|
+
|
|
20
|
+
The library provides `BaseImageUpload` for image galleries and `FileUpload` for general documents. Both rely on a two-step process: uploading to a temporary location first, then associating the path with the entity.
|
|
21
|
+
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
### Image Upload with Sorting
|
|
25
|
+
```tsx
|
|
26
|
+
import { BaseImageUpload, TAdminImage } from 'denwa-react-shared';
|
|
27
|
+
|
|
28
|
+
const Gallery = () => {
|
|
29
|
+
return (
|
|
30
|
+
<BaseImageUpload
|
|
31
|
+
isMultiple
|
|
32
|
+
label="Галерея"
|
|
33
|
+
images={images}
|
|
34
|
+
onUpdateTempImage={handleTempUpload}
|
|
35
|
+
onUpdateItems={handleReorder}
|
|
36
|
+
onUpdateDeleteItems={handleDelete}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Core Patterns
|
|
43
|
+
|
|
44
|
+
### Two-Step Upload Process
|
|
45
|
+
Components emit native File objects. You must upload these to a temporary storage API and return the resulting path to the component's state.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
const handleTempUpload = async (file: File) => {
|
|
49
|
+
const formData = new FormData();
|
|
50
|
+
formData.append('file', file);
|
|
51
|
+
|
|
52
|
+
const response = await api.upload(formData); // Your API
|
|
53
|
+
return response.path; // e.g. "/temp/random-id.png"
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### File Attachments
|
|
58
|
+
For non-image files, use `FileUpload`. It supports single or multiple files with size/extension limits.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { FileUpload } from 'denwa-react-shared';
|
|
62
|
+
|
|
63
|
+
<FileUpload
|
|
64
|
+
label="Документ"
|
|
65
|
+
onUpload={handleFileUpload}
|
|
66
|
+
onRemove={handleFileRemove}
|
|
67
|
+
items={data.files}
|
|
68
|
+
isMultiple={false}
|
|
69
|
+
/>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Common Mistakes
|
|
73
|
+
|
|
74
|
+
### CRITICAL Forgetting to handle reordering
|
|
75
|
+
Wrong:
|
|
76
|
+
```tsx
|
|
77
|
+
// Only handling uploads
|
|
78
|
+
<BaseImageUpload onUpdateTempImage={upload} />
|
|
79
|
+
```
|
|
80
|
+
Correct:
|
|
81
|
+
```tsx
|
|
82
|
+
<BaseImageUpload
|
|
83
|
+
onUpdateTempImage={upload}
|
|
84
|
+
onUpdateItems={(newItems) => setImages(newItems)}
|
|
85
|
+
/>
|
|
86
|
+
```
|
|
87
|
+
If `onUpdateItems` is missing, drag-and-drop reordering will visually work but won't persist to state.
|
|
88
|
+
|
|
89
|
+
Source: src/shared/ui/image-upload/base-image-upload.tsx
|
|
90
|
+
|
|
91
|
+
### HIGH Passing only URLs instead of Image objects
|
|
92
|
+
Wrong:
|
|
93
|
+
```tsx
|
|
94
|
+
<BaseImageUpload images={["/image1.png"]} />
|
|
95
|
+
```
|
|
96
|
+
Correct:
|
|
97
|
+
```tsx
|
|
98
|
+
<BaseImageUpload images={[{ id: 1, path: "/image1.png" }]} />
|
|
99
|
+
```
|
|
100
|
+
`BaseImageUpload` expects an array of objects containing `id` and `path`, not raw strings.
|
|
101
|
+
|
|
102
|
+
Source: src/shared/ui/image-upload/types.ts
|
|
103
|
+
|
|
104
|
+
### MEDIUM Missing upload button due to limits
|
|
105
|
+
If the `limit` prop is reached, the "Upload" button disappears. Ensure you communicate this UI behavior or provide a way to delete items.
|
|
106
|
+
|
|
107
|
+
Source: src/shared/ui/image-upload/base-image-upload.tsx:55
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: session-auth
|
|
3
|
+
description: >
|
|
4
|
+
Manage authentication sessions, parse server responses, and validate identity
|
|
5
|
+
cookies using Zod schemas. Use for handling login flow results and
|
|
6
|
+
session integrity checks.
|
|
7
|
+
type: core
|
|
8
|
+
library: denwa-react-shared
|
|
9
|
+
library_version: "1.0.88"
|
|
10
|
+
sources:
|
|
11
|
+
- "Denwa799/react-shared:src/shared/schemas/index.ts"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session & Auth Management
|
|
15
|
+
|
|
16
|
+
This skill covers the standardization of server responses and session state validation using Zod schemas. All API interactions should be validated against these schemas to ensure data consistency.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { responseSchema, sessionCookieSchema } from 'denwa-react-shared';
|
|
22
|
+
|
|
23
|
+
// Example: Validating an API response
|
|
24
|
+
const handleApiResponse = (data: unknown) => {
|
|
25
|
+
const result = responseSchema.safeParse(data);
|
|
26
|
+
if (!result.success) {
|
|
27
|
+
console.error('API format mismatch:', result.error);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
return result.data;
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Core Patterns
|
|
35
|
+
|
|
36
|
+
### Parsing Session Cookies
|
|
37
|
+
Use `sessionCookieSchema` to validate the structure of the authentication cookie before using it in the application.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { sessionCookieSchema } from 'denwa-react-shared';
|
|
41
|
+
|
|
42
|
+
const validateSession = (cookieData: unknown) => {
|
|
43
|
+
const session = sessionCookieSchema.parse(cookieData);
|
|
44
|
+
return {
|
|
45
|
+
isLoggedIn: !!session.tokens.accessToken.token,
|
|
46
|
+
userRoles: session.roles,
|
|
47
|
+
isAdmin: session.roles.includes('admin'),
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Handling Standard Error Envelopes
|
|
53
|
+
The `responseSchema` includes a structured `error` field. Always check for both the top-level `error` object and `statusCode`.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { responseSchema } from 'denwa-react-shared';
|
|
57
|
+
|
|
58
|
+
async function fetchData(url: string) {
|
|
59
|
+
const response = await fetch(url);
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
|
|
62
|
+
const parsed = responseSchema.parse(data);
|
|
63
|
+
if (parsed.error) {
|
|
64
|
+
throw new Error(parsed.error.message || 'Unknown error');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return parsed.data;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Common Mistakes
|
|
72
|
+
|
|
73
|
+
### MEDIUM Manually parsing cookies without schema
|
|
74
|
+
Wrong:
|
|
75
|
+
```typescript
|
|
76
|
+
const id = JSON.parse(cookies.get('session')).id;
|
|
77
|
+
```
|
|
78
|
+
Correct:
|
|
79
|
+
```typescript
|
|
80
|
+
const session = sessionCookieSchema.parse(cookies.get('session'));
|
|
81
|
+
const id = session.id;
|
|
82
|
+
```
|
|
83
|
+
Manually accessing properties bypasses validation and creates runtime risks if the session structure changes.
|
|
84
|
+
|
|
85
|
+
Source: maintainer interview
|
|
86
|
+
|
|
87
|
+
### HIGH Ignoring the data/response duality
|
|
88
|
+
Wrong:
|
|
89
|
+
```typescript
|
|
90
|
+
// Expecting data to always be the result
|
|
91
|
+
const users = response.data;
|
|
92
|
+
```
|
|
93
|
+
Correct:
|
|
94
|
+
```typescript
|
|
95
|
+
// Checking both data and any legacy response fields
|
|
96
|
+
const users = response.data ?? response.response;
|
|
97
|
+
```
|
|
98
|
+
The `responseSchema` allows for flexibility in return fields (`data` vs `response`). Agents should account for both.
|
|
99
|
+
|
|
100
|
+
Source: src/shared/schemas/index.ts
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: text-editor
|
|
3
|
+
description: >
|
|
4
|
+
Implement rich text editing using BaseTextEditor (Slate.js).
|
|
5
|
+
Covers content synchronization (JSON and HTML) and toolbar configuration.
|
|
6
|
+
type: framework
|
|
7
|
+
library: denwa-react-shared
|
|
8
|
+
framework: react
|
|
9
|
+
library_version: "1.0.88"
|
|
10
|
+
requires:
|
|
11
|
+
- session-auth
|
|
12
|
+
sources:
|
|
13
|
+
- "Denwa799/react-shared:src/shared/ui/text-editor/text-editor.tsx"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Slate Rich Text Editor
|
|
17
|
+
|
|
18
|
+
`BaseTextEditor` provides a rich text editing experience based on Slate.js. It handles serialization to HTML and synchronization with external form state through debounced handlers.
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { BaseTextEditor, BaseTextEditorRefMethods } from 'denwa-react-shared';
|
|
24
|
+
|
|
25
|
+
const MyForm = () => {
|
|
26
|
+
const editorRef = useRef<BaseTextEditorRefMethods>(null);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<BaseTextEditor
|
|
30
|
+
boldText="Жирный"
|
|
31
|
+
italicText="Курсив"
|
|
32
|
+
underlineText="Подчеркнутый"
|
|
33
|
+
// ... more labels
|
|
34
|
+
onSetContent={(jsonString, lang) => setFieldValue('content', jsonString)}
|
|
35
|
+
onSetHtml={(htmlString, lang) => setFieldValue('contentHtml', htmlString)}
|
|
36
|
+
onErrorMessage={(msg) => alert(msg)}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Core Patterns
|
|
43
|
+
|
|
44
|
+
### Content Initialization
|
|
45
|
+
To set the editor value (e.g. when loading an existing entity), use the `setValue` method on the ref. It expects the serialized JSON string found in the database.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (data?.content) {
|
|
50
|
+
editorRef.current?.setValue(data.content);
|
|
51
|
+
}
|
|
52
|
+
}, [data]);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Syncing JSON and HTML
|
|
56
|
+
The editor emits events for both the raw Slate JSON structure (for editing) and the rendered HTML (for display/SEO). Always provide both `onSetContent` and `onSetHtml`.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
<BaseTextEditor
|
|
60
|
+
onSetContent={(json) => updateJson(json)}
|
|
61
|
+
onSetHtml={(html) => updateHtml(html)}
|
|
62
|
+
/>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Common Mistakes
|
|
66
|
+
|
|
67
|
+
### HIGH Passing raw HTML to setValue
|
|
68
|
+
Wrong:
|
|
69
|
+
```tsx
|
|
70
|
+
editorRef.current?.setValue("<p>Hello world</p>");
|
|
71
|
+
```
|
|
72
|
+
Correct:
|
|
73
|
+
```tsx
|
|
74
|
+
editorRef.current?.setValue(JSON.stringify([{ type: 'paragraph', children: [{ text: 'Hello world' }] }]));
|
|
75
|
+
```
|
|
76
|
+
`setValue` expects a serialized JSON string representing the Slate document structure. Passing raw HTML will cause parsing errors or an empty editor.
|
|
77
|
+
|
|
78
|
+
Source: src/shared/ui/text-editor/text-editor.tsx:113
|
|
79
|
+
|
|
80
|
+
### MEDIUM Missing synchronization handlers
|
|
81
|
+
Wrong:
|
|
82
|
+
```tsx
|
|
83
|
+
<BaseTextEditor onSetHtml={(html) => setHtml(html)} />
|
|
84
|
+
// Missing onSetContent
|
|
85
|
+
```
|
|
86
|
+
Correct:
|
|
87
|
+
```tsx
|
|
88
|
+
<BaseTextEditor
|
|
89
|
+
onSetContent={(json) => setJson(json)}
|
|
90
|
+
onSetHtml={(html) => setHtml(html)}
|
|
91
|
+
/>
|
|
92
|
+
```
|
|
93
|
+
Failing to provide `onSetContent` prevents the application from saving the internal editor state, making future edits impossible even if the HTML is saved.
|
|
94
|
+
|
|
95
|
+
Source: src/shared/ui/text-editor/text-editor.tsx:83
|
|
96
|
+
|
|
97
|
+
### MEDIUM Ignoring the debounce
|
|
98
|
+
The editor uses a 1-second debounce (`TIME.seconds.seconds1`) before firing sync events. Do not expect immediate state updates on every keystroke.
|
|
99
|
+
|
|
100
|
+
Source: src/shared/ui/text-editor/text-editor.tsx:86
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: utility-hooks
|
|
3
|
+
description: >
|
|
4
|
+
Essential helper hooks for responsive design, provider composition,
|
|
5
|
+
and application lifecycle management.
|
|
6
|
+
type: framework
|
|
7
|
+
library: denwa-react-shared
|
|
8
|
+
framework: react
|
|
9
|
+
library_version: "1.0.88"
|
|
10
|
+
requires:
|
|
11
|
+
- session-auth
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Shared Utilities & Hooks
|
|
15
|
+
|
|
16
|
+
Standardized helpers to reduce boilerplate in provider setup and responsive logic.
|
|
17
|
+
|
|
18
|
+
## Setup
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
import { ProviderComposer, useViewPort } from 'denwa-react-shared';
|
|
22
|
+
|
|
23
|
+
const App = ({ children }) => {
|
|
24
|
+
return (
|
|
25
|
+
<ProviderComposer
|
|
26
|
+
providers={[
|
|
27
|
+
<AuthProvider key="auth" />,
|
|
28
|
+
<ConfigProvider key="config" />,
|
|
29
|
+
<ReactQueryProvider key="query" />,
|
|
30
|
+
]}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</ProviderComposer>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Core Patterns
|
|
39
|
+
|
|
40
|
+
### Responsive Logic with useViewPort
|
|
41
|
+
Standardizes breakpoints across the admin panel.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
const { isMobile, isTablet, isLaptop } = useViewPort();
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div>
|
|
48
|
+
{isMobile ? <MobileNav /> : <DesktopNav />}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Local Storage Wrapper
|
|
54
|
+
Safe wrappers for browser storage with type-safety.
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { getStorageItem, setStorageItem } from 'denwa-react-shared';
|
|
58
|
+
|
|
59
|
+
const theme = getStorageItem('admin_theme', 'light');
|
|
60
|
+
setStorageItem('admin_theme', 'dark');
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Common Mistakes
|
|
64
|
+
|
|
65
|
+
### MEDIUM Pyramid of doom in Providers
|
|
66
|
+
Wrong:
|
|
67
|
+
```tsx
|
|
68
|
+
<Auth>
|
|
69
|
+
<Config>
|
|
70
|
+
<Query>
|
|
71
|
+
<Layout>{children}</Layout>
|
|
72
|
+
</Query>
|
|
73
|
+
</Config>
|
|
74
|
+
</Auth>
|
|
75
|
+
```
|
|
76
|
+
Correct:
|
|
77
|
+
```tsx
|
|
78
|
+
<ProviderComposer providers={[<Auth/>, <Config/>, <Query/>]}>
|
|
79
|
+
<Layout>{children}</Layout>
|
|
80
|
+
</ProviderComposer>
|
|
81
|
+
```
|
|
82
|
+
`ProviderComposer` simplifies deep nesting of React providers, making the root file more readable.
|
|
83
|
+
|
|
84
|
+
Source: src/shared/lib/provider-composer/index.tsx
|
|
85
|
+
|
|
86
|
+
### MEDIUM Hardcoding breakpoints
|
|
87
|
+
Wrong:
|
|
88
|
+
```tsx
|
|
89
|
+
const isMobile = useMediaQuery('(max-width: 768px)');
|
|
90
|
+
```
|
|
91
|
+
Correct:
|
|
92
|
+
```tsx
|
|
93
|
+
const { isMobile } = useViewPort();
|
|
94
|
+
```
|
|
95
|
+
Internal library components (like `AdminTable`) use breakpoints from `useViewPort`. Custom components should use the same hook to ensure consistent responsive behavior.
|
|
96
|
+
|
|
97
|
+
Source: src/shared/lib/hooks/use-view-port.ts
|