@tinya-code/gas-admin-ui 0.1.0-alpha.4 → 0.2.0-alpha.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.
Files changed (40) hide show
  1. package/README.md +568 -64
  2. package/fesm2022/{tinya-code-gas-admin-ui-calendar-view-r-RXDE37.mjs → tinya-code-gas-admin-ui-calendar-view-FfMmRiix.mjs} +4 -4
  3. package/fesm2022/{tinya-code-gas-admin-ui-calendar-view-r-RXDE37.mjs.map → tinya-code-gas-admin-ui-calendar-view-FfMmRiix.mjs.map} +1 -1
  4. package/fesm2022/{tinya-code-gas-admin-ui-condition-evaluator-BBzWUuHl.mjs → tinya-code-gas-admin-ui-condition-evaluator-B1it00js.mjs} +2 -2
  5. package/fesm2022/{tinya-code-gas-admin-ui-condition-evaluator-BBzWUuHl.mjs.map → tinya-code-gas-admin-ui-condition-evaluator-B1it00js.mjs.map} +1 -1
  6. package/fesm2022/{tinya-code-gas-admin-ui-data-source.service-LD8AM3Rn.mjs → tinya-code-gas-admin-ui-data-source.service-CvL9YHyu.mjs} +2 -3
  7. package/fesm2022/{tinya-code-gas-admin-ui-data-source.service-LD8AM3Rn.mjs.map → tinya-code-gas-admin-ui-data-source.service-CvL9YHyu.mjs.map} +1 -1
  8. package/fesm2022/{tinya-code-gas-admin-ui-error-mapper.service-Ci0UpXtV.mjs → tinya-code-gas-admin-ui-error-mapper.service-CFYzwAbD.mjs} +2 -2
  9. package/fesm2022/{tinya-code-gas-admin-ui-error-mapper.service-Ci0UpXtV.mjs.map → tinya-code-gas-admin-ui-error-mapper.service-CFYzwAbD.mjs.map} +1 -1
  10. package/fesm2022/{tinya-code-gas-admin-ui-file-Dg2N3XQ2.mjs → tinya-code-gas-admin-ui-file-Cc2DUvgR.mjs} +3 -3
  11. package/fesm2022/{tinya-code-gas-admin-ui-file-Dg2N3XQ2.mjs.map → tinya-code-gas-admin-ui-file-Cc2DUvgR.mjs.map} +1 -1
  12. package/fesm2022/{tinya-code-gas-admin-ui-form-view-BwGupSbi.mjs → tinya-code-gas-admin-ui-form-view-Dyp3oWnC.mjs} +9 -10
  13. package/fesm2022/{tinya-code-gas-admin-ui-form-view-BwGupSbi.mjs.map → tinya-code-gas-admin-ui-form-view-Dyp3oWnC.mjs.map} +1 -1
  14. package/fesm2022/{tinya-code-gas-admin-ui-image-Bjw9ImlS.mjs → tinya-code-gas-admin-ui-image-BlDRZe-F.mjs} +3 -3
  15. package/fesm2022/{tinya-code-gas-admin-ui-image-Bjw9ImlS.mjs.map → tinya-code-gas-admin-ui-image-BlDRZe-F.mjs.map} +1 -1
  16. package/fesm2022/{tinya-code-gas-admin-ui-kanban-view-CeGueI57.mjs → tinya-code-gas-admin-ui-kanban-view-zlQLxOlJ.mjs} +4 -4
  17. package/fesm2022/{tinya-code-gas-admin-ui-kanban-view-CeGueI57.mjs.map → tinya-code-gas-admin-ui-kanban-view-zlQLxOlJ.mjs.map} +1 -1
  18. package/fesm2022/{tinya-code-gas-admin-ui-list-CMzl9MkX.mjs → tinya-code-gas-admin-ui-list-D6Z4fVTb.mjs} +2 -2
  19. package/fesm2022/{tinya-code-gas-admin-ui-list-CMzl9MkX.mjs.map → tinya-code-gas-admin-ui-list-D6Z4fVTb.mjs.map} +1 -1
  20. package/fesm2022/{tinya-code-gas-admin-ui-multiselect-CuWwAfdY.mjs → tinya-code-gas-admin-ui-multiselect-fNpo9uMF.mjs} +3 -3
  21. package/fesm2022/{tinya-code-gas-admin-ui-multiselect-CuWwAfdY.mjs.map → tinya-code-gas-admin-ui-multiselect-fNpo9uMF.mjs.map} +1 -1
  22. package/fesm2022/{tinya-code-gas-admin-ui-relation-B3W3Ynsp.mjs → tinya-code-gas-admin-ui-relation-BSqbQB28.mjs} +3 -3
  23. package/fesm2022/{tinya-code-gas-admin-ui-relation-B3W3Ynsp.mjs.map → tinya-code-gas-admin-ui-relation-BSqbQB28.mjs.map} +1 -1
  24. package/fesm2022/{tinya-code-gas-admin-ui-render-registry-B_D5Jhp9.mjs → tinya-code-gas-admin-ui-render-registry-CIOMJ68Y.mjs} +10 -10
  25. package/fesm2022/{tinya-code-gas-admin-ui-render-registry-B_D5Jhp9.mjs.map → tinya-code-gas-admin-ui-render-registry-CIOMJ68Y.mjs.map} +1 -1
  26. package/fesm2022/{tinya-code-gas-admin-ui-api.service-CZrUeclM.mjs → tinya-code-gas-admin-ui-schema-catalog-C9BqNPt8.mjs} +161 -7
  27. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-C9BqNPt8.mjs.map +1 -0
  28. package/fesm2022/{tinya-code-gas-admin-ui-seed-CNbQtREA.mjs → tinya-code-gas-admin-ui-seed-DD6zUd4v.mjs} +2 -2
  29. package/fesm2022/{tinya-code-gas-admin-ui-seed-CNbQtREA.mjs.map → tinya-code-gas-admin-ui-seed-DD6zUd4v.mjs.map} +1 -1
  30. package/fesm2022/{tinya-code-gas-admin-ui-select-DzaRfHXO.mjs → tinya-code-gas-admin-ui-select-BUbb36-T.mjs} +3 -3
  31. package/fesm2022/{tinya-code-gas-admin-ui-select-DzaRfHXO.mjs.map → tinya-code-gas-admin-ui-select-BUbb36-T.mjs.map} +1 -1
  32. package/fesm2022/{tinya-code-gas-admin-ui-upload.service-C1g545w5.mjs → tinya-code-gas-admin-ui-upload.service-BWZkVOBg.mjs} +2 -2
  33. package/fesm2022/{tinya-code-gas-admin-ui-upload.service-C1g545w5.mjs.map → tinya-code-gas-admin-ui-upload.service-BWZkVOBg.mjs.map} +1 -1
  34. package/fesm2022/tinya-code-gas-admin-ui.mjs +65 -11
  35. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +1 -1
  36. package/package.json +8 -2
  37. package/types/tinya-code-gas-admin-ui.d.ts +69 -3
  38. package/fesm2022/tinya-code-gas-admin-ui-api.service-CZrUeclM.mjs.map +0 -1
  39. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-CDrz9pVN.mjs +0 -131
  40. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-CDrz9pVN.mjs.map +0 -1
