@isaiahiroko/tilmart-ngx-apps-admin 0.2.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 (75) hide show
  1. package/README.md +9 -0
  2. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs +154 -0
  3. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-articles.module-OeLRnPwN.mjs.map +1 -0
  4. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs +84 -0
  5. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-audit.module-C9gvw8X2.mjs.map +1 -0
  6. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs +294 -0
  7. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-baskets.module-_lHPl1dT.mjs.map +1 -0
  8. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs +224 -0
  9. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-catalog.module-Bvx2tVOi.mjs.map +1 -0
  10. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs +106 -0
  11. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-claims.module-Ccsd8Yx3.mjs.map +1 -0
  12. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs +261 -0
  13. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-compatibility-rules.module-xN-JUWwp.mjs.map +1 -0
  14. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs +365 -0
  15. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs.map +1 -0
  16. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs +106 -0
  17. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs.map +1 -0
  18. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs +106 -0
  19. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-customers.module-12XdV9zo.mjs.map +1 -0
  20. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs +286 -0
  21. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-quality.module-Bfgh3zE5.mjs.map +1 -0
  22. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs +104 -0
  23. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-data-requests.module-C6tPNlup.mjs.map +1 -0
  24. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs +73 -0
  25. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-disputes.module-DmUoAS0Z.mjs.map +1 -0
  26. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs +404 -0
  27. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-homepage.module-BZ3UmxE3.mjs.map +1 -0
  28. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs +290 -0
  29. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-inbox.module-BZDTa1L-.mjs.map +1 -0
  30. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs +112 -0
  31. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-macros.module-BO4bdER2.mjs.map +1 -0
  32. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs +116 -0
  33. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-moderation.module-CZOCrias.mjs.map +1 -0
  34. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs +281 -0
  35. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-navigation.module-DoKzzRgn.mjs.map +1 -0
  36. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs +115 -0
  37. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-order-board.module-DIOjhptn.mjs.map +1 -0
  38. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs +185 -0
  39. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-orders.module-Ddw49QW0.mjs.map +1 -0
  40. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs +106 -0
  41. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payments.module-BhqAtTZs.mjs.map +1 -0
  42. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs +70 -0
  43. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-payouts.module-CoiscPIZ.mjs.map +1 -0
  44. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs +274 -0
  45. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-procurement.module-qXW-g0pf.mjs.map +1 -0
  46. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs +106 -0
  47. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-promotions.module-DG9uTDqX.mjs.map +1 -0
  48. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs +84 -0
  49. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-queues.module-K-zYYVFy.mjs.map +1 -0
  50. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs +106 -0
  51. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-redirects.module-Hm2dO_bC.mjs.map +1 -0
  52. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs +106 -0
  53. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-referrals.module-QLNhuh7q.mjs.map +1 -0
  54. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs +104 -0
  55. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-reports.module-DBZov0We.mjs.map +1 -0
  56. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs +106 -0
  57. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-returns.module-Bd2gdo6_.mjs.map +1 -0
  58. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs +81 -0
  59. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs.map +1 -0
  60. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs +105 -0
  61. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-routes.module-BkVY06ci.mjs.map +1 -0
  62. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs +222 -0
  63. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-settings.module-DKeZTldO.mjs.map +1 -0
  64. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs +150 -0
  65. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-shipments.module-XuaOKr8v.mjs.map +1 -0
  66. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs +107 -0
  67. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-stock.module-DYiAkF7E.mjs.map +1 -0
  68. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs +92 -0
  69. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-trace.module-Pwr-82Rc.mjs.map +1 -0
  70. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs +111 -0
  71. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin-users.module-Bx_7fI2g.mjs.map +1 -0
  72. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs +475 -0
  73. package/fesm2022/isaiahiroko-tilmart-ngx-apps-admin.mjs.map +1 -0
  74. package/package.json +34 -0
  75. package/types/isaiahiroko-tilmart-ngx-apps-admin.d.ts +32 -0
@@ -0,0 +1,365 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, NgModule } from '@angular/core';
3
+ import * as i1$2 from '@angular/router';
4
+ import { RouterModule } from '@angular/router';
5
+ import { BaseDirective, using, notify, SharedModule } from '@thescaffold/ngx-core';
6
+ import * as i1$1 from '@thescaffold/ngx-ui';
7
+ import { ElementsModule } from '@thescaffold/ngx-ui';
8
+ import { R as RichEditorComponent, a as RichEditorModule } from './isaiahiroko-tilmart-ngx-apps-admin-rich-editor.module-DW9Gy_QQ.mjs';
9
+ import { takeUntil, firstValueFrom } from 'rxjs';
10
+ import * as i1 from '@angular/forms';
11
+
12
+ const BLOCK_TYPES = [
13
+ { value: 'rich_text', label: 'Rich text', fields: [{ key: 'html', label: 'Body', kind: 'rich' }] },
14
+ {
15
+ value: 'hero',
16
+ label: 'Hero',
17
+ fields: [
18
+ { key: 'heading', label: 'Heading', kind: 'text' },
19
+ { key: 'subheading', label: 'Subheading', kind: 'text' },
20
+ { key: 'imageUrl', label: 'Image URL', kind: 'text' },
21
+ { key: 'ctaLabel', label: 'Button label', kind: 'text' },
22
+ { key: 'ctaUrl', label: 'Button URL', kind: 'text' },
23
+ ],
24
+ },
25
+ {
26
+ value: 'cta',
27
+ label: 'Call to action',
28
+ fields: [
29
+ { key: 'heading', label: 'Heading', kind: 'text' },
30
+ { key: 'body', label: 'Body', kind: 'text' },
31
+ { key: 'buttonLabel', label: 'Button label', kind: 'text' },
32
+ { key: 'buttonUrl', label: 'Button URL', kind: 'text' },
33
+ ],
34
+ },
35
+ {
36
+ value: 'product_rail',
37
+ label: 'Product rail',
38
+ fields: [
39
+ { key: 'categoryId', label: 'From category ID', kind: 'id' },
40
+ { key: 'productIds', label: 'Or product IDs', kind: 'ids' },
41
+ { key: 'limit', label: 'Max items', kind: 'num' },
42
+ ],
43
+ },
44
+ {
45
+ value: 'category_grid',
46
+ label: 'Category grid',
47
+ fields: [{ key: 'categoryIds', label: 'Category IDs', kind: 'ids' }],
48
+ },
49
+ {
50
+ value: 'image',
51
+ label: 'Image',
52
+ fields: [
53
+ { key: 'url', label: 'Image URL', kind: 'text' },
54
+ { key: 'alt', label: 'Alt text', kind: 'text' },
55
+ { key: 'linkUrl', label: 'Links to', kind: 'text' },
56
+ ],
57
+ },
58
+ { value: 'gallery', label: 'Gallery', fields: [{ key: 'imageUrls', label: 'Image URLs', kind: 'ids' }] },
59
+ { value: 'faq', label: 'FAQ', fields: [{ key: 'items', label: 'Items — [{"q":"…","a":"…"}]', kind: 'json' }] },
60
+ { value: 'columns', label: 'Columns', fields: [{ key: 'columns', label: 'Columns JSON', kind: 'json' }] },
61
+ {
62
+ value: 'embed',
63
+ label: 'Embed',
64
+ fields: [
65
+ { key: 'url', label: 'Embed URL', kind: 'text' },
66
+ { key: 'html', label: 'Or raw HTML', kind: 'text' },
67
+ ],
68
+ },
69
+ ];
70
+ const STATUSES = ['draft', 'published', 'archived'];
71
+ const TEMPLATES = ['default', 'landing', 'full-width'];
72
+ // P6.10, CMS-01 — the CMS page builder. Left: the Page list. Right: the
73
+ // selected page's metadata + its drag-ordered ContentBlock list, each
74
+ // with a type-aware editor (`rich_text` uses the `@thescaffold/editor-*`
75
+ // WYSIWYG). The storefront page renderer is a later task; ops CRUD is
76
+ // covered by `admin:*:apps:commerce:*`.
77
+ class ContentComponent extends BaseDirective {
78
+ constructor() {
79
+ super(...arguments);
80
+ this.pageSource = this.apiService.on('apps/commerce/page');
81
+ this.blockSource = this.apiService.on('apps/commerce/content-block');
82
+ this.blockTypes = BLOCK_TYPES;
83
+ this.statuses = STATUSES;
84
+ this.templates = TEMPLATES;
85
+ this.loading = true;
86
+ this.pages = [];
87
+ this.blocks = [];
88
+ this.blocksLoading = false;
89
+ this.newBlockType = BLOCK_TYPES[0].value;
90
+ this.blockModel = {};
91
+ this.rawHtml = false;
92
+ this.newPageSlug = '';
93
+ this.newPageTitle = '';
94
+ this.dragIndex = null;
95
+ this.init = () => {
96
+ this.loadPages();
97
+ };
98
+ }
99
+ blockTypeLabel(v) {
100
+ return BLOCK_TYPES.find((t) => t.value === v)?.label ?? v;
101
+ }
102
+ blockTypeDef(v) {
103
+ return BLOCK_TYPES.find((t) => t.value === v);
104
+ }
105
+ get orderedBlocks() {
106
+ return [...this.blocks].sort((a, b) => a.position - b.position);
107
+ }
108
+ // ── pages ─────────────────────────────────────────────────────────
109
+ loadPages() {
110
+ this.loading = true;
111
+ this.pageSource
112
+ .getMany({ perPage: 200 })
113
+ .pipe(takeUntil(this.$))
114
+ .subscribe(using((res) => {
115
+ this.pages = (res?.data ?? []).sort((a, b) => String(a.title).localeCompare(String(b.title)));
116
+ this.loading = false;
117
+ if (this.selected) {
118
+ this.selected = this.pages.find((p) => p.id === this.selected.id);
119
+ }
120
+ }));
121
+ }
122
+ async createPage() {
123
+ const slug = this.newPageSlug.trim().toLowerCase().replace(/\s+/g, '-');
124
+ const title = this.newPageTitle.trim();
125
+ if (!slug || !title)
126
+ return;
127
+ try {
128
+ const res = await firstValueFrom(this.pageSource.create({ slug, title, status: 'draft', template: 'default' }));
129
+ this.newPageSlug = '';
130
+ this.newPageTitle = '';
131
+ notify('Page created.', undefined, 'success');
132
+ this.loadPages();
133
+ if (res?.data?.id)
134
+ setTimeout(() => this.select({ ...res.data }), 200);
135
+ }
136
+ catch {
137
+ notify('Could not create the page (slug may be taken).', undefined, 'error');
138
+ }
139
+ }
140
+ select(page) {
141
+ this.selected = page;
142
+ this.editingBlock = undefined;
143
+ this.loadBlocks();
144
+ }
145
+ async savePage() {
146
+ if (!this.selected)
147
+ return;
148
+ const p = this.selected;
149
+ try {
150
+ await firstValueFrom(this.pageSource.update(p.id, {
151
+ slug: p.slug,
152
+ title: p.title,
153
+ excerpt: p.excerpt || undefined,
154
+ status: p.status,
155
+ template: p.template,
156
+ publishAt: p.publishAt ? new Date(p.publishAt).toISOString() : null,
157
+ }));
158
+ notify('Page saved.', undefined, 'success');
159
+ this.loadPages();
160
+ }
161
+ catch {
162
+ notify('Could not save the page.', undefined, 'error');
163
+ }
164
+ }
165
+ async deletePage() {
166
+ if (!this.selected)
167
+ return;
168
+ try {
169
+ await firstValueFrom(this.pageSource.remove(this.selected.id));
170
+ this.selected = undefined;
171
+ this.blocks = [];
172
+ notify('Page deleted.', undefined, 'success');
173
+ this.loadPages();
174
+ }
175
+ catch {
176
+ notify('Could not delete the page.', undefined, 'error');
177
+ }
178
+ }
179
+ // ── blocks ────────────────────────────────────────────────────────
180
+ loadBlocks() {
181
+ if (!this.selected)
182
+ return;
183
+ this.blocksLoading = true;
184
+ this.blockSource
185
+ .getMany({ pageId: this.selected.id, perPage: 200 })
186
+ .pipe(takeUntil(this.$))
187
+ .subscribe(using((res) => {
188
+ this.blocks = (res?.data ?? [])
189
+ .filter((b) => b.pageId === this.selected.id)
190
+ .map((b) => ({ ...b, config: b.config ?? {} }))
191
+ .sort((a, b) => a.position - b.position);
192
+ this.blocksLoading = false;
193
+ }));
194
+ }
195
+ async addBlock() {
196
+ if (!this.selected)
197
+ return;
198
+ try {
199
+ await firstValueFrom(this.blockSource.create({
200
+ pageId: this.selected.id,
201
+ type: this.newBlockType,
202
+ position: this.blocks.length,
203
+ config: {},
204
+ enabled: true,
205
+ }));
206
+ this.loadBlocks();
207
+ }
208
+ catch {
209
+ notify('Could not add the block.', undefined, 'error');
210
+ }
211
+ }
212
+ editBlock(b) {
213
+ this.editingBlock = b;
214
+ this.rawHtml = false;
215
+ this.blockModel = {};
216
+ for (const field of this.blockTypeDef(b.type)?.fields ?? []) {
217
+ const value = (b.config ?? {})[field.key];
218
+ this.blockModel[field.key] =
219
+ field.kind === 'json'
220
+ ? JSON.stringify(value ?? (field.key === 'items' || field.key === 'columns' ? [] : {}), null, 2)
221
+ : Array.isArray(value)
222
+ ? value.join(', ')
223
+ : value != null
224
+ ? String(value)
225
+ : '';
226
+ }
227
+ }
228
+ cancelBlockEdit() {
229
+ this.editingBlock = undefined;
230
+ this.blockModel = {};
231
+ }
232
+ async saveBlock() {
233
+ if (!this.editingBlock)
234
+ return;
235
+ const def = this.blockTypeDef(this.editingBlock.type);
236
+ const config = { ...(this.editingBlock.config ?? {}) };
237
+ for (const field of def?.fields ?? []) {
238
+ const raw = (this.blockModel[field.key] ?? '').trim();
239
+ if (field.kind === 'ids') {
240
+ config[field.key] = raw ? raw.split(',').map((s) => s.trim()).filter(Boolean) : [];
241
+ }
242
+ else if (field.kind === 'num') {
243
+ config[field.key] = raw ? Number(raw) : undefined;
244
+ }
245
+ else if (field.kind === 'json') {
246
+ try {
247
+ config[field.key] = raw ? JSON.parse(raw) : [];
248
+ }
249
+ catch {
250
+ notify(`"${field.label}" is not valid JSON.`, undefined, 'error');
251
+ return;
252
+ }
253
+ }
254
+ else {
255
+ config[field.key] = raw || undefined;
256
+ }
257
+ }
258
+ try {
259
+ await firstValueFrom(this.blockSource.update(this.editingBlock.id, { config }));
260
+ this.editingBlock.config = config;
261
+ notify('Block saved.', undefined, 'success');
262
+ this.cancelBlockEdit();
263
+ }
264
+ catch {
265
+ notify('Could not save the block.', undefined, 'error');
266
+ }
267
+ }
268
+ async toggleBlock(b) {
269
+ const next = !b.enabled;
270
+ try {
271
+ await firstValueFrom(this.blockSource.update(b.id, { enabled: next }));
272
+ b.enabled = next;
273
+ }
274
+ catch {
275
+ notify('Could not update the block.', undefined, 'error');
276
+ }
277
+ }
278
+ async removeBlock(b) {
279
+ try {
280
+ await firstValueFrom(this.blockSource.remove(b.id));
281
+ this.blocks = this.blocks.filter((x) => x.id !== b.id);
282
+ if (this.editingBlock?.id === b.id)
283
+ this.cancelBlockEdit();
284
+ notify('Block removed.', undefined, 'success');
285
+ }
286
+ catch {
287
+ notify('Could not remove the block.', undefined, 'error');
288
+ }
289
+ }
290
+ onDragStart(index) {
291
+ this.dragIndex = index;
292
+ }
293
+ onDragOver(event) {
294
+ event.preventDefault();
295
+ }
296
+ onDrop(targetIndex) {
297
+ if (this.dragIndex === null || this.dragIndex === targetIndex)
298
+ return;
299
+ const ordered = this.orderedBlocks;
300
+ const [moved] = ordered.splice(this.dragIndex, 1);
301
+ ordered.splice(targetIndex, 0, moved);
302
+ this.dragIndex = null;
303
+ void this.persistOrder(ordered);
304
+ }
305
+ moveBlock(index, dir) {
306
+ const ordered = this.orderedBlocks;
307
+ const target = index + dir;
308
+ if (target < 0 || target >= ordered.length)
309
+ return;
310
+ [ordered[index], ordered[target]] = [ordered[target], ordered[index]];
311
+ void this.persistOrder(ordered);
312
+ }
313
+ async persistOrder(ordered) {
314
+ const changed = [];
315
+ ordered.forEach((b, i) => {
316
+ if (b.position !== i) {
317
+ b.position = i;
318
+ changed.push(b);
319
+ }
320
+ });
321
+ this.blocks = ordered;
322
+ try {
323
+ await Promise.all(changed.map((b) => firstValueFrom(this.blockSource.update(b.id, { position: b.position }))));
324
+ }
325
+ catch {
326
+ notify('Could not save the new order.', undefined, 'error');
327
+ this.loadBlocks();
328
+ }
329
+ }
330
+ onRichChange(field, html) {
331
+ this.blockModel[field] = html ?? '';
332
+ }
333
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
334
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: ContentComponent, isStandalone: false, selector: "x-content", usesInheritance: true, ngImport: i0, template: "<div class=\"x-cms-head\">\n <div class=\"x-cms-title\">Content pages</div>\n <div class=\"x-cms-sub\">Ops-authored storefront pages \u2014 metadata and a block list per page.</div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-cms-layout\">\n <aside class=\"x-cms-list\">\n <div class=\"x-cms-new\">\n <input type=\"text\" placeholder=\"slug\" [(ngModel)]=\"newPageSlug\" />\n <input type=\"text\" placeholder=\"Title\" [(ngModel)]=\"newPageTitle\" />\n <button type=\"button\" (click)=\"createPage()\">Add page</button>\n </div>\n @for (p of pages; track p.id) {\n <button\n type=\"button\"\n class=\"x-cms-list-item\"\n [class.x-cms-list-active]=\"selected?.id === p.id\"\n (click)=\"select(p)\"\n >\n <span class=\"x-cms-list-title\">{{ p.title }}</span>\n <span class=\"x-cms-list-meta\">/{{ p.slug }} \u00B7 {{ p.status }}</span>\n </button>\n } @empty {\n <div class=\"x-cms-empty\">No pages yet.</div>\n }\n </aside>\n\n <section class=\"x-cms-editor\">\n @if (!selected) {\n <div class=\"x-cms-empty\">Pick a page, or add one.</div>\n } @else {\n <div class=\"x-cms-meta\">\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"selected.title\" />\n </label>\n <label class=\"x-cms-f\">\n Slug\n <input type=\"text\" [(ngModel)]=\"selected.slug\" />\n </label>\n </div>\n <label class=\"x-cms-f\">\n Excerpt\n <input type=\"text\" [(ngModel)]=\"selected.excerpt\" />\n </label>\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Status\n <select [(ngModel)]=\"selected.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Template\n <select [(ngModel)]=\"selected.template\">\n @for (t of templates; track t) { <option [value]=\"t\">{{ t }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Publish at\n <input type=\"datetime-local\" [(ngModel)]=\"selected.publishAt\" />\n </label>\n </div>\n <div class=\"x-cms-meta-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"savePage()\">Save page</button>\n <button type=\"button\" class=\"x-cms-btn x-cms-danger\" (click)=\"deletePage()\">Delete page</button>\n </div>\n </div>\n\n <div class=\"x-cms-blocks-head\">\n <span>Blocks</span>\n <span class=\"x-cms-add-block\">\n <select [(ngModel)]=\"newBlockType\">\n @for (t of blockTypes; track t.value) { <option [value]=\"t.value\">{{ t.label }}</option> }\n </select>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"addBlock()\">Add block</button>\n </span>\n </div>\n\n @if (blocksLoading) {\n <x-element-loader></x-element-loader>\n } @else if (!orderedBlocks.length) {\n <div class=\"x-cms-empty\">No blocks on this page yet.</div>\n } @else {\n <div class=\"x-cms-blocks\">\n @for (b of orderedBlocks; track b.id; let i = $index) {\n <div\n class=\"x-cms-block\"\n [class.x-cms-block-off]=\"!b.enabled\"\n draggable=\"true\"\n (dragstart)=\"onDragStart(i)\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop(i)\"\n >\n <div class=\"x-cms-block-head\">\n <span class=\"x-cms-grip\">\u283F</span>\n <div class=\"x-cms-block-order\">\n <button type=\"button\" (click)=\"moveBlock(i, -1)\" [disabled]=\"i === 0\">\u25B2</button>\n <button type=\"button\" (click)=\"moveBlock(i, 1)\" [disabled]=\"i === orderedBlocks.length - 1\">\u25BC</button>\n </div>\n <span class=\"x-cms-block-type\">{{ blockTypeLabel(b.type) }}</span>\n <span class=\"x-cms-block-actions\">\n <button type=\"button\" class=\"x-cms-toggle\" (click)=\"toggleBlock(b)\">{{ b.enabled ? 'On' : 'Off' }}</button>\n <button type=\"button\" class=\"x-cms-link\" (click)=\"editBlock(b)\">Edit</button>\n <button type=\"button\" class=\"x-cms-link x-cms-danger\" (click)=\"removeBlock(b)\">Delete</button>\n </span>\n </div>\n\n @if (editingBlock?.id === b.id) {\n <div class=\"x-cms-block-edit\">\n @for (field of blockTypeDef(b.type)?.fields ?? []; track field.key) {\n <label class=\"x-cms-f\">\n {{ field.label }}\n @if (field.kind === 'rich') {\n @if (rawHtml) {\n <textarea rows=\"6\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <x-rich-editor\n [content]=\"blockModel[field.key]\"\n (contentChange)=\"onRichChange(field.key, $event)\"\n ></x-rich-editor>\n }\n <button type=\"button\" class=\"x-cms-link\" (click)=\"rawHtml = !rawHtml\">\n {{ rawHtml ? 'Use the editor' : 'Edit raw HTML' }}\n </button>\n } @else if (field.kind === 'json') {\n <textarea rows=\"5\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <input\n [type]=\"field.kind === 'num' ? 'number' : 'text'\"\n [(ngModel)]=\"blockModel[field.key]\"\n />\n }\n </label>\n }\n <div class=\"x-cms-edit-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"saveBlock()\">Save block</button>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"cancelBlockEdit()\">Cancel</button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n }\n </section>\n </div>\n}\n\n<style>\n .x-cms-head { margin-bottom: 20px; }\n .x-cms-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-cms-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-cms-empty { font-size: 14px; color: var(--color-text-muted); padding: 12px 0; }\n .x-cms-layout { display: grid; grid-template-columns: 260px 1fr; gap: 20px; align-items: start; }\n .x-cms-list { display: flex; flex-direction: column; gap: 6px; }\n .x-cms-new { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }\n .x-cms-new input, .x-cms-f input, .x-cms-f select, .x-cms-f textarea,\n .x-cms-add-block select {\n border: 1px solid var(--color-border); border-radius: var(--radius-md);\n padding: 6px 10px; font-size: 13px; background: var(--color-surface); color: var(--color-text);\n }\n .x-cms-new button, .x-cms-btn {\n border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-md);\n padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted);\n }\n .x-cms-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-cms-list-item {\n text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);\n background: var(--color-surface); padding: 8px 10px; cursor: pointer;\n display: flex; flex-direction: column; gap: 2px;\n }\n .x-cms-list-active { border-color: var(--color-accent); }\n .x-cms-list-title { font-size: 13px; font-weight: 700; color: var(--color-text); }\n .x-cms-list-meta { font-size: 11px; color: var(--color-text-muted); }\n .x-cms-editor { min-width: 0; }\n .x-cms-meta {\n background: var(--color-surface); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px;\n margin-bottom: 20px;\n }\n .x-cms-f { display: flex; flex-direction: column; gap: 4px; flex: 1; font-size: 11.5px; color: var(--color-text-muted); font-weight: 700; }\n .x-cms-f-row { display: flex; gap: 10px; }\n .x-cms-meta-actions, .x-cms-edit-actions { display: flex; gap: 8px; }\n .x-cms-blocks-head {\n display: flex; justify-content: space-between; align-items: center;\n font-size: 13px; font-weight: 800; color: var(--color-text); margin-bottom: 10px;\n }\n .x-cms-add-block { display: flex; gap: 8px; }\n .x-cms-blocks { display: flex; flex-direction: column; gap: 10px; }\n .x-cms-block { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 12px 14px; }\n .x-cms-block-off { opacity: 0.55; }\n .x-cms-block-head { display: flex; align-items: center; gap: 10px; }\n .x-cms-grip { cursor: grab; color: var(--color-text-muted); }\n .x-cms-block-order { display: flex; flex-direction: column; }\n .x-cms-block-order button { border: 0; background: transparent; cursor: pointer; color: var(--color-text-muted); font-size: 10px; line-height: 1.1; padding: 1px 3px; }\n .x-cms-block-order button:disabled { opacity: 0.3; cursor: default; }\n .x-cms-block-type { font-weight: 800; text-transform: uppercase; letter-spacing: 0.4px; font-size: 11px; color: var(--color-accent-hover); flex: 1; }\n .x-cms-block-actions { display: flex; gap: 8px; align-items: center; }\n .x-cms-toggle { border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: transparent; padding: 3px 10px; font-size: 11.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted); }\n .x-cms-link { border: 0; background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; color: var(--color-accent-hover); }\n .x-cms-danger { color: var(--color-danger, #c0362c); }\n .x-cms-block-edit { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--color-border); display: flex; flex-direction: column; gap: 10px; }\n x-rich-editor { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); min-height: 160px; }\n</style>\n", styles: [".x-cms-head{margin-bottom:20px}.x-cms-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-cms-sub{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}.x-cms-empty{font-size:14px;color:var(--color-text-muted);padding:12px 0}.x-cms-layout{display:grid;grid-template-columns:260px 1fr;gap:20px;align-items:start}.x-cms-list{display:flex;flex-direction:column;gap:6px}.x-cms-new{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}.x-cms-new input,.x-cms-f input,.x-cms-f select,.x-cms-f textarea,.x-cms-add-block select{border:1px solid var(--color-border);border-radius:var(--radius-md);padding:6px 10px;font-size:13px;background:var(--color-surface);color:var(--color-text)}.x-cms-new button,.x-cms-btn{border:1px solid var(--color-border);background:transparent;border-radius:var(--radius-md);padding:6px 14px;font-size:12.5px;font-weight:700;cursor:pointer;color:var(--color-text-muted)}.x-cms-btn-primary{border-color:var(--color-accent);color:var(--color-accent-hover)}.x-cms-list-item{text-align:left;border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);padding:8px 10px;cursor:pointer;display:flex;flex-direction:column;gap:2px}.x-cms-list-active{border-color:var(--color-accent)}.x-cms-list-title{font-size:13px;font-weight:700;color:var(--color-text)}.x-cms-list-meta{font-size:11px;color:var(--color-text-muted)}.x-cms-editor{min-width:0}.x-cms-meta{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px;display:flex;flex-direction:column;gap:10px;margin-bottom:20px}.x-cms-f{display:flex;flex-direction:column;gap:4px;flex:1;font-size:11.5px;color:var(--color-text-muted);font-weight:700}.x-cms-f-row{display:flex;gap:10px}.x-cms-meta-actions,.x-cms-edit-actions{display:flex;gap:8px}.x-cms-blocks-head{display:flex;justify-content:space-between;align-items:center;font-size:13px;font-weight:800;color:var(--color-text);margin-bottom:10px}.x-cms-add-block{display:flex;gap:8px}.x-cms-blocks{display:flex;flex-direction:column;gap:10px}.x-cms-block{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:12px 14px}.x-cms-block-off{opacity:.55}.x-cms-block-head{display:flex;align-items:center;gap:10px}.x-cms-grip{cursor:grab;color:var(--color-text-muted)}.x-cms-block-order{display:flex;flex-direction:column}.x-cms-block-order button{border:0;background:transparent;cursor:pointer;color:var(--color-text-muted);font-size:10px;line-height:1.1;padding:1px 3px}.x-cms-block-order button:disabled{opacity:.3;cursor:default}.x-cms-block-type{font-weight:800;text-transform:uppercase;letter-spacing:.4px;font-size:11px;color:var(--color-accent-hover);flex:1}.x-cms-block-actions{display:flex;gap:8px;align-items:center}.x-cms-toggle{border:1px solid var(--color-border);border-radius:var(--radius-sm);background:transparent;padding:3px 10px;font-size:11.5px;font-weight:700;cursor:pointer;color:var(--color-text-muted)}.x-cms-link{border:0;background:transparent;cursor:pointer;font-size:12px;font-weight:700;color:var(--color-accent-hover)}.x-cms-danger{color:var(--color-danger, #c0362c)}.x-cms-block-edit{margin-top:12px;padding-top:12px;border-top:1px solid var(--color-border);display:flex;flex-direction:column;gap:10px}x-rich-editor{display:block;border:1px solid var(--color-border);border-radius:var(--radius-md);min-height:160px}\n"], dependencies: [{ kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i1$1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "component", type: RichEditorComponent, selector: "x-rich-editor", inputs: ["content"], outputs: ["contentChange"] }] }); }
335
+ }
336
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentComponent, decorators: [{
337
+ type: Component,
338
+ args: [{ standalone: false, selector: 'x-content', template: "<div class=\"x-cms-head\">\n <div class=\"x-cms-title\">Content pages</div>\n <div class=\"x-cms-sub\">Ops-authored storefront pages \u2014 metadata and a block list per page.</div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-cms-layout\">\n <aside class=\"x-cms-list\">\n <div class=\"x-cms-new\">\n <input type=\"text\" placeholder=\"slug\" [(ngModel)]=\"newPageSlug\" />\n <input type=\"text\" placeholder=\"Title\" [(ngModel)]=\"newPageTitle\" />\n <button type=\"button\" (click)=\"createPage()\">Add page</button>\n </div>\n @for (p of pages; track p.id) {\n <button\n type=\"button\"\n class=\"x-cms-list-item\"\n [class.x-cms-list-active]=\"selected?.id === p.id\"\n (click)=\"select(p)\"\n >\n <span class=\"x-cms-list-title\">{{ p.title }}</span>\n <span class=\"x-cms-list-meta\">/{{ p.slug }} \u00B7 {{ p.status }}</span>\n </button>\n } @empty {\n <div class=\"x-cms-empty\">No pages yet.</div>\n }\n </aside>\n\n <section class=\"x-cms-editor\">\n @if (!selected) {\n <div class=\"x-cms-empty\">Pick a page, or add one.</div>\n } @else {\n <div class=\"x-cms-meta\">\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"selected.title\" />\n </label>\n <label class=\"x-cms-f\">\n Slug\n <input type=\"text\" [(ngModel)]=\"selected.slug\" />\n </label>\n </div>\n <label class=\"x-cms-f\">\n Excerpt\n <input type=\"text\" [(ngModel)]=\"selected.excerpt\" />\n </label>\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Status\n <select [(ngModel)]=\"selected.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Template\n <select [(ngModel)]=\"selected.template\">\n @for (t of templates; track t) { <option [value]=\"t\">{{ t }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Publish at\n <input type=\"datetime-local\" [(ngModel)]=\"selected.publishAt\" />\n </label>\n </div>\n <div class=\"x-cms-meta-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"savePage()\">Save page</button>\n <button type=\"button\" class=\"x-cms-btn x-cms-danger\" (click)=\"deletePage()\">Delete page</button>\n </div>\n </div>\n\n <div class=\"x-cms-blocks-head\">\n <span>Blocks</span>\n <span class=\"x-cms-add-block\">\n <select [(ngModel)]=\"newBlockType\">\n @for (t of blockTypes; track t.value) { <option [value]=\"t.value\">{{ t.label }}</option> }\n </select>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"addBlock()\">Add block</button>\n </span>\n </div>\n\n @if (blocksLoading) {\n <x-element-loader></x-element-loader>\n } @else if (!orderedBlocks.length) {\n <div class=\"x-cms-empty\">No blocks on this page yet.</div>\n } @else {\n <div class=\"x-cms-blocks\">\n @for (b of orderedBlocks; track b.id; let i = $index) {\n <div\n class=\"x-cms-block\"\n [class.x-cms-block-off]=\"!b.enabled\"\n draggable=\"true\"\n (dragstart)=\"onDragStart(i)\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop(i)\"\n >\n <div class=\"x-cms-block-head\">\n <span class=\"x-cms-grip\">\u283F</span>\n <div class=\"x-cms-block-order\">\n <button type=\"button\" (click)=\"moveBlock(i, -1)\" [disabled]=\"i === 0\">\u25B2</button>\n <button type=\"button\" (click)=\"moveBlock(i, 1)\" [disabled]=\"i === orderedBlocks.length - 1\">\u25BC</button>\n </div>\n <span class=\"x-cms-block-type\">{{ blockTypeLabel(b.type) }}</span>\n <span class=\"x-cms-block-actions\">\n <button type=\"button\" class=\"x-cms-toggle\" (click)=\"toggleBlock(b)\">{{ b.enabled ? 'On' : 'Off' }}</button>\n <button type=\"button\" class=\"x-cms-link\" (click)=\"editBlock(b)\">Edit</button>\n <button type=\"button\" class=\"x-cms-link x-cms-danger\" (click)=\"removeBlock(b)\">Delete</button>\n </span>\n </div>\n\n @if (editingBlock?.id === b.id) {\n <div class=\"x-cms-block-edit\">\n @for (field of blockTypeDef(b.type)?.fields ?? []; track field.key) {\n <label class=\"x-cms-f\">\n {{ field.label }}\n @if (field.kind === 'rich') {\n @if (rawHtml) {\n <textarea rows=\"6\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <x-rich-editor\n [content]=\"blockModel[field.key]\"\n (contentChange)=\"onRichChange(field.key, $event)\"\n ></x-rich-editor>\n }\n <button type=\"button\" class=\"x-cms-link\" (click)=\"rawHtml = !rawHtml\">\n {{ rawHtml ? 'Use the editor' : 'Edit raw HTML' }}\n </button>\n } @else if (field.kind === 'json') {\n <textarea rows=\"5\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <input\n [type]=\"field.kind === 'num' ? 'number' : 'text'\"\n [(ngModel)]=\"blockModel[field.key]\"\n />\n }\n </label>\n }\n <div class=\"x-cms-edit-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"saveBlock()\">Save block</button>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"cancelBlockEdit()\">Cancel</button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n }\n </section>\n </div>\n}\n\n<style>\n .x-cms-head { margin-bottom: 20px; }\n .x-cms-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-cms-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-cms-empty { font-size: 14px; color: var(--color-text-muted); padding: 12px 0; }\n .x-cms-layout { display: grid; grid-template-columns: 260px 1fr; gap: 20px; align-items: start; }\n .x-cms-list { display: flex; flex-direction: column; gap: 6px; }\n .x-cms-new { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }\n .x-cms-new input, .x-cms-f input, .x-cms-f select, .x-cms-f textarea,\n .x-cms-add-block select {\n border: 1px solid var(--color-border); border-radius: var(--radius-md);\n padding: 6px 10px; font-size: 13px; background: var(--color-surface); color: var(--color-text);\n }\n .x-cms-new button, .x-cms-btn {\n border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-md);\n padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted);\n }\n .x-cms-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-cms-list-item {\n text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);\n background: var(--color-surface); padding: 8px 10px; cursor: pointer;\n display: flex; flex-direction: column; gap: 2px;\n }\n .x-cms-list-active { border-color: var(--color-accent); }\n .x-cms-list-title { font-size: 13px; font-weight: 700; color: var(--color-text); }\n .x-cms-list-meta { font-size: 11px; color: var(--color-text-muted); }\n .x-cms-editor { min-width: 0; }\n .x-cms-meta {\n background: var(--color-surface); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px;\n margin-bottom: 20px;\n }\n .x-cms-f { display: flex; flex-direction: column; gap: 4px; flex: 1; font-size: 11.5px; color: var(--color-text-muted); font-weight: 700; }\n .x-cms-f-row { display: flex; gap: 10px; }\n .x-cms-meta-actions, .x-cms-edit-actions { display: flex; gap: 8px; }\n .x-cms-blocks-head {\n display: flex; justify-content: space-between; align-items: center;\n font-size: 13px; font-weight: 800; color: var(--color-text); margin-bottom: 10px;\n }\n .x-cms-add-block { display: flex; gap: 8px; }\n .x-cms-blocks { display: flex; flex-direction: column; gap: 10px; }\n .x-cms-block { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 12px 14px; }\n .x-cms-block-off { opacity: 0.55; }\n .x-cms-block-head { display: flex; align-items: center; gap: 10px; }\n .x-cms-grip { cursor: grab; color: var(--color-text-muted); }\n .x-cms-block-order { display: flex; flex-direction: column; }\n .x-cms-block-order button { border: 0; background: transparent; cursor: pointer; color: var(--color-text-muted); font-size: 10px; line-height: 1.1; padding: 1px 3px; }\n .x-cms-block-order button:disabled { opacity: 0.3; cursor: default; }\n .x-cms-block-type { font-weight: 800; text-transform: uppercase; letter-spacing: 0.4px; font-size: 11px; color: var(--color-accent-hover); flex: 1; }\n .x-cms-block-actions { display: flex; gap: 8px; align-items: center; }\n .x-cms-toggle { border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: transparent; padding: 3px 10px; font-size: 11.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted); }\n .x-cms-link { border: 0; background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; color: var(--color-accent-hover); }\n .x-cms-danger { color: var(--color-danger, #c0362c); }\n .x-cms-block-edit { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--color-border); display: flex; flex-direction: column; gap: 10px; }\n x-rich-editor { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); min-height: 160px; }\n</style>\n" }]
339
+ }] });
340
+
341
+ class ContentModule {
342
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
343
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: ContentModule, declarations: [ContentComponent], imports: [SharedModule,
344
+ ElementsModule,
345
+ RichEditorModule, i1$2.RouterModule] }); }
346
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentModule, imports: [SharedModule,
347
+ ElementsModule,
348
+ RichEditorModule,
349
+ RouterModule.forChild([{ path: '', component: ContentComponent }])] }); }
350
+ }
351
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: ContentModule, decorators: [{
352
+ type: NgModule,
353
+ args: [{
354
+ declarations: [ContentComponent],
355
+ imports: [
356
+ SharedModule,
357
+ ElementsModule,
358
+ RichEditorModule,
359
+ RouterModule.forChild([{ path: '', component: ContentComponent }]),
360
+ ],
361
+ }]
362
+ }] });
363
+
364
+ export { ContentModule };
365
+ //# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isaiahiroko-tilmart-ngx-apps-admin-content.module-DLdKAUqc.mjs","sources":["../../../projects/admin/src/lib/pages/content/content.component.ts","../../../projects/admin/src/lib/pages/content/content.component.html","../../../projects/admin/src/lib/pages/content/content.module.ts"],"sourcesContent":["import { Component } from '@angular/core';\nimport { BaseDirective, notify, using } from '@thescaffold/ngx-core';\nimport { firstValueFrom, takeUntil } from 'rxjs';\n\ninterface PageVm {\n id: string;\n slug: string;\n title: string;\n excerpt?: string;\n status: string;\n template: string;\n publishAt?: string;\n seoMetaId?: string;\n}\n\ninterface BlockVm {\n id: string;\n pageId: string;\n type: string;\n position: number;\n config: Record<string, any>;\n enabled: boolean;\n}\n\ninterface BlockTypeDef {\n value: string;\n label: string;\n // 'rich' = the WYSIWYG editor; the rest are plain inputs / json.\n fields: { key: string; label: string; kind: 'rich' | 'text' | 'ids' | 'id' | 'num' | 'json' }[];\n}\n\nconst BLOCK_TYPES: BlockTypeDef[] = [\n { value: 'rich_text', label: 'Rich text', fields: [{ key: 'html', label: 'Body', kind: 'rich' }] },\n {\n value: 'hero',\n label: 'Hero',\n fields: [\n { key: 'heading', label: 'Heading', kind: 'text' },\n { key: 'subheading', label: 'Subheading', kind: 'text' },\n { key: 'imageUrl', label: 'Image URL', kind: 'text' },\n { key: 'ctaLabel', label: 'Button label', kind: 'text' },\n { key: 'ctaUrl', label: 'Button URL', kind: 'text' },\n ],\n },\n {\n value: 'cta',\n label: 'Call to action',\n fields: [\n { key: 'heading', label: 'Heading', kind: 'text' },\n { key: 'body', label: 'Body', kind: 'text' },\n { key: 'buttonLabel', label: 'Button label', kind: 'text' },\n { key: 'buttonUrl', label: 'Button URL', kind: 'text' },\n ],\n },\n {\n value: 'product_rail',\n label: 'Product rail',\n fields: [\n { key: 'categoryId', label: 'From category ID', kind: 'id' },\n { key: 'productIds', label: 'Or product IDs', kind: 'ids' },\n { key: 'limit', label: 'Max items', kind: 'num' },\n ],\n },\n {\n value: 'category_grid',\n label: 'Category grid',\n fields: [{ key: 'categoryIds', label: 'Category IDs', kind: 'ids' }],\n },\n {\n value: 'image',\n label: 'Image',\n fields: [\n { key: 'url', label: 'Image URL', kind: 'text' },\n { key: 'alt', label: 'Alt text', kind: 'text' },\n { key: 'linkUrl', label: 'Links to', kind: 'text' },\n ],\n },\n { value: 'gallery', label: 'Gallery', fields: [{ key: 'imageUrls', label: 'Image URLs', kind: 'ids' }] },\n { value: 'faq', label: 'FAQ', fields: [{ key: 'items', label: 'Items — [{\"q\":\"…\",\"a\":\"…\"}]', kind: 'json' }] },\n { value: 'columns', label: 'Columns', fields: [{ key: 'columns', label: 'Columns JSON', kind: 'json' }] },\n {\n value: 'embed',\n label: 'Embed',\n fields: [\n { key: 'url', label: 'Embed URL', kind: 'text' },\n { key: 'html', label: 'Or raw HTML', kind: 'text' },\n ],\n },\n];\n\nconst STATUSES = ['draft', 'published', 'archived'];\nconst TEMPLATES = ['default', 'landing', 'full-width'];\n\n// P6.10, CMS-01 — the CMS page builder. Left: the Page list. Right: the\n// selected page's metadata + its drag-ordered ContentBlock list, each\n// with a type-aware editor (`rich_text` uses the `@thescaffold/editor-*`\n// WYSIWYG). The storefront page renderer is a later task; ops CRUD is\n// covered by `admin:*:apps:commerce:*`.\n@Component({\n standalone: false,\n selector: 'x-content',\n templateUrl: './content.component.html',\n})\nexport class ContentComponent extends BaseDirective {\n private readonly pageSource = this.apiService.on('apps/commerce/page');\n private readonly blockSource = this.apiService.on('apps/commerce/content-block');\n\n readonly blockTypes = BLOCK_TYPES;\n readonly statuses = STATUSES;\n readonly templates = TEMPLATES;\n\n loading = true;\n pages: PageVm[] = [];\n selected?: PageVm;\n\n blocks: BlockVm[] = [];\n blocksLoading = false;\n newBlockType = BLOCK_TYPES[0].value;\n editingBlock?: BlockVm;\n blockModel: Record<string, string> = {};\n rawHtml = false;\n\n newPageSlug = '';\n newPageTitle = '';\n\n private dragIndex: number | null = null;\n\n override init = () => {\n this.loadPages();\n };\n\n blockTypeLabel(v: string): string {\n return BLOCK_TYPES.find((t) => t.value === v)?.label ?? v;\n }\n\n blockTypeDef(v: string): BlockTypeDef | undefined {\n return BLOCK_TYPES.find((t) => t.value === v);\n }\n\n get orderedBlocks(): BlockVm[] {\n return [...this.blocks].sort((a, b) => a.position - b.position);\n }\n\n // ── pages ─────────────────────────────────────────────────────────\n\n loadPages(): void {\n this.loading = true;\n this.pageSource\n .getMany({ perPage: 200 } as any)\n .pipe(takeUntil(this.$))\n .subscribe(\n using((res: any) => {\n this.pages = (res?.data ?? []).sort((a: any, b: any) =>\n String(a.title).localeCompare(String(b.title)),\n );\n this.loading = false;\n if (this.selected) {\n this.selected = this.pages.find((p) => p.id === this.selected!.id);\n }\n }),\n );\n }\n\n async createPage(): Promise<void> {\n const slug = this.newPageSlug.trim().toLowerCase().replace(/\\s+/g, '-');\n const title = this.newPageTitle.trim();\n if (!slug || !title) return;\n try {\n const res: any = await firstValueFrom(\n this.pageSource.create({ slug, title, status: 'draft', template: 'default' } as any),\n );\n this.newPageSlug = '';\n this.newPageTitle = '';\n notify('Page created.', undefined, 'success');\n this.loadPages();\n if (res?.data?.id) setTimeout(() => this.select({ ...res.data }), 200);\n } catch {\n notify('Could not create the page (slug may be taken).', undefined, 'error');\n }\n }\n\n select(page: PageVm): void {\n this.selected = page;\n this.editingBlock = undefined;\n this.loadBlocks();\n }\n\n async savePage(): Promise<void> {\n if (!this.selected) return;\n const p = this.selected;\n try {\n await firstValueFrom(\n this.pageSource.update(p.id, {\n slug: p.slug,\n title: p.title,\n excerpt: p.excerpt || undefined,\n status: p.status,\n template: p.template,\n publishAt: p.publishAt ? new Date(p.publishAt).toISOString() : null,\n } as any),\n );\n notify('Page saved.', undefined, 'success');\n this.loadPages();\n } catch {\n notify('Could not save the page.', undefined, 'error');\n }\n }\n\n async deletePage(): Promise<void> {\n if (!this.selected) return;\n try {\n await firstValueFrom(this.pageSource.remove(this.selected.id));\n this.selected = undefined;\n this.blocks = [];\n notify('Page deleted.', undefined, 'success');\n this.loadPages();\n } catch {\n notify('Could not delete the page.', undefined, 'error');\n }\n }\n\n // ── blocks ────────────────────────────────────────────────────────\n\n private loadBlocks(): void {\n if (!this.selected) return;\n this.blocksLoading = true;\n this.blockSource\n .getMany({ pageId: this.selected.id, perPage: 200 } as any)\n .pipe(takeUntil(this.$))\n .subscribe(\n using((res: any) => {\n this.blocks = ((res?.data ?? []) as BlockVm[])\n .filter((b) => b.pageId === this.selected!.id)\n .map((b) => ({ ...b, config: b.config ?? {} }))\n .sort((a, b) => a.position - b.position);\n this.blocksLoading = false;\n }),\n );\n }\n\n async addBlock(): Promise<void> {\n if (!this.selected) return;\n try {\n await firstValueFrom(\n this.blockSource.create({\n pageId: this.selected.id,\n type: this.newBlockType,\n position: this.blocks.length,\n config: {},\n enabled: true,\n } as any),\n );\n this.loadBlocks();\n } catch {\n notify('Could not add the block.', undefined, 'error');\n }\n }\n\n editBlock(b: BlockVm): void {\n this.editingBlock = b;\n this.rawHtml = false;\n this.blockModel = {};\n for (const field of this.blockTypeDef(b.type)?.fields ?? []) {\n const value = (b.config ?? {})[field.key];\n this.blockModel[field.key] =\n field.kind === 'json'\n ? JSON.stringify(value ?? (field.key === 'items' || field.key === 'columns' ? [] : {}), null, 2)\n : Array.isArray(value)\n ? value.join(', ')\n : value != null\n ? String(value)\n : '';\n }\n }\n\n cancelBlockEdit(): void {\n this.editingBlock = undefined;\n this.blockModel = {};\n }\n\n async saveBlock(): Promise<void> {\n if (!this.editingBlock) return;\n const def = this.blockTypeDef(this.editingBlock.type);\n const config: Record<string, any> = { ...(this.editingBlock.config ?? {}) };\n for (const field of def?.fields ?? []) {\n const raw = (this.blockModel[field.key] ?? '').trim();\n if (field.kind === 'ids') {\n config[field.key] = raw ? raw.split(',').map((s) => s.trim()).filter(Boolean) : [];\n } else if (field.kind === 'num') {\n config[field.key] = raw ? Number(raw) : undefined;\n } else if (field.kind === 'json') {\n try {\n config[field.key] = raw ? JSON.parse(raw) : [];\n } catch {\n notify(`\"${field.label}\" is not valid JSON.`, undefined, 'error');\n return;\n }\n } else {\n config[field.key] = raw || undefined;\n }\n }\n try {\n await firstValueFrom(this.blockSource.update(this.editingBlock.id, { config } as any));\n this.editingBlock.config = config;\n notify('Block saved.', undefined, 'success');\n this.cancelBlockEdit();\n } catch {\n notify('Could not save the block.', undefined, 'error');\n }\n }\n\n async toggleBlock(b: BlockVm): Promise<void> {\n const next = !b.enabled;\n try {\n await firstValueFrom(this.blockSource.update(b.id, { enabled: next } as any));\n b.enabled = next;\n } catch {\n notify('Could not update the block.', undefined, 'error');\n }\n }\n\n async removeBlock(b: BlockVm): Promise<void> {\n try {\n await firstValueFrom(this.blockSource.remove(b.id));\n this.blocks = this.blocks.filter((x) => x.id !== b.id);\n if (this.editingBlock?.id === b.id) this.cancelBlockEdit();\n notify('Block removed.', undefined, 'success');\n } catch {\n notify('Could not remove the block.', undefined, 'error');\n }\n }\n\n onDragStart(index: number): void {\n this.dragIndex = index;\n }\n\n onDragOver(event: DragEvent): void {\n event.preventDefault();\n }\n\n onDrop(targetIndex: number): void {\n if (this.dragIndex === null || this.dragIndex === targetIndex) return;\n const ordered = this.orderedBlocks;\n const [moved] = ordered.splice(this.dragIndex, 1);\n ordered.splice(targetIndex, 0, moved);\n this.dragIndex = null;\n void this.persistOrder(ordered);\n }\n\n moveBlock(index: number, dir: -1 | 1): void {\n const ordered = this.orderedBlocks;\n const target = index + dir;\n if (target < 0 || target >= ordered.length) return;\n [ordered[index], ordered[target]] = [ordered[target], ordered[index]];\n void this.persistOrder(ordered);\n }\n\n private async persistOrder(ordered: BlockVm[]): Promise<void> {\n const changed: BlockVm[] = [];\n ordered.forEach((b, i) => {\n if (b.position !== i) {\n b.position = i;\n changed.push(b);\n }\n });\n this.blocks = ordered;\n try {\n await Promise.all(\n changed.map((b) =>\n firstValueFrom(this.blockSource.update(b.id, { position: b.position } as any)),\n ),\n );\n } catch {\n notify('Could not save the new order.', undefined, 'error');\n this.loadBlocks();\n }\n }\n\n onRichChange(field: string, html: string): void {\n this.blockModel[field] = html ?? '';\n }\n}\n","<div class=\"x-cms-head\">\n <div class=\"x-cms-title\">Content pages</div>\n <div class=\"x-cms-sub\">Ops-authored storefront pages — metadata and a block list per page.</div>\n</div>\n\n@if (loading) {\n <x-element-loader></x-element-loader>\n} @else {\n <div class=\"x-cms-layout\">\n <aside class=\"x-cms-list\">\n <div class=\"x-cms-new\">\n <input type=\"text\" placeholder=\"slug\" [(ngModel)]=\"newPageSlug\" />\n <input type=\"text\" placeholder=\"Title\" [(ngModel)]=\"newPageTitle\" />\n <button type=\"button\" (click)=\"createPage()\">Add page</button>\n </div>\n @for (p of pages; track p.id) {\n <button\n type=\"button\"\n class=\"x-cms-list-item\"\n [class.x-cms-list-active]=\"selected?.id === p.id\"\n (click)=\"select(p)\"\n >\n <span class=\"x-cms-list-title\">{{ p.title }}</span>\n <span class=\"x-cms-list-meta\">/{{ p.slug }} · {{ p.status }}</span>\n </button>\n } @empty {\n <div class=\"x-cms-empty\">No pages yet.</div>\n }\n </aside>\n\n <section class=\"x-cms-editor\">\n @if (!selected) {\n <div class=\"x-cms-empty\">Pick a page, or add one.</div>\n } @else {\n <div class=\"x-cms-meta\">\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Title\n <input type=\"text\" [(ngModel)]=\"selected.title\" />\n </label>\n <label class=\"x-cms-f\">\n Slug\n <input type=\"text\" [(ngModel)]=\"selected.slug\" />\n </label>\n </div>\n <label class=\"x-cms-f\">\n Excerpt\n <input type=\"text\" [(ngModel)]=\"selected.excerpt\" />\n </label>\n <div class=\"x-cms-f-row\">\n <label class=\"x-cms-f\">\n Status\n <select [(ngModel)]=\"selected.status\">\n @for (s of statuses; track s) { <option [value]=\"s\">{{ s }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Template\n <select [(ngModel)]=\"selected.template\">\n @for (t of templates; track t) { <option [value]=\"t\">{{ t }}</option> }\n </select>\n </label>\n <label class=\"x-cms-f\">\n Publish at\n <input type=\"datetime-local\" [(ngModel)]=\"selected.publishAt\" />\n </label>\n </div>\n <div class=\"x-cms-meta-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"savePage()\">Save page</button>\n <button type=\"button\" class=\"x-cms-btn x-cms-danger\" (click)=\"deletePage()\">Delete page</button>\n </div>\n </div>\n\n <div class=\"x-cms-blocks-head\">\n <span>Blocks</span>\n <span class=\"x-cms-add-block\">\n <select [(ngModel)]=\"newBlockType\">\n @for (t of blockTypes; track t.value) { <option [value]=\"t.value\">{{ t.label }}</option> }\n </select>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"addBlock()\">Add block</button>\n </span>\n </div>\n\n @if (blocksLoading) {\n <x-element-loader></x-element-loader>\n } @else if (!orderedBlocks.length) {\n <div class=\"x-cms-empty\">No blocks on this page yet.</div>\n } @else {\n <div class=\"x-cms-blocks\">\n @for (b of orderedBlocks; track b.id; let i = $index) {\n <div\n class=\"x-cms-block\"\n [class.x-cms-block-off]=\"!b.enabled\"\n draggable=\"true\"\n (dragstart)=\"onDragStart(i)\"\n (dragover)=\"onDragOver($event)\"\n (drop)=\"onDrop(i)\"\n >\n <div class=\"x-cms-block-head\">\n <span class=\"x-cms-grip\">⠿</span>\n <div class=\"x-cms-block-order\">\n <button type=\"button\" (click)=\"moveBlock(i, -1)\" [disabled]=\"i === 0\">▲</button>\n <button type=\"button\" (click)=\"moveBlock(i, 1)\" [disabled]=\"i === orderedBlocks.length - 1\">▼</button>\n </div>\n <span class=\"x-cms-block-type\">{{ blockTypeLabel(b.type) }}</span>\n <span class=\"x-cms-block-actions\">\n <button type=\"button\" class=\"x-cms-toggle\" (click)=\"toggleBlock(b)\">{{ b.enabled ? 'On' : 'Off' }}</button>\n <button type=\"button\" class=\"x-cms-link\" (click)=\"editBlock(b)\">Edit</button>\n <button type=\"button\" class=\"x-cms-link x-cms-danger\" (click)=\"removeBlock(b)\">Delete</button>\n </span>\n </div>\n\n @if (editingBlock?.id === b.id) {\n <div class=\"x-cms-block-edit\">\n @for (field of blockTypeDef(b.type)?.fields ?? []; track field.key) {\n <label class=\"x-cms-f\">\n {{ field.label }}\n @if (field.kind === 'rich') {\n @if (rawHtml) {\n <textarea rows=\"6\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <x-rich-editor\n [content]=\"blockModel[field.key]\"\n (contentChange)=\"onRichChange(field.key, $event)\"\n ></x-rich-editor>\n }\n <button type=\"button\" class=\"x-cms-link\" (click)=\"rawHtml = !rawHtml\">\n {{ rawHtml ? 'Use the editor' : 'Edit raw HTML' }}\n </button>\n } @else if (field.kind === 'json') {\n <textarea rows=\"5\" [(ngModel)]=\"blockModel[field.key]\"></textarea>\n } @else {\n <input\n [type]=\"field.kind === 'num' ? 'number' : 'text'\"\n [(ngModel)]=\"blockModel[field.key]\"\n />\n }\n </label>\n }\n <div class=\"x-cms-edit-actions\">\n <button type=\"button\" class=\"x-cms-btn x-cms-btn-primary\" (click)=\"saveBlock()\">Save block</button>\n <button type=\"button\" class=\"x-cms-btn\" (click)=\"cancelBlockEdit()\">Cancel</button>\n </div>\n </div>\n }\n </div>\n }\n </div>\n }\n }\n </section>\n </div>\n}\n\n<style>\n .x-cms-head { margin-bottom: 20px; }\n .x-cms-title { font-size: 24px; font-weight: 800; color: var(--color-text); }\n .x-cms-sub { font-size: 13.5px; color: var(--color-text-muted); margin-top: 2px; }\n .x-cms-empty { font-size: 14px; color: var(--color-text-muted); padding: 12px 0; }\n .x-cms-layout { display: grid; grid-template-columns: 260px 1fr; gap: 20px; align-items: start; }\n .x-cms-list { display: flex; flex-direction: column; gap: 6px; }\n .x-cms-new { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }\n .x-cms-new input, .x-cms-f input, .x-cms-f select, .x-cms-f textarea,\n .x-cms-add-block select {\n border: 1px solid var(--color-border); border-radius: var(--radius-md);\n padding: 6px 10px; font-size: 13px; background: var(--color-surface); color: var(--color-text);\n }\n .x-cms-new button, .x-cms-btn {\n border: 1px solid var(--color-border); background: transparent; border-radius: var(--radius-md);\n padding: 6px 14px; font-size: 12.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted);\n }\n .x-cms-btn-primary { border-color: var(--color-accent); color: var(--color-accent-hover); }\n .x-cms-list-item {\n text-align: left; border: 1px solid var(--color-border); border-radius: var(--radius-md);\n background: var(--color-surface); padding: 8px 10px; cursor: pointer;\n display: flex; flex-direction: column; gap: 2px;\n }\n .x-cms-list-active { border-color: var(--color-accent); }\n .x-cms-list-title { font-size: 13px; font-weight: 700; color: var(--color-text); }\n .x-cms-list-meta { font-size: 11px; color: var(--color-text-muted); }\n .x-cms-editor { min-width: 0; }\n .x-cms-meta {\n background: var(--color-surface); border: 1px solid var(--color-border);\n border-radius: var(--radius-lg); padding: 16px; display: flex; flex-direction: column; gap: 10px;\n margin-bottom: 20px;\n }\n .x-cms-f { display: flex; flex-direction: column; gap: 4px; flex: 1; font-size: 11.5px; color: var(--color-text-muted); font-weight: 700; }\n .x-cms-f-row { display: flex; gap: 10px; }\n .x-cms-meta-actions, .x-cms-edit-actions { display: flex; gap: 8px; }\n .x-cms-blocks-head {\n display: flex; justify-content: space-between; align-items: center;\n font-size: 13px; font-weight: 800; color: var(--color-text); margin-bottom: 10px;\n }\n .x-cms-add-block { display: flex; gap: 8px; }\n .x-cms-blocks { display: flex; flex-direction: column; gap: 10px; }\n .x-cms-block { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-lg); padding: 12px 14px; }\n .x-cms-block-off { opacity: 0.55; }\n .x-cms-block-head { display: flex; align-items: center; gap: 10px; }\n .x-cms-grip { cursor: grab; color: var(--color-text-muted); }\n .x-cms-block-order { display: flex; flex-direction: column; }\n .x-cms-block-order button { border: 0; background: transparent; cursor: pointer; color: var(--color-text-muted); font-size: 10px; line-height: 1.1; padding: 1px 3px; }\n .x-cms-block-order button:disabled { opacity: 0.3; cursor: default; }\n .x-cms-block-type { font-weight: 800; text-transform: uppercase; letter-spacing: 0.4px; font-size: 11px; color: var(--color-accent-hover); flex: 1; }\n .x-cms-block-actions { display: flex; gap: 8px; align-items: center; }\n .x-cms-toggle { border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: transparent; padding: 3px 10px; font-size: 11.5px; font-weight: 700; cursor: pointer; color: var(--color-text-muted); }\n .x-cms-link { border: 0; background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; color: var(--color-accent-hover); }\n .x-cms-danger { color: var(--color-danger, #c0362c); }\n .x-cms-block-edit { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--color-border); display: flex; flex-direction: column; gap: 10px; }\n x-rich-editor { display: block; border: 1px solid var(--color-border); border-radius: var(--radius-md); min-height: 160px; }\n</style>\n","import { NgModule } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { SharedModule } from '@thescaffold/ngx-core';\nimport { ElementsModule } from '@thescaffold/ngx-ui';\nimport { RichEditorModule } from '../_shared/rich-editor/rich-editor.module';\nimport { ContentComponent } from './content.component';\n\n@NgModule({\n declarations: [ContentComponent],\n imports: [\n SharedModule,\n ElementsModule,\n RichEditorModule,\n RouterModule.forChild([{ path: '', component: ContentComponent }]),\n ],\n})\nexport class ContentModule {}\n"],"names":["i2","i3.RichEditorComponent","i1"],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import * as i0 from '@angular/core';
2
+ import { Component, NgModule } from '@angular/core';
3
+ import * as i1$1 from '@angular/router';
4
+ import { RouterModule } from '@angular/router';
5
+ import { BaseDirective, using, titleCase, SharedModule } from '@thescaffold/ngx-core';
6
+ import * as i1 from '@thescaffold/ngx-ui';
7
+ import { ElementsModule, WebgetsModule } from '@thescaffold/ngx-ui';
8
+ import { BehaviorSubject, takeUntil, map } from 'rxjs';
9
+ import * as i2 from '@angular/common';
10
+
11
+ // P7.1 — ops console section pass (PRD 6.2, TRD 9.2). A standard list
12
+ // view on `x-webget-collection` over `apps/commerce/coupon`, matching the
13
+ // established admin pattern (users, routes, inbox). ops CRUD is covered
14
+ // by `admin:*:apps:commerce:*`.
15
+ class CouponsComponent extends BaseDirective {
16
+ constructor() {
17
+ super(...arguments);
18
+ this.pluralName = 'coupons';
19
+ this.dataSource = this.apiService.on('apps/commerce/coupon');
20
+ this.columns = [
21
+ { key: 'code', value: 'Code' },
22
+ { key: 'usageCount', value: 'Used' },
23
+ { key: 'usageLimit', value: 'Limit' },
24
+ { key: 'enabled', value: 'Enabled', type: 'class', class: 'badge text-bg-light border' },
25
+ ];
26
+ this.page = 1;
27
+ this.perPage = 25;
28
+ this.list$ = new BehaviorSubject(null);
29
+ this.init = () => {
30
+ this.list();
31
+ };
32
+ }
33
+ list(page = this.page, perPage = this.perPage) {
34
+ this.dataSource
35
+ .getMany({ page, perPage })
36
+ .pipe(takeUntil(this.$), map((response) => this.toCollection(response)))
37
+ .subscribe(using((data) => this.list$.next(data)));
38
+ }
39
+ toCollection(response) {
40
+ return {
41
+ title: titleCase(this.pluralName),
42
+ columns: this.columns,
43
+ rows: response?.data,
44
+ hasHeaders: true,
45
+ hasTextFilter: true,
46
+ page: response?.meta?.current_page,
47
+ perPage: response?.meta?.per_page,
48
+ total: response?.meta?.total,
49
+ onPage: (page) => this.list(page),
50
+ };
51
+ }
52
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
53
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.16", type: CouponsComponent, isStandalone: false, selector: "x-coupons", usesInheritance: true, ngImport: i0, template: `
54
+ <div class="x-page-head">
55
+ <div class="x-page-title">Coupons</div>
56
+ <div class="x-page-subtitle">Redeemable codes bound to a promotion, with usage and spend limits.</div>
57
+ </div>
58
+
59
+ @if (list$ | async; as list) {
60
+ <x-webget-collection [data]="list"></x-webget-collection>
61
+ } @else {
62
+ <x-element-loader></x-element-loader>
63
+ }
64
+ `, isInline: true, styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}\n"], dependencies: [{ kind: "component", type: i1.LoaderComponent, selector: "x-element-loader", inputs: ["data"] }, { kind: "component", type: i1.CollectionComponent, selector: "x-webget-collection", inputs: ["data"], outputs: ["search", "filter", "refresh", "select", "page", "show"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] }); }
65
+ }
66
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsComponent, decorators: [{
67
+ type: Component,
68
+ args: [{ standalone: false, selector: 'x-coupons', template: `
69
+ <div class="x-page-head">
70
+ <div class="x-page-title">Coupons</div>
71
+ <div class="x-page-subtitle">Redeemable codes bound to a promotion, with usage and spend limits.</div>
72
+ </div>
73
+
74
+ @if (list$ | async; as list) {
75
+ <x-webget-collection [data]="list"></x-webget-collection>
76
+ } @else {
77
+ <x-element-loader></x-element-loader>
78
+ }
79
+ `, styles: [".x-page-head{margin-bottom:20px}.x-page-title{font-size:24px;font-weight:800;color:var(--color-text)}.x-page-subtitle{font-size:13.5px;color:var(--color-text-muted);margin-top:2px}\n"] }]
80
+ }] });
81
+
82
+ class CouponsModule {
83
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
84
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.16", ngImport: i0, type: CouponsModule, declarations: [CouponsComponent], imports: [SharedModule,
85
+ ElementsModule,
86
+ WebgetsModule, i1$1.RouterModule] }); }
87
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsModule, imports: [SharedModule,
88
+ ElementsModule,
89
+ WebgetsModule,
90
+ RouterModule.forChild([{ path: '', component: CouponsComponent }])] }); }
91
+ }
92
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: CouponsModule, decorators: [{
93
+ type: NgModule,
94
+ args: [{
95
+ declarations: [CouponsComponent],
96
+ imports: [
97
+ SharedModule,
98
+ ElementsModule,
99
+ WebgetsModule,
100
+ RouterModule.forChild([{ path: '', component: CouponsComponent }]),
101
+ ],
102
+ }]
103
+ }] });
104
+
105
+ export { CouponsModule };
106
+ //# sourceMappingURL=isaiahiroko-tilmart-ngx-apps-admin-coupons.module-CQNCSlsd.mjs.map