@llmnative/react 1.19.0 → 1.19.2
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 +4 -2
- package/dist/index.css +1 -1
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +150 -139
- package/dist/index.mjs.map +1 -1
- package/dist/logHeader-Busbc8OO.mjs.map +1 -1
- package/dist/logHeader-CUsdmGAo.js +2 -0
- package/dist/{logHeader-CbzOhx-u.js.map → logHeader-CUsdmGAo.js.map} +1 -1
- package/dist/vite.js +2 -2
- package/dist/vite.js.map +1 -1
- package/dist/vite.mjs +1 -1
- package/dist/vite.mjs.map +1 -1
- package/llms-full.txt +1140 -0
- package/llms.txt +82 -0
- package/package.json +48 -43
- package/scripts/cli/setup-project.js +60 -36
- package/templates/_shared/layouts/Default.tsx +23 -0
- package/templates/_shared/sections/Footer.tsx +9 -0
- package/templates/_shared/sections/Header.tsx +24 -0
- package/templates/_shared/sections/PageHeader.tsx +10 -0
- package/templates/_shared/sections/Sidebar.tsx +22 -0
- package/templates/admin/conf/menu.ts +14 -0
- package/templates/admin/data/mockData.ts +21 -0
- package/templates/admin/pages/home/HomePage.tsx +32 -0
- package/templates/admin/pages/roles/RolesPage.tsx +25 -0
- package/templates/admin/pages/settings/SettingsPage.tsx +24 -0
- package/templates/admin/pages/users/UsersPage.tsx +54 -0
- package/templates/blank/conf/menu.ts +8 -0
- package/templates/blank/data/mockData.ts +6 -0
- package/templates/blank/pages/home/HomePage.tsx +25 -0
- package/templates/crm/conf/menu.ts +14 -0
- package/templates/crm/data/mockData.ts +24 -0
- package/templates/crm/pages/companies/CompaniesPage.tsx +42 -0
- package/templates/crm/pages/contacts/ContactsPage.tsx +48 -0
- package/templates/crm/pages/deals/DealsPage.tsx +61 -0
- package/templates/crm/pages/home/HomePage.tsx +35 -0
- package/templates/inventory/conf/menu.ts +12 -0
- package/templates/inventory/data/mockData.ts +16 -0
- package/templates/inventory/pages/categories/CategoriesPage.tsx +23 -0
- package/templates/inventory/pages/home/HomePage.tsx +35 -0
- package/templates/inventory/pages/products/ProductsPage.tsx +56 -0
- package/templates/project/conf/menu.ts +14 -0
- package/templates/project/data/mockData.ts +21 -0
- package/templates/project/pages/home/HomePage.tsx +33 -0
- package/templates/project/pages/projects/ProjectsPage.tsx +50 -0
- package/templates/project/pages/tasks/TasksPage.tsx +67 -0
- package/templates/project/pages/team/TeamPage.tsx +25 -0
- package/dist/logHeader-CbzOhx-u.js +0 -2
package/llms.txt
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# @llmnative/react
|
|
2
|
+
|
|
3
|
+
> Framework React AI-first per generazione deterministica di UI da configurazione dichiarativa.
|
|
4
|
+
> Data-driven di default, schema-driven opzionale. Ottimizzato per minimo consumo di token AI.
|
|
5
|
+
> Apache-2.0 — versione 1.0.0
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Requisito: **Node.js 24 LTS** (`>=24.18.0 <25`, `engines` del pacchetto).
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @llmnative/react
|
|
13
|
+
npx @llmnative/react create # scaffold progetto Vite completo
|
|
14
|
+
npx @llmnative/react create --yes --provider=mock # scaffold senza backend
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import '@llmnative/react/dist/index.css' // necessario una volta sola
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Cosa fa
|
|
22
|
+
|
|
23
|
+
`Grid` genera tabelle e gallery CRUD complete (fetch real-time, modal add/edit/delete, sorting, pagination, groupBy) da una configurazione colonne. `Form` gestisce stato, validazione, oggetti annidati e lifecycle hooks verso qualsiasi provider. I provider (Firebase, Supabase, mock) si swappano in `<App>` senza toccare i componenti.
|
|
24
|
+
|
|
25
|
+
## 5 pattern core — 90% dei casi d'uso
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
// 1 — Lista CRUD con real-time
|
|
29
|
+
<Grid path="/users" columns={[{ key: 'name', label: 'Nome', sortable: true }, { key: 'email', label: 'Email' }]} actions={['add','edit','delete']} pagination={{ limit: 20 }} />
|
|
30
|
+
|
|
31
|
+
// 2 — Form standalone
|
|
32
|
+
<Form path="/products" appearance="card" showBack>
|
|
33
|
+
<Input name="title" label="Titolo" required />
|
|
34
|
+
<Select name="status" label="Stato" options={['draft','published']} />
|
|
35
|
+
</Form>
|
|
36
|
+
|
|
37
|
+
// 3 — Oggetti annidati e array dinamici
|
|
38
|
+
<Input name="address.city" label="Città" />
|
|
39
|
+
<Repeat name="items" defaultLength={1}>{(i) => <Input name={`items.${i}.name`} />}</Repeat>
|
|
40
|
+
|
|
41
|
+
// 4 — Colonne con renderer custom e Badge
|
|
42
|
+
<Grid path="/orders" columns={[{ key: 'status', render: ({ value }) => <Badge variant="success">{value}</Badge> }, { key: 'amount', render: ({ value }) => `€ ${value.toFixed(2)}` }]} />
|
|
43
|
+
|
|
44
|
+
// 5 — Form con lifecycle hooks
|
|
45
|
+
<Form path="/posts" onLoad={d => ({...d, price: d.price/100})} onSave={async ({record}) => ({...record, price: record.price*100})} onComplete={async ({action}) => { if (action === 'save') navigate('/posts') }} />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Provider matrix
|
|
49
|
+
|
|
50
|
+
| Servizio | Driver disponibili | Obbligatorio |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| data | `mock`, `dbRealtime` (Firebase RTDB), `firestoreDb` (Firestore), `supabaseDb` | sì (fallback: mock) |
|
|
53
|
+
| storage | `firestorage`, `supabaseStorage` | no |
|
|
54
|
+
| auth | `googleAuth`, `firebaseAuth`, `supabaseAuth`, `dropboxAuth` | sì (fallback: googleAuth) |
|
|
55
|
+
| email | `gmail` | no |
|
|
56
|
+
| ai | `openai`, `gemini`, `anthropic`, `deepseek`, `mistral`, `openrouter`, `opencode`, `cloudflare`, `groq` | no |
|
|
57
|
+
| credentials | `googleServiceAccount` | no |
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
<App providers={{ firebase: { apiKey, authDomain, databaseURL }, services: { data: 'dbRealtime', storage: 'firestorage', auth: 'googleAuth' } }} />
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Struttura componenti
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
ui/ → primitivi stateless: Alert, Badge, Button, Card, DescriptionList, Image, Modal, Table, Gallery, Tab, Repeat, Icon, LocaleSwitcher, ThemeSwitcher, ProviderSwitcher, LayoutBuilder, ContextMenu
|
|
67
|
+
ui/fields/ → campi form: Input, Select, UploadImage, UploadDocument, UploadCSV, CodeEditor, ImageField, RichText, Prompt, Crop, ContextMenu
|
|
68
|
+
blocks/ → composizioni: Menu, Breadcrumbs, Notifications, Search, Carousel, Dropdown, Toolbar, SideNav, ListCard, AuthButton
|
|
69
|
+
widgets/ → smart (hanno stato e provider): Grid, Form, MarkdownReader, ImageEditor, TabDynamic
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Tutti gli import da `'@llmnative/react'` — mai da sottodirectory.
|
|
73
|
+
|
|
74
|
+
## Non adatto per
|
|
75
|
+
|
|
76
|
+
- Marketing sites, e-commerce, landing page con layout custom
|
|
77
|
+
- UI pixel-perfect che non mappano a CRUD
|
|
78
|
+
- Applicazioni senza dati strutturati
|
|
79
|
+
|
|
80
|
+
## Riferimento completo
|
|
81
|
+
|
|
82
|
+
→ [llms-full.txt](llms-full.txt) — API di ogni componente, ogni provider, ogni hook, tipi TypeScript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmnative/react",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.2",
|
|
4
4
|
"description": "LLM-native React framework for deterministic UI generation. Data-driven by default, schema-driven optional. Ultra-low token consumption — built for AI agents, not just humans.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"test": "vitest run",
|
|
28
28
|
"test:e2e": "npx playwright test",
|
|
29
29
|
"test:e2e:proxy-scaffold": "node scripts/e2e/test-proxy-scaffold.js",
|
|
30
|
+
"test:e2e:scaffold": "node scripts/e2e/test-scaffold-templates.js",
|
|
30
31
|
"test:e2e:proxy-runtime": "vitest run tests/e2e/proxy-runtime.test.ts --environment node",
|
|
31
32
|
"test:integration": "npx firebase emulators:exec --only database,firestore,storage --project demo-test \"npx vitest run --config vitest.integration.config.mts\"",
|
|
32
33
|
"test:watch": "vitest",
|
|
@@ -41,7 +42,10 @@
|
|
|
41
42
|
"dist/**/*.map",
|
|
42
43
|
"bin",
|
|
43
44
|
"scripts/cli",
|
|
44
|
-
"
|
|
45
|
+
"templates",
|
|
46
|
+
"themes",
|
|
47
|
+
"llms.txt",
|
|
48
|
+
"llms-full.txt"
|
|
45
49
|
],
|
|
46
50
|
"repository": {
|
|
47
51
|
"type": "git",
|
|
@@ -92,69 +96,70 @@
|
|
|
92
96
|
}
|
|
93
97
|
},
|
|
94
98
|
"devDependencies": {
|
|
95
|
-
"@firebase/rules-unit-testing": "^5.0.
|
|
99
|
+
"@firebase/rules-unit-testing": "^5.0.2",
|
|
96
100
|
"@phosphor-icons/react": "^2.1.10",
|
|
97
|
-
"@playwright/test": "^1.
|
|
98
|
-
"@supabase/supabase-js": "^2.
|
|
101
|
+
"@playwright/test": "^1.63.0",
|
|
102
|
+
"@supabase/supabase-js": "^2.117.2",
|
|
99
103
|
"@tailwindcss/postcss": "^4.3.3",
|
|
100
|
-
"@testing-library/dom": "^10.4.
|
|
101
|
-
"@testing-library/jest-dom": "^7.0.
|
|
102
|
-
"@testing-library/react": "^16.3.
|
|
103
|
-
"@testing-library/user-event": "^14.6.
|
|
104
|
-
"@types/google.maps": "^3.
|
|
104
|
+
"@testing-library/dom": "^10.4.2",
|
|
105
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
106
|
+
"@testing-library/react": "^16.3.3",
|
|
107
|
+
"@testing-library/user-event": "^14.6.7",
|
|
108
|
+
"@types/google.maps": "^3.66.4",
|
|
105
109
|
"@types/papaparse": "^5.5.2",
|
|
106
110
|
"@types/prismjs": "^1.26.6",
|
|
107
|
-
"@types/react": "^19.
|
|
108
|
-
"@types/react-dom": "^19.
|
|
109
|
-
"@
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"react
|
|
118
|
-
"react-
|
|
111
|
+
"@types/react": "^19.3.0",
|
|
112
|
+
"@types/react-dom": "^19.3.0",
|
|
113
|
+
"@typescript/typescript6": "^6.0.2",
|
|
114
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
115
|
+
"autoprefixer": "^10.6.1",
|
|
116
|
+
"firebase": "^12.19.0",
|
|
117
|
+
"happy-dom": "^20.14.5",
|
|
118
|
+
"jsdom": "^30.1.1",
|
|
119
|
+
"lucide-react": "^1.49.0",
|
|
120
|
+
"postcss": "^8.5.28",
|
|
121
|
+
"react": "^19.3.0",
|
|
122
|
+
"react-dom": "^19.3.0",
|
|
123
|
+
"react-router-dom": "^7.18.4",
|
|
119
124
|
"tailwindcss": "^4.3.3",
|
|
120
|
-
"typescript": "
|
|
121
|
-
"vite": "^8.
|
|
122
|
-
"vitest": "
|
|
123
|
-
"ws": "^8.
|
|
125
|
+
"typescript": "^7.0.2",
|
|
126
|
+
"vite": "^8.3.2",
|
|
127
|
+
"vitest": "^5.0.3",
|
|
128
|
+
"ws": "^8.22.0"
|
|
124
129
|
},
|
|
125
130
|
"dependencies": {
|
|
126
131
|
"@codemirror/autocomplete": "^6.20.3",
|
|
127
|
-
"@codemirror/commands": "^6.
|
|
132
|
+
"@codemirror/commands": "^6.11.1",
|
|
128
133
|
"@codemirror/lang-css": "^6.3.1",
|
|
129
|
-
"@codemirror/lang-html": "^6.4.
|
|
134
|
+
"@codemirror/lang-html": "^6.4.12",
|
|
130
135
|
"@codemirror/lang-javascript": "^6.2.5",
|
|
131
136
|
"@codemirror/lang-json": "^6.0.2",
|
|
132
|
-
"@codemirror/lang-liquid": "^6.3.
|
|
137
|
+
"@codemirror/lang-liquid": "^6.3.3",
|
|
133
138
|
"@codemirror/language": "^6.12.4",
|
|
134
139
|
"@codemirror/lint": "^6.9.7",
|
|
135
|
-
"@codemirror/search": "^6.7.
|
|
136
|
-
"@codemirror/state": "^6.7.
|
|
137
|
-
"@codemirror/view": "^6.43.
|
|
138
|
-
"@tiptap/extension-character-count": "^3.
|
|
139
|
-
"@tiptap/extension-image": "^3.
|
|
140
|
-
"@tiptap/extension-link": "^3.
|
|
141
|
-
"@tiptap/extension-placeholder": "^3.
|
|
142
|
-
"@tiptap/extension-table": "^3.
|
|
143
|
-
"@tiptap/extension-underline": "^3.
|
|
144
|
-
"@tiptap/react": "^3.
|
|
145
|
-
"@tiptap/starter-kit": "^3.
|
|
140
|
+
"@codemirror/search": "^6.7.2",
|
|
141
|
+
"@codemirror/state": "^6.7.6",
|
|
142
|
+
"@codemirror/view": "^6.43.13",
|
|
143
|
+
"@tiptap/extension-character-count": "^3.31.4",
|
|
144
|
+
"@tiptap/extension-image": "^3.31.4",
|
|
145
|
+
"@tiptap/extension-link": "^3.31.4",
|
|
146
|
+
"@tiptap/extension-placeholder": "^3.31.4",
|
|
147
|
+
"@tiptap/extension-table": "^3.31.4",
|
|
148
|
+
"@tiptap/extension-underline": "^3.31.4",
|
|
149
|
+
"@tiptap/react": "^3.31.4",
|
|
150
|
+
"@tiptap/starter-kit": "^3.31.4",
|
|
146
151
|
"buffer": "^6.0.3",
|
|
147
152
|
"clsx": "^2.1.1",
|
|
148
153
|
"codemirror": "^6.0.2",
|
|
149
|
-
"liquidjs": "^10.
|
|
150
|
-
"papaparse": "^5.
|
|
154
|
+
"liquidjs": "^10.30.0",
|
|
155
|
+
"papaparse": "^5.7.0",
|
|
151
156
|
"prismjs": "^1.30.0",
|
|
152
157
|
"react-markdown": "^10.1.0",
|
|
153
158
|
"rehype-autolink-headings": "^7.1.0",
|
|
154
159
|
"rehype-sanitize": "^6.0.0",
|
|
155
160
|
"rehype-slug": "^6.0.0",
|
|
156
161
|
"remark-gfm": "^4.0.1",
|
|
157
|
-
"tailwind-merge": "^3.
|
|
162
|
+
"tailwind-merge": "^3.7.0",
|
|
158
163
|
"tui-image-editor": "^3.15.3"
|
|
159
164
|
},
|
|
160
165
|
"publishConfig": {
|
|
@@ -37,6 +37,8 @@ function resetProjectDirectory() {
|
|
|
37
37
|
'.env.example',
|
|
38
38
|
'.firebaserc',
|
|
39
39
|
'database.rules.json',
|
|
40
|
+
'firestore.rules',
|
|
41
|
+
'firestore.indexes.json',
|
|
40
42
|
'firebase.json',
|
|
41
43
|
'storage.rules',
|
|
42
44
|
'index.html',
|
|
@@ -167,17 +169,18 @@ function normalizeFirebaseProjectId(projectName) {
|
|
|
167
169
|
|
|
168
170
|
function createPackageJson(params) {
|
|
169
171
|
const packagePath = path.resolve(__dirname, '../../package.json');
|
|
170
|
-
const
|
|
172
|
+
const frameworkPackage = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
173
|
+
const version = frameworkPackage.version;
|
|
171
174
|
|
|
172
175
|
ensureFile(path.join(root, 'package.json'), JSON.stringify({
|
|
173
176
|
name: normalizeFirebaseProjectId(params.projectname) || 'llmnative-app',
|
|
174
177
|
version: '0.1.0',
|
|
175
|
-
private: true,
|
|
176
|
-
type: 'module',
|
|
177
|
-
engines: {
|
|
178
|
-
node: '>=24.18.0 <25',
|
|
179
|
-
},
|
|
180
|
-
packageManager: 'npm@11.11.0',
|
|
178
|
+
private: true,
|
|
179
|
+
type: 'module',
|
|
180
|
+
engines: {
|
|
181
|
+
node: '>=24.18.0 <25',
|
|
182
|
+
},
|
|
183
|
+
packageManager: 'npm@11.11.0',
|
|
181
184
|
scripts: {
|
|
182
185
|
dev: 'vite',
|
|
183
186
|
build: 'vite build',
|
|
@@ -185,23 +188,24 @@ function createPackageJson(params) {
|
|
|
185
188
|
typecheck: 'tsc --noEmit',
|
|
186
189
|
},
|
|
187
190
|
dependencies: {
|
|
188
|
-
'@phosphor-icons/react': '^2.1.10',
|
|
189
|
-
firebase: '^12.16.0',
|
|
190
|
-
'lucide-react': '^1.26.0',
|
|
191
|
-
react: '^19.2.8',
|
|
192
|
-
'react-dom': '^19.2.8',
|
|
193
|
-
'@llmnative/react': `^${version}`,
|
|
194
|
-
'react-router-dom': '^7.18.1',
|
|
195
|
-
},
|
|
196
|
-
devDependencies: {
|
|
197
|
-
'@tailwindcss/postcss': '^4.3.3',
|
|
198
|
-
'@types/react': '^19.2.17',
|
|
199
|
-
'@types/react-dom': '^19.2.3',
|
|
200
|
-
'@vitejs/plugin-react': '^6.0.4',
|
|
201
|
-
autoprefixer: '^10.5.4',
|
|
202
|
-
postcss: '^8.5.23',
|
|
203
|
-
|
|
204
|
-
|
|
191
|
+
'@phosphor-icons/react': '^2.1.10',
|
|
192
|
+
firebase: '^12.16.0',
|
|
193
|
+
'lucide-react': '^1.26.0',
|
|
194
|
+
react: '^19.2.8',
|
|
195
|
+
'react-dom': '^19.2.8',
|
|
196
|
+
'@llmnative/react': `^${version}`,
|
|
197
|
+
'react-router-dom': '^7.18.1',
|
|
198
|
+
},
|
|
199
|
+
devDependencies: {
|
|
200
|
+
'@tailwindcss/postcss': '^4.3.3',
|
|
201
|
+
'@types/react': '^19.2.17',
|
|
202
|
+
'@types/react-dom': '^19.2.3',
|
|
203
|
+
'@vitejs/plugin-react': '^6.0.4',
|
|
204
|
+
autoprefixer: '^10.5.4',
|
|
205
|
+
postcss: '^8.5.23',
|
|
206
|
+
// Same compiler the framework is built and type-checked with.
|
|
207
|
+
typescript: frameworkPackage.devDependencies.typescript,
|
|
208
|
+
vite: '^8.1.5',
|
|
205
209
|
},
|
|
206
210
|
}, null, 2));
|
|
207
211
|
}
|
|
@@ -293,7 +297,8 @@ function createFirebaseConfig(params) {
|
|
|
293
297
|
const hostingSite = params.hosting === '' ? params.projectname : params.hosting;
|
|
294
298
|
|
|
295
299
|
ensureFile(path.join(root, 'firebase.json'), JSON.stringify({
|
|
296
|
-
|
|
300
|
+
firestore: { rules: 'firestore.rules', indexes: 'firestore.indexes.json' },
|
|
301
|
+
storage: { rules: 'storage.rules' },
|
|
297
302
|
...(includeHosting && {
|
|
298
303
|
hosting: {
|
|
299
304
|
site: normalizeFirebaseProjectId(hostingSite),
|
|
@@ -305,11 +310,21 @@ function createFirebaseConfig(params) {
|
|
|
305
310
|
}),
|
|
306
311
|
}, null, 2));
|
|
307
312
|
|
|
308
|
-
ensureFile(path.join(root, '
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
+
ensureFile(path.join(root, 'firestore.rules'), `
|
|
314
|
+
rules_version = '2';
|
|
315
|
+
|
|
316
|
+
service cloud.firestore {
|
|
317
|
+
match /databases/{database}/documents {
|
|
318
|
+
match /{document=**} {
|
|
319
|
+
allow read, write: if request.auth != null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
`);
|
|
324
|
+
|
|
325
|
+
ensureFile(path.join(root, 'firestore.indexes.json'), JSON.stringify({
|
|
326
|
+
indexes: [],
|
|
327
|
+
fieldOverrides: [],
|
|
313
328
|
}, null, 2));
|
|
314
329
|
|
|
315
330
|
ensureFile(path.join(root, 'storage.rules'), `
|
|
@@ -352,6 +367,10 @@ function copyTemplateFiles(params) {
|
|
|
352
367
|
const sharedSrc = path.resolve(__dirname, '../../templates/_shared');
|
|
353
368
|
const templateSrc = path.resolve(__dirname, `../../templates/${params.template}`);
|
|
354
369
|
|
|
370
|
+
if (!fs.existsSync(sharedSrc) || !fs.existsSync(templateSrc)) {
|
|
371
|
+
throw new Error(`Template "${params.template}" not found in ${path.dirname(templateSrc)}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
355
374
|
// Copy shared layouts and sections
|
|
356
375
|
copyDir(path.join(sharedSrc, 'layouts'), path.join(root, 'src/layouts'));
|
|
357
376
|
copyDir(path.join(sharedSrc, 'sections'), path.join(root, 'src/sections'));
|
|
@@ -437,7 +456,7 @@ const selectedProvider = env.VITE_PROVIDER ?? '${params.provider}';
|
|
|
437
456
|
const selectedAIProvider = env.VITE_AI_PROVIDER ?? '${params.aiProvider ?? 'none'}';
|
|
438
457
|
|
|
439
458
|
const dataDriver =
|
|
440
|
-
selectedProvider === 'firebase' ? '
|
|
459
|
+
selectedProvider === 'firebase' ? 'firestoreDb'
|
|
441
460
|
: selectedProvider === 'supabase' ? 'supabaseDb'
|
|
442
461
|
: 'mock';
|
|
443
462
|
|
|
@@ -446,6 +465,11 @@ const storageDriver =
|
|
|
446
465
|
: selectedProvider === 'supabase' ? 'supabaseStorage'
|
|
447
466
|
: undefined;
|
|
448
467
|
|
|
468
|
+
const authDriver =
|
|
469
|
+
selectedProvider === 'firebase' ? 'firebaseAuth'
|
|
470
|
+
: selectedProvider === 'supabase' ? 'supabaseAuth'
|
|
471
|
+
: 'googleAuth';
|
|
472
|
+
|
|
449
473
|
const aiDriver = selectedAIProvider !== 'none'
|
|
450
474
|
? selectedAIProvider
|
|
451
475
|
: undefined;
|
|
@@ -503,10 +527,11 @@ export const providers: AppProvidersConfig = {
|
|
|
503
527
|
clientId: env.VITE_DROPBOX_CLIENT_ID ?? '',
|
|
504
528
|
rootPath: env.VITE_DROPBOX_ROOT_PATH ?? '',
|
|
505
529
|
},
|
|
530
|
+
ai: aiConfig,
|
|
506
531
|
services: {
|
|
507
532
|
data: dataDriver,
|
|
508
533
|
...(storageDriver ? { storage: storageDriver } : {}),
|
|
509
|
-
auth:
|
|
534
|
+
auth: authDriver,
|
|
510
535
|
...(aiDriver ? { ai: aiDriver } : {}),
|
|
511
536
|
},
|
|
512
537
|
};
|
|
@@ -518,18 +543,17 @@ import { createRoot } from 'react-dom/client';
|
|
|
518
543
|
import { App } from '@llmnative/react';
|
|
519
544
|
import './styles/globals.css';
|
|
520
545
|
|
|
521
|
-
import {
|
|
546
|
+
import { appConfig, providers } from './conf/app';
|
|
522
547
|
import { menu } from './conf/menu';
|
|
523
|
-
|
|
524
|
-
const env = import.meta.env;
|
|
548
|
+
import Default from './layouts/Default';
|
|
525
549
|
|
|
526
550
|
createRoot(document.getElementById('root')!).render(
|
|
527
551
|
<React.StrictMode>
|
|
528
552
|
<App
|
|
529
553
|
importPage={(pageSource) => import(/* @vite-ignore */ pageSource)}
|
|
530
554
|
menuConfig={menu}
|
|
555
|
+
LayoutDefault={Default}
|
|
531
556
|
providers={providers}
|
|
532
|
-
aiConfig={aiConfig}
|
|
533
557
|
iconProvider={appConfig.iconProvider}
|
|
534
558
|
themeProvider={{
|
|
535
559
|
theme: appConfig.theme,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import Header from '../sections/Header';
|
|
3
|
+
import Sidebar from '../sections/Sidebar';
|
|
4
|
+
import PageHeader from '../sections/PageHeader';
|
|
5
|
+
import Footer from '../sections/Footer';
|
|
6
|
+
|
|
7
|
+
export default function Default({ children }: { children?: React.ReactNode }) {
|
|
8
|
+
const [navOpen, setNavOpen] = useState(false);
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex h-screen flex-col overflow-hidden">
|
|
12
|
+
<Header onMenuToggle={() => setNavOpen(open => !open)} />
|
|
13
|
+
<div className="flex min-h-0 flex-1 overflow-hidden">
|
|
14
|
+
<Sidebar open={navOpen} onClose={() => setNavOpen(false)} />
|
|
15
|
+
<main className="min-h-0 min-w-0 flex-1 overflow-auto p-4">
|
|
16
|
+
<PageHeader />
|
|
17
|
+
{children}
|
|
18
|
+
</main>
|
|
19
|
+
</div>
|
|
20
|
+
<Footer />
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ActionButton, AuthButton, Brand } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
type HeaderProps = {
|
|
5
|
+
onMenuToggle: () => void;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default function Header({ onMenuToggle }: HeaderProps) {
|
|
9
|
+
return (
|
|
10
|
+
<header className="flex h-14 shrink-0 items-center gap-3 border-b bg-background px-4">
|
|
11
|
+
<ActionButton
|
|
12
|
+
icon="menu"
|
|
13
|
+
ariaLabel="Toggle navigation"
|
|
14
|
+
variant="link"
|
|
15
|
+
wrapperClassName="lg:hidden"
|
|
16
|
+
onClick={onMenuToggle}
|
|
17
|
+
/>
|
|
18
|
+
<Brand url="/" label="[projectname]" />
|
|
19
|
+
<div className="ms-auto">
|
|
20
|
+
<AuthButton />
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Modal, SideNav } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
type SidebarProps = {
|
|
5
|
+
open: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function Sidebar({ open, onClose }: SidebarProps) {
|
|
10
|
+
return (
|
|
11
|
+
<>
|
|
12
|
+
<div className="hidden lg:flex">
|
|
13
|
+
<SideNav menuKey="main" />
|
|
14
|
+
</div>
|
|
15
|
+
{open && (
|
|
16
|
+
<Modal title="[projectname]" position="left" size="sm" footer={false} onClose={onClose}>
|
|
17
|
+
<SideNav menuKey="main" embedded />
|
|
18
|
+
</Modal>
|
|
19
|
+
)}
|
|
20
|
+
</>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MenuConfig } from '@llmnative/react';
|
|
2
|
+
import HomePage from '../pages/home/HomePage';
|
|
3
|
+
import UsersPage from '../pages/users/UsersPage';
|
|
4
|
+
import RolesPage from '../pages/roles/RolesPage';
|
|
5
|
+
import SettingsPage from '../pages/settings/SettingsPage';
|
|
6
|
+
|
|
7
|
+
export const menu: MenuConfig = {
|
|
8
|
+
main: [
|
|
9
|
+
{ path: '/', title: 'Overview', icon: 'layout-dashboard', page: HomePage, end: true },
|
|
10
|
+
{ path: '/users', title: 'Users', icon: 'users', page: UsersPage, group: 'Management' },
|
|
11
|
+
{ path: '/roles', title: 'Roles', icon: 'shield', page: RolesPage, group: 'Management' },
|
|
12
|
+
{ path: '/settings', title: 'Settings', icon: 'settings', page: SettingsPage, group: 'System' },
|
|
13
|
+
],
|
|
14
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const mockData = {
|
|
2
|
+
'/users': {
|
|
3
|
+
'u-1': { name: 'Alice Martin', email: 'alice@example.com', role: 'admin', status: 'active', createdAt: 1710000000000 },
|
|
4
|
+
'u-2': { name: 'Bob Chen', email: 'bob@example.com', role: 'editor', status: 'active', createdAt: 1712000000000 },
|
|
5
|
+
'u-3': { name: 'Clara Rossi', email: 'clara@example.com', role: 'viewer', status: 'inactive', createdAt: 1714000000000 },
|
|
6
|
+
'u-4': { name: 'David Park', email: 'david@example.com', role: 'editor', status: 'active', createdAt: 1716000000000 },
|
|
7
|
+
},
|
|
8
|
+
'/roles': {
|
|
9
|
+
'r-1': { name: 'admin', label: 'Administrator', description: 'Full access to all resources' },
|
|
10
|
+
'r-2': { name: 'editor', label: 'Editor', description: 'Can create and edit content' },
|
|
11
|
+
'r-3': { name: 'viewer', label: 'Viewer', description: 'Read-only access' },
|
|
12
|
+
},
|
|
13
|
+
'/settings': {
|
|
14
|
+
'app': {
|
|
15
|
+
siteName: '[projectname]',
|
|
16
|
+
supportEmail: 'support@example.com',
|
|
17
|
+
timezone: 'UTC',
|
|
18
|
+
maintenanceMode: false,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Card } from '@llmnative/react';
|
|
3
|
+
import { mockData } from '../../data/mockData';
|
|
4
|
+
|
|
5
|
+
function StatCard({ label, value, sub }: { label: string; value: number | string; sub?: string }) {
|
|
6
|
+
return (
|
|
7
|
+
<Card title={label}>
|
|
8
|
+
<p className="text-2xl font-semibold">{value}</p>
|
|
9
|
+
{sub && <p className="text-xs text-muted-foreground">{sub}</p>}
|
|
10
|
+
</Card>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function HomePage() {
|
|
15
|
+
const users = Object.values(mockData['/users']);
|
|
16
|
+
const roles = Object.values(mockData['/roles']);
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="mx-auto max-w-5xl space-y-8 px-2 py-4">
|
|
20
|
+
<div className="space-y-1">
|
|
21
|
+
<h1 className="text-2xl font-semibold">Admin panel</h1>
|
|
22
|
+
<p className="text-sm text-muted-foreground">System overview and management.</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
|
26
|
+
<StatCard label="Total users" value={users.length} />
|
|
27
|
+
<StatCard label="Active users" value={users.filter(u => u.status === 'active').length} />
|
|
28
|
+
<StatCard label="Roles defined" value={roles.length} />
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Grid, Input } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
export default function RolesPage() {
|
|
5
|
+
return (
|
|
6
|
+
<Grid
|
|
7
|
+
path="/roles"
|
|
8
|
+
columns={[
|
|
9
|
+
{ key: 'name', label: 'Key', sortable: true },
|
|
10
|
+
{ key: 'label', label: 'Display name' },
|
|
11
|
+
{ key: 'description', label: 'Description' },
|
|
12
|
+
]}
|
|
13
|
+
actions={['add', 'edit', 'delete']}
|
|
14
|
+
view="table"
|
|
15
|
+
sortable
|
|
16
|
+
form={
|
|
17
|
+
<>
|
|
18
|
+
<Input name="name" label="Key (e.g. editor)" required />
|
|
19
|
+
<Input name="label" label="Display name" required />
|
|
20
|
+
<Input name="description" label="Description" />
|
|
21
|
+
</>
|
|
22
|
+
}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Form, Input, Select } from '@llmnative/react';
|
|
3
|
+
|
|
4
|
+
const timezoneOptions = [
|
|
5
|
+
{ label: 'UTC', value: 'UTC' },
|
|
6
|
+
{ label: 'Europe/Rome', value: 'Europe/Rome' },
|
|
7
|
+
{ label: 'America/New_York', value: 'America/New_York' },
|
|
8
|
+
{ label: 'Asia/Tokyo', value: 'Asia/Tokyo' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export default function SettingsPage() {
|
|
12
|
+
return (
|
|
13
|
+
<div className="mx-auto max-w-2xl px-2 py-4">
|
|
14
|
+
<Form
|
|
15
|
+
path="/settings/app"
|
|
16
|
+
appearance="card"
|
|
17
|
+
>
|
|
18
|
+
<Input name="siteName" label="Site name" required />
|
|
19
|
+
<Input name="supportEmail" label="Support email" type="email" />
|
|
20
|
+
<Select name="timezone" label="Timezone" options={timezoneOptions} />
|
|
21
|
+
</Form>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|