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