nnews-react 0.4.4 โ 0.5.0
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/README.md +393 -418
- package/dist/index.cjs +130 -86
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +93 -12
- package/dist/index.js +9679 -7943
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -1,68 +1,135 @@
|
|
|
1
|
-
# nnews-react
|
|
1
|
+
# nnews-react - React Component Library for NNews CMS
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
## Overview
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
**nnews-react** is a React component library (npm package) for the NNews content management system. It provides ready-made components, hooks, API services, context providers, and a theming system that consuming applications integrate via `NNewsProvider`. Includes AI-powered article generation with ChatGPT and DALL-E 3.
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
โจ **Complete Auth Suite** - Login, Register, Password Recovery, Change Password
|
|
12
|
-
๐ค **User Management** - Full CRUD with profile editing
|
|
13
|
-
๐ญ **Role Management** - Role-based access control
|
|
14
|
-
๐ฏ **Security** - Device fingerprinting with FingerprintJS
|
|
15
|
-
๐ **Type-Safe** - Full TypeScript support
|
|
12
|
+
Built with **TypeScript**, **Tailwind CSS**, **Radix UI**, and **i18next** for internationalization (English and Portuguese).
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
๐ **Article Management** - Full CRUD with Markdown editor
|
|
19
|
-
๐ **Category System** - Hierarchical categories with parent-child relationships
|
|
20
|
-
๐ท๏ธ **Tag Management** - Flexible tagging with merge functionality
|
|
21
|
-
โ๏ธ **Markdown Editor** - Live preview with syntax highlighting
|
|
22
|
-
๐๏ธ **Article Viewer** - Beautiful article display with GitHub-flavored Markdown
|
|
23
|
-
๐ **Access Control** - Role-based content visibility
|
|
14
|
+
The `example-app/` directory contains a full working application demonstrating library usage in dark mode.
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
๐จ **Theme Support** - Light/Dark mode with system detection
|
|
27
|
-
๐ฆ **Tree-shakeable** - Import only what you need
|
|
28
|
-
โฟ **Accessible** - WCAG compliant
|
|
29
|
-
๐ฑ **Responsive** - Mobile-first design
|
|
16
|
+
---
|
|
30
17
|
|
|
31
|
-
##
|
|
18
|
+
## ๐ Features
|
|
19
|
+
|
|
20
|
+
### Content Management
|
|
21
|
+
- ๐ **Article Management** โ Full CRUD with rich text (Quill), Markdown, and plain text editors
|
|
22
|
+
- ๐ค **AI-Powered Content** โ Create and update articles with ChatGPT, generate images with DALL-E 3
|
|
23
|
+
- ๐ **Hierarchical Categories** โ Parent-child category system with full CRUD
|
|
24
|
+
- ๐ท๏ธ **Tag System** โ Flexible tagging with merge functionality
|
|
25
|
+
- ๐ **Search & Filters** โ Search articles by keyword, filter by category, status, tag, or roles
|
|
26
|
+
- ๐ **Pagination** โ Built-in `PagedResult<T>` pattern with `hasNext`/`hasPrevious`
|
|
27
|
+
- ๐ผ๏ธ **Image Upload** โ Featured image upload with preview and placeholder fallback
|
|
28
|
+
|
|
29
|
+
### Architecture
|
|
30
|
+
- ๐จ **Theme System** โ Configurable `light`/`dark`/`system` mode via `NNewsConfig.theme`
|
|
31
|
+
- ๐ **Internationalization** โ i18next with `nnews` namespace, EN/PT built-in, extensible
|
|
32
|
+
- ๐ข **Multi-tenancy** โ `X-Tenant-Id` header injection via Axios interceptor
|
|
33
|
+
- ๐ **Role-based Access** โ Content visibility control per role
|
|
34
|
+
- ๐ฆ **Tree-shakeable** โ ESM + CJS dual output, import only what you need
|
|
35
|
+
- โฟ **Accessible** โ Radix UI primitives with focus trapping, keyboard navigation, ARIA
|
|
36
|
+
- ๐ฑ **Responsive** โ Mobile-first Tailwind CSS design
|
|
37
|
+
|
|
38
|
+
### UI System
|
|
39
|
+
- ๐ช **Normalized Modals** โ All modals use a single Radix-based `Modal` component
|
|
40
|
+
- โ
**ConfirmModal** โ Reusable confirmation dialog with danger/warning/default variants
|
|
41
|
+
- ๐งฉ **UI Primitives** โ Button, Input, Label, Avatar, Modal components
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## ๐ ๏ธ Technologies Used
|
|
46
|
+
|
|
47
|
+
### Core
|
|
48
|
+
- **React 18+** โ UI framework (peer dependency)
|
|
49
|
+
- **TypeScript 5** โ Type safety
|
|
50
|
+
- **Vite** โ Build tooling with SWC plugin
|
|
51
|
+
|
|
52
|
+
### Styling & UI
|
|
53
|
+
- **Tailwind CSS** โ Utility-first CSS with class-based dark mode
|
|
54
|
+
- **Radix UI** โ Accessible Dialog, Dropdown, Select, Tabs, Toast primitives
|
|
55
|
+
- **class-variance-authority** โ Component variant system
|
|
56
|
+
- **lucide-react** โ Icon library
|
|
57
|
+
|
|
58
|
+
### Editors
|
|
59
|
+
- **react-quill-new** โ WYSIWYG rich text editor (HTML)
|
|
60
|
+
- **react-markdown** + **remark-gfm** + **rehype-highlight** โ Markdown rendering with syntax highlighting
|
|
61
|
+
|
|
62
|
+
### State & Data
|
|
63
|
+
- **Axios** โ HTTP client with interceptors
|
|
64
|
+
- **react-hook-form** + **Zod** โ Form handling and validation
|
|
65
|
+
- **i18next** + **react-i18next** โ Internationalization
|
|
66
|
+
|
|
67
|
+
### Testing
|
|
68
|
+
- **Vitest** โ Unit test runner with coverage
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## ๐ Project Structure
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
nnews-react/
|
|
76
|
+
โโโ src/
|
|
77
|
+
โ โโโ components/ # React components
|
|
78
|
+
โ โ โโโ ui/ # Primitives (Button, Input, Modal, ConfirmModal, etc.)
|
|
79
|
+
โ โ โโโ ArticleList.tsx # Article grid with image placeholders
|
|
80
|
+
โ โ โโโ ArticleEditor.tsx # Multi-format article editor
|
|
81
|
+
โ โ โโโ ArticleViewer.tsx # Article display (Markdown/HTML/Plain)
|
|
82
|
+
โ โ โโโ AIArticleGenerator.tsx # AI content generation modal
|
|
83
|
+
โ โ โโโ CategoryList.tsx # Category table
|
|
84
|
+
โ โ โโโ CategoryModal.tsx # Category create/edit modal
|
|
85
|
+
โ โ โโโ TagList.tsx # Tag chips with actions
|
|
86
|
+
โ โ โโโ TagModal.tsx # Tag create/edit modal
|
|
87
|
+
โ โ โโโ TagMerge.tsx # Tag merge modal
|
|
88
|
+
โ โ โโโ RichTextEditor.tsx # Quill WYSIWYG editor
|
|
89
|
+
โ โ โโโ MarkdownEditor.tsx # Markdown editor with preview
|
|
90
|
+
โ โโโ contexts/ # NNewsContext (provider, config, Axios setup)
|
|
91
|
+
โ โโโ hooks/ # useArticles, useCategories, useTags
|
|
92
|
+
โ โโโ services/ # ArticleAPI, CategoryAPI, TagAPI
|
|
93
|
+
โ โโโ types/ # TypeScript types, enums, theme config
|
|
94
|
+
โ โโโ i18n/ # i18next setup, EN/PT translations
|
|
95
|
+
โ โโโ utils/ # cn(), validators
|
|
96
|
+
โ โโโ styles/ # Tailwind CSS base styles
|
|
97
|
+
โ โโโ index.ts # Public API exports
|
|
98
|
+
โโโ example-app/ # Full demo application (dark mode)
|
|
99
|
+
โโโ docs/ # Additional documentation
|
|
100
|
+
โโโ dist/ # Build output (ESM + CJS + types + CSS)
|
|
101
|
+
โโโ package.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## โ๏ธ Installation
|
|
32
107
|
|
|
33
108
|
```bash
|
|
34
|
-
npm install nnews-react
|
|
109
|
+
npm install nnews-react
|
|
110
|
+
```
|
|
35
111
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
112
|
+
**Peer dependencies:**
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm install react react-dom react-router-dom nauth-react
|
|
39
116
|
```
|
|
40
117
|
|
|
41
|
-
|
|
118
|
+
**Tailwind CSS setup:**
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm install -D tailwindcss postcss autoprefixer tailwindcss-animate
|
|
122
|
+
```
|
|
42
123
|
|
|
43
124
|
```javascript
|
|
44
125
|
// tailwind.config.js
|
|
45
126
|
export default {
|
|
46
127
|
darkMode: ['class'],
|
|
47
128
|
content: [
|
|
48
|
-
'./src/**/*.{
|
|
49
|
-
'./node_modules/nnews-react/dist/**/*.{js,
|
|
50
|
-
],
|
|
51
|
-
theme: {
|
|
52
|
-
extend: {
|
|
53
|
-
typography: {
|
|
54
|
-
DEFAULT: {
|
|
55
|
-
css: {
|
|
56
|
-
maxWidth: 'none',
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
plugins: [
|
|
63
|
-
require('tailwindcss-animate'),
|
|
64
|
-
require('@tailwindcss/typography'), // For Markdown rendering
|
|
129
|
+
'./src/**/*.{ts,tsx}',
|
|
130
|
+
'./node_modules/nnews-react/dist/**/*.{js,mjs}',
|
|
65
131
|
],
|
|
132
|
+
plugins: [require('tailwindcss-animate')],
|
|
66
133
|
};
|
|
67
134
|
```
|
|
68
135
|
|
|
@@ -73,321 +140,152 @@ export default {
|
|
|
73
140
|
@tailwind utilities;
|
|
74
141
|
```
|
|
75
142
|
|
|
76
|
-
|
|
143
|
+
---
|
|
77
144
|
|
|
78
|
-
|
|
145
|
+
## ๐ Quick Start
|
|
146
|
+
|
|
147
|
+
### 1. Setup Provider
|
|
79
148
|
|
|
80
149
|
```tsx
|
|
81
|
-
import {
|
|
82
|
-
import { NAuthProvider, NNewsProvider, ThemeProvider } from 'nnews-react';
|
|
150
|
+
import { NNewsProvider } from 'nnews-react';
|
|
83
151
|
import 'nnews-react/styles';
|
|
84
|
-
import './index.css';
|
|
85
152
|
|
|
86
153
|
function App() {
|
|
87
154
|
return (
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}}
|
|
100
|
-
>
|
|
101
|
-
<YourApp />
|
|
102
|
-
</NNewsProvider>
|
|
103
|
-
</NAuthProvider>
|
|
104
|
-
</ThemeProvider>
|
|
105
|
-
</BrowserRouter>
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
```env
|
|
111
|
-
# .env
|
|
112
|
-
VITE_API_URL=https://your-nauth-api.com
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### 2. Use Authentication Components
|
|
116
|
-
|
|
117
|
-
```tsx
|
|
118
|
-
import {
|
|
119
|
-
LoginForm,
|
|
120
|
-
RegisterForm,
|
|
121
|
-
UserEditForm,
|
|
122
|
-
RoleList,
|
|
123
|
-
SearchForm,
|
|
124
|
-
useAuth,
|
|
125
|
-
useProtectedRoute,
|
|
126
|
-
} from 'nnews-react';
|
|
127
|
-
import { useNavigate } from 'react-router-dom';
|
|
128
|
-
|
|
129
|
-
// Login Page
|
|
130
|
-
function LoginPage() {
|
|
131
|
-
const navigate = useNavigate();
|
|
132
|
-
return (
|
|
133
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
134
|
-
<LoginForm
|
|
135
|
-
onSuccess={() => navigate('/dashboard')}
|
|
136
|
-
showRememberMe
|
|
137
|
-
showForgotPassword
|
|
138
|
-
/>
|
|
139
|
-
</div>
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Protected Dashboard
|
|
144
|
-
function Dashboard() {
|
|
145
|
-
const { user, logout } = useAuth();
|
|
146
|
-
useProtectedRoute({ redirectTo: '/login' });
|
|
147
|
-
|
|
148
|
-
return (
|
|
149
|
-
<div>
|
|
150
|
-
<h1>Welcome, {user?.name}</h1>
|
|
151
|
-
<button onClick={logout}>Logout</button>
|
|
152
|
-
</div>
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// User Management
|
|
157
|
-
function CreateUserPage() {
|
|
158
|
-
const navigate = useNavigate();
|
|
159
|
-
return (
|
|
160
|
-
<UserEditForm
|
|
161
|
-
onSuccess={(user) => {
|
|
162
|
-
console.log('User created:', user);
|
|
163
|
-
navigate('/users');
|
|
155
|
+
<NNewsProvider
|
|
156
|
+
config={{
|
|
157
|
+
apiUrl: import.meta.env.VITE_NNEWS_API_URL,
|
|
158
|
+
tenantId: import.meta.env.VITE_TENANT_ID,
|
|
159
|
+
headers: {
|
|
160
|
+
Authorization: `Bearer ${token}`,
|
|
161
|
+
},
|
|
162
|
+
language: 'en', // 'en' | 'pt'
|
|
163
|
+
theme: {
|
|
164
|
+
mode: 'dark', // 'light' | 'dark' | 'system'
|
|
165
|
+
},
|
|
164
166
|
}}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
>
|
|
168
|
+
<YourApp />
|
|
169
|
+
</NNewsProvider>
|
|
167
170
|
);
|
|
168
171
|
}
|
|
169
172
|
```
|
|
170
173
|
|
|
171
|
-
###
|
|
174
|
+
### 2. Use Components
|
|
172
175
|
|
|
173
176
|
```tsx
|
|
174
177
|
import {
|
|
175
178
|
ArticleList,
|
|
176
|
-
ArticleViewer,
|
|
177
179
|
ArticleEditor,
|
|
180
|
+
ArticleViewer,
|
|
181
|
+
AIArticleGenerator,
|
|
178
182
|
CategoryList,
|
|
179
183
|
CategoryModal,
|
|
180
184
|
TagList,
|
|
181
185
|
TagModal,
|
|
186
|
+
TagMerge,
|
|
187
|
+
ConfirmModal,
|
|
182
188
|
useArticles,
|
|
183
189
|
useCategories,
|
|
184
190
|
useTags,
|
|
185
191
|
} from 'nnews-react';
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 3. Fetch and Display Articles
|
|
186
195
|
|
|
187
|
-
|
|
196
|
+
```tsx
|
|
188
197
|
function ArticlesPage() {
|
|
189
198
|
const { articles, loading, fetchArticles, deleteArticle } = useArticles();
|
|
190
199
|
|
|
191
200
|
useEffect(() => {
|
|
192
|
-
fetchArticles({ page: 1, pageSize:
|
|
201
|
+
fetchArticles({ page: 1, pageSize: 10, status: 1 }); // Published only
|
|
193
202
|
}, []);
|
|
194
203
|
|
|
195
204
|
return (
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
onEditClick={(article) => navigate(`/articles/${article.id}/edit`)}
|
|
203
|
-
onDeleteClick={deleteArticle}
|
|
204
|
-
showActions
|
|
205
|
-
/>
|
|
206
|
-
</div>
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Article Viewer Page
|
|
211
|
-
function ArticleViewPage({ articleId }: { articleId: number }) {
|
|
212
|
-
const { getArticleById } = useArticles();
|
|
213
|
-
const [article, setArticle] = useState(null);
|
|
214
|
-
|
|
215
|
-
useEffect(() => {
|
|
216
|
-
getArticleById(articleId).then(setArticle);
|
|
217
|
-
}, [articleId]);
|
|
218
|
-
|
|
219
|
-
if (!article) return <div>Loading...</div>;
|
|
220
|
-
|
|
221
|
-
return (
|
|
222
|
-
<ArticleViewer
|
|
223
|
-
article={article}
|
|
224
|
-
onBack={() => navigate('/articles')}
|
|
225
|
-
onEdit={(article) => navigate(`/articles/${article.id}/edit`)}
|
|
205
|
+
<ArticleList
|
|
206
|
+
articles={articles}
|
|
207
|
+
loading={loading}
|
|
208
|
+
onArticleClick={(article) => navigate(`/articles/${article.articleId}`)}
|
|
209
|
+
onEditClick={(article) => navigate(`/articles/edit/${article.articleId}`)}
|
|
210
|
+
onDeleteClick={(article) => setArticleToDelete(article)}
|
|
226
211
|
showActions
|
|
227
212
|
/>
|
|
228
213
|
);
|
|
229
214
|
}
|
|
215
|
+
```
|
|
230
216
|
|
|
231
|
-
|
|
232
|
-
function ArticleEditorPage({ articleId }: { articleId?: number }) {
|
|
233
|
-
const { createArticle, updateArticle, getArticleById } = useArticles();
|
|
234
|
-
const { categories } = useCategories();
|
|
235
|
-
const { tags } = useTags();
|
|
236
|
-
const [article, setArticle] = useState(null);
|
|
237
|
-
|
|
238
|
-
useEffect(() => {
|
|
239
|
-
if (articleId) {
|
|
240
|
-
getArticleById(articleId).then(setArticle);
|
|
241
|
-
}
|
|
242
|
-
fetchCategories();
|
|
243
|
-
fetchTags();
|
|
244
|
-
}, [articleId]);
|
|
245
|
-
|
|
246
|
-
const handleSave = async (data) => {
|
|
247
|
-
if (article) {
|
|
248
|
-
await updateArticle({ ...data, id: article.id });
|
|
249
|
-
} else {
|
|
250
|
-
await createArticle(data);
|
|
251
|
-
}
|
|
252
|
-
navigate('/articles');
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
return (
|
|
256
|
-
<ArticleEditor
|
|
257
|
-
article={article}
|
|
258
|
-
categories={categories}
|
|
259
|
-
tags={tags}
|
|
260
|
-
onSave={handleSave}
|
|
261
|
-
onCancel={() => navigate('/articles')}
|
|
262
|
-
/>
|
|
263
|
-
);
|
|
264
|
-
}
|
|
217
|
+
---
|
|
265
218
|
|
|
266
|
-
|
|
267
|
-
function CategoriesPage() {
|
|
268
|
-
const { categories, createCategory, updateCategory, deleteCategory } = useCategories();
|
|
269
|
-
const [modalOpen, setModalOpen] = useState(false);
|
|
270
|
-
const [selectedCategory, setSelectedCategory] = useState(null);
|
|
219
|
+
## ๐ง NNewsProvider Configuration
|
|
271
220
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
setSelectedCategory(null);
|
|
292
|
-
}}
|
|
293
|
-
category={selectedCategory}
|
|
294
|
-
categories={categories}
|
|
295
|
-
onSave={selectedCategory ? updateCategory : createCategory}
|
|
296
|
-
/>
|
|
297
|
-
</div>
|
|
298
|
-
);
|
|
221
|
+
```tsx
|
|
222
|
+
interface NNewsConfig {
|
|
223
|
+
apiUrl: string; // Required โ NNews API base URL
|
|
224
|
+
tenantId?: string; // Multi-tenant ID (X-Tenant-Id header)
|
|
225
|
+
apiClient?: AxiosInstance; // Custom Axios instance
|
|
226
|
+
headers?: Record<string, string>; // Dynamic headers (e.g., Authorization)
|
|
227
|
+
language?: string; // 'en' | 'pt' (default: 'en')
|
|
228
|
+
translations?: Record<string, Record<string, unknown>>; // Custom translations
|
|
229
|
+
theme?: {
|
|
230
|
+
mode?: 'light' | 'dark' | 'system'; // Theme mode (default: 'system')
|
|
231
|
+
classNames?: { // CSS class overrides
|
|
232
|
+
root?: string;
|
|
233
|
+
card?: string;
|
|
234
|
+
table?: string;
|
|
235
|
+
modal?: string;
|
|
236
|
+
button?: string;
|
|
237
|
+
input?: string;
|
|
238
|
+
};
|
|
239
|
+
};
|
|
299
240
|
}
|
|
300
241
|
```
|
|
301
242
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
### Authentication Components
|
|
305
|
-
- `LoginForm` - Email/password login with validation
|
|
306
|
-
- `RegisterForm` - Multi-step registration with password strength
|
|
307
|
-
- `ForgotPasswordForm` - Password recovery request
|
|
308
|
-
- `ResetPasswordForm` - Password reset with token
|
|
309
|
-
- `ChangePasswordForm` - Change password for authenticated users
|
|
243
|
+
The provider creates an Axios instance with request interceptors that inject `Authorization` and `X-Tenant-Id` headers dynamically. It also wraps children in a themed container with the `dark` class when appropriate.
|
|
310
244
|
|
|
311
|
-
|
|
312
|
-
- `UserEditForm` - Create and edit users with full profile management (dual mode)
|
|
313
|
-
- `SearchForm` - Search and browse users with pagination
|
|
245
|
+
---
|
|
314
246
|
|
|
315
|
-
|
|
316
|
-
- `RoleList` - List and manage roles with CRUD operations
|
|
317
|
-
- `RoleForm` - Create and edit roles
|
|
247
|
+
## ๐ Hooks API
|
|
318
248
|
|
|
319
|
-
###
|
|
320
|
-
- `ArticleList` - Display paginated list of articles with actions
|
|
321
|
-
- `ArticleViewer` - Beautiful article display with Markdown rendering
|
|
322
|
-
- `ArticleEditor` - Full-featured article editor with Markdown support
|
|
323
|
-
- `MarkdownEditor` - Standalone Markdown editor with live preview
|
|
324
|
-
- `CategoryList` - Display and manage categories
|
|
325
|
-
- `CategoryModal` - Modal for creating/editing categories
|
|
326
|
-
- `TagList` - Display and manage tags
|
|
327
|
-
- `TagModal` - Modal for creating/editing tags
|
|
249
|
+
### useArticles
|
|
328
250
|
|
|
329
|
-
### UI Components
|
|
330
|
-
`Button`, `Input`, `Label`, `Card`, `Avatar`, `DropdownMenu`, `Toaster`
|
|
331
|
-
|
|
332
|
-
## Hooks
|
|
333
|
-
|
|
334
|
-
### Authentication Hooks
|
|
335
251
|
```tsx
|
|
336
|
-
// Authentication state
|
|
337
|
-
const { user, isAuthenticated, login, logout, isLoading } = useAuth();
|
|
338
|
-
|
|
339
|
-
// User management
|
|
340
252
|
const {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
//
|
|
354
|
-
useProtectedRoute({ redirectTo: '/login', requireAdmin: false });
|
|
355
|
-
|
|
356
|
-
// Theme management
|
|
357
|
-
const { theme, setTheme } = useTheme();
|
|
253
|
+
articles, // PagedResult<Article> | null
|
|
254
|
+
loading, // boolean
|
|
255
|
+
error, // Error | null
|
|
256
|
+
fetchArticles, // (params?: ArticleSearchParams) => Promise<void>
|
|
257
|
+
getArticleById, // (id: number) => Promise<Article>
|
|
258
|
+
createArticle, // (article: ArticleInput) => Promise<Article>
|
|
259
|
+
updateArticle, // (article: ArticleUpdate) => Promise<Article>
|
|
260
|
+
deleteArticle, // (id: number) => Promise<void>
|
|
261
|
+
refresh, // () => Promise<void>
|
|
262
|
+
} = useArticles();
|
|
263
|
+
|
|
264
|
+
// ArticleSearchParams supports:
|
|
265
|
+
// categoryId, status, roles, tags, searchTerm, page, pageSize
|
|
358
266
|
```
|
|
359
267
|
|
|
360
|
-
###
|
|
361
|
-
```tsx
|
|
362
|
-
// Article management
|
|
363
|
-
const {
|
|
364
|
-
articles,
|
|
365
|
-
loading,
|
|
366
|
-
error,
|
|
367
|
-
fetchArticles,
|
|
368
|
-
getArticleById,
|
|
369
|
-
createArticle,
|
|
370
|
-
updateArticle,
|
|
371
|
-
deleteArticle,
|
|
372
|
-
refresh,
|
|
373
|
-
} = useArticles();
|
|
268
|
+
### useCategories
|
|
374
269
|
|
|
375
|
-
|
|
270
|
+
```tsx
|
|
376
271
|
const {
|
|
377
|
-
categories,
|
|
272
|
+
categories, // Category[]
|
|
378
273
|
loading,
|
|
379
274
|
error,
|
|
380
|
-
fetchCategories,
|
|
275
|
+
fetchCategories, // (params?: CategoryFilterParams) => Promise<void>
|
|
381
276
|
getCategoryById,
|
|
382
277
|
createCategory,
|
|
383
278
|
updateCategory,
|
|
384
279
|
deleteCategory,
|
|
385
280
|
refresh,
|
|
386
281
|
} = useCategories();
|
|
282
|
+
```
|
|
387
283
|
|
|
388
|
-
|
|
284
|
+
### useTags
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
389
287
|
const {
|
|
390
|
-
tags,
|
|
288
|
+
tags, // Tag[]
|
|
391
289
|
loading,
|
|
392
290
|
error,
|
|
393
291
|
fetchTags,
|
|
@@ -395,181 +293,258 @@ const {
|
|
|
395
293
|
createTag,
|
|
396
294
|
updateTag,
|
|
397
295
|
deleteTag,
|
|
398
|
-
mergeTags,
|
|
296
|
+
mergeTags, // (sourceId: number, targetId: number) => Promise<void>
|
|
399
297
|
refresh,
|
|
400
298
|
} = useTags();
|
|
401
299
|
```
|
|
402
300
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
### NAuth Configuration
|
|
406
|
-
```tsx
|
|
407
|
-
<NAuthProvider
|
|
408
|
-
config={{
|
|
409
|
-
apiUrl: 'https://your-api.com', // Required
|
|
410
|
-
timeout: 30000, // Optional
|
|
411
|
-
enableFingerprinting: true, // Optional
|
|
412
|
-
storageType: 'localStorage', // Optional
|
|
413
|
-
redirectOnUnauthorized: '/login', // Optional
|
|
414
|
-
onAuthChange: (user) => {}, // Optional
|
|
415
|
-
onLogin: (user) => {}, // Optional
|
|
416
|
-
onLogout: () => {}, // Optional
|
|
417
|
-
}}
|
|
418
|
-
>
|
|
419
|
-
<App />
|
|
420
|
-
</NAuthProvider>
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
### NNews Configuration
|
|
424
|
-
```tsx
|
|
425
|
-
<NNewsProvider
|
|
426
|
-
config={{
|
|
427
|
-
apiUrl: 'https://your-api.com', // Required
|
|
428
|
-
timeout: 30000, // Optional
|
|
429
|
-
}}
|
|
430
|
-
>
|
|
431
|
-
<App />
|
|
432
|
-
</NNewsProvider>
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
## API Clients
|
|
436
|
-
|
|
437
|
-
### NAuth API Client
|
|
438
|
-
```tsx
|
|
439
|
-
import { createNAuthClient } from 'nnews-react';
|
|
301
|
+
---
|
|
440
302
|
|
|
441
|
-
|
|
303
|
+
## ๐ API Services
|
|
442
304
|
|
|
443
|
-
|
|
444
|
-
await authApi.getMe();
|
|
445
|
-
await authApi.updateUser({ name: 'New Name' });
|
|
446
|
-
await authApi.uploadImage(file);
|
|
447
|
-
```
|
|
305
|
+
The library exposes class-based API clients that can be used directly:
|
|
448
306
|
|
|
449
|
-
### NNews API Services
|
|
450
307
|
```tsx
|
|
451
308
|
import { ArticleAPI, CategoryAPI, TagAPI } from 'nnews-react';
|
|
452
309
|
import axios from 'axios';
|
|
453
310
|
|
|
454
|
-
const
|
|
311
|
+
const client = axios.create({ baseURL: 'https://your-api.com' });
|
|
312
|
+
const articleApi = new ArticleAPI(client);
|
|
455
313
|
|
|
456
|
-
// Article
|
|
457
|
-
|
|
458
|
-
await articleApi.
|
|
314
|
+
// Article endpoints
|
|
315
|
+
await articleApi.listArticles(categoryId?, status?, page, pageSize);
|
|
316
|
+
await articleApi.listByCategory(categoryId, page, pageSize);
|
|
317
|
+
await articleApi.listByRoles(page, pageSize);
|
|
318
|
+
await articleApi.listByTag(tagSlug, page, pageSize);
|
|
319
|
+
await articleApi.search(keyword, page, pageSize);
|
|
459
320
|
await articleApi.getArticleById(id);
|
|
460
321
|
await articleApi.createArticle(data);
|
|
461
322
|
await articleApi.updateArticle(data);
|
|
462
323
|
await articleApi.deleteArticle(id);
|
|
324
|
+
await articleApi.uploadImage(file);
|
|
325
|
+
await articleApi.createArticleWithAI(request);
|
|
326
|
+
await articleApi.updateArticleWithAI(request);
|
|
463
327
|
|
|
464
|
-
// Category
|
|
465
|
-
const categoryApi = new CategoryAPI(
|
|
328
|
+
// Category endpoints
|
|
329
|
+
const categoryApi = new CategoryAPI(client);
|
|
466
330
|
await categoryApi.listCategories();
|
|
331
|
+
await categoryApi.listByParent(roles?, parentId?);
|
|
332
|
+
await categoryApi.getCategoryById(id);
|
|
467
333
|
await categoryApi.createCategory(data);
|
|
334
|
+
await categoryApi.updateCategory(data);
|
|
335
|
+
await categoryApi.deleteCategory(id);
|
|
468
336
|
|
|
469
|
-
// Tag
|
|
470
|
-
const tagApi = new TagAPI(
|
|
337
|
+
// Tag endpoints
|
|
338
|
+
const tagApi = new TagAPI(client);
|
|
471
339
|
await tagApi.listTags();
|
|
340
|
+
await tagApi.listByRoles();
|
|
341
|
+
await tagApi.getTagById(id);
|
|
342
|
+
await tagApi.createTag(data);
|
|
343
|
+
await tagApi.updateTag(data);
|
|
344
|
+
await tagApi.deleteTag(id);
|
|
472
345
|
await tagApi.mergeTags(sourceId, targetId);
|
|
473
346
|
```
|
|
474
347
|
|
|
475
|
-
|
|
348
|
+
---
|
|
476
349
|
|
|
477
|
-
|
|
478
|
-
<LoginForm
|
|
479
|
-
className="shadow-2xl"
|
|
480
|
-
styles={{
|
|
481
|
-
container: 'bg-white',
|
|
482
|
-
button: 'bg-purple-600',
|
|
483
|
-
}}
|
|
484
|
-
/>
|
|
485
|
-
```
|
|
350
|
+
## ๐ช Modal System
|
|
486
351
|
|
|
487
|
-
|
|
352
|
+
All modals use a single normalized `Modal` component built on Radix UI Dialog:
|
|
488
353
|
|
|
489
354
|
```tsx
|
|
490
355
|
import {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
formatPhone,
|
|
495
|
-
validatePasswordStrength,
|
|
496
|
-
cn, // Utility for merging Tailwind classes
|
|
356
|
+
Modal, ModalContent, ModalHeader, ModalBody, ModalFooter,
|
|
357
|
+
ModalTitle, ModalDescription, ModalClose,
|
|
358
|
+
ConfirmModal,
|
|
497
359
|
} from 'nnews-react';
|
|
360
|
+
|
|
361
|
+
// Confirmation dialog
|
|
362
|
+
<ConfirmModal
|
|
363
|
+
open={isOpen}
|
|
364
|
+
onOpenChange={setIsOpen}
|
|
365
|
+
onConfirm={handleDelete}
|
|
366
|
+
title="Delete Article"
|
|
367
|
+
description="This action cannot be undone."
|
|
368
|
+
confirmLabel="Delete"
|
|
369
|
+
variant="danger" // 'danger' | 'warning' | 'default'
|
|
370
|
+
loading={isDeleting}
|
|
371
|
+
/>
|
|
372
|
+
|
|
373
|
+
// Custom modal
|
|
374
|
+
<Modal open={isOpen} onOpenChange={setIsOpen}>
|
|
375
|
+
<ModalContent className="max-w-2xl">
|
|
376
|
+
<ModalHeader>
|
|
377
|
+
<ModalTitle>Edit Item</ModalTitle>
|
|
378
|
+
<ModalDescription>Fill in the details below.</ModalDescription>
|
|
379
|
+
</ModalHeader>
|
|
380
|
+
<ModalBody>
|
|
381
|
+
{/* Your form content */}
|
|
382
|
+
</ModalBody>
|
|
383
|
+
<ModalFooter>
|
|
384
|
+
<ModalClose asChild>
|
|
385
|
+
<button>Cancel</button>
|
|
386
|
+
</ModalClose>
|
|
387
|
+
<button onClick={handleSave}>Save</button>
|
|
388
|
+
</ModalFooter>
|
|
389
|
+
</ModalContent>
|
|
390
|
+
</Modal>
|
|
498
391
|
```
|
|
499
392
|
|
|
500
|
-
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## ๐ Internationalization
|
|
501
396
|
|
|
502
|
-
|
|
397
|
+
Built-in translations for English and Portuguese. Extend with custom translations:
|
|
503
398
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
399
|
+
```tsx
|
|
400
|
+
<NNewsProvider
|
|
401
|
+
config={{
|
|
402
|
+
apiUrl: '...',
|
|
403
|
+
language: 'pt',
|
|
404
|
+
translations: {
|
|
405
|
+
en: { custom_key: 'Custom value' },
|
|
406
|
+
pt: { custom_key: 'Valor customizado' },
|
|
407
|
+
},
|
|
408
|
+
}}
|
|
409
|
+
>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
513
413
|
|
|
514
|
-
## TypeScript
|
|
414
|
+
## ๐ TypeScript Types
|
|
515
415
|
|
|
516
416
|
```tsx
|
|
517
417
|
import type {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
Article,
|
|
525
|
-
Category,
|
|
526
|
-
Tag,
|
|
527
|
-
ArticleStatus,
|
|
528
|
-
ArticleInput,
|
|
529
|
-
ArticleUpdate,
|
|
530
|
-
CategoryInput,
|
|
531
|
-
CategoryUpdate,
|
|
532
|
-
TagInput,
|
|
533
|
-
TagUpdate,
|
|
418
|
+
Article, ArticleInput, ArticleUpdate, AIArticleRequest,
|
|
419
|
+
Category, CategoryInput, CategoryUpdate,
|
|
420
|
+
Tag, TagInput, TagUpdate,
|
|
421
|
+
PagedResult, Role,
|
|
422
|
+
NNewsConfig, NNewsTheme, NNewsThemeMode,
|
|
423
|
+
ArticleSearchParams, CategoryFilterParams, TagSearchParams,
|
|
534
424
|
} from 'nnews-react';
|
|
425
|
+
|
|
426
|
+
import { ArticleStatus, ContentType } from 'nnews-react';
|
|
427
|
+
// ArticleStatus: Draft(0), Published(1), Archived(2), Scheduled(3), Review(4)
|
|
428
|
+
// ContentType: PlainText(1), Html(2), MarkDown(3)
|
|
535
429
|
```
|
|
536
430
|
|
|
537
|
-
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## โ๏ธ Environment Variables (Example App)
|
|
538
434
|
|
|
435
|
+
```bash
|
|
436
|
+
cp example-app/.env.example example-app/.env
|
|
539
437
|
```
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
VITE_API_URL=http://localhost:5007 # NAuth backend URL
|
|
441
|
+
VITE_NNEWS_API_URL=http://localhost:5008 # NNews backend URL
|
|
442
|
+
VITE_TENANT_ID=my-tenant # Tenant identifier
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## ๐งช Testing
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
npm test # Run tests once
|
|
451
|
+
npm run test:watch # Watch mode
|
|
452
|
+
npm run test:coverage # Coverage report (text, JSON, HTML)
|
|
548
453
|
```
|
|
549
454
|
|
|
550
|
-
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## ๐ง Development
|
|
551
458
|
|
|
552
459
|
```bash
|
|
553
|
-
|
|
554
|
-
npm
|
|
555
|
-
npm run
|
|
556
|
-
npm
|
|
557
|
-
npm run lint
|
|
460
|
+
# Library (root)
|
|
461
|
+
npm install # Install dependencies
|
|
462
|
+
npm run dev # Vite dev server
|
|
463
|
+
npm run build # Type-check + Vite build (outputs to dist/)
|
|
464
|
+
npm run lint # ESLint
|
|
465
|
+
npm run type-check # tsc --noEmit
|
|
466
|
+
|
|
467
|
+
# Example App (example-app/)
|
|
468
|
+
cd example-app
|
|
469
|
+
npm install
|
|
470
|
+
npm run dev # Start dev server
|
|
471
|
+
npm run build # Production build
|
|
558
472
|
```
|
|
559
473
|
|
|
560
|
-
|
|
474
|
+
### Build Output
|
|
475
|
+
|
|
476
|
+
The library outputs both ESM and CJS with TypeScript declarations:
|
|
477
|
+
|
|
478
|
+
| File | Description |
|
|
479
|
+
|------|-------------|
|
|
480
|
+
| `dist/index.js` | ES Module bundle |
|
|
481
|
+
| `dist/index.cjs` | CommonJS bundle |
|
|
482
|
+
| `dist/index.d.ts` | TypeScript declarations |
|
|
483
|
+
| `dist/style.css` | Bundled CSS styles |
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## ๐ CI/CD
|
|
488
|
+
|
|
489
|
+
### GitHub Actions
|
|
490
|
+
|
|
491
|
+
- **version-tag.yml** โ Semantic versioning with GitVersion on push to `main`
|
|
492
|
+
- **npm-publish.yml** โ Publishes to NPM on tag creation
|
|
493
|
+
- **create-release.yml** โ GitHub release creation
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## ๐ Additional Documentation
|
|
498
|
+
|
|
499
|
+
| Document | Description |
|
|
500
|
+
|----------|-------------|
|
|
501
|
+
| [AI Features Guide](docs/AI_FEATURES_GUIDE.md) | ChatGPT and DALL-E 3 integration details |
|
|
502
|
+
| [Changelog](docs/CHANGELOG.md) | Version history and changes |
|
|
503
|
+
| [Role API Documentation](docs/ROLE_API_DOCUMENTATION.md) | Role management API reference |
|
|
504
|
+
| [Testing Guide](docs/TESTING_GUIDE.md) | Testing patterns and conventions |
|
|
505
|
+
| [Update Summary](docs/UPDATE_SUMMARY.md) | Recent update summary |
|
|
506
|
+
| [User API Documentation](docs/USER_API_DOCUMENTATION.md) | User management API reference |
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## ๐ฆ Publishing
|
|
561
511
|
|
|
562
512
|
```bash
|
|
563
513
|
npm run build
|
|
564
514
|
npm publish --access public
|
|
565
515
|
```
|
|
566
516
|
|
|
567
|
-
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## ๐ค Contributing
|
|
520
|
+
|
|
521
|
+
1. Fork the repository
|
|
522
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
523
|
+
3. Make your changes
|
|
524
|
+
4. Run tests (`npm test`)
|
|
525
|
+
5. Commit your changes (`git commit -m 'feat: add amazing feature'`)
|
|
526
|
+
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
527
|
+
7. Open a Pull Request
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## ๐จโ๐ป Author
|
|
532
|
+
|
|
533
|
+
Developed by **[Rodrigo Landim](https://github.com/landim32)**
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## ๐ License
|
|
538
|
+
|
|
539
|
+
This project is licensed under the **MIT License** โ see the [LICENSE](LICENSE) file for details.
|
|
540
|
+
|
|
541
|
+
---
|
|
542
|
+
|
|
543
|
+
## ๐ Support
|
|
568
544
|
|
|
569
|
-
|
|
545
|
+
- **Issues**: [GitHub Issues](https://github.com/landim32/NNews.React/issues)
|
|
546
|
+
- **NPM**: [nnews-react](https://www.npmjs.com/package/nnews-react)
|
|
570
547
|
|
|
571
|
-
|
|
548
|
+
---
|
|
572
549
|
|
|
573
|
-
|
|
574
|
-
- [NPM](https://www.npmjs.com/package/nnews-react)
|
|
575
|
-
- [Documentation](https://github.com/landim32/NNews.React#readme)
|
|
550
|
+
**โญ If you find this project useful, please consider giving it a star!**
|