package/README.md CHANGED
@@ -1,26 +1,59 @@
1
1
  # @tinya-code/gas-admin-ui
2
2
 
3
- Motor de render del ecosistema `@tinya-code`: el front que **sólo declara** su
4
- dominio (`schemas/*.schema.ts`) y consume el motor compilado — mismo contrato
5
- que `@tinya-code/gas-admin` en el back, pero para Angular.
3
+ Angular rendering engine for the `@tinya-code` ecosystem. You declare your domain in typed schema files — the engine renders views, forms, lists, actions, shell, and routes. You never write components by hand.
4
+
5
+ ## Requirements
6
+
7
+ | Requirement | Version / detail |
8
+ | --- | --- |
9
+ | Angular | `22.2` (standalone, signals) |
10
+ | Angular peers | `@angular/{cdk,common,core,forms,platform-browser,router} ^22.2` |
11
+ | Icons / charts | `@lucide/angular ^1.51`, `chart.js ^4.5` |
12
+ | CSS | Tailwind v4 — with the engine's `@source` ([step 2](#2-declare-the-tailwind-source)) |
13
+ | Backend | [`@tinya-code/gas-admin`](https://www.npmjs.com/package/@tinya-code/gas-admin) — its CLI generates and validates front-end schemas |
14
+
15
+ ---
16
+
17
+ ## Quick start from scratch
18
+
19
+ ### 1. Install the package
6
20
 
7
21
  ```bash
8
22
  npm i @tinya-code/gas-admin-ui
9
23
  ```
10
24
 
11
- **Peers:** `@angular/{cdk,common,core,forms,platform-browser,router} ^22.2`,
12
- `@lucide/angular ^1.51`, `chart.js ^4.5`.
25
+ An Angular 22.2 project already has `@angular/*` — verify the remaining peers (`@lucide/angular`, `chart.js`).
26
+
27
+ ### 2. Declare the Tailwind source
13
28
 
14
- ## Uso mínimo
29
+ In `src/styles.css`:
15
30
 
16
- **1. Wiring** — `app.config.ts` entrega el dominio al motor (catálogo +
17
- HTTP con interceptor del envelope + loaders de config/sesión):
31
+ ```css
32
+ /* Required: lets Tailwind scan the engine's templates for utility classes */
33
+ @source '../node_modules/@tinya-code/gas-admin-ui/fesm2022';
34
+
35
+ @import 'tailwindcss';
36
+ @import './styles/theme.css'; /* your design tokens */
37
+ @import './styles/base.css'; /* your base resets */
38
+ ```
39
+
40
+ Without this the app compiles and tests pass, but the engine UI renders with no styles.
41
+
42
+ ### 3. Wire the engine — `app.config.ts`
18
43
 
19
44
  ```ts
45
+ // src/app/app.config.ts
46
+ import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
47
+ import { provideRouter } from '@angular/router';
20
48
  import { API_URL, provideGasAdmin } from '@tinya-code/gas-admin-ui';
49
+ import { routes } from './app.routes';
50
+ import { schemas } from './schemas/registry';
51
+ import { authSchema } from './schemas/auth.schema';
52
+ import { environment } from '../environments/environment';
21
53
 
22
54
  export const appConfig: ApplicationConfig = {
23
55
  providers: [
56
+ provideBrowserGlobalErrorListeners(),
24
57
  provideRouter(routes),
25
58
  provideGasAdmin({ schemas, auth: authSchema }),
26
59
  { provide: API_URL, useValue: environment.apiUrl },
@@ -28,97 +61,568 @@ export const appConfig: ApplicationConfig = {
28
61
  };
29
62
  ```
30
63
 
31
- **2. Rutas** — `app.routes.ts`:
64
+ ```ts
65
+ // src/environments/environment.ts
66
+ export const environment = {
67
+ production: false,
68
+ apiUrl: 'http://localhost:3000/api/v1',
69
+ };
70
+ ```
71
+
72
+ ### 4. Declare routes — `app.routes.ts`
32
73
 
33
74
  ```ts
75
+ // src/app/app.routes.ts
76
+ import { Routes } from '@angular/router';
34
77
  import { authGuard, shellRoutes } from '@tinya-code/gas-admin-ui';
78
+ import { schemas } from './schemas/registry';
79
+
80
+ const firstResource = schemas[0]?.id || 'dashboard';
35
81
 
36
82
  export const routes: Routes = [
37
83
  { path: '', pathMatch: 'full', redirectTo: firstResource },
38
- { path: 'login', loadComponent: () => import('@tinya-code/gas-admin-ui').then((m) => m.Login) },
39
- { path: '', canActivate: [authGuard], loadChildren: () => shellRoutes },
84
+ {
85
+ path: 'login',
86
+ loadComponent: () => import('@tinya-code/gas-admin-ui').then((m) => m.Login),
87
+ },
88
+ {
89
+ path: '',
90
+ canActivate: [authGuard],
91
+ loadChildren: () => shellRoutes,
92
+ },
40
93
  { path: '**', redirectTo: firstResource },
41
94
  ];
42
95
  ```
43
96
 
44
- **3. Dominio** — la carpeta `src/app/schemas/` se genera desde el back. Con
45
- el CLI del back instalado (`npm i -D @tinya-code/gas-admin`):
97
+ `login` lives outside the shell on purpose — if it were inside, `authGuard` would block it.
98
+
99
+ ### 5. Declare auth — `auth.schema.ts`
100
+
101
+ ```ts
102
+ // src/app/schemas/auth.schema.ts
103
+ import type { AuthSchema } from '@tinya-code/gas-admin-ui';
104
+
105
+ export const authSchema: AuthSchema = {
106
+ providers: ['password'],
107
+ loginFields: [
108
+ { key: 'email', type: 'email', required: true },
109
+ { key: 'password', type: 'password', required: true },
110
+ ],
111
+ redirectAfterLogin: '/dashboard',
112
+ rememberMe: false,
113
+ };
114
+ ```
115
+
116
+ ### 6. Declare your schemas — `registry.ts`
117
+
118
+ ```ts
119
+ // src/app/schemas/registry.ts
120
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
121
+ import { dashboardSchema } from './dashboard.schema';
122
+ import { businessSchema } from './business.schema';
123
+ import { clientsSchema } from './clients.schema';
124
+ import { ranksSchema } from './ranks.schema';
125
+ import { rewardsSchema } from './rewards.schema';
126
+ import { couponsSchema } from './coupons.schema';
127
+ import { feedbackSchema } from './feedback.schema';
128
+
129
+ export const schemas: ResourceSchema[] = [
130
+ dashboardSchema,
131
+ businessSchema,
132
+ clientsSchema,
133
+ ranksSchema,
134
+ rewardsSchema,
135
+ couponsSchema,
136
+ feedbackSchema,
137
+ ];
138
+ ```
139
+
140
+ The array order determines both menu order and the default redirect route.
141
+
142
+ ### 7. Start the app
46
143
 
47
144
  ```bash
48
- npx gas-admin scaffold
145
+ npm start
49
146
  ```
50
147
 
51
- Por cada recurso de `api/schema/resources/` escribe su `*.schema.ts`
52
- (campos, validators, enums y endpoint derivados de la proyección back), más
53
- `registry.ts` (el array ordenado que alimenta menú y rutas),
54
- `auth.schema.ts` (las claves exactas del `input` de `/auth/login`) y
55
- `seed-catalog.ts` (siembra el catálogo para los specs). Los `label` salen
56
- de las keys: reemplazá los placeholders por los del dominio y sumá las
57
- vistas/filtros que quieras (el recorrido de capacidades está en la
58
- cabecera de `registry.ts`). Estructura equivalente a mano si preferís
59
- escribirlo. La validación de que todo siga coincidiendo con el back la hace
60
- `gas-admin validate --with-front` (`contract-check` de
61
- `@tinya-code/gas-admin`).
148
+ The shell starts at `/login` and, once authenticated, shows the sidebar menu and views derived from your schemas.
62
149
 
63
- ## Requisitos del consumidor
150
+ ---
64
151
 
65
- ### Tailwind v4 — `@source` obligatorio
152
+ ## Schema examples (real implementations)
66
153
 
67
- Tailwind genera utilities escaneando **tus** fuentes; el motor vive en
68
- `node_modules` y no lo escanea solo. Agregar en `styles.css`:
154
+ The examples below are taken from a production consumer of this library.
69
155
 
70
- ```css
71
- @source '../node_modules/@tinya-code/gas-admin-ui/fesm2022';
156
+ ### Dashboard — `kind: 'dashboard'`
157
+
158
+ A read-only panel with metric cards. No list, no form.
159
+
160
+ ```ts
161
+ // src/app/schemas/dashboard.schema.ts
162
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
163
+
164
+ export const dashboardSchema: ResourceSchema = {
165
+ id: 'dashboard',
166
+ label: 'Panel',
167
+ labelPlural: 'Dashboard',
168
+ kind: 'dashboard',
169
+ uiViews: {
170
+ overview: { type: 'dashboard' },
171
+ },
172
+ endpoint: {
173
+ get: '/admin/dashboard',
174
+ },
175
+ keyField: 'id',
176
+ titleField: 'label',
177
+ listColumns: [],
178
+ fields: [],
179
+ widgets: [
180
+ { type: 'metric-card', key: 'totalClients', label: 'Total Clients', hint: 'Registered memberships', width: 4 },
181
+ { type: 'metric-card', key: 'activeClients30d', label: 'Active (30d)', hint: 'Visited in last 30 days', width: 4 },
182
+ { type: 'metric-card', key: 'visitsThisMonth', label: 'Visits This Month', hint: 'Current month', width: 4 },
183
+ { type: 'metric-card', key: 'averageRating', label: 'Average Rating', hint: 'Historical satisfaction', width: 4 },
184
+ { type: 'metric-card', key: 'issuedCoupons', label: 'Issued Coupons', hint: 'All time', width: 4 },
185
+ { type: 'metric-card', key: 'usedCoupons', label: 'Redeemed Coupons', hint: 'Effective rewards', width: 4 },
186
+ ],
187
+ };
188
+ ```
189
+
190
+ ---
191
+
192
+ ### Singleton — `kind: 'singleton'` with sections layout
193
+
194
+ One record, no list. The form is divided into labelled sections.
195
+
196
+ ```ts
197
+ // src/app/schemas/business.schema.ts
198
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
199
+
200
+ export const businessSchema: ResourceSchema = {
201
+ id: 'business',
202
+ label: 'My Business',
203
+ labelPlural: 'My Business',
204
+ kind: 'singleton',
205
+ uiViews: {
206
+ settings: { type: 'form' },
207
+ },
208
+ endpoint: {
209
+ get: '/admin/businesses/current',
210
+ update: '/admin/businesses/current',
211
+ },
212
+ keyField: 'key',
213
+ titleField: 'name',
214
+ listColumns: [],
215
+ layout: {
216
+ mode: 'sections',
217
+ sections: [
218
+ { id: 'general', label: 'General' },
219
+ { id: 'branding', label: 'Design & Card' },
220
+ { id: 'social', label: 'Social & Links' },
221
+ ],
222
+ },
223
+ fields: [
224
+ { key: 'name', type: 'text', label: 'Business Name', required: true, validators: { minLength: 2, maxLength: 120 }, section: 'general', width: 6 },
225
+ { key: 'slug', type: 'slug', from: 'name', label: 'Slug', required: true, lockAfterCreate: true, section: 'general', width: 6 },
226
+ { key: 'timezone', type: 'text', label: 'Timezone', required: true, help: 'e.g. America/Lima', section: 'general', width: 6 },
227
+ {
228
+ key: 'visitResetPolicy',
229
+ type: 'select',
230
+ label: 'Visit Reset Policy',
231
+ section: 'general',
232
+ width: 6,
233
+ options: [
234
+ { value: 'none', label: 'None' },
235
+ { value: 'monthly', label: 'Monthly' },
236
+ { value: 'yearly', label: 'Yearly' },
237
+ ],
238
+ },
239
+ { key: 'colorPrimary', type: 'text', label: 'Primary Color (HEX)', placeholder: '#4A2C11', section: 'branding', width: 6 },
240
+ { key: 'colorSecondary', type: 'text', label: 'Secondary Color (HEX)', placeholder: '#D4A373', section: 'branding', width: 6 },
241
+ { key: 'logoUrl', type: 'image', label: 'Logo', readonly: true, section: 'branding', width: 12 },
242
+ { key: 'googleReviewUrl', type: 'url', label: 'Google Reviews URL', section: 'social', width: 6 },
243
+ { key: 'instagramUrl', type: 'url', label: 'Instagram', section: 'social', width: 6 },
244
+ ],
245
+ };
246
+ ```
247
+
248
+ ---
249
+
250
+ ### Collection with full CRUD — `kind: 'collection'`
251
+
252
+ List + create + edit. Includes search, filters, and field validators.
253
+
254
+ ```ts
255
+ // src/app/schemas/ranks.schema.ts
256
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
257
+
258
+ export const ranksSchema: ResourceSchema = {
259
+ id: 'ranks',
260
+ label: 'Rank',
261
+ labelPlural: 'Ranks',
262
+ kind: 'collection',
263
+ uiViews: {
264
+ list: { type: 'list' },
265
+ create: { type: 'form', path: 'new' },
266
+ edit: { type: 'form', path: ':key/edit' },
267
+ },
268
+ endpoint: {
269
+ list: '/admin/businesses/current/ranks',
270
+ get: '/admin/businesses/current/ranks/{key}',
271
+ create: '/admin/businesses/current/ranks',
272
+ update: '/admin/businesses/current/ranks/{key}',
273
+ remove: '/admin/businesses/current/ranks/{key}',
274
+ },
275
+ keyField: 'id',
276
+ titleField: 'name',
277
+ search: ['name'],
278
+ filters: [
279
+ { key: 'includeInactive', label: 'Include Inactive', type: 'boolean' },
280
+ ],
281
+ listColumns: [
282
+ { key: 'level', label: 'Level' },
283
+ { key: 'name', label: 'Name' },
284
+ { key: 'minVisits', label: 'Min Visits' },
285
+ { key: 'benefitType', label: 'Benefit Type' },
286
+ { key: 'benefitValue',label: 'Value' },
287
+ { key: 'active', label: 'Active', format: 'boolean' },
288
+ ],
289
+ fields: [
290
+ { key: 'name', type: 'text', label: 'Rank Name', required: true, validators: { minLength: 1, maxLength: 80 } },
291
+ { key: 'level', type: 'number', label: 'Level', required: true, min: 1, help: 'Must be unique among active ranks' },
292
+ { key: 'minVisits',type: 'number', label: 'Minimum Visits', required: true, min: 0 },
293
+ {
294
+ key: 'benefitType',
295
+ type: 'select',
296
+ label: 'Benefit Type',
297
+ required: true,
298
+ options: [
299
+ { value: 'none', label: 'None' },
300
+ { value: 'percentage_discount', label: 'Percentage Discount' },
301
+ { value: 'fixed_discount', label: 'Fixed Discount' },
302
+ { value: 'free_item', label: 'Free Item' },
303
+ { value: 'priority', label: 'Priority Service' },
304
+ ],
305
+ },
306
+ { key: 'benefitValue', type: 'number', label: 'Benefit Value' },
307
+ { key: 'benefitDescription', type: 'textarea', label: 'Benefit Description', validators: { maxLength: 200 } },
308
+ { key: 'color', type: 'text', label: 'Color (HEX)', placeholder: '#CD7F32' },
309
+ { key: 'iconUrl', type: 'image', label: 'Icon', readonly: true },
310
+ { key: 'active', type: 'boolean', label: 'Active', readonlyOn: 'create' },
311
+ ],
312
+ };
313
+ ```
314
+
315
+ ---
316
+
317
+ ### Read-only collection with detail view
318
+
319
+ List + detail only — no create, no edit form.
320
+
321
+ ```ts
322
+ // src/app/schemas/clients.schema.ts
323
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
324
+
325
+ export const clientsSchema: ResourceSchema = {
326
+ id: 'clients',
327
+ label: 'Client',
328
+ labelPlural: 'Clients',
329
+ kind: 'collection',
330
+ uiViews: {
331
+ list: { type: 'list' },
332
+ detail: { type: 'detail', path: ':key' },
333
+ edit: { type: 'form', path: ':key/edit' },
334
+ },
335
+ endpoint: {
336
+ list: '/admin/businesses/current/clients',
337
+ get: '/admin/businesses/current/clients/{key}',
338
+ update: '/admin/businesses/current/clients/{key}/status',
339
+ // no create / remove → those actions won't appear in the UI
340
+ },
341
+ keyField: 'membershipId',
342
+ titleField: 'name',
343
+ search: ['name', 'phone'],
344
+ filters: [
345
+ { key: 'active', label: 'Active', type: 'boolean' },
346
+ ],
347
+ listColumns: [
348
+ { key: 'name', label: 'Name' },
349
+ { key: 'phone', label: 'Phone' },
350
+ { key: 'email', label: 'Email' },
351
+ { key: 'totalVisits', label: 'Total Visits' },
352
+ { key: 'lastVisitAt', label: 'Last Visit' },
353
+ { key: 'active', label: 'Active', format: 'boolean' },
354
+ { key: 'registeredAt',label: 'Registered' },
355
+ ],
356
+ detail: {
357
+ enabled: true,
358
+ headerFields: ['name', 'phone', 'active'],
359
+ },
360
+ fields: [
361
+ { key: 'membershipId', type: 'readonly-text', label: 'Membership ID', readonly: true },
362
+ { key: 'name', type: 'text', label: 'Name', readonly: true },
363
+ { key: 'phone', type: 'phone', label: 'Phone', readonly: true },
364
+ { key: 'email', type: 'email', label: 'Email', readonly: true },
365
+ { key: 'active', type: 'boolean', label: 'Active Membership' },
366
+ { key: 'totalVisits', type: 'number', label: 'Total Visits', readonly: true },
367
+ { key: 'currentPeriodVisits', type: 'number', label: 'Period Visits', readonly: true },
368
+ { key: 'registeredAt', type: 'date', label: 'Registered At', readonly: true },
369
+ { key: 'lastVisitAt', type: 'date', label: 'Last Visit', readonly: true },
370
+ ],
371
+ };
372
+ ```
373
+
374
+ ---
375
+
376
+ ### Read-only collection with select filter
377
+
378
+ ```ts
379
+ // src/app/schemas/coupons.schema.ts
380
+ import type { ResourceSchema } from '@tinya-code/gas-admin-ui';
381
+
382
+ export const couponsSchema: ResourceSchema = {
383
+ id: 'coupons',
384
+ label: 'Coupon',
385
+ labelPlural: 'Coupons',
386
+ kind: 'collection',
387
+ uiViews: {
388
+ list: { type: 'list' },
389
+ detail: { type: 'detail', path: ':key' },
390
+ },
391
+ endpoint: {
392
+ list: '/admin/businesses/current/coupons',
393
+ get: '/admin/businesses/current/coupons/{key}',
394
+ },
395
+ keyField: 'id',
396
+ titleField: 'code',
397
+ search: ['code'],
398
+ filters: [
399
+ {
400
+ key: 'status',
401
+ label: 'Status',
402
+ type: 'select',
403
+ options: [
404
+ { value: 'issued', label: 'Issued' },
405
+ { value: 'used', label: 'Redeemed' },
406
+ { value: 'expired', label: 'Expired' },
407
+ ],
408
+ },
409
+ ],
410
+ listColumns: [
411
+ { key: 'code', label: 'Code' },
412
+ { key: 'status', label: 'Status' },
413
+ { key: 'issuedAt', label: 'Issued' },
414
+ { key: 'expiresAt',label: 'Expires' },
415
+ { key: 'usedAt', label: 'Redeemed' },
416
+ ],
417
+ detail: {
418
+ enabled: true,
419
+ headerFields: ['code', 'status', 'issuedAt'],
420
+ },
421
+ fields: [
422
+ { key: 'id', type: 'readonly-text', label: 'ID', readonly: true },
423
+ { key: 'code', type: 'text', label: 'Code', readonly: true },
424
+ { key: 'status', type: 'text', label: 'Status', readonly: true },
425
+ { key: 'issuedAt', type: 'date', label: 'Issued At', readonly: true },
426
+ { key: 'expiresAt', type: 'date', label: 'Expires At', readonly: true },
427
+ { key: 'usedAt', type: 'date', label: 'Redeemed At',readonly: true },
428
+ ],
429
+ };
430
+ ```
431
+
432
+ ---
433
+
434
+ ## Firebase Auth
435
+
436
+ The package supports federated authentication with Google and Firebase Email/Password via the decoupled `FirebaseAuthAdapter`. Firebase is declared as an optional `peerDependency` — the library does not bundle it.
437
+
438
+ ### 1. Install Firebase in the consumer app
439
+
440
+ ```bash
441
+ npm i firebase
72
442
  ```
73
443
 
74
- Sin esto, la app compila pero la UI pierde las clases del motor (visual roto,
75
- tests en verde).
444
+ ### 2. Declare providers in `AuthSchema`
76
445
 
77
- ### Tests del consumidor — desde `node_modules` (sin alias)
446
+ ```ts
447
+ // src/app/schemas/auth.schema.ts
448
+ import type { AuthSchema } from '@tinya-code/gas-admin-ui';
78
449
 
79
- Los specs del consumidor resuelven `@tinya-code/gas-admin-ui` desde
80
- `node_modules` (la versión publicada en `dependencies`), sin alias de
81
- `tsconfig.spec.json` ni clone del repo como directorio hermano en CI: la
82
- app y los tests usan la MISMA copia compilada ⇒ Angular se instancia una
83
- sola vez (verificado en admin-schema: 116/116 sin alias). Para probar un
84
- cambio del motor en el consumidor: publicar un alpha y bumpear la
85
- dependencia.
450
+ // Option A — password only (default, no Firebase required)
451
+ export const authSchema: AuthSchema = {
452
+ providers: ['password'],
453
+ loginFields: [
454
+ { key: 'email', type: 'email', required: true },
455
+ { key: 'password', type: 'password', required: true },
456
+ ],
457
+ redirectAfterLogin: '/dashboard',
458
+ rememberMe: false,
459
+ };
86
460
 
87
- Si una combinación de versiones llegara a reproducir `NG0203`/`NG01908`
88
- (doble instancia de Angular: vitest prebundlea `@angular/*` pero la lib
89
- corre en ESM nativo), el workaround es compilar la lib desde source en el
90
- grafo de tests (`paths` en `tsconfig.spec.json` apuntando al source del
91
- paquete) junto con un clone del repo como directorio hermano en CI.
461
+ // Option B — Google only
462
+ export const authSchema: AuthSchema = {
463
+ providers: ['google'],
464
+ loginFields: [],
465
+ redirectAfterLogin: '/dashboard',
466
+ rememberMe: false,
467
+ firebase: { exchangeEndpoint: '/auth/firebase' },
468
+ };
92
469
 
93
- ## API pública
470
+ // Option C — Google + Password (both)
471
+ export const authSchema: AuthSchema = {
472
+ providers: ['google', 'password'],
473
+ loginFields: [
474
+ { key: 'email', type: 'email', required: true },
475
+ { key: 'password', type: 'password', required: true },
476
+ ],
477
+ redirectAfterLogin: '/dashboard',
478
+ rememberMe: false,
479
+ firebase: { exchangeEndpoint: '/auth/firebase' },
480
+ };
481
+ ```
482
+
483
+ ### 3. Implement the adapter and wire it in `app.config.ts`
94
484
 
95
- Sólo lo exportado por `public-api.ts` es contrato; el resto es privado y
96
- puede cambiar entre alphas. Superficie v1:
485
+ ```ts
486
+ // src/app/app.config.ts
487
+ import { initializeApp } from 'firebase/app';
488
+ import {
489
+ getAuth,
490
+ GoogleAuthProvider,
491
+ signInWithPopup,
492
+ signInWithEmailAndPassword,
493
+ signOut,
494
+ } from 'firebase/auth';
495
+ import {
496
+ API_URL,
497
+ provideGasAdmin,
498
+ provideFirebaseAdapter,
499
+ } from '@tinya-code/gas-admin-ui';
500
+ import type { FirebaseAuthAdapter } from '@tinya-code/gas-admin-ui';
501
+ import { environment } from '../environments/environment';
502
+
503
+ const firebaseApp = initializeApp(environment.firebase);
504
+ const firebaseAuth = getAuth(firebaseApp);
97
505
 
98
- | Grupo | Exportaciones |
506
+ class AppFirebaseAuthAdapter implements FirebaseAuthAdapter {
507
+ async signInWithGoogle(): Promise<string> {
508
+ const cred = await signInWithPopup(firebaseAuth, new GoogleAuthProvider());
509
+ return cred.user.getIdToken();
510
+ }
511
+ async signInWithEmailAndPassword(email: string, password: string): Promise<string> {
512
+ const cred = await signInWithEmailAndPassword(firebaseAuth, email, password);
513
+ return cred.user.getIdToken();
514
+ }
515
+ async signOut(): Promise<void> {
516
+ await signOut(firebaseAuth);
517
+ }
518
+ }
519
+
520
+ export const appConfig: ApplicationConfig = {
521
+ providers: [
522
+ provideRouter(routes),
523
+ provideGasAdmin({ schemas, auth: authSchema }),
524
+ provideFirebaseAdapter(new AppFirebaseAuthAdapter()), // ← add this
525
+ { provide: API_URL, useValue: environment.apiUrl },
526
+ ],
527
+ };
528
+ ```
529
+
530
+ ### 4. Backend endpoint — `POST /auth/firebase`
531
+
532
+ The library sends the Firebase ID token using the **same envelope transport** as every other request. Your backend must:
533
+
534
+ 1. Receive `{ idToken: string }` in the payload.
535
+ 2. Verify it with the Firebase Admin SDK (`getAuth().verifyIdToken(idToken)`).
536
+ 3. Return `{ token, user }` — identical shape to `/auth/login`.
537
+
538
+ ```json
539
+ // Request body (Content-Type: text/plain)
540
+ { "token": null, "method": "POST", "path": "/auth/firebase", "payload": { "idToken": "..." } }
541
+
542
+ // Response
543
+ { "token": "<your-app-jwt>", "user": { "id": "...", "email": "...", "name": "...", "role": "..." }, "meta": {} }
544
+ ```
545
+
546
+ For a full consumer integration guide including a backend handler example, environment config, and a pre-production checklist, see [`doc/firebase-consumer-example.md`](../../doc/firebase-consumer-example.md).
547
+
548
+ ---
549
+
550
+ ## Consumer requirements
551
+
552
+ ### Tailwind v4 — mandatory `@source`
553
+
554
+ Tailwind generates utilities by scanning **your** source files. The engine lives in `node_modules` and is not scanned by default. The `@source` directive in `styles.css` (step 2) is required — without it the app compiles and tests pass green, but the engine UI renders without any styles.
555
+
556
+ ### Tests — resolve from `node_modules` (no alias)
557
+
558
+ Consumer specs must resolve `@tinya-code/gas-admin-ui` from `node_modules` (the published, compiled version), without `tsconfig.spec.json` aliases. This guarantees Angular is instantiated exactly once. To test a library change, publish an alpha and bump the dependency.
559
+
560
+ ---
561
+
562
+ ## Generated schema structure (`npx gas-admin scaffold`)
563
+
564
+ ```text
565
+ src/app/schemas/
566
+ ├── auth.schema.ts # auth provider and login fields
567
+ ├── registry.ts # ordered array that feeds the menu and routes
568
+ ├── seed-catalog.ts # seeds the catalog for specs
569
+ ├── dashboard.schema.ts # kind: dashboard
570
+ ├── business.schema.ts # kind: singleton
571
+ ├── clients.schema.ts # kind: collection (read-only with detail)
572
+ ├── ranks.schema.ts # kind: collection (full CRUD)
573
+ ├── rewards.schema.ts # kind: collection (full CRUD)
574
+ ├── coupons.schema.ts # kind: collection (read-only with filters)
575
+ └── feedback.schema.ts # kind: collection (read-only with search)
576
+ ```
577
+
578
+ `scaffold` fails with exit 1 if the target folder already contains `*.ts` files — use `--force` to regenerate.
579
+
580
+ ---
581
+
582
+ ## Public API
583
+
584
+ Only what `public-api.ts` exports is stable contract; everything else is private and may change between alphas.
585
+
586
+ | Group | Exports |
99
587
  | --- | --- |
100
- | Wiring | `provideGasAdmin`, `GAS_SCHEMAS`, `setSchemaCatalog`, `getSchema`, `allSchemas`, `getAuth`, `API_URL` |
101
- | Rutas/guard | `authGuard`, `shellRoutes`, `resourceRoutes` |
102
- | Componentes | `Login`, `Sidebar`, `Dashboard`, `ListView`, `FormView`, `ResourcePage`, `ComposerHost`, `FieldHost`, `ConfirmDialog`, `Toast` |
103
- | Servicios | `ApiService`, `AuthService`, `NotificationService`, `DataSourceService`, `UploadService`, `ErrorMapperService`, `apiErrorInterceptor` |
104
- | Modelos | `ResourceSchema`, `AuthSchema`, `ResourceAction`, tipos de `views`/`columns`/`fields` |
105
- | Registros | `fieldDescriptors`, `columnFormatDescriptors`, `layoutDescriptors`, `viewDescriptors`, `registerRenderDescriptor`, `renderKinds`, `ValidatorName` |
588
+ | Wiring & catalog | `provideGasAdmin`, `provideFirebaseAdapter`, `GAS_SCHEMAS`, `setSchemaCatalog`, `getSchema`, `allSchemas`, `getAuth`, type `GasAdminConfig` |
589
+ | Tokens & adapters | `API_URL`, `FIREBASE_AUTH_ADAPTER`, type `FirebaseAuthAdapter` |
590
+ | Routes & guard | `authGuard`, `shellRoutes`, `resourceRoutes` |
591
+ | Pages | `Login`, `Dashboard`, `ListView`, `FormView`, `ResourcePage` |
592
+ | Components | `Sidebar`, `ComposerHost`, `FieldHost`, `ConfirmDialog`, `Toast` |
593
+ | Services | `ApiService`, `AuthService` (+ `SESSION_TOKEN_KEY`, `SESSION_USER_KEY`), `NotificationService`, `DataSourceService`, `UploadService`, `ErrorMapperService`, `apiErrorInterceptor` |
106
594
  | Forms | `buildFormSchema`, `seedModel`, `buildPayload` |
595
+ | Schema models | `ResourceSchema`, `AuthSchema`, `AuthProvider`, `FirebaseAuthConfig`, `ResourceAction`, `ApiError` and the full field/widget/view/layout vocabulary |
596
+ | Render registries | `fieldDescriptors`, `columnFormatDescriptors`, `viewDescriptors`, `layoutDescriptors`, `renderKinds` |
597
+ | Custom render | `registerRenderDescriptor`, `unregisterRenderDescriptor`, `provideRenderDescriptors`, `getRenderDescriptor`, `isSupported`, `isRenderSupported`, types `RenderKind`, `ComponentRenderKind` |
598
+ | Composition | `validateComposition`, `MAX_DEPTH`, `SchemaCompositionError` |
599
+ | Custom actions | `registerAction`, `getAction`, `actionsFor`, `actionType`, `actionTypes`, types `ActionDescriptor`, `ActionSurface`, `ActionContext`, `ActionResult` |
600
+ | Validators | type `ValidatorName` |
601
+ | Utilities | `interpolateTemplate` |
602
+
603
+ ---
604
+
605
+ ## See also
606
+
607
+ - **Backend** — domain declaration, `doc`, `validate`:
608
+ [`@tinya-code/gas-admin`](https://github.com/Tinya-Code/gas-admin) → `docs/guia-de-uso.md`.
609
+ - **Firebase consumer guide** — full wiring, backend contract, pre-production checklist:
610
+ [`doc/firebase-consumer-example.md`](../../doc/firebase-consumer-example.md).
611
+
612
+ ---
107
613
 
108
- ## Desarrollo (este repo)
614
+ ## Development (this repo)
109
615
 
110
616
  ```bash
111
- ng build gas-admin-ui # ng-packagr → dist/gas-admin-ui (fesm2022 + types)
112
- ng test gas-admin-ui # vitest: 489 tests del motor (fixtures congelados)
617
+ ng build gas-admin-ui # ng-packagr → dist/gas-admin-ui
618
+ ng test gas-admin-ui # vitest: engine tests
113
619
  ```
114
620
 
115
- Publicación (tag `alpha` obligatorio en prereleases):
621
+ Publish (npm requires explicit `--tag` for pre-releases):
116
622
 
117
623
  ```bash
118
624
  npm publish ./dist/gas-admin-ui --tag alpha
119
625
  ```
120
626
 
121
- Los specs que renderizan los schemas **reales** de un consumidor no viven
122
- acá (la lib no puede importar al consumidor): viven en el repo del
123
- consumidor importando desde este paquete — ver los 16 specs de
124
- integración de `admin-schema`.
627
+ - `dist/` is gitignored — the build is always local and never committed.
628
+ - Specs that render a consumer's real schemas live in the consumer repo, not here.