@kustomizer/visual-editor 0.0.1 → 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.
@@ -1,16 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injectable, signal, computed, makeEnvironmentProviders, ViewContainerRef, DestroyRef, input, effect, reflectComponentType, ChangeDetectionStrategy, Component, viewChild, output } from '@angular/core';
2
+ import { InjectionToken, inject, Injectable, makeEnvironmentProviders, ViewContainerRef, DestroyRef, input, effect, reflectComponentType, ChangeDetectionStrategy, Component, computed, NgZone, signal, viewChild, output } from '@angular/core';
3
3
  import { Router, ActivatedRoute } from '@angular/router';
4
- import { of, startWith, map, delay, throwError, firstValueFrom, forkJoin, BehaviorSubject, tap as tap$1, Subject, debounceTime, switchMap as switchMap$1, takeUntil, filter } from 'rxjs';
5
- import { tap, catchError, shareReplay, take, map as map$1, switchMap } from 'rxjs/operators';
4
+ import { of, startWith, map, delay, throwError, isObservable, Observable, BehaviorSubject, tap, Subject, filter, firstValueFrom, debounceTime, switchMap as switchMap$1, takeUntil } from 'rxjs';
5
+ import { switchMap, map as map$1, catchError } from 'rxjs/operators';
6
6
  import { HttpClient } from '@angular/common/http';
7
7
  import { createActionGroup, emptyProps, props, createReducer, on, createFeatureSelector, createSelector, provideState, Store } from '@ngrx/store';
8
8
  import { DomSanitizer } from '@angular/platform-browser';
9
9
  import { toSignal } from '@angular/core/rxjs-interop';
10
- import * as i1$1 from '@angular/common';
11
- import { CommonModule } from '@angular/common';
12
- import * as i1 from '@angular/forms';
13
- import { FormsModule } from '@angular/forms';
14
10
 
15
11
  /**
16
12
  * Abstract navigation service that consumers can implement
@@ -57,6 +53,9 @@ class DefaultRouterNavigationService extends VisualEditorNavigation {
57
53
  this.router.navigate([previewPath]);
58
54
  }
59
55
  break;
56
+ case 'setup':
57
+ this.router.navigate(['/setup']);
58
+ break;
60
59
  }
61
60
  }
62
61
  confirm(options) {
@@ -82,10 +81,10 @@ class DefaultRouterNavigationService extends VisualEditorNavigation {
82
81
  return this.router.events.pipe(startWith(null), // Trigger initial emission
83
82
  map(() => extractPageIdFromUrl()));
84
83
  }
85
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultRouterNavigationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
86
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultRouterNavigationService });
84
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DefaultRouterNavigationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
85
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DefaultRouterNavigationService });
87
86
  }
88
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DefaultRouterNavigationService, decorators: [{
87
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DefaultRouterNavigationService, decorators: [{
89
88
  type: Injectable
90
89
  }] });
91
90
 
@@ -218,27 +217,27 @@ class PageMemoryRepository {
218
217
  publishedAt: page.publishedAt,
219
218
  };
220
219
  }
221
- getPages(environment) {
220
+ getPages() {
222
221
  const summaries = Array.from(this.pages.values())
223
222
  .map((p) => this.toSummary(p))
224
223
  .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
225
224
  return of(summaries).pipe(delay(this.DELAY_MS));
226
225
  }
227
- getPage(id, environment) {
226
+ getPage(id) {
228
227
  const page = this.pages.get(id);
229
228
  if (!page) {
230
229
  return throwError(() => new Error(`Page not found: ${id}`)).pipe(delay(this.DELAY_MS));
231
230
  }
232
231
  return of(structuredClone(page)).pipe(delay(this.DELAY_MS));
233
232
  }
234
- getPublishedPageBySlug(slug, environment) {
233
+ getPublishedPageBySlug(slug) {
235
234
  const page = Array.from(this.pages.values()).find((p) => p.slug === slug && p.status === 'published');
236
235
  if (!page) {
237
236
  return throwError(() => new Error(`Published page not found: ${slug}`)).pipe(delay(this.DELAY_MS));
238
237
  }
239
238
  return of(structuredClone(page)).pipe(delay(this.DELAY_MS));
240
239
  }
241
- createPage(request, environment) {
240
+ createPage(request) {
242
241
  // Check for duplicate slug
243
242
  const existingSlug = Array.from(this.pages.values()).find((p) => p.slug === request.slug);
244
243
  if (existingSlug) {
@@ -260,7 +259,7 @@ class PageMemoryRepository {
260
259
  console.log('[PageMemoryRepository] Created page:', page.id, page.title);
261
260
  return of(structuredClone(page)).pipe(delay(this.DELAY_MS));
262
261
  }
263
- updatePage(id, request, environment) {
262
+ updatePage(id, request) {
264
263
  const page = this.pages.get(id);
265
264
  if (!page) {
266
265
  return throwError(() => new Error(`Page not found: ${id}`)).pipe(delay(this.DELAY_MS));
@@ -290,7 +289,7 @@ class PageMemoryRepository {
290
289
  console.log('[PageMemoryRepository] Sections JSON:', JSON.stringify(updatedPage.sections, null, 2));
291
290
  return of(structuredClone(updatedPage)).pipe(delay(this.DELAY_MS));
292
291
  }
293
- deletePage(id, environment) {
292
+ deletePage(id) {
294
293
  if (!this.pages.has(id)) {
295
294
  return throwError(() => new Error(`Page not found: ${id}`)).pipe(delay(this.DELAY_MS));
296
295
  }
@@ -298,7 +297,7 @@ class PageMemoryRepository {
298
297
  console.log('[PageMemoryRepository] Deleted page:', id);
299
298
  return of(undefined).pipe(delay(this.DELAY_MS));
300
299
  }
301
- publishPage(id, request, environment) {
300
+ publishPage(id, request) {
302
301
  const page = this.pages.get(id);
303
302
  if (!page) {
304
303
  return throwError(() => new Error(`Page not found: ${id}`)).pipe(delay(this.DELAY_MS));
@@ -318,7 +317,7 @@ class PageMemoryRepository {
318
317
  console.log('[PageMemoryRepository] Published page:', id);
319
318
  return of(structuredClone(updatedPage)).pipe(delay(this.DELAY_MS));
320
319
  }
321
- unpublishPage(id, request, environment) {
320
+ unpublishPage(id, request) {
322
321
  const page = this.pages.get(id);
323
322
  if (!page) {
324
323
  return throwError(() => new Error(`Page not found: ${id}`)).pipe(delay(this.DELAY_MS));
@@ -336,7 +335,7 @@ class PageMemoryRepository {
336
335
  console.log('[PageMemoryRepository] Unpublished page:', id);
337
336
  return of(structuredClone(updatedPage)).pipe(delay(this.DELAY_MS));
338
337
  }
339
- duplicatePage(id, environment) {
338
+ duplicatePage(id) {
340
339
  const page = this.pages.get(id);
341
340
  if (!page) {
342
341
  return throwError(() => new Error(`Page not found: ${id}`)).pipe(delay(this.DELAY_MS));
@@ -363,573 +362,50 @@ class PageMemoryRepository {
363
362
  this.seedData();
364
363
  console.log('[PageMemoryRepository] Reset to initial state');
365
364
  }
366
- deletePagesByEnvironment(environment) {
367
- this.pages.clear();
368
- console.log('[PageMemoryRepository] Deleted all pages for environment:', environment ?? 'production');
369
- return of(undefined).pipe(delay(this.DELAY_MS));
370
- }
371
365
  // Utility method to get all pages (useful for debugging)
372
366
  getAllPages() {
373
367
  return Array.from(this.pages.values());
374
368
  }
375
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageMemoryRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
376
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageMemoryRepository, providedIn: 'root' });
369
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageMemoryRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
370
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageMemoryRepository, providedIn: 'root' });
377
371
  }
378
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageMemoryRepository, decorators: [{
372
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageMemoryRepository, decorators: [{
379
373
  type: Injectable,
380
374
  args: [{ providedIn: 'root' }]
381
375
  }], ctorParameters: () => [] });
382
376
 
383
- const SUPABASE_CONFIG = new InjectionToken('SUPABASE_CONFIG');
384
- class SupabaseAuthService {
385
- config = inject(SUPABASE_CONFIG, { optional: true });
386
- supabase = null;
387
- supabasePromise = null;
388
- authSubscription = null;
389
- _session = signal(null, ...(ngDevMode ? [{ debugName: "_session" }] : []));
390
- _user = signal(null, ...(ngDevMode ? [{ debugName: "_user" }] : []));
391
- _loading = signal(true, ...(ngDevMode ? [{ debugName: "_loading" }] : []));
392
- _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
393
- _initialized = signal(false, ...(ngDevMode ? [{ debugName: "_initialized" }] : []));
394
- session = this._session.asReadonly();
395
- user = this._user.asReadonly();
396
- loading = this._loading.asReadonly();
397
- error = this._error.asReadonly();
398
- initialized = this._initialized.asReadonly();
399
- isAuthenticated = computed(() => this._initialized() && !!this._session(), ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
400
- accessToken = computed(() => this._session()?.access_token ?? null, ...(ngDevMode ? [{ debugName: "accessToken" }] : []));
401
- userEmail = computed(() => this._user()?.email ?? null, ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
402
- async getSupabaseClient() {
403
- if (this.supabase)
404
- return this.supabase;
405
- if (this.supabasePromise)
406
- return this.supabasePromise;
407
- if (!this.config || typeof window === 'undefined') {
408
- this._loading.set(false);
409
- this._initialized.set(true);
410
- return null;
411
- }
412
- this.supabasePromise = this.initializeClient();
413
- return this.supabasePromise;
414
- }
415
- async initializeClient() {
416
- try {
417
- const { createClient } = await import('@supabase/supabase-js');
418
- this.supabase = createClient(this.config.supabaseUrl, this.config.supabaseAnonKey, {
419
- auth: {
420
- autoRefreshToken: true,
421
- persistSession: true,
422
- detectSessionInUrl: true,
423
- },
424
- });
425
- const { data: { subscription } } = this.supabase.auth.onAuthStateChange((event, session) => {
426
- this._session.set(session);
427
- this._user.set(session?.user ?? null);
428
- this._loading.set(false);
429
- this._initialized.set(true);
430
- if (event === 'SIGNED_OUT') {
431
- this._error.set(null);
432
- }
433
- });
434
- this.authSubscription = subscription;
435
- const { data: { session }, error } = await this.supabase.auth.getSession();
436
- if (!error) {
437
- this._session.set(session);
438
- this._user.set(session?.user ?? null);
439
- }
440
- this._loading.set(false);
441
- this._initialized.set(true);
442
- return this.supabase;
443
- }
444
- catch (err) {
445
- this._error.set(err instanceof Error ? err.message : 'Failed to initialize');
446
- this._loading.set(false);
447
- this._initialized.set(true);
448
- return null;
449
- }
450
- }
451
- async ensureInitialized() {
452
- await this.getSupabaseClient();
453
- }
454
- async signInWithPassword(email, password) {
455
- const client = await this.getSupabaseClient();
456
- if (!client) {
457
- return { success: false, error: 'Supabase not initialized' };
458
- }
459
- this._loading.set(true);
460
- this._error.set(null);
461
- try {
462
- const { data, error } = await client.auth.signInWithPassword({
463
- email,
464
- password,
465
- });
466
- if (error)
467
- throw error;
468
- this._session.set(data.session);
469
- this._user.set(data.user);
470
- return { success: true };
471
- }
472
- catch (err) {
473
- const message = err instanceof Error ? err.message : 'Login failed';
474
- this._error.set(message);
475
- return { success: false, error: message };
476
- }
477
- finally {
478
- this._loading.set(false);
479
- }
480
- }
481
- async signUp(email, password) {
482
- const client = await this.getSupabaseClient();
483
- if (!client) {
484
- return { success: false, error: 'Supabase not initialized' };
485
- }
486
- this._loading.set(true);
487
- this._error.set(null);
488
- try {
489
- const { data, error } = await client.auth.signUp({
490
- email,
491
- password,
492
- });
493
- if (error)
494
- throw error;
495
- if (data.session) {
496
- this._session.set(data.session);
497
- this._user.set(data.user);
498
- }
499
- return { success: true };
500
- }
501
- catch (err) {
502
- const message = err instanceof Error ? err.message : 'Sign up failed';
503
- this._error.set(message);
504
- return { success: false, error: message };
505
- }
506
- finally {
507
- this._loading.set(false);
508
- }
509
- }
510
- async signOut() {
511
- const client = await this.getSupabaseClient();
512
- if (!client)
513
- return;
514
- this._loading.set(true);
515
- try {
516
- await client.auth.signOut();
517
- this._session.set(null);
518
- this._user.set(null);
519
- }
520
- catch (err) {
521
- this._error.set(err instanceof Error ? err.message : 'Sign out failed');
522
- }
523
- finally {
524
- this._loading.set(false);
525
- }
526
- }
527
- async resetPassword(email) {
528
- const client = await this.getSupabaseClient();
529
- if (!client) {
530
- return { success: false, error: 'Supabase not initialized' };
531
- }
532
- try {
533
- const { error } = await client.auth.resetPasswordForEmail(email);
534
- if (error)
535
- throw error;
536
- return { success: true };
537
- }
538
- catch (err) {
539
- return { success: false, error: err instanceof Error ? err.message : 'Reset failed' };
540
- }
541
- }
542
- async getClient() {
543
- return this.getSupabaseClient();
544
- }
545
- ngOnDestroy() {
546
- this.authSubscription?.unsubscribe();
547
- }
548
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SupabaseAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
549
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SupabaseAuthService, providedIn: 'root' });
550
- }
551
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SupabaseAuthService, decorators: [{
552
- type: Injectable,
553
- args: [{ providedIn: 'root' }]
554
- }] });
555
-
556
377
  /**
557
- * Token para inyectar el dominio de la tienda.
558
- * Debe ser configurado por la aplicación que instala el editor.
559
- *
560
- * Ejemplo de uso:
561
- * ```typescript
562
- * providers: [
563
- * { provide: STORE_DOMAIN, useValue: 'nike.com' }
564
- * ]
565
- * ```
566
- *
567
- * Si no se provee, el servicio intentará detectar el dominio desde window.location.hostname
378
+ * Injection token for Shopify configuration
379
+ * Can be provided as a static object or as an Observable for dynamic config
568
380
  */
569
- const STORE_DOMAIN = new InjectionToken('STORE_DOMAIN');
570
- class ShopAuthService {
571
- http = inject(HttpClient);
572
- config = inject(SUPABASE_CONFIG, { optional: true });
573
- authService = inject(SupabaseAuthService);
574
- storeDomainConfig = inject(STORE_DOMAIN, { optional: true });
575
- // Estado interno
576
- _shop = signal(null, ...(ngDevMode ? [{ debugName: "_shop" }] : []));
577
- _user = signal(null, ...(ngDevMode ? [{ debugName: "_user" }] : []));
578
- _license = signal(null, ...(ngDevMode ? [{ debugName: "_license" }] : []));
579
- _loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : []));
580
- _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
581
- _initialized = signal(false, ...(ngDevMode ? [{ debugName: "_initialized" }] : []));
582
- // Exponer estado como readonly
583
- shop = this._shop.asReadonly();
584
- user = this._user.asReadonly();
585
- license = this._license.asReadonly();
586
- loading = this._loading.asReadonly();
587
- error = this._error.asReadonly();
588
- initialized = this._initialized.asReadonly();
589
- // Computed helpers
590
- shopId = computed(() => this._shop()?.id ?? null, ...(ngDevMode ? [{ debugName: "shopId" }] : []));
591
- shopName = computed(() => this._shop()?.name ?? null, ...(ngDevMode ? [{ debugName: "shopName" }] : []));
592
- shopifyDomain = computed(() => this._shop()?.shopify_domain ?? null, ...(ngDevMode ? [{ debugName: "shopifyDomain" }] : []));
593
- userEmail = computed(() => this._user()?.email ?? null, ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
594
- userRole = computed(() => this._user()?.role ?? null, ...(ngDevMode ? [{ debugName: "userRole" }] : []));
595
- isOwner = computed(() => this._user()?.role === 'owner', ...(ngDevMode ? [{ debugName: "isOwner" }] : []));
596
- isAdmin = computed(() => ['owner', 'admin'].includes(this._user()?.role ?? ''), ...(ngDevMode ? [{ debugName: "isAdmin" }] : []));
597
- canEdit = computed(() => ['owner', 'admin'].includes(this._user()?.role ?? ''), ...(ngDevMode ? [{ debugName: "canEdit" }] : []));
598
- licenseTier = computed(() => this._license()?.tier ?? null, ...(ngDevMode ? [{ debugName: "licenseTier" }] : []));
599
- licenseActive = computed(() => this._license()?.active ?? false, ...(ngDevMode ? [{ debugName: "licenseActive" }] : []));
600
- licenseExpiresAt = computed(() => this._license()?.expires_at ?? null, ...(ngDevMode ? [{ debugName: "licenseExpiresAt" }] : []));
601
- hasAccess = computed(() => {
602
- return this._shop() !== null && this._user() !== null && this._license()?.active === true;
603
- }, ...(ngDevMode ? [{ debugName: "hasAccess" }] : []));
604
- daysRemaining = computed(() => {
605
- const expiresAt = this._license()?.expires_at;
606
- if (!expiresAt)
607
- return null;
608
- const days = Math.ceil((new Date(expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24));
609
- return Math.max(0, days);
610
- }, ...(ngDevMode ? [{ debugName: "daysRemaining" }] : []));
611
- isLicenseExpiringSoon = computed(() => {
612
- const days = this.daysRemaining();
613
- return days !== null && days <= 7;
614
- }, ...(ngDevMode ? [{ debugName: "isLicenseExpiringSoon" }] : []));
615
- /**
616
- * Obtiene el dominio actual.
617
- * Prioridad:
618
- * 1. STORE_DOMAIN token (inyectado)
619
- * 2. Query param ?shop= (solo en localhost)
620
- * 3. window.location.hostname
621
- */
622
- getCurrentDomain() {
623
- if (this.storeDomainConfig) {
624
- return this.storeDomainConfig;
625
- }
626
- if (typeof window !== 'undefined') {
627
- const hostname = window.location.hostname;
628
- if (hostname === 'localhost' || hostname === '127.0.0.1') {
629
- const params = new URLSearchParams(window.location.search);
630
- const shopParam = params.get('shop');
631
- if (shopParam) {
632
- localStorage.setItem('kustomizer_shop_domain', shopParam);
633
- return shopParam;
634
- }
635
- const stored = localStorage.getItem('kustomizer_shop_domain');
636
- if (stored) {
637
- return stored;
638
- }
639
- }
640
- return hostname;
641
- }
642
- return '';
643
- }
644
- /**
645
- * Autentica al usuario para el dominio actual.
646
- * Esta es la función principal que debe llamarse al cargar la app.
647
- */
648
- async authenticate() {
649
- // Si ya está inicializado y tiene datos, retornar cache
650
- if (this._initialized() && this._shop()) {
651
- return {
652
- shop: this._shop(),
653
- user: this._user(),
654
- license: this._license(),
655
- };
656
- }
657
- // Esperar a que el auth service esté listo
658
- await this.authService.ensureInitialized();
659
- const token = this.authService.accessToken();
660
- const email = this.authService.userEmail();
661
- if (!token || !email) {
662
- this._error.set({ code: 'NOT_AUTHENTICATED', message: 'User is not authenticated' });
663
- this._initialized.set(true);
664
- return { shop: null, user: null, license: null };
665
- }
666
- const domain = this.getCurrentDomain();
667
- if (!domain) {
668
- this._error.set({ code: 'NO_DOMAIN', message: 'Could not determine current domain' });
669
- this._initialized.set(true);
670
- return { shop: null, user: null, license: null };
671
- }
672
- if (domain === 'localhost' || domain === '127.0.0.1') {
673
- this._error.set({
674
- code: 'LOCALHOST_NO_SHOP',
675
- message: 'Running on localhost. Add ?shop=your-store.myshopify.com to the URL'
676
- });
677
- this._initialized.set(true);
678
- return { shop: null, user: null, license: null };
679
- }
680
- this._loading.set(true);
681
- this._error.set(null);
682
- try {
683
- const url = this.config
684
- ? `${this.config.supabaseUrl}/functions/v1/shop_auth`
685
- : '/functions/v1/shop_auth';
686
- const anonKey = this.config?.supabaseAnonKey ?? '';
687
- const response = await firstValueFrom(this.http.post(url, { domain, email }, {
688
- headers: {
689
- 'Authorization': `Bearer ${anonKey}`,
690
- 'apikey': anonKey,
691
- 'Content-Type': 'application/json',
692
- },
693
- }));
694
- this._shop.set(response.shop);
695
- this._user.set(response.user);
696
- this._license.set(response.license);
697
- this._initialized.set(true);
698
- this._loading.set(false);
699
- return {
700
- shop: response.shop,
701
- user: response.user,
702
- license: response.license,
703
- };
704
- }
705
- catch (err) {
706
- console.error('[ShopAuthService] Authentication failed:', err);
707
- const httpError = err;
708
- let errorCode = 'UNKNOWN_ERROR';
709
- let errorMessage = 'Authentication failed';
710
- if (httpError.error?.code) {
711
- errorCode = httpError.error.code;
712
- errorMessage = httpError.error.error || httpError.error.message || errorMessage;
713
- }
714
- else if (httpError.status === 404) {
715
- errorCode = 'SHOP_NOT_FOUND';
716
- errorMessage = 'This domain is not registered';
717
- }
718
- else if (httpError.status === 403) {
719
- errorCode = 'NO_ACCESS';
720
- errorMessage = 'You do not have access to this shop';
721
- }
722
- this._error.set({ code: errorCode, message: errorMessage });
723
- this._shop.set(null);
724
- this._user.set(null);
725
- this._license.set(null);
726
- this._initialized.set(true);
727
- this._loading.set(false);
728
- return { shop: null, user: null, license: null };
729
- }
730
- }
731
- /**
732
- * Limpia el estado (para logout)
733
- */
734
- clear() {
735
- this._shop.set(null);
736
- this._user.set(null);
737
- this._license.set(null);
738
- this._error.set(null);
739
- this._initialized.set(false);
740
- if (typeof window !== 'undefined') {
741
- localStorage.removeItem('kustomizer_shop_domain');
742
- }
743
- }
744
- /**
745
- * Refresca la autenticación
746
- */
747
- async refresh() {
748
- this._initialized.set(false);
749
- return this.authenticate();
750
- }
751
- /**
752
- * Verifica si el error actual indica que el usuario no tiene acceso
753
- */
754
- isNoAccessError() {
755
- const code = this._error()?.code;
756
- return code === 'NO_ACCESS' || code === 'SHOP_NOT_FOUND';
757
- }
758
- /**
759
- * Verifica si el error actual indica que la licencia expiró
760
- */
761
- isLicenseExpiredError() {
762
- return this._error()?.code === 'LICENSE_EXPIRED';
763
- }
764
- /**
765
- * Verifica si el usuario está inactivo
766
- */
767
- isUserInactiveError() {
768
- return this._error()?.code === 'USER_INACTIVE';
769
- }
770
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
771
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopAuthService, providedIn: 'root' });
772
- }
773
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopAuthService, decorators: [{
774
- type: Injectable,
775
- args: [{ providedIn: 'root' }]
776
- }] });
777
-
778
- const SHOPIFY_APP_CONFIG = new InjectionToken('SHOPIFY_APP_CONFIG', {
381
+ const SHOPIFY_CONFIG = new InjectionToken('SHOPIFY_CONFIG', {
779
382
  providedIn: 'root',
780
383
  factory: () => ({
781
- appUrl: 'https://kustomizer.xyz'
782
- })
384
+ shopDomain: '',
385
+ accessToken: '',
386
+ apiVersion: '2026-01',
387
+ proxyUrl: '/shopify-api/graphql.json',
388
+ }),
389
+ });
390
+ /**
391
+ * Injection token for Storefront API configuration.
392
+ * Used by the public-storefront to read metafields via the Storefront API.
393
+ */
394
+ const STOREFRONT_CONFIG = new InjectionToken('STOREFRONT_CONFIG');
395
+ /**
396
+ * Injection token for the client storefront URL.
397
+ * The editor uses this to:
398
+ * 1. Fetch the component manifest (`{url}/kustomizer/manifest.json`)
399
+ * 2. Embed the live preview iframe (`{url}/kustomizer/editor`)
400
+ */
401
+ const STOREFRONT_URL = new InjectionToken('STOREFRONT_URL', {
402
+ providedIn: 'root',
403
+ factory: () => '',
783
404
  });
784
- class ShopifyTokenService {
785
- http = inject(HttpClient);
786
- config = inject(SHOPIFY_APP_CONFIG);
787
- authService = inject(SupabaseAuthService);
788
- shopAuthService = inject(ShopAuthService);
789
- CACHE_TTL_MS = 5 * 60 * 1000;
790
- tokenCache = new Map();
791
- _currentToken = signal(null, ...(ngDevMode ? [{ debugName: "_currentToken" }] : []));
792
- _currentDomain = signal(null, ...(ngDevMode ? [{ debugName: "_currentDomain" }] : []));
793
- _loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : []));
794
- _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
795
- pendingRequest = null;
796
- currentToken = this._currentToken.asReadonly();
797
- currentDomain = this._currentDomain.asReadonly();
798
- loading = this._loading.asReadonly();
799
- error = this._error.asReadonly();
800
- hasToken = computed(() => !!this._currentToken(), ...(ngDevMode ? [{ debugName: "hasToken" }] : []));
801
- isReady = computed(() => this.authService.isAuthenticated() && !!this.shopAuthService.shop(), ...(ngDevMode ? [{ debugName: "isReady" }] : []));
802
- getToken() {
803
- if (!this.authService.isAuthenticated()) {
804
- return throwError(() => this.createError('NOT_AUTHENTICATED', 'User is not authenticated'));
805
- }
806
- const shop = this.shopAuthService.shop();
807
- if (!shop?.shopify_domain) {
808
- return throwError(() => this.createError('NO_ACTIVE_STORE', 'No active store selected'));
809
- }
810
- return this.getTokenForDomain(shop.shopify_domain);
811
- }
812
- getTokenForDomain(shopifyDomain) {
813
- const cached = this.getCachedToken(shopifyDomain);
814
- if (cached) {
815
- this._currentToken.set(cached.accessToken);
816
- this._currentDomain.set(cached.shopifyDomain);
817
- return of({
818
- accessToken: cached.accessToken,
819
- shopifyDomain: cached.shopifyDomain
820
- });
821
- }
822
- if (this.pendingRequest) {
823
- return this.pendingRequest;
824
- }
825
- return this.fetchToken(shopifyDomain);
826
- }
827
- refreshToken() {
828
- const shop = this.shopAuthService.shop();
829
- if (!shop?.shopify_domain) {
830
- return throwError(() => this.createError('NO_ACTIVE_STORE', 'No active store selected'));
831
- }
832
- this.invalidateCache(shop.shopify_domain);
833
- return this.fetchToken(shop.shopify_domain);
834
- }
835
- invalidateCache(shopifyDomain) {
836
- if (shopifyDomain) {
837
- this.tokenCache.delete(shopifyDomain);
838
- if (this._currentDomain() === shopifyDomain) {
839
- this._currentToken.set(null);
840
- this._currentDomain.set(null);
841
- }
842
- }
843
- else {
844
- this.tokenCache.clear();
845
- this._currentToken.set(null);
846
- this._currentDomain.set(null);
847
- }
848
- }
849
- clear() {
850
- this.invalidateCache();
851
- this._error.set(null);
852
- this._loading.set(false);
853
- this.pendingRequest = null;
854
- }
855
- fetchToken(shopifyDomain) {
856
- this._loading.set(true);
857
- this._error.set(null);
858
- const supabaseToken = this.authService.accessToken();
859
- if (!supabaseToken) {
860
- this._loading.set(false);
861
- return throwError(() => this.createError('NOT_AUTHENTICATED', 'No authentication token available'));
862
- }
863
- const url = `${this.config.appUrl}/api/get-token`;
864
- this.pendingRequest = this.http.post(url, { shopifyDomain }, {
865
- headers: {
866
- 'Authorization': `Bearer ${supabaseToken}`,
867
- 'Content-Type': 'application/json'
868
- }
869
- }).pipe(tap(response => {
870
- this.setCachedToken(shopifyDomain, response);
871
- this._currentToken.set(response.accessToken);
872
- this._currentDomain.set(response.shopifyDomain);
873
- this._loading.set(false);
874
- this.pendingRequest = null;
875
- }), catchError((error) => {
876
- this._loading.set(false);
877
- this.pendingRequest = null;
878
- const tokenError = this.handleHttpError(error);
879
- this._error.set(tokenError);
880
- return throwError(() => tokenError);
881
- }), shareReplay(1), take(1));
882
- return this.pendingRequest;
883
- }
884
- getCachedToken(shopifyDomain) {
885
- const entry = this.tokenCache.get(shopifyDomain);
886
- if (entry && Date.now() < entry.expiresAt) {
887
- return entry;
888
- }
889
- if (entry) {
890
- this.tokenCache.delete(shopifyDomain);
891
- }
892
- return null;
893
- }
894
- setCachedToken(shopifyDomain, response) {
895
- this.tokenCache.set(shopifyDomain, {
896
- accessToken: response.accessToken,
897
- shopifyDomain: response.shopifyDomain,
898
- expiresAt: Date.now() + this.CACHE_TTL_MS
899
- });
900
- }
901
- handleHttpError(error) {
902
- if (error.error instanceof ErrorEvent) {
903
- return this.createError('NETWORK_ERROR', 'Network error occurred', error.status);
904
- }
905
- switch (error.status) {
906
- case 401:
907
- return this.createError('SESSION_EXPIRED', 'Session expired. Please login again.', 401);
908
- case 403:
909
- return this.createError('NO_STORE_ACCESS', 'You do not have access to this store.', 403);
910
- case 404:
911
- return this.createError('NO_STORE_ACCESS', 'Store not found or not linked.', 404);
912
- default:
913
- const message = error.error?.error || error.error?.message || 'Failed to get token';
914
- return this.createError('TOKEN_FETCH_FAILED', message, error.status);
915
- }
916
- }
917
- createError(code, message, status) {
918
- return { code, message, status };
919
- }
920
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyTokenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
921
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyTokenService, providedIn: 'root' });
922
- }
923
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyTokenService, decorators: [{
924
- type: Injectable,
925
- args: [{ providedIn: 'root' }]
926
- }] });
927
405
 
928
406
  class ShopifyGraphQLClient {
929
407
  http = inject(HttpClient);
930
- authService = inject(SupabaseAuthService);
931
- shopAuthService = inject(ShopAuthService);
932
- config = inject(SHOPIFY_APP_CONFIG);
408
+ config = inject(SHOPIFY_CONFIG);
933
409
  query(query, variables) {
934
410
  return this.executeOperation(query, variables);
935
411
  }
@@ -937,96 +413,100 @@ class ShopifyGraphQLClient {
937
413
  return this.executeOperation(mutation, variables);
938
414
  }
939
415
  executeOperation(operation, variables) {
940
- const shop = this.shopAuthService.shop();
941
- if (!shop?.shopify_domain) {
942
- return throwError(() => this.createError('TOKEN_ERROR', 'No active store selected'));
943
- }
944
- const supabaseToken = this.authService.accessToken();
945
- if (!supabaseToken) {
946
- return throwError(() => this.createError('UNAUTHORIZED', 'Not authenticated'));
947
- }
948
- const shopifyDomain = shop.shopify_domain;
949
- const proxyUrl = `${this.config.appUrl}/api/graphql-proxy`;
950
- return this.http.post(proxyUrl, {
951
- shopifyDomain,
952
- query: operation,
953
- variables,
954
- }, {
955
- headers: {
416
+ const config$ = isObservable(this.config)
417
+ ? this.config
418
+ : new Observable(subscriber => {
419
+ subscriber.next(this.config);
420
+ subscriber.complete();
421
+ });
422
+ return config$.pipe(switchMap((config) => {
423
+ // Use proxy URL if available (for development), otherwise direct Shopify URL
424
+ const url = config.proxyUrl
425
+ ? config.proxyUrl
426
+ : `https://${config.shopDomain}/admin/api/${config.apiVersion}/graphql.json`;
427
+ const request = {
428
+ query: operation,
429
+ variables,
430
+ };
431
+ // When using proxy, headers are set by the proxy config
432
+ const headers = {
956
433
  'Content-Type': 'application/json',
957
- 'Authorization': `Bearer ${supabaseToken}`,
958
- },
959
- }).pipe(map$1(response => this.handleResponse(response)), catchError(error => this.handleHttpError(error)));
434
+ };
435
+ // Only add access token header when not using proxy
436
+ if (!config.proxyUrl) {
437
+ headers['X-Shopify-Access-Token'] = config.accessToken;
438
+ }
439
+ return this.http.post(url, request, { headers }).pipe(map$1(response => this.handleResponse(response)), catchError(error => this.handleError(error)));
440
+ }));
960
441
  }
961
442
  handleResponse(response) {
962
443
  if (response.errors && response.errors.length > 0) {
963
- const error = {
444
+ throw {
964
445
  code: 'GRAPHQL_ERROR',
965
446
  message: response.errors[0].message,
966
- errors: response.errors.map(e => ({ message: e.message }))
447
+ errors: response.errors,
967
448
  };
968
- throw error;
969
449
  }
970
450
  if (!response.data) {
971
- const error = {
451
+ throw {
972
452
  code: 'EMPTY_RESPONSE',
973
- message: 'GraphQL response contains no data'
453
+ message: 'GraphQL response contains no data',
974
454
  };
975
- throw error;
976
455
  }
977
456
  return response.data;
978
457
  }
979
- handleHttpError(error) {
980
- if (error.error instanceof ErrorEvent) {
981
- return throwError(() => this.createError('NETWORK_ERROR', error.error.message));
458
+ handleError(error) {
459
+ let errorCode = 'UNKNOWN_ERROR';
460
+ let errorMessage = 'An unknown error occurred';
461
+ if (typeof ErrorEvent !== 'undefined' && error.error instanceof ErrorEvent) {
462
+ errorCode = 'NETWORK_ERROR';
463
+ errorMessage = error.error.message;
982
464
  }
983
- let code;
984
- let message;
985
- switch (error.status) {
986
- case 401:
987
- code = 'UNAUTHORIZED';
988
- message = 'Session expired. Please login again.';
989
- break;
990
- case 403:
991
- code = 'FORBIDDEN';
992
- message = 'Access denied to this store.';
993
- break;
994
- case 404:
995
- code = 'TOKEN_ERROR';
996
- message = 'Store not connected. Please install the Shopify app.';
997
- break;
998
- case 429:
999
- code = 'RATE_LIMIT';
1000
- message = 'Rate limit exceeded. Please try again later.';
1001
- break;
1002
- case 500:
1003
- case 502:
1004
- case 503:
1005
- case 504:
1006
- code = 'SERVER_ERROR';
1007
- message = 'Server error. Please try again.';
1008
- break;
1009
- default:
1010
- code = 'HTTP_ERROR';
1011
- message = error.error?.error || `HTTP ${error.status}: ${error.statusText || 'Unknown error'}`;
465
+ else {
466
+ switch (error.status) {
467
+ case 401:
468
+ errorCode = 'UNAUTHORIZED';
469
+ errorMessage = 'Invalid or expired access token';
470
+ break;
471
+ case 403:
472
+ errorCode = 'FORBIDDEN';
473
+ errorMessage = 'Insufficient permissions';
474
+ break;
475
+ case 429:
476
+ errorCode = 'RATE_LIMIT';
477
+ errorMessage = 'Rate limit exceeded';
478
+ break;
479
+ case 500:
480
+ case 502:
481
+ case 503:
482
+ case 504:
483
+ errorCode = 'SERVER_ERROR';
484
+ errorMessage = 'Shopify server error';
485
+ break;
486
+ default:
487
+ errorCode = 'HTTP_ERROR';
488
+ errorMessage = `HTTP ${error.status}: ${error.statusText}`;
489
+ }
1012
490
  }
1013
- return throwError(() => this.createError(code, message, error.status, error));
1014
- }
1015
- createError(code, message, status, original) {
1016
- return { code, message, status, original };
491
+ return throwError(() => ({
492
+ code: errorCode,
493
+ message: errorMessage,
494
+ status: error.status,
495
+ original: error,
496
+ }));
1017
497
  }
1018
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyGraphQLClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1019
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyGraphQLClient, providedIn: 'root' });
498
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyGraphQLClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
499
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyGraphQLClient, providedIn: 'root' });
1020
500
  }
1021
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyGraphQLClient, decorators: [{
501
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyGraphQLClient, decorators: [{
1022
502
  type: Injectable,
1023
503
  args: [{ providedIn: 'root' }]
1024
504
  }] });
1025
505
 
1026
- const NAMESPACE = 'kustomizer';
1027
- const INDEX_KEY = 'pages_index';
506
+ const NAMESPACE$1 = 'kustomizer';
507
+ const INDEX_KEY$1 = 'pages_index';
1028
508
  const MAX_METAFIELD_SIZE = 64 * 1024;
1029
- const GET_SHOP_METAFIELD_QUERY = `
509
+ const GET_SHOP_METAFIELD_QUERY$1 = `
1030
510
  query GetShopMetafield($namespace: String!, $key: String!) {
1031
511
  shop {
1032
512
  id
@@ -1069,6 +549,39 @@ const DELETE_METAFIELDS_MUTATION = `
1069
549
  }
1070
550
  }
1071
551
  `;
552
+ const CREATE_METAFIELD_DEFINITION_MUTATION = `
553
+ mutation CreateMetafieldDefinition($definition: MetafieldDefinitionInput!) {
554
+ metafieldDefinitionCreate(definition: $definition) {
555
+ createdDefinition {
556
+ id
557
+ }
558
+ userErrors {
559
+ field
560
+ message
561
+ code
562
+ }
563
+ }
564
+ }
565
+ `;
566
+ const DELETE_METAFIELD_DEFINITION_MUTATION = `
567
+ mutation DeleteMetafieldDefinition($id: ID!) {
568
+ metafieldDefinitionDelete(id: $id) {
569
+ deletedDefinitionId
570
+ userErrors {
571
+ field
572
+ message
573
+ code
574
+ }
575
+ }
576
+ }
577
+ `;
578
+ const GET_METAFIELD_DEFINITION_QUERY = `
579
+ query GetMetafieldDefinition($ownerType: MetafieldOwnerType!, $namespace: String, $key: String!) {
580
+ metafieldDefinition(identifier: { ownerType: $ownerType, namespace: $namespace, key: $key }) {
581
+ id
582
+ }
583
+ }
584
+ `;
1072
585
  const GET_SHOP_ID_QUERY = `
1073
586
  query GetShopId {
1074
587
  shop {
@@ -1078,20 +591,8 @@ const GET_SHOP_ID_QUERY = `
1078
591
  `;
1079
592
  class ShopifyMetafieldRepository {
1080
593
  client = inject(ShopifyGraphQLClient);
1081
- resolveEnvironment(environment) {
1082
- const trimmed = environment?.trim();
1083
- return trimmed ? trimmed : 'production';
1084
- }
1085
- buildIndexKey(environment) {
1086
- const env = this.resolveEnvironment(environment);
1087
- return `${env}.${INDEX_KEY}`;
1088
- }
1089
- buildPageKey(pageId, environment) {
1090
- const env = this.resolveEnvironment(environment);
1091
- return `${env}.page_${pageId}`;
1092
- }
1093
594
  getMetafield(namespace, key) {
1094
- return this.client.query(GET_SHOP_METAFIELD_QUERY, { namespace, key }).pipe(map$1(response => {
595
+ return this.client.query(GET_SHOP_METAFIELD_QUERY$1, { namespace, key }).pipe(map$1(response => {
1095
596
  if (!response.shop.metafield) {
1096
597
  return null;
1097
598
  }
@@ -1164,9 +665,8 @@ class ShopifyMetafieldRepository {
1164
665
  getShopId() {
1165
666
  return this.client.query(GET_SHOP_ID_QUERY).pipe(map$1(response => response.shop.id));
1166
667
  }
1167
- getPageIndex(environment) {
1168
- const key = this.buildIndexKey(environment);
1169
- return this.getMetafield(NAMESPACE, key).pipe(map$1(metafield => {
668
+ getPageIndex() {
669
+ return this.getMetafield(NAMESPACE$1, INDEX_KEY$1).pipe(map$1(metafield => {
1170
670
  if (!metafield) {
1171
671
  return this.getDefaultIndex();
1172
672
  }
@@ -1186,20 +686,19 @@ class ShopifyMetafieldRepository {
1186
686
  }
1187
687
  }));
1188
688
  }
1189
- updatePageIndex(index, environment) {
689
+ updatePageIndex(index) {
1190
690
  return this.getShopId().pipe(switchMap(shopId => {
1191
691
  const updatedIndex = {
1192
692
  ...index,
1193
693
  version: index.version + 1,
1194
694
  lastUpdated: new Date().toISOString(),
1195
695
  };
1196
- const key = this.buildIndexKey(environment);
1197
- return this.setMetafield(NAMESPACE, key, updatedIndex, shopId).pipe(map$1(() => updatedIndex));
696
+ return this.setMetafield(NAMESPACE$1, INDEX_KEY$1, updatedIndex, shopId).pipe(map$1(() => updatedIndex));
1198
697
  }));
1199
698
  }
1200
- getPageMetafield(pageId, environment) {
1201
- const key = this.buildPageKey(pageId, environment);
1202
- return this.getMetafield(NAMESPACE, key).pipe(map$1(metafield => {
699
+ getPageMetafield(pageId) {
700
+ const key = `page_${pageId}`;
701
+ return this.getMetafield(NAMESPACE$1, key).pipe(map$1(metafield => {
1203
702
  if (!metafield) {
1204
703
  return null;
1205
704
  }
@@ -1215,21 +714,81 @@ class ShopifyMetafieldRepository {
1215
714
  }
1216
715
  }));
1217
716
  }
1218
- setPageMetafield(pageId, page, environment) {
1219
- const key = this.buildPageKey(pageId, environment);
717
+ setPageMetafield(pageId, page) {
718
+ const key = `page_${pageId}`;
1220
719
  return this.getShopId().pipe(switchMap(shopId => {
1221
- return this.setMetafield(NAMESPACE, key, page, shopId).pipe(map$1(() => page));
720
+ return this.setMetafield(NAMESPACE$1, key, page, shopId).pipe(map$1(() => page));
1222
721
  }));
1223
722
  }
1224
- deletePageMetafield(pageId, environment) {
1225
- const key = this.buildPageKey(pageId, environment);
1226
- return this.deleteMetafield(NAMESPACE, key);
723
+ deletePageMetafield(pageId) {
724
+ const key = `page_${pageId}`;
725
+ return this.deleteMetafield(NAMESPACE$1, key);
1227
726
  }
1228
- resetPageIndex(environment) {
1229
- const key = this.buildIndexKey(environment);
1230
- const resetIndex = this.getDefaultIndex();
1231
- return this.getShopId().pipe(switchMap(shopId => {
1232
- return this.setMetafield(NAMESPACE, key, resetIndex, shopId).pipe(map$1(() => resetIndex));
727
+ /**
728
+ * Create a MetafieldDefinition with PUBLIC_READ storefront access.
729
+ * Idempotent: if definition already exists, returns the existing ID.
730
+ */
731
+ createMetafieldDefinition(namespace, key, name) {
732
+ return this.client.mutate(CREATE_METAFIELD_DEFINITION_MUTATION, {
733
+ definition: {
734
+ namespace,
735
+ key,
736
+ name,
737
+ ownerType: 'SHOP',
738
+ type: 'json',
739
+ access: {
740
+ storefront: 'PUBLIC_READ',
741
+ },
742
+ },
743
+ }).pipe(switchMap(response => {
744
+ const { createdDefinition, userErrors } = response.metafieldDefinitionCreate;
745
+ if (createdDefinition) {
746
+ return of(createdDefinition.id);
747
+ }
748
+ // If definition already exists, look it up instead of failing
749
+ const alreadyExists = userErrors.some(e => e.code === 'TAKEN' || e.code === 'ALREADY_EXISTS');
750
+ if (alreadyExists) {
751
+ return this.getMetafieldDefinitionId(namespace, key);
752
+ }
753
+ throw {
754
+ code: 'METAFIELD_DEFINITION_ERROR',
755
+ message: userErrors[0]?.message || 'Failed to create metafield definition',
756
+ errors: userErrors,
757
+ };
758
+ }));
759
+ }
760
+ /**
761
+ * Delete a MetafieldDefinition by ID. Does not delete associated metafields.
762
+ */
763
+ deleteMetafieldDefinition(definitionId) {
764
+ return this.client.mutate(DELETE_METAFIELD_DEFINITION_MUTATION, {
765
+ id: definitionId,
766
+ }).pipe(map$1(response => {
767
+ if (response.metafieldDefinitionDelete.userErrors.length > 0) {
768
+ throw {
769
+ code: 'METAFIELD_DEFINITION_ERROR',
770
+ message: response.metafieldDefinitionDelete.userErrors[0].message,
771
+ errors: response.metafieldDefinitionDelete.userErrors,
772
+ };
773
+ }
774
+ }));
775
+ }
776
+ /**
777
+ * Look up a MetafieldDefinition ID by namespace and key.
778
+ */
779
+ getMetafieldDefinitionId(namespace, key) {
780
+ return this.client.query(GET_METAFIELD_DEFINITION_QUERY, {
781
+ ownerType: 'SHOP',
782
+ namespace,
783
+ key,
784
+ }).pipe(map$1(response => {
785
+ if (!response.metafieldDefinition) {
786
+ throw {
787
+ code: 'DEFINITION_NOT_FOUND',
788
+ message: `MetafieldDefinition not found for ${namespace}.${key}`,
789
+ };
790
+ }
791
+ return response.metafieldDefinition.id;
1233
792
  }));
1234
793
  }
1235
794
  validateSize(data) {
@@ -1249,31 +808,25 @@ class ShopifyMetafieldRepository {
1249
808
  pages: [],
1250
809
  };
1251
810
  }
1252
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyMetafieldRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1253
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyMetafieldRepository, providedIn: 'root' });
811
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyMetafieldRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
812
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyMetafieldRepository, providedIn: 'root' });
1254
813
  }
1255
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyMetafieldRepository, decorators: [{
814
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyMetafieldRepository, decorators: [{
1256
815
  type: Injectable,
1257
816
  args: [{ providedIn: 'root' }]
1258
817
  }] });
1259
818
 
1260
819
  class PageShopifyRepository {
1261
820
  metafieldRepo = inject(ShopifyMetafieldRepository);
1262
- resolveEnvironment(environment) {
1263
- const trimmed = environment?.trim();
1264
- return trimmed ? trimmed : 'production';
1265
- }
1266
- getPages(environment) {
1267
- const env = this.resolveEnvironment(environment);
1268
- return this.metafieldRepo.getPageIndex(env).pipe(map$1(index => {
821
+ getPages() {
822
+ return this.metafieldRepo.getPageIndex().pipe(map$1(index => {
1269
823
  return index.pages
1270
824
  .map(entry => this.mapIndexEntryToSummary(entry))
1271
825
  .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1272
826
  }));
1273
827
  }
1274
- getPage(id, environment) {
1275
- const env = this.resolveEnvironment(environment);
1276
- return this.metafieldRepo.getPageMetafield(id, env).pipe(map$1(page => {
828
+ getPage(id) {
829
+ return this.metafieldRepo.getPageMetafield(id).pipe(map$1(page => {
1277
830
  if (!page) {
1278
831
  throw {
1279
832
  code: 'PAGE_NOT_FOUND',
@@ -1283,9 +836,8 @@ class PageShopifyRepository {
1283
836
  return page;
1284
837
  }));
1285
838
  }
1286
- getPublishedPageBySlug(slug, environment) {
1287
- const env = this.resolveEnvironment(environment);
1288
- return this.metafieldRepo.getPageIndex(env).pipe(switchMap(index => {
839
+ getPublishedPageBySlug(slug) {
840
+ return this.metafieldRepo.getPageIndex().pipe(switchMap(index => {
1289
841
  const entry = index.pages.find(p => p.slug === slug && p.status === 'published');
1290
842
  if (!entry) {
1291
843
  return throwError(() => ({
@@ -1293,12 +845,11 @@ class PageShopifyRepository {
1293
845
  message: `Published page with slug '${slug}' not found`,
1294
846
  }));
1295
847
  }
1296
- return this.getPage(entry.id, env);
848
+ return this.getPage(entry.id);
1297
849
  }));
1298
850
  }
1299
- createPage(request, environment) {
1300
- const env = this.resolveEnvironment(environment);
1301
- return this.metafieldRepo.getPageIndex(env).pipe(switchMap(index => {
851
+ createPage(request) {
852
+ return this.metafieldRepo.getPageIndex().pipe(switchMap(index => {
1302
853
  this.validateUniqueSlug(request.slug, index);
1303
854
  const newPage = {
1304
855
  id: crypto.randomUUID(),
@@ -1321,15 +872,17 @@ class PageShopifyRepository {
1321
872
  percentUsed: validation.percentUsed,
1322
873
  }));
1323
874
  }
1324
- return this.metafieldRepo.setPageMetafield(newPage.id, newPage, env).pipe(switchMap(() => {
1325
- const updatedIndex = this.addPageToIndex(index, newPage, validation.size);
1326
- return this.metafieldRepo.updatePageIndex(updatedIndex, env);
875
+ return this.metafieldRepo.setPageMetafield(newPage.id, newPage).pipe(switchMap(() => {
876
+ // Create MetafieldDefinition with PUBLIC_READ for Storefront API access
877
+ return this.metafieldRepo.createMetafieldDefinition('kustomizer', `page_${newPage.id}`, `Kustomizer Page: ${newPage.title}`).pipe(catchError(() => of(null)));
878
+ }), switchMap((definitionId) => {
879
+ const updatedIndex = this.addPageToIndex(index, newPage, validation.size, definitionId ?? undefined);
880
+ return this.metafieldRepo.updatePageIndex(updatedIndex);
1327
881
  }), map$1(() => newPage));
1328
882
  }));
1329
883
  }
1330
- updatePage(id, request, environment) {
1331
- const env = this.resolveEnvironment(environment);
1332
- return this.getPage(id, env).pipe(switchMap(currentPage => {
884
+ updatePage(id, request) {
885
+ return this.getPage(id).pipe(switchMap(currentPage => {
1333
886
  if (currentPage.version !== request.version) {
1334
887
  return throwError(() => ({
1335
888
  code: 'VERSION_CONFLICT',
@@ -1337,7 +890,7 @@ class PageShopifyRepository {
1337
890
  currentVersion: currentPage.version,
1338
891
  }));
1339
892
  }
1340
- return this.metafieldRepo.getPageIndex(env).pipe(switchMap(index => {
893
+ return this.metafieldRepo.getPageIndex().pipe(switchMap(index => {
1341
894
  if (request.slug && request.slug !== currentPage.slug) {
1342
895
  this.validateUniqueSlug(request.slug, index, id);
1343
896
  }
@@ -1360,23 +913,30 @@ class PageShopifyRepository {
1360
913
  percentUsed: validation.percentUsed,
1361
914
  }));
1362
915
  }
1363
- return this.metafieldRepo.setPageMetafield(id, updatedPage, env).pipe(switchMap(() => {
916
+ return this.metafieldRepo.setPageMetafield(id, updatedPage).pipe(switchMap(() => {
1364
917
  const updatedIndex = this.updatePageInIndex(index, updatedPage, validation.size);
1365
- return this.metafieldRepo.updatePageIndex(updatedIndex, env);
918
+ return this.metafieldRepo.updatePageIndex(updatedIndex);
1366
919
  }), map$1(() => updatedPage));
1367
920
  }));
1368
921
  }));
1369
922
  }
1370
- deletePage(id, environment) {
1371
- const env = this.resolveEnvironment(environment);
1372
- return this.metafieldRepo.deletePageMetafield(id, env).pipe(switchMap(() => this.metafieldRepo.getPageIndex(env)), switchMap(index => {
1373
- const updatedIndex = this.removePageFromIndex(index, id);
1374
- return this.metafieldRepo.updatePageIndex(updatedIndex, env);
1375
- }), map$1(() => undefined));
923
+ deletePage(id) {
924
+ return this.metafieldRepo.getPageIndex().pipe(switchMap(index => {
925
+ const entry = index.pages.find(p => p.id === id);
926
+ return this.metafieldRepo.deletePageMetafield(id).pipe(switchMap(() => {
927
+ // Delete the MetafieldDefinition if we have its ID
928
+ if (entry?.definitionId) {
929
+ return this.metafieldRepo.deleteMetafieldDefinition(entry.definitionId).pipe(catchError(() => of(undefined)));
930
+ }
931
+ return of(undefined);
932
+ }), switchMap(() => {
933
+ const updatedIndex = this.removePageFromIndex(index, id);
934
+ return this.metafieldRepo.updatePageIndex(updatedIndex);
935
+ }), map$1(() => undefined));
936
+ }));
1376
937
  }
1377
- publishPage(id, request, environment) {
1378
- const env = this.resolveEnvironment(environment);
1379
- return this.getPage(id, env).pipe(switchMap(currentPage => {
938
+ publishPage(id, request) {
939
+ return this.getPage(id).pipe(switchMap(currentPage => {
1380
940
  if (currentPage.version !== request.version) {
1381
941
  return throwError(() => ({
1382
942
  code: 'VERSION_CONFLICT',
@@ -1392,15 +952,14 @@ class PageShopifyRepository {
1392
952
  updatedAt: new Date().toISOString(),
1393
953
  };
1394
954
  const validation = this.metafieldRepo.validateSize(publishedPage);
1395
- return this.metafieldRepo.setPageMetafield(id, publishedPage, env).pipe(switchMap(() => this.metafieldRepo.getPageIndex(env)), switchMap(index => {
955
+ return this.metafieldRepo.setPageMetafield(id, publishedPage).pipe(switchMap(() => this.metafieldRepo.getPageIndex()), switchMap(index => {
1396
956
  const updatedIndex = this.updatePageInIndex(index, publishedPage, validation.size);
1397
- return this.metafieldRepo.updatePageIndex(updatedIndex, env);
957
+ return this.metafieldRepo.updatePageIndex(updatedIndex);
1398
958
  }), map$1(() => publishedPage));
1399
959
  }));
1400
960
  }
1401
- unpublishPage(id, request, environment) {
1402
- const env = this.resolveEnvironment(environment);
1403
- return this.getPage(id, env).pipe(switchMap(currentPage => {
961
+ unpublishPage(id, request) {
962
+ return this.getPage(id).pipe(switchMap(currentPage => {
1404
963
  if (currentPage.version !== request.version) {
1405
964
  return throwError(() => ({
1406
965
  code: 'VERSION_CONFLICT',
@@ -1422,16 +981,15 @@ class PageShopifyRepository {
1422
981
  updatedAt: new Date().toISOString(),
1423
982
  };
1424
983
  const validation = this.metafieldRepo.validateSize(unpublishedPage);
1425
- return this.metafieldRepo.setPageMetafield(id, unpublishedPage, env).pipe(switchMap(() => this.metafieldRepo.getPageIndex(env)), switchMap(index => {
984
+ return this.metafieldRepo.setPageMetafield(id, unpublishedPage).pipe(switchMap(() => this.metafieldRepo.getPageIndex()), switchMap(index => {
1426
985
  const updatedIndex = this.updatePageInIndex(index, unpublishedPage, validation.size);
1427
- return this.metafieldRepo.updatePageIndex(updatedIndex, env);
986
+ return this.metafieldRepo.updatePageIndex(updatedIndex);
1428
987
  }), map$1(() => unpublishedPage));
1429
988
  }));
1430
989
  }
1431
- duplicatePage(id, environment) {
1432
- const env = this.resolveEnvironment(environment);
1433
- return this.getPage(id, env).pipe(switchMap(sourcePage => {
1434
- return this.metafieldRepo.getPageIndex(env).pipe(switchMap(index => {
990
+ duplicatePage(id) {
991
+ return this.getPage(id).pipe(switchMap(sourcePage => {
992
+ return this.metafieldRepo.getPageIndex().pipe(switchMap(index => {
1435
993
  const newSlug = this.generateUniqueSlug(sourcePage.slug, index);
1436
994
  const duplicatedPage = {
1437
995
  ...sourcePage,
@@ -1454,21 +1012,43 @@ class PageShopifyRepository {
1454
1012
  percentUsed: validation.percentUsed,
1455
1013
  }));
1456
1014
  }
1457
- return this.metafieldRepo.setPageMetafield(duplicatedPage.id, duplicatedPage, env).pipe(switchMap(() => {
1458
- const updatedIndex = this.addPageToIndex(index, duplicatedPage, validation.size);
1459
- return this.metafieldRepo.updatePageIndex(updatedIndex, env);
1015
+ return this.metafieldRepo.setPageMetafield(duplicatedPage.id, duplicatedPage).pipe(switchMap(() => {
1016
+ return this.metafieldRepo.createMetafieldDefinition('kustomizer', `page_${duplicatedPage.id}`, `Kustomizer Page: ${duplicatedPage.title}`).pipe(catchError(() => of(null)));
1017
+ }), switchMap((definitionId) => {
1018
+ const updatedIndex = this.addPageToIndex(index, duplicatedPage, validation.size, definitionId ?? undefined);
1019
+ return this.metafieldRepo.updatePageIndex(updatedIndex);
1460
1020
  }), map$1(() => duplicatedPage));
1461
1021
  }));
1462
1022
  }));
1463
1023
  }
1464
- deletePagesByEnvironment(environment) {
1465
- const env = this.resolveEnvironment(environment);
1466
- return this.metafieldRepo.getPageIndex(env).pipe(switchMap(index => {
1467
- const deletions = index.pages.map(entry => this.metafieldRepo.deletePageMetafield(entry.id, env));
1468
- if (deletions.length === 0) {
1469
- return this.metafieldRepo.resetPageIndex(env).pipe(map$1(() => undefined));
1024
+ /**
1025
+ * Backfill MetafieldDefinitions for existing pages that don't have one.
1026
+ * Creates definitions with PUBLIC_READ storefront access.
1027
+ */
1028
+ ensureDefinitionsExist() {
1029
+ // Ensure the pages_index definition exists with PUBLIC_READ
1030
+ return this.metafieldRepo.createMetafieldDefinition('kustomizer', 'pages_index', 'Kustomizer Pages Index').pipe(catchError(() => of(null)), switchMap(() => this.metafieldRepo.getPageIndex()), switchMap(index => {
1031
+ const pagesWithoutDefinition = index.pages.filter(p => !p.definitionId);
1032
+ if (pagesWithoutDefinition.length === 0) {
1033
+ return of({ created: 0, skipped: index.pages.length, index: null });
1034
+ }
1035
+ // Create definitions sequentially to avoid rate limits
1036
+ let chain$ = of({ created: 0, skipped: 0, updatedPages: [...index.pages] });
1037
+ for (const page of pagesWithoutDefinition) {
1038
+ chain$ = chain$.pipe(switchMap(acc => {
1039
+ return this.metafieldRepo.createMetafieldDefinition('kustomizer', `page_${page.id}`, `Kustomizer Page: ${page.title}`).pipe(map$1(definitionId => {
1040
+ const updatedPages = acc.updatedPages.map(p => p.id === page.id ? { ...p, definitionId } : p);
1041
+ return { created: acc.created + 1, skipped: acc.skipped, updatedPages };
1042
+ }), catchError(() => of({ ...acc, skipped: acc.skipped + 1 })));
1043
+ }));
1470
1044
  }
1471
- return forkJoin(deletions).pipe(switchMap(() => this.metafieldRepo.resetPageIndex(env)), map$1(() => undefined));
1045
+ return chain$.pipe(switchMap(result => {
1046
+ const updatedIndex = { ...index, pages: result.updatedPages };
1047
+ if (result.created > 0) {
1048
+ return this.metafieldRepo.updatePageIndex(updatedIndex).pipe(map$1(() => ({ created: result.created, skipped: result.skipped })));
1049
+ }
1050
+ return of({ created: result.created, skipped: result.skipped });
1051
+ }));
1472
1052
  }));
1473
1053
  }
1474
1054
  validateUniqueSlug(slug, index, excludeId) {
@@ -1489,7 +1069,7 @@ class PageShopifyRepository {
1489
1069
  }
1490
1070
  return newSlug;
1491
1071
  }
1492
- addPageToIndex(index, page, size) {
1072
+ addPageToIndex(index, page, size, definitionId) {
1493
1073
  const entry = {
1494
1074
  id: page.id,
1495
1075
  slug: page.slug,
@@ -1498,6 +1078,7 @@ class PageShopifyRepository {
1498
1078
  updatedAt: page.updatedAt,
1499
1079
  publishedAt: page.publishedAt,
1500
1080
  size,
1081
+ ...(definitionId ? { definitionId } : {}),
1501
1082
  };
1502
1083
  return {
1503
1084
  ...index,
@@ -1540,10 +1121,10 @@ class PageShopifyRepository {
1540
1121
  publishedAt: entry.publishedAt,
1541
1122
  };
1542
1123
  }
1543
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageShopifyRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1544
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageShopifyRepository, providedIn: 'root' });
1124
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageShopifyRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1125
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageShopifyRepository, providedIn: 'root' });
1545
1126
  }
1546
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageShopifyRepository, decorators: [{
1127
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageShopifyRepository, decorators: [{
1547
1128
  type: Injectable,
1548
1129
  args: [{ providedIn: 'root' }]
1549
1130
  }] });
@@ -1556,127 +1137,95 @@ const USE_IN_MEMORY_PAGES = new InjectionToken('USE_IN_MEMORY_PAGES', {
1556
1137
  providedIn: 'root',
1557
1138
  factory: () => false,
1558
1139
  });
1559
- /**
1560
- * Injection token for page environment (metafield key prefix).
1561
- * Default is 'production'.
1562
- */
1563
- const PAGE_ENVIRONMENT = new InjectionToken('PAGE_ENVIRONMENT', {
1564
- providedIn: 'root',
1565
- factory: () => 'production',
1566
- });
1567
1140
  class PageService {
1568
1141
  useInMemory = inject(USE_IN_MEMORY_PAGES);
1569
- pageEnvironment = inject(PAGE_ENVIRONMENT);
1570
1142
  memoryRepo = inject(PageMemoryRepository);
1571
1143
  shopifyRepo = inject(PageShopifyRepository);
1572
- resolveEnvironment(environment) {
1573
- const trimmed = environment?.trim();
1574
- return trimmed ? trimmed : this.pageEnvironment;
1575
- }
1576
1144
  /**
1577
1145
  * Get all pages (summaries only for listing)
1578
1146
  */
1579
- getPages(environment) {
1580
- const env = this.resolveEnvironment(environment);
1147
+ getPages() {
1581
1148
  if (this.useInMemory) {
1582
- return this.memoryRepo.getPages(env);
1149
+ return this.memoryRepo.getPages();
1583
1150
  }
1584
- return this.shopifyRepo.getPages(env);
1151
+ return this.shopifyRepo.getPages();
1585
1152
  }
1586
1153
  /**
1587
1154
  * Get a single page by ID (full content)
1588
1155
  */
1589
- getPage(id, environment) {
1590
- const env = this.resolveEnvironment(environment);
1156
+ getPage(id) {
1591
1157
  if (this.useInMemory) {
1592
- return this.memoryRepo.getPage(id, env);
1158
+ return this.memoryRepo.getPage(id);
1593
1159
  }
1594
- return this.shopifyRepo.getPage(id, env);
1160
+ return this.shopifyRepo.getPage(id);
1595
1161
  }
1596
1162
  /**
1597
1163
  * Get a published page by slug (for public rendering)
1598
1164
  */
1599
- getPublishedPageBySlug(slug, environment) {
1600
- const env = this.resolveEnvironment(environment);
1165
+ getPublishedPageBySlug(slug) {
1601
1166
  if (this.useInMemory) {
1602
- return this.memoryRepo.getPublishedPageBySlug(slug, env);
1167
+ return this.memoryRepo.getPublishedPageBySlug(slug);
1603
1168
  }
1604
- return this.shopifyRepo.getPublishedPageBySlug(slug, env);
1169
+ return this.shopifyRepo.getPublishedPageBySlug(slug);
1605
1170
  }
1606
1171
  /**
1607
1172
  * Create a new page
1608
1173
  */
1609
- createPage(request, environment) {
1610
- const env = this.resolveEnvironment(environment);
1174
+ createPage(request) {
1611
1175
  if (this.useInMemory) {
1612
- return this.memoryRepo.createPage(request, env);
1176
+ return this.memoryRepo.createPage(request);
1613
1177
  }
1614
- return this.shopifyRepo.createPage(request, env);
1178
+ return this.shopifyRepo.createPage(request);
1615
1179
  }
1616
1180
  /**
1617
1181
  * Update an existing page
1618
1182
  */
1619
- updatePage(id, request, environment) {
1620
- const env = this.resolveEnvironment(environment);
1183
+ updatePage(id, request) {
1621
1184
  if (this.useInMemory) {
1622
- return this.memoryRepo.updatePage(id, request, env);
1185
+ return this.memoryRepo.updatePage(id, request);
1623
1186
  }
1624
- return this.shopifyRepo.updatePage(id, request, env);
1187
+ return this.shopifyRepo.updatePage(id, request);
1625
1188
  }
1626
1189
  /**
1627
1190
  * Delete a page
1628
1191
  */
1629
- deletePage(id, environment) {
1630
- const env = this.resolveEnvironment(environment);
1192
+ deletePage(id) {
1631
1193
  if (this.useInMemory) {
1632
- return this.memoryRepo.deletePage(id, env);
1194
+ return this.memoryRepo.deletePage(id);
1633
1195
  }
1634
- return this.shopifyRepo.deletePage(id, env);
1196
+ return this.shopifyRepo.deletePage(id);
1635
1197
  }
1636
1198
  /**
1637
1199
  * Publish a page (changes status to 'published')
1638
1200
  */
1639
- publishPage(id, request, environment) {
1640
- const env = this.resolveEnvironment(environment);
1201
+ publishPage(id, request) {
1641
1202
  if (this.useInMemory) {
1642
- return this.memoryRepo.publishPage(id, request, env);
1203
+ return this.memoryRepo.publishPage(id, request);
1643
1204
  }
1644
- return this.shopifyRepo.publishPage(id, request, env);
1205
+ return this.shopifyRepo.publishPage(id, request);
1645
1206
  }
1646
1207
  /**
1647
1208
  * Unpublish a page (changes status to 'draft')
1648
1209
  */
1649
- unpublishPage(id, request, environment) {
1650
- const env = this.resolveEnvironment(environment);
1210
+ unpublishPage(id, request) {
1651
1211
  if (this.useInMemory) {
1652
- return this.memoryRepo.unpublishPage(id, request, env);
1212
+ return this.memoryRepo.unpublishPage(id, request);
1653
1213
  }
1654
- return this.shopifyRepo.unpublishPage(id, request, env);
1214
+ return this.shopifyRepo.unpublishPage(id, request);
1655
1215
  }
1656
1216
  /**
1657
1217
  * Duplicate an existing page
1658
1218
  */
1659
- duplicatePage(id, environment) {
1660
- const env = this.resolveEnvironment(environment);
1661
- if (this.useInMemory) {
1662
- return this.memoryRepo.duplicatePage(id, env);
1663
- }
1664
- return this.shopifyRepo.duplicatePage(id, env);
1665
- }
1666
- /**
1667
- * Delete all pages for a given environment
1668
- */
1669
- deletePagesByEnvironment(environment) {
1670
- const env = this.resolveEnvironment(environment);
1219
+ duplicatePage(id) {
1671
1220
  if (this.useInMemory) {
1672
- return this.memoryRepo.deletePagesByEnvironment(env);
1221
+ return this.memoryRepo.duplicatePage(id);
1673
1222
  }
1674
- return this.shopifyRepo.deletePagesByEnvironment(env);
1223
+ return this.shopifyRepo.duplicatePage(id);
1675
1224
  }
1676
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1677
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageService, providedIn: 'root' });
1225
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1226
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageService, providedIn: 'root' });
1678
1227
  }
1679
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageService, decorators: [{
1228
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageService, decorators: [{
1680
1229
  type: Injectable,
1681
1230
  args: [{ providedIn: 'root' }]
1682
1231
  }] });
@@ -1699,10 +1248,10 @@ class RoutePageLoadingStrategy extends PageLoadingStrategy {
1699
1248
  getPageIdToLoad() {
1700
1249
  return this.navigation.getCurrentPageId();
1701
1250
  }
1702
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RoutePageLoadingStrategy, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1703
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RoutePageLoadingStrategy });
1251
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: RoutePageLoadingStrategy, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1252
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: RoutePageLoadingStrategy });
1704
1253
  }
1705
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: RoutePageLoadingStrategy, decorators: [{
1254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: RoutePageLoadingStrategy, decorators: [{
1706
1255
  type: Injectable
1707
1256
  }] });
1708
1257
  /**
@@ -1725,10 +1274,10 @@ class InputPageLoadingStrategy extends PageLoadingStrategy {
1725
1274
  getPageIdToLoad() {
1726
1275
  return this.pageId$.asObservable();
1727
1276
  }
1728
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InputPageLoadingStrategy, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1729
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InputPageLoadingStrategy });
1277
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: InputPageLoadingStrategy, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1278
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: InputPageLoadingStrategy });
1730
1279
  }
1731
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: InputPageLoadingStrategy, decorators: [{
1280
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: InputPageLoadingStrategy, decorators: [{
1732
1281
  type: Injectable
1733
1282
  }] });
1734
1283
 
@@ -2795,10 +2344,10 @@ class ComponentRegistryService {
2795
2344
  }
2796
2345
  return errors;
2797
2346
  }
2798
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ComponentRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2799
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ComponentRegistryService, providedIn: 'root' });
2347
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComponentRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2348
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComponentRegistryService, providedIn: 'root' });
2800
2349
  }
2801
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ComponentRegistryService, decorators: [{
2350
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ComponentRegistryService, decorators: [{
2802
2351
  type: Injectable,
2803
2352
  args: [{ providedIn: 'root' }]
2804
2353
  }], ctorParameters: () => [] });
@@ -2904,8 +2453,8 @@ class DynamicRendererComponent {
2904
2453
  }
2905
2454
  this.viewContainerRef.clear();
2906
2455
  }
2907
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DynamicRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2908
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: DynamicRendererComponent, isStandalone: true, selector: "lib-dynamic-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2456
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DynamicRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2457
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DynamicRendererComponent, isStandalone: true, selector: "lib-dynamic-renderer", inputs: { element: { classPropertyName: "element", publicName: "element", isSignal: true, isRequired: true, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
2909
2458
  @if (error()) {
2910
2459
  <div class="dynamic-renderer-error">
2911
2460
  <span>Unknown component type: {{ element().type }}</span>
@@ -2913,7 +2462,7 @@ class DynamicRendererComponent {
2913
2462
  }
2914
2463
  `, isInline: true, styles: [":host{display:contents}.dynamic-renderer-error{padding:1rem;background:#fee2e2;border:1px dashed #ef4444;color:#dc2626;border-radius:4px;font-size:.875rem}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2915
2464
  }
2916
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DynamicRendererComponent, decorators: [{
2465
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DynamicRendererComponent, decorators: [{
2917
2466
  type: Component,
2918
2467
  args: [{ selector: 'lib-dynamic-renderer', template: `
2919
2468
  @if (error()) {
@@ -2961,8 +2510,8 @@ class SlotRendererComponent {
2961
2510
  return true;
2962
2511
  });
2963
2512
  }, ...(ngDevMode ? [{ debugName: "filteredChildren" }] : []));
2964
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SlotRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2965
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: SlotRendererComponent, isStandalone: true, selector: "lib-slot-renderer", inputs: { slot: { classPropertyName: "slot", publicName: "slot", isSignal: true, isRequired: true, transformFunction: null }, children: { classPropertyName: "children", publicName: "children", isSignal: true, isRequired: true, transformFunction: null }, parentElementId: { classPropertyName: "parentElementId", publicName: "parentElementId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
2513
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SlotRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2514
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: SlotRendererComponent, isStandalone: true, selector: "lib-slot-renderer", inputs: { slot: { classPropertyName: "slot", publicName: "slot", isSignal: true, isRequired: true, transformFunction: null }, children: { classPropertyName: "children", publicName: "children", isSignal: true, isRequired: true, transformFunction: null }, parentElementId: { classPropertyName: "parentElementId", publicName: "parentElementId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
2966
2515
  @if (filteredChildren().length === 0 && slot().emptyPlaceholder) {
2967
2516
  <div class="slot-empty" [attr.data-slot]="slot().name">
2968
2517
  {{ slot().emptyPlaceholder }}
@@ -2977,7 +2526,7 @@ class SlotRendererComponent {
2977
2526
  }
2978
2527
  `, isInline: true, styles: [":host{display:contents}.slot-empty{padding:2rem;border:2px dashed #d1d5db;color:#9ca3af;text-align:center;border-radius:4px;font-size:.875rem}\n"], dependencies: [{ kind: "component", type: DynamicRendererComponent, selector: "lib-dynamic-renderer", inputs: ["element", "context"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2979
2528
  }
2980
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: SlotRendererComponent, decorators: [{
2529
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: SlotRendererComponent, decorators: [{
2981
2530
  type: Component,
2982
2531
  args: [{ selector: 'lib-slot-renderer', imports: [DynamicRendererComponent], template: `
2983
2532
  @if (filteredChildren().length === 0 && slot().emptyPlaceholder) {
@@ -3533,7 +3082,7 @@ class VisualEditorFacade {
3533
3082
  version: currentPage.version,
3534
3083
  };
3535
3084
  console.log('Saving page:', request);
3536
- return this.pageService.updatePage(pageId, request).pipe(tap$1((page) => {
3085
+ return this.pageService.updatePage(pageId, request).pipe(tap((page) => {
3537
3086
  this.store.dispatch(VisualEditorActions.updatePageContext({
3538
3087
  context: { version: page.version },
3539
3088
  }));
@@ -3552,7 +3101,7 @@ class VisualEditorFacade {
3552
3101
  ...metadata,
3553
3102
  version: currentPage.version,
3554
3103
  };
3555
- return this.pageService.updatePage(pageId, request).pipe(tap$1((page) => {
3104
+ return this.pageService.updatePage(pageId, request).pipe(tap((page) => {
3556
3105
  this.store.dispatch(VisualEditorActions.updatePageContext({
3557
3106
  context: {
3558
3107
  title: page.title,
@@ -3576,7 +3125,7 @@ class VisualEditorFacade {
3576
3125
  if (!currentPage) {
3577
3126
  throw new Error('No page loaded in editor');
3578
3127
  }
3579
- return this.pageService.publishPage(pageId, { version: currentPage.version }).pipe(tap$1((page) => {
3128
+ return this.pageService.publishPage(pageId, { version: currentPage.version }).pipe(tap((page) => {
3580
3129
  this.store.dispatch(VisualEditorActions.updatePageContext({
3581
3130
  context: { status: page.status, version: page.version },
3582
3131
  }));
@@ -3590,7 +3139,7 @@ class VisualEditorFacade {
3590
3139
  if (!currentPage) {
3591
3140
  throw new Error('No page loaded in editor');
3592
3141
  }
3593
- return this.pageService.unpublishPage(pageId, { version: currentPage.version }).pipe(tap$1((page) => {
3142
+ return this.pageService.unpublishPage(pageId, { version: currentPage.version }).pipe(tap((page) => {
3594
3143
  this.store.dispatch(VisualEditorActions.updatePageContext({
3595
3144
  context: { status: page.status, version: page.version },
3596
3145
  }));
@@ -3620,25 +3169,152 @@ class VisualEditorFacade {
3620
3169
  markClean() {
3621
3170
  this.store.dispatch(VisualEditorActions.markClean());
3622
3171
  }
3623
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VisualEditorFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3624
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VisualEditorFacade, providedIn: 'root' });
3172
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditorFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3173
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditorFacade, providedIn: 'root' });
3625
3174
  }
3626
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VisualEditorFacade, decorators: [{
3175
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditorFacade, decorators: [{
3627
3176
  type: Injectable,
3628
3177
  args: [{ providedIn: 'root' }]
3629
3178
  }] });
3630
3179
 
3631
- const MAX_DEPTH = 12;
3632
- class DragDropService {
3633
- store = inject(Store);
3634
- registry = inject(ComponentRegistryService);
3635
- sections = toSignal(this.store.select(selectSections), { initialValue: [] });
3636
- dragItem = signal(null, ...(ngDevMode ? [{ debugName: "dragItem" }] : []));
3637
- dropTarget = signal(null, ...(ngDevMode ? [{ debugName: "dropTarget" }] : []));
3638
- isDragging = computed(() => this.dragItem() !== null, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
3639
- startDrag(item, event) {
3640
- this.dragItem.set(item);
3641
- this.store.dispatch(VisualEditorActions.startDrag({ elementId: item.elementId }));
3180
+ /**
3181
+ * Manages postMessage communication between the editor (parent) and the
3182
+ * storefront preview (iframe).
3183
+ */
3184
+ class IframeBridgeService {
3185
+ zone = inject(NgZone);
3186
+ iframe = null;
3187
+ origin = '*';
3188
+ messages$ = new Subject();
3189
+ messageHandler = (event) => this.handleMessage(event);
3190
+ constructor() {
3191
+ this.zone.runOutsideAngular(() => {
3192
+ window.addEventListener('message', this.messageHandler);
3193
+ });
3194
+ }
3195
+ ngOnDestroy() {
3196
+ window.removeEventListener('message', this.messageHandler);
3197
+ this.messages$.complete();
3198
+ }
3199
+ /**
3200
+ * Connect to an iframe element. Must be called after the iframe loads.
3201
+ */
3202
+ connect(iframe, origin) {
3203
+ this.iframe = iframe;
3204
+ this.origin = origin || '*';
3205
+ }
3206
+ /**
3207
+ * Disconnect from the current iframe.
3208
+ */
3209
+ disconnect() {
3210
+ this.iframe = null;
3211
+ }
3212
+ // ─── Outbound (Parent → Iframe) ──────────────────────────────────────────
3213
+ sendPageUpdate(sections) {
3214
+ this.send({ type: 'kustomizer:page-update', sections });
3215
+ }
3216
+ sendSelectElement(elementId) {
3217
+ this.send({ type: 'kustomizer:select-element', elementId });
3218
+ }
3219
+ sendDeselect() {
3220
+ this.send({ type: 'kustomizer:deselect' });
3221
+ }
3222
+ // ─── Inbound (Iframe → Parent) ───────────────────────────────────────────
3223
+ onReady() {
3224
+ return this.messages$.pipe(filter((msg) => msg.type === 'kustomizer:ready'), map(() => undefined));
3225
+ }
3226
+ onElementClicked() {
3227
+ return this.messages$.pipe(filter((msg) => msg.type === 'kustomizer:element-clicked'), map(({ elementId, sectionId }) => ({ elementId, sectionId })));
3228
+ }
3229
+ onElementHover() {
3230
+ return this.messages$.pipe(filter((msg) => msg.type === 'kustomizer:element-hover'), map(({ elementId }) => elementId));
3231
+ }
3232
+ onElementMoved() {
3233
+ return this.messages$.pipe(filter((msg) => msg.type === 'kustomizer:element-moved'), map(({ elementId, newIndex, targetSectionId }) => ({
3234
+ elementId,
3235
+ newIndex,
3236
+ targetSectionId,
3237
+ })));
3238
+ }
3239
+ // ─── Private ─────────────────────────────────────────────────────────────
3240
+ send(message) {
3241
+ if (!this.iframe?.contentWindow)
3242
+ return;
3243
+ this.iframe.contentWindow.postMessage(message, this.origin);
3244
+ }
3245
+ handleMessage(event) {
3246
+ const data = event.data;
3247
+ if (!data?.type?.startsWith('kustomizer:'))
3248
+ return;
3249
+ // Only accept messages from the connected iframe's origin
3250
+ if (this.origin !== '*' && event.origin !== this.origin)
3251
+ return;
3252
+ this.zone.run(() => {
3253
+ this.messages$.next(data);
3254
+ });
3255
+ }
3256
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: IframeBridgeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3257
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: IframeBridgeService });
3258
+ }
3259
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: IframeBridgeService, decorators: [{
3260
+ type: Injectable
3261
+ }], ctorParameters: () => [] });
3262
+
3263
+ /**
3264
+ * Service that holds and dynamically loads the per-merchant storefront URL.
3265
+ *
3266
+ * Resolution order:
3267
+ * 1. Metafield value loaded from GET /api/storefront-url (per-shop)
3268
+ * 2. Static STOREFRONT_URL injection token (env var / window global fallback)
3269
+ * 3. Empty string (editor works with local components only)
3270
+ */
3271
+ class StorefrontUrlService {
3272
+ http = inject(HttpClient);
3273
+ staticUrl = inject(STOREFRONT_URL);
3274
+ /** Current storefront URL — reactive signal used by the editor and iframe. */
3275
+ url = signal('', ...(ngDevMode ? [{ debugName: "url" }] : []));
3276
+ /**
3277
+ * Load the storefront URL from the backend metafield.
3278
+ * Falls back to the static STOREFRONT_URL token if the endpoint
3279
+ * returns no value or fails.
3280
+ *
3281
+ * Called during APP_INITIALIZER.
3282
+ */
3283
+ async load() {
3284
+ try {
3285
+ const res = await firstValueFrom(this.http.get('/api/storefront-url'));
3286
+ if (res.url) {
3287
+ this.url.set(res.url);
3288
+ return;
3289
+ }
3290
+ }
3291
+ catch {
3292
+ // Non-fatal — fall back to static token
3293
+ }
3294
+ // Fallback: use the static injection token (env var / window global)
3295
+ if (this.staticUrl) {
3296
+ this.url.set(this.staticUrl);
3297
+ }
3298
+ }
3299
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3300
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, providedIn: 'root' });
3301
+ }
3302
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, decorators: [{
3303
+ type: Injectable,
3304
+ args: [{ providedIn: 'root' }]
3305
+ }] });
3306
+
3307
+ const MAX_DEPTH = 12;
3308
+ class DragDropService {
3309
+ store = inject(Store);
3310
+ registry = inject(ComponentRegistryService);
3311
+ sections = toSignal(this.store.select(selectSections), { initialValue: [] });
3312
+ dragItem = signal(null, ...(ngDevMode ? [{ debugName: "dragItem" }] : []));
3313
+ dropTarget = signal(null, ...(ngDevMode ? [{ debugName: "dropTarget" }] : []));
3314
+ isDragging = computed(() => this.dragItem() !== null, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
3315
+ startDrag(item, event) {
3316
+ this.dragItem.set(item);
3317
+ this.store.dispatch(VisualEditorActions.startDrag({ elementId: item.elementId }));
3642
3318
  if (event.dataTransfer) {
3643
3319
  event.dataTransfer.effectAllowed = 'move';
3644
3320
  event.dataTransfer.setData('text/plain', item.elementId);
@@ -3870,10 +3546,10 @@ class DragDropService {
3870
3546
  return false;
3871
3547
  return a.every((id, i) => id === b[i]);
3872
3548
  }
3873
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DragDropService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3874
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DragDropService, providedIn: 'root' });
3549
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DragDropService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3550
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DragDropService, providedIn: 'root' });
3875
3551
  }
3876
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: DragDropService, decorators: [{
3552
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DragDropService, decorators: [{
3877
3553
  type: Injectable,
3878
3554
  args: [{ providedIn: 'root' }]
3879
3555
  }] });
@@ -3885,7 +3561,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
3885
3561
  class BlockTreeItemComponent {
3886
3562
  facade = inject(VisualEditorFacade);
3887
3563
  dndService = inject(DragDropService);
3888
- sanitizer = inject(DomSanitizer);
3889
3564
  treeBlock = viewChild('treeBlock', ...(ngDevMode ? [{ debugName: "treeBlock" }] : []));
3890
3565
  block = input.required(...(ngDevMode ? [{ debugName: "block" }] : []));
3891
3566
  context = input.required(...(ngDevMode ? [{ debugName: "context" }] : []));
@@ -3913,20 +3588,14 @@ class BlockTreeItemComponent {
3913
3588
  }), ...(ngDevMode ? [{ debugName: "childContext" }] : []));
3914
3589
  blockIcon = computed(() => {
3915
3590
  const iconMap = {
3916
- 'text-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3h12a1 1 0 011 1v1a1 1 0 01-1 1h-5v10a1 1 0 01-2 0V6H4a1 1 0 01-1-1V4a1 1 0 011-1z"/></svg>',
3917
- 'button-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M3 6a2 2 0 012-2h10a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V6zm4 4a1 1 0 100 2h6a1 1 0 100-2H7z"/></svg>',
3918
- 'image-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6zM6 7a1 1 0 110-2 1 1 0 010 2z"/></svg>',
3919
- 'icon-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2l2.39 4.84L18 7.77l-4 3.9.94 5.5L10 14.27l-4.94 2.9.94-5.5-4-3.9 5.61-.93L10 2z"/></svg>',
3920
- 'feature-item': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2l2.39 4.84L18 7.77l-4 3.9.94 5.5L10 14.27l-4.94 2.9.94-5.5-4-3.9 5.61-.93L10 2z"/></svg>',
3921
- 'card-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm0 2h12v4H4V5zm0 6h8v4H4v-4z"/></svg>',
3922
- 'video-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm4 3.5l5 3.5-5 3.5v-7z"/></svg>',
3923
- 'picture-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6zM6 7a1 1 0 110-2 1 1 0 010 2z"/></svg>',
3924
- 'flex-layout-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M3 3h4v14H3V3zm5 0h4v14H8V3zm5 0h4v14h-4V3z"/></svg>',
3591
+ 'text-block': '📝',
3592
+ 'button-block': '🔘',
3593
+ 'image-block': '🖼️',
3594
+ 'icon-block': '',
3595
+ 'feature-item': '',
3596
+ 'card-block': '🃏',
3925
3597
  };
3926
- const def = this.facade.getDefinition(this.block().type);
3927
- const raw = iconMap[this.block().type] ?? def?.icon ?? '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2l1.5 3h3.5l-2.5 2.5 1 3.5-3.5-2-3.5 2 1-3.5L4 5h3.5L10 2zm-6 10h12v2H4v-2zm2 4h8v2H6v-2z"/></svg>';
3928
- const html = raw.trim().startsWith('<') ? raw : `<span style="font-family:'Material Icons',sans-serif;font-weight:normal;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility">${raw}</span>`;
3929
- return this.sanitizer.bypassSecurityTrustHtml(html);
3598
+ return iconMap[this.block().type] ?? '🧩';
3930
3599
  }, ...(ngDevMode ? [{ debugName: "blockIcon" }] : []));
3931
3600
  blockName = computed(() => {
3932
3601
  const block = this.block();
@@ -4088,8 +3757,8 @@ class BlockTreeItemComponent {
4088
3757
  zone,
4089
3758
  };
4090
3759
  }
4091
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: BlockTreeItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4092
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: BlockTreeItemComponent, isStandalone: true, selector: "lib-block-tree-item", inputs: { block: { classPropertyName: "block", publicName: "block", isSignal: true, isRequired: true, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, totalSiblings: { classPropertyName: "totalSiblings", publicName: "totalSiblings", isSignal: true, isRequired: true, transformFunction: null }, expandAll: { classPropertyName: "expandAll", publicName: "expandAll", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectBlock: "selectBlock", deleteBlock: "deleteBlock", duplicateBlock: "duplicateBlock", moveBlock: "moveBlock", openBlockPicker: "openBlockPicker" }, viewQueries: [{ propertyName: "treeBlock", first: true, predicate: ["treeBlock"], descendants: true, isSignal: true }], ngImport: i0, template: `
3760
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BlockTreeItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3761
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: BlockTreeItemComponent, isStandalone: true, selector: "lib-block-tree-item", inputs: { block: { classPropertyName: "block", publicName: "block", isSignal: true, isRequired: true, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, totalSiblings: { classPropertyName: "totalSiblings", publicName: "totalSiblings", isSignal: true, isRequired: true, transformFunction: null }, expandAll: { classPropertyName: "expandAll", publicName: "expandAll", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectBlock: "selectBlock", deleteBlock: "deleteBlock", duplicateBlock: "duplicateBlock", moveBlock: "moveBlock", openBlockPicker: "openBlockPicker" }, viewQueries: [{ propertyName: "treeBlock", first: true, predicate: ["treeBlock"], descendants: true, isSignal: true }], ngImport: i0, template: `
4093
3762
  <div
4094
3763
  class="tree-block"
4095
3764
  #treeBlock
@@ -4123,7 +3792,7 @@ class BlockTreeItemComponent {
4123
3792
  <span class="block-indent"></span>
4124
3793
  }
4125
3794
  <span class="icon-drag-wrapper">
4126
- <span class="block-icon" [innerHTML]="blockIcon()"></span>
3795
+ <span class="block-icon">{{ blockIcon() }}</span>
4127
3796
  <span
4128
3797
  class="drag-handle"
4129
3798
  role="button"
@@ -4192,9 +3861,9 @@ class BlockTreeItemComponent {
4192
3861
  }
4193
3862
  </div>
4194
3863
  }
4195
- `, isInline: true, styles: [":host{display:block;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.375rem .75rem .375rem .5rem;cursor:pointer;transition:background .15s;position:relative}.tree-block:hover{background:#f6f6f7;border-radius:8px;margin-right:.375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin-right:.375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name,.tree-block.selected .expand-btn,.tree-block.selected .drag-handle,.tree-block.selected .tree-btn{color:inherit}.tree-block.drag-over-before{box-shadow:inset 0 2px #005bd3}.tree-block.drag-over-inside{background:#005bd314;box-shadow:inset 0 0 0 2px #005bd3}.tree-block.drag-over-after{box-shadow:inset 0 -2px #005bd3}.tree-block.is-dragging{opacity:.4;cursor:grabbing}.drag-handle{cursor:grab;color:#8c9196;opacity:0;transition:opacity .15s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:4px}.drag-handle:hover{color:#303030;background:#e3e3e3}.drag-handle:focus-visible{opacity:1;outline:2px solid #005bd3;outline-offset:1px}.tree-block:hover .drag-handle{opacity:1}@media(prefers-reduced-motion:reduce){.tree-block,.drag-handle,.tree-actions,.expand-icon{transition:none}}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#8c9196;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .15s}.block-indent{width:18px;height:1px;background:#e3e3e3;flex-shrink:0}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-block:hover .icon-drag-wrapper .block-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .block-icon{visibility:hidden}.block-icon{font-size:.875rem;color:#8c9196;flex-shrink:0;display:flex;align-items:center}.block-name{flex:1;font-size:.8125rem;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#8c9196;transition:all .15s}.tree-btn:hover{background:#e3e3e3;color:#303030}.tree-btn.delete:hover{background:#fef2f0;color:#d72c0d}.material-icon{font-family:Material Icons,sans-serif;font-size:inherit;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.nested-content{border-left:1px solid #e3e3e3;margin-left:1.25rem}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1rem);margin:.25rem .5rem;padding:.375rem .5rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#005bd3;transition:all .15s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.8125rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "component", type: BlockTreeItemComponent, selector: "lib-block-tree-item", inputs: ["block", "context", "index", "totalSiblings", "expandAll"], outputs: ["selectBlock", "deleteBlock", "duplicateBlock", "moveBlock", "openBlockPicker"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3864
+ `, isInline: true, styles: [":host{display:block}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.5rem .75rem .5rem .5rem;cursor:pointer;transition:background .2s;position:relative}.tree-block:hover{background:#f5f5f5;border-radius:8px;margin-right:.375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin-right:.375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name,.tree-block.selected .expand-btn,.tree-block.selected .drag-handle,.tree-block.selected .tree-btn{color:inherit}.tree-block.drag-over-before{box-shadow:inset 0 2px #4a90d9}.tree-block.drag-over-inside{background:#4a90d91a;box-shadow:inset 0 0 0 2px #4a90d9}.tree-block.drag-over-after{box-shadow:inset 0 -2px #4a90d9}.tree-block.is-dragging{opacity:.4;cursor:grabbing}.drag-handle{cursor:grab;color:#999;opacity:0;transition:opacity .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:2px}.drag-handle:hover{color:#666;background:#e0e0e0}.drag-handle:focus-visible{opacity:1;outline:2px solid #4a90d9;outline-offset:1px}.tree-block:hover .drag-handle{opacity:1}@media(prefers-reduced-motion:reduce){.tree-block,.drag-handle,.tree-actions,.expand-icon{transition:none}}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .2s}.block-indent{width:18px;height:1px;background:#ddd;flex-shrink:0}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-block:hover .icon-drag-wrapper .block-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .block-icon{visibility:hidden}.block-icon{font-size:.875rem;flex-shrink:0}.block-name{flex:1;font-size:.75rem;color:#555;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.6875rem;color:#666}.tree-btn:hover{background:#d0d0d0}.tree-btn.delete:hover{background:#e74c3c;color:#fff}.material-icon{font-family:Material Icons,sans-serif;font-size:.75rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.nested-content{border-left:1px solid #e0e0e0;margin-left:1.25rem}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1rem);margin:.375rem .5rem;padding:.375rem .5rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#005bd3;transition:all .2s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.75rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "component", type: BlockTreeItemComponent, selector: "lib-block-tree-item", inputs: ["block", "context", "index", "totalSiblings", "expandAll"], outputs: ["selectBlock", "deleteBlock", "duplicateBlock", "moveBlock", "openBlockPicker"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4196
3865
  }
4197
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: BlockTreeItemComponent, decorators: [{
3866
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BlockTreeItemComponent, decorators: [{
4198
3867
  type: Component,
4199
3868
  args: [{ selector: 'lib-block-tree-item', imports: [BlockTreeItemComponent], template: `
4200
3869
  <div
@@ -4230,7 +3899,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
4230
3899
  <span class="block-indent"></span>
4231
3900
  }
4232
3901
  <span class="icon-drag-wrapper">
4233
- <span class="block-icon" [innerHTML]="blockIcon()"></span>
3902
+ <span class="block-icon">{{ blockIcon() }}</span>
4234
3903
  <span
4235
3904
  class="drag-handle"
4236
3905
  role="button"
@@ -4299,7 +3968,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
4299
3968
  }
4300
3969
  </div>
4301
3970
  }
4302
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.375rem .75rem .375rem .5rem;cursor:pointer;transition:background .15s;position:relative}.tree-block:hover{background:#f6f6f7;border-radius:8px;margin-right:.375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin-right:.375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name,.tree-block.selected .expand-btn,.tree-block.selected .drag-handle,.tree-block.selected .tree-btn{color:inherit}.tree-block.drag-over-before{box-shadow:inset 0 2px #005bd3}.tree-block.drag-over-inside{background:#005bd314;box-shadow:inset 0 0 0 2px #005bd3}.tree-block.drag-over-after{box-shadow:inset 0 -2px #005bd3}.tree-block.is-dragging{opacity:.4;cursor:grabbing}.drag-handle{cursor:grab;color:#8c9196;opacity:0;transition:opacity .15s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:4px}.drag-handle:hover{color:#303030;background:#e3e3e3}.drag-handle:focus-visible{opacity:1;outline:2px solid #005bd3;outline-offset:1px}.tree-block:hover .drag-handle{opacity:1}@media(prefers-reduced-motion:reduce){.tree-block,.drag-handle,.tree-actions,.expand-icon{transition:none}}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#8c9196;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .15s}.block-indent{width:18px;height:1px;background:#e3e3e3;flex-shrink:0}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-block:hover .icon-drag-wrapper .block-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .block-icon{visibility:hidden}.block-icon{font-size:.875rem;color:#8c9196;flex-shrink:0;display:flex;align-items:center}.block-name{flex:1;font-size:.8125rem;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#8c9196;transition:all .15s}.tree-btn:hover{background:#e3e3e3;color:#303030}.tree-btn.delete:hover{background:#fef2f0;color:#d72c0d}.material-icon{font-family:Material Icons,sans-serif;font-size:inherit;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.nested-content{border-left:1px solid #e3e3e3;margin-left:1.25rem}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1rem);margin:.25rem .5rem;padding:.375rem .5rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#005bd3;transition:all .15s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.8125rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}\n"] }]
3971
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.5rem .75rem .5rem .5rem;cursor:pointer;transition:background .2s;position:relative}.tree-block:hover{background:#f5f5f5;border-radius:8px;margin-right:.375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin-right:.375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name,.tree-block.selected .expand-btn,.tree-block.selected .drag-handle,.tree-block.selected .tree-btn{color:inherit}.tree-block.drag-over-before{box-shadow:inset 0 2px #4a90d9}.tree-block.drag-over-inside{background:#4a90d91a;box-shadow:inset 0 0 0 2px #4a90d9}.tree-block.drag-over-after{box-shadow:inset 0 -2px #4a90d9}.tree-block.is-dragging{opacity:.4;cursor:grabbing}.drag-handle{cursor:grab;color:#999;opacity:0;transition:opacity .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:2px}.drag-handle:hover{color:#666;background:#e0e0e0}.drag-handle:focus-visible{opacity:1;outline:2px solid #4a90d9;outline-offset:1px}.tree-block:hover .drag-handle{opacity:1}@media(prefers-reduced-motion:reduce){.tree-block,.drag-handle,.tree-actions,.expand-icon{transition:none}}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .2s}.block-indent{width:18px;height:1px;background:#ddd;flex-shrink:0}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-block:hover .icon-drag-wrapper .block-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .block-icon{visibility:hidden}.block-icon{font-size:.875rem;flex-shrink:0}.block-name{flex:1;font-size:.75rem;color:#555;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.6875rem;color:#666}.tree-btn:hover{background:#d0d0d0}.tree-btn.delete:hover{background:#e74c3c;color:#fff}.material-icon{font-family:Material Icons,sans-serif;font-size:.75rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.nested-content{border-left:1px solid #e0e0e0;margin-left:1.25rem}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1rem);margin:.375rem .5rem;padding:.375rem .5rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#005bd3;transition:all .2s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.75rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}\n"] }]
4303
3972
  }], propDecorators: { treeBlock: [{ type: i0.ViewChild, args: ['treeBlock', { isSignal: true }] }], block: [{ type: i0.Input, args: [{ isSignal: true, alias: "block", required: true }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], totalSiblings: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalSiblings", required: true }] }], expandAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandAll", required: false }] }], selectBlock: [{ type: i0.Output, args: ["selectBlock"] }], deleteBlock: [{ type: i0.Output, args: ["deleteBlock"] }], duplicateBlock: [{ type: i0.Output, args: ["duplicateBlock"] }], moveBlock: [{ type: i0.Output, args: ["moveBlock"] }], openBlockPicker: [{ type: i0.Output, args: ["openBlockPicker"] }] } });
4304
3973
 
4305
3974
  const FILES_QUERY = `
@@ -4414,10 +4083,10 @@ class ShopifyFilesService {
4414
4083
  return 'unknown';
4415
4084
  }
4416
4085
  }
4417
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyFilesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4418
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyFilesService, providedIn: 'root' });
4086
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4087
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilesService, providedIn: 'root' });
4419
4088
  }
4420
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyFilesService, decorators: [{
4089
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilesService, decorators: [{
4421
4090
  type: Injectable,
4422
4091
  args: [{ providedIn: 'root' }]
4423
4092
  }] });
@@ -4440,7 +4109,7 @@ class ShopifyFilePickerComponent {
4440
4109
  endCursor = null;
4441
4110
  hasNextPage = false;
4442
4111
  ngOnInit() {
4443
- this.searchSubject.pipe(debounceTime(300), tap$1(() => {
4112
+ this.searchSubject.pipe(debounceTime(300), tap(() => {
4444
4113
  this.files.set([]);
4445
4114
  this.endCursor = null;
4446
4115
  this.hasNextPage = false;
@@ -4553,8 +4222,8 @@ class ShopifyFilePickerComponent {
4553
4222
  },
4554
4223
  });
4555
4224
  }
4556
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyFilePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4557
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ShopifyFilePickerComponent, isStandalone: true, selector: "lib-shopify-file-picker", inputs: { mediaType: { classPropertyName: "mediaType", publicName: "mediaType", isSignal: true, isRequired: true, transformFunction: null }, currentValue: { classPropertyName: "currentValue", publicName: "currentValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected", closed: "closed" }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
4225
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4226
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ShopifyFilePickerComponent, isStandalone: true, selector: "lib-shopify-file-picker", inputs: { mediaType: { classPropertyName: "mediaType", publicName: "mediaType", isSignal: true, isRequired: true, transformFunction: null }, currentValue: { classPropertyName: "currentValue", publicName: "currentValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected", closed: "closed" }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
4558
4227
  <div
4559
4228
  class="file-picker-overlay"
4560
4229
  role="dialog"
@@ -4649,9 +4318,9 @@ class ShopifyFilePickerComponent {
4649
4318
  </div>
4650
4319
  </div>
4651
4320
  </div>
4652
- `, isInline: true, styles: [".file-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1100;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.file-picker{background:#fff;border-radius:12px;width:720px;max-width:95vw;height:80vh;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e3e3e3}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#8c9196;line-height:1;padding:.25rem;border-radius:8px;transition:all .15s}.close-btn:hover{background:#f6f6f7;color:#303030}.picker-search{padding:.75rem 1.25rem;border-bottom:1px solid #e3e3e3}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;color:#303030;outline:none;transition:border-color .15s,box-shadow .15s;box-sizing:border-box}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-search-input::placeholder{color:#8c9196}.picker-content{flex:1;overflow-y:auto;padding:1rem 1.25rem;min-height:0}.file-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem;align-content:start}.file-card{display:flex;flex-direction:column;align-items:center;padding:.375rem;border:2px solid #e3e3e3;border-radius:8px;background:#fff;cursor:pointer;transition:all .15s;position:relative;text-align:center;font-family:inherit;overflow:hidden}.file-card:hover{border-color:#005bd3;background:#f8fafe}.file-card:focus-visible{outline:2px solid #005bd3;outline-offset:2px}.file-card.selected{border-color:#005bd3;background:#f0f5ff;box-shadow:0 0 0 1px #005bd3}.file-card.current{border-color:#008060}.file-thumbnail{width:100%;height:80px;object-fit:cover;border-radius:6px;background:#f6f6f7}.video-thumbnail{width:100%;height:80px;display:flex;align-items:center;justify-content:center;background:#1a1a1a;border-radius:6px;color:#fff}.video-icon{opacity:.8}.file-name{display:block;margin-top:.375rem;font-size:.6875rem;color:#8c9196;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.current-badge{position:absolute;top:.375rem;right:.375rem;padding:.125rem .375rem;background:#008060;color:#fff;font-size:.625rem;font-weight:600;border-radius:6px}.picker-loading{padding:2rem;text-align:center;color:#8c9196;font-size:.8125rem}.picker-error{padding:2rem;text-align:center;color:#d72c0d}.picker-error p{margin:0 0 .75rem;font-size:.8125rem}.retry-btn{padding:.375rem .75rem;border:1px solid #d72c0d;border-radius:8px;background:#fff;color:#d72c0d;cursor:pointer;font-size:.8125rem;font-family:inherit;transition:background .15s}.retry-btn:hover{background:#fef2f0}.picker-empty{padding:2rem;text-align:center;color:#8c9196;font-size:.8125rem}.picker-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:.75rem 1.25rem;border-top:1px solid #e3e3e3}.cancel-btn{padding:.5rem 1rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#303030;transition:all .15s}.cancel-btn:hover{background:#f6f6f7}.select-btn{padding:.5rem 1rem;border:none;border-radius:8px;background:#005bd3;color:#fff;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;transition:background .15s}.select-btn:hover:not(:disabled){background:#004299}.select-btn:disabled{opacity:.5;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4321
+ `, isInline: true, styles: [".file-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1100}.file-picker{background:#fff;border-radius:12px;width:560px;max-height:70vh;display:flex;flex-direction:column;box-shadow:0 12px 40px #00000040}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e0e0e0}.picker-header h3{margin:0;font-size:1rem;font-weight:600;color:#303030}.close-btn{border:none;background:none;font-size:1.5rem;cursor:pointer;color:#666;line-height:1;padding:.25rem;border-radius:4px}.close-btn:hover{background:#f0f2f5;color:#303030}.picker-search{padding:.75rem 1.25rem;border-bottom:1px solid #e0e0e0}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.875rem;outline:none;transition:border-color .2s;box-sizing:border-box}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-content{flex:1;overflow-y:auto;padding:1rem 1.25rem;min-height:200px}.file-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem}.file-card{display:flex;flex-direction:column;align-items:center;padding:.5rem;border:2px solid #e0e0e0;border-radius:8px;background:#fff;cursor:pointer;transition:all .15s;position:relative;text-align:center}.file-card:hover{border-color:#005bd3;background:#f8fafe}.file-card:focus-visible{outline:2px solid #005bd3;outline-offset:2px}.file-card.selected{border-color:#005bd3;background:#f0f5ff;box-shadow:0 0 0 1px #005bd3}.file-card.current{border-color:#008060}.file-thumbnail{width:100%;aspect-ratio:1;object-fit:cover;border-radius:4px;background:#f0f2f5}.video-thumbnail{width:100%;aspect-ratio:1;display:flex;align-items:center;justify-content:center;background:#1a1a1a;border-radius:4px;color:#fff}.video-icon{opacity:.8}.file-name{display:block;margin-top:.375rem;font-size:.6875rem;color:#616161;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.current-badge{position:absolute;top:.25rem;right:.25rem;padding:.125rem .375rem;background:#008060;color:#fff;font-size:.625rem;font-weight:600;border-radius:4px}.picker-loading{padding:2rem;text-align:center;color:#616161;font-size:.875rem}.picker-error{padding:2rem;text-align:center;color:#d72c0d}.picker-error p{margin:0 0 .75rem;font-size:.875rem}.retry-btn{padding:.375rem .75rem;border:1px solid #d72c0d;border-radius:6px;background:#fff;color:#d72c0d;cursor:pointer;font-size:.8125rem}.retry-btn:hover{background:#fef2f0}.picker-empty{padding:2rem;text-align:center;color:#616161;font-size:.875rem}.picker-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:.75rem 1.25rem;border-top:1px solid #e0e0e0}.cancel-btn{padding:.5rem 1rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;color:#303030}.cancel-btn:hover{background:#f0f2f5}.select-btn{padding:.5rem 1rem;border:none;border-radius:8px;background:#005bd3;color:#fff;cursor:pointer;font-size:.8125rem;font-weight:500}.select-btn:hover:not(:disabled){background:#004299}.select-btn:disabled{opacity:.5;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4653
4322
  }
4654
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopifyFilePickerComponent, decorators: [{
4323
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilePickerComponent, decorators: [{
4655
4324
  type: Component,
4656
4325
  args: [{ selector: 'lib-shopify-file-picker', template: `
4657
4326
  <div
@@ -4748,7 +4417,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
4748
4417
  </div>
4749
4418
  </div>
4750
4419
  </div>
4751
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".file-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1100;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.file-picker{background:#fff;border-radius:12px;width:720px;max-width:95vw;height:80vh;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e3e3e3}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#8c9196;line-height:1;padding:.25rem;border-radius:8px;transition:all .15s}.close-btn:hover{background:#f6f6f7;color:#303030}.picker-search{padding:.75rem 1.25rem;border-bottom:1px solid #e3e3e3}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;color:#303030;outline:none;transition:border-color .15s,box-shadow .15s;box-sizing:border-box}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-search-input::placeholder{color:#8c9196}.picker-content{flex:1;overflow-y:auto;padding:1rem 1.25rem;min-height:0}.file-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem;align-content:start}.file-card{display:flex;flex-direction:column;align-items:center;padding:.375rem;border:2px solid #e3e3e3;border-radius:8px;background:#fff;cursor:pointer;transition:all .15s;position:relative;text-align:center;font-family:inherit;overflow:hidden}.file-card:hover{border-color:#005bd3;background:#f8fafe}.file-card:focus-visible{outline:2px solid #005bd3;outline-offset:2px}.file-card.selected{border-color:#005bd3;background:#f0f5ff;box-shadow:0 0 0 1px #005bd3}.file-card.current{border-color:#008060}.file-thumbnail{width:100%;height:80px;object-fit:cover;border-radius:6px;background:#f6f6f7}.video-thumbnail{width:100%;height:80px;display:flex;align-items:center;justify-content:center;background:#1a1a1a;border-radius:6px;color:#fff}.video-icon{opacity:.8}.file-name{display:block;margin-top:.375rem;font-size:.6875rem;color:#8c9196;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.current-badge{position:absolute;top:.375rem;right:.375rem;padding:.125rem .375rem;background:#008060;color:#fff;font-size:.625rem;font-weight:600;border-radius:6px}.picker-loading{padding:2rem;text-align:center;color:#8c9196;font-size:.8125rem}.picker-error{padding:2rem;text-align:center;color:#d72c0d}.picker-error p{margin:0 0 .75rem;font-size:.8125rem}.retry-btn{padding:.375rem .75rem;border:1px solid #d72c0d;border-radius:8px;background:#fff;color:#d72c0d;cursor:pointer;font-size:.8125rem;font-family:inherit;transition:background .15s}.retry-btn:hover{background:#fef2f0}.picker-empty{padding:2rem;text-align:center;color:#8c9196;font-size:.8125rem}.picker-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:.75rem 1.25rem;border-top:1px solid #e3e3e3}.cancel-btn{padding:.5rem 1rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#303030;transition:all .15s}.cancel-btn:hover{background:#f6f6f7}.select-btn{padding:.5rem 1rem;border:none;border-radius:8px;background:#005bd3;color:#fff;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;transition:background .15s}.select-btn:hover:not(:disabled){background:#004299}.select-btn:disabled{opacity:.5;cursor:not-allowed}\n"] }]
4420
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".file-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1100}.file-picker{background:#fff;border-radius:12px;width:560px;max-height:70vh;display:flex;flex-direction:column;box-shadow:0 12px 40px #00000040}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e0e0e0}.picker-header h3{margin:0;font-size:1rem;font-weight:600;color:#303030}.close-btn{border:none;background:none;font-size:1.5rem;cursor:pointer;color:#666;line-height:1;padding:.25rem;border-radius:4px}.close-btn:hover{background:#f0f2f5;color:#303030}.picker-search{padding:.75rem 1.25rem;border-bottom:1px solid #e0e0e0}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.875rem;outline:none;transition:border-color .2s;box-sizing:border-box}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-content{flex:1;overflow-y:auto;padding:1rem 1.25rem;min-height:200px}.file-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem}.file-card{display:flex;flex-direction:column;align-items:center;padding:.5rem;border:2px solid #e0e0e0;border-radius:8px;background:#fff;cursor:pointer;transition:all .15s;position:relative;text-align:center}.file-card:hover{border-color:#005bd3;background:#f8fafe}.file-card:focus-visible{outline:2px solid #005bd3;outline-offset:2px}.file-card.selected{border-color:#005bd3;background:#f0f5ff;box-shadow:0 0 0 1px #005bd3}.file-card.current{border-color:#008060}.file-thumbnail{width:100%;aspect-ratio:1;object-fit:cover;border-radius:4px;background:#f0f2f5}.video-thumbnail{width:100%;aspect-ratio:1;display:flex;align-items:center;justify-content:center;background:#1a1a1a;border-radius:4px;color:#fff}.video-icon{opacity:.8}.file-name{display:block;margin-top:.375rem;font-size:.6875rem;color:#616161;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.current-badge{position:absolute;top:.25rem;right:.25rem;padding:.125rem .375rem;background:#008060;color:#fff;font-size:.625rem;font-weight:600;border-radius:4px}.picker-loading{padding:2rem;text-align:center;color:#616161;font-size:.875rem}.picker-error{padding:2rem;text-align:center;color:#d72c0d}.picker-error p{margin:0 0 .75rem;font-size:.875rem}.retry-btn{padding:.375rem .75rem;border:1px solid #d72c0d;border-radius:6px;background:#fff;color:#d72c0d;cursor:pointer;font-size:.8125rem}.retry-btn:hover{background:#fef2f0}.picker-empty{padding:2rem;text-align:center;color:#616161;font-size:.875rem}.picker-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:.75rem 1.25rem;border-top:1px solid #e0e0e0}.cancel-btn{padding:.5rem 1rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;color:#303030}.cancel-btn:hover{background:#f0f2f5}.select-btn{padding:.5rem 1rem;border:none;border-radius:8px;background:#005bd3;color:#fff;cursor:pointer;font-size:.8125rem;font-weight:500}.select-btn:hover:not(:disabled){background:#004299}.select-btn:disabled{opacity:.5;cursor:not-allowed}\n"] }]
4752
4421
  }], propDecorators: { mediaType: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaType", required: true }] }], currentValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentValue", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], closed: [{ type: i0.Output, args: ["closed"] }], searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }] } });
4753
4422
 
4754
4423
  /**
@@ -4765,6 +4434,8 @@ class VisualEditorComponent {
4765
4434
  loadingStrategy = inject(PageLoadingStrategy);
4766
4435
  config = inject(VISUAL_EDITOR_CONFIG);
4767
4436
  dndService = inject(DragDropService);
4437
+ iframeBridge = inject(IframeBridgeService);
4438
+ storefrontUrlService = inject(StorefrontUrlService);
4768
4439
  sanitizer = inject(DomSanitizer);
4769
4440
  destroy$ = new Subject();
4770
4441
  // Configuration-driven UI options
@@ -4774,6 +4445,15 @@ class VisualEditorComponent {
4774
4445
  isPreviewMode = signal(false, ...(ngDevMode ? [{ debugName: "isPreviewMode" }] : []));
4775
4446
  editorContext = computed(() => ({ isEditor: !this.isPreviewMode() }), ...(ngDevMode ? [{ debugName: "editorContext" }] : []));
4776
4447
  canvasEl = viewChild('canvasEl', ...(ngDevMode ? [{ debugName: "canvasEl" }] : []));
4448
+ // Iframe preview
4449
+ previewFrame = viewChild('previewFrame', ...(ngDevMode ? [{ debugName: "previewFrame" }] : []));
4450
+ previewUrl = computed(() => {
4451
+ const url = this.storefrontUrlService.url();
4452
+ if (!url)
4453
+ return null;
4454
+ return this.sanitizer.bypassSecurityTrustResourceUrl(`${url}/kustomizer/editor`);
4455
+ }, ...(ngDevMode ? [{ debugName: "previewUrl" }] : []));
4456
+ iframeReady = false;
4777
4457
  propertiesTab = signal('props', ...(ngDevMode ? [{ debugName: "propertiesTab" }] : []));
4778
4458
  expandedGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedGroups" }] : []));
4779
4459
  expandedSections = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedSections" }] : []));
@@ -4898,23 +4578,19 @@ class VisualEditorComponent {
4898
4578
  });
4899
4579
  }, ...(ngDevMode ? [{ debugName: "filteredBlockPresetsForNestedSlot" }] : []));
4900
4580
  sectionIconMap = {
4901
- 'hero-section': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M2 4a2 2 0 012-2h12a2 2 0 012 2v4H2V4zm0 6h16v6a2 2 0 01-2 2H4a2 2 0 01-2-2v-6z"/></svg>',
4902
- 'features-grid': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M3 3h6v6H3V3zm8 0h6v6h-6V3zm-8 8h6v6H3v-6zm8 0h6v6h-6v-6z"/></svg>',
4903
- 'testimonials': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M2 5a2 2 0 012-2h12a2 2 0 012 2v7a2 2 0 01-2 2H6l-4 4V5z"/></svg>',
4904
- 'cta-section': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2a8 8 0 100 16 8 8 0 000-16zm1 11H9v-2h2v2zm0-4H9V5h2v4z"/></svg>',
4905
- 'footer-section': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M2 4a2 2 0 012-2h12a2 2 0 012 2v12a2 2 0 01-2 2H4a2 2 0 01-2-2V4zm2 10v2h12v-2H4z"/></svg>',
4906
- 'layout-section': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M3 3h14a1 1 0 011 1v12a1 1 0 01-1 1H3a1 1 0 01-1-1V4a1 1 0 011-1zm1 2v10h5V5H4zm7 0v10h5V5h-5z"/></svg>',
4581
+ 'hero-section': '🎯',
4582
+ 'features-grid': '',
4583
+ 'testimonials': '💬',
4584
+ 'cta-section': '📢',
4585
+ 'footer-section': '📋',
4907
4586
  };
4908
4587
  blockIconMap = {
4909
- 'text-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3h12a1 1 0 011 1v1a1 1 0 01-1 1h-5v10a1 1 0 01-2 0V6H4a1 1 0 01-1-1V4a1 1 0 011-1z"/></svg>',
4910
- 'button-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M3 6a2 2 0 012-2h10a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2V6zm4 4a1 1 0 100 2h6a1 1 0 100-2H7z"/></svg>',
4911
- 'image-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6zM6 7a1 1 0 110-2 1 1 0 010 2z"/></svg>',
4912
- 'icon-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2l2.39 4.84L18 7.77l-4 3.9.94 5.5L10 14.27l-4.94 2.9.94-5.5-4-3.9 5.61-.93L10 2z"/></svg>',
4913
- 'feature-item': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2l2.39 4.84L18 7.77l-4 3.9.94 5.5L10 14.27l-4.94 2.9.94-5.5-4-3.9 5.61-.93L10 2z"/></svg>',
4914
- 'card-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm0 2h12v4H4V5zm0 6h8v4H4v-4z"/></svg>',
4915
- 'video-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm4 3.5l5 3.5-5 3.5v-7z"/></svg>',
4916
- 'picture-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6zM6 7a1 1 0 110-2 1 1 0 010 2z"/></svg>',
4917
- 'flex-layout-block': '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M3 3h4v14H3V3zm5 0h4v14H8V3zm5 0h4v14h-4V3z"/></svg>',
4588
+ 'text-block': '📝',
4589
+ 'button-block': '🔘',
4590
+ 'image-block': '🖼️',
4591
+ 'icon-block': '',
4592
+ 'feature-item': '',
4593
+ 'card-block': '🃏',
4918
4594
  };
4919
4595
  selectedBlockDefinition = computed(() => this.facade.selectedBlockDefinition(), ...(ngDevMode ? [{ debugName: "selectedBlockDefinition" }] : []));
4920
4596
  // Inline name editing
@@ -4974,6 +4650,27 @@ class VisualEditorComponent {
4974
4650
  cancelEditingName() {
4975
4651
  this.isEditingName.set(false);
4976
4652
  }
4653
+ constructor() {
4654
+ // Sync sections to iframe whenever they change
4655
+ effect(() => {
4656
+ const sections = this.facade.sections();
4657
+ if (this.iframeReady && this.previewUrl()) {
4658
+ this.iframeBridge.sendPageUpdate(sections);
4659
+ }
4660
+ });
4661
+ // Sync selection to iframe
4662
+ effect(() => {
4663
+ const selected = this.facade.selectedElement()?.id ?? null;
4664
+ if (!this.iframeReady || !this.previewUrl())
4665
+ return;
4666
+ if (selected) {
4667
+ this.iframeBridge.sendSelectElement(selected);
4668
+ }
4669
+ else {
4670
+ this.iframeBridge.sendDeselect();
4671
+ }
4672
+ });
4673
+ }
4977
4674
  ngOnInit() {
4978
4675
  // Load page using the configured loading strategy
4979
4676
  this.loadingStrategy
@@ -4993,13 +4690,39 @@ class VisualEditorComponent {
4993
4690
  this.navigation.navigate({ type: 'page-list' });
4994
4691
  },
4995
4692
  });
4693
+ // Listen for iframe events
4694
+ if (this.previewUrl()) {
4695
+ this.iframeBridge
4696
+ .onReady()
4697
+ .pipe(takeUntil(this.destroy$))
4698
+ .subscribe(() => {
4699
+ this.iframeReady = true;
4700
+ // Send initial page state
4701
+ this.iframeBridge.sendPageUpdate(this.facade.sections());
4702
+ });
4703
+ this.iframeBridge
4704
+ .onElementClicked()
4705
+ .pipe(takeUntil(this.destroy$))
4706
+ .subscribe(({ elementId, sectionId }) => {
4707
+ this.facade.selectElement(sectionId, elementId);
4708
+ });
4709
+ }
4996
4710
  }
4997
4711
  ngOnDestroy() {
4998
4712
  this.destroy$.next();
4999
4713
  this.destroy$.complete();
4714
+ this.iframeBridge.disconnect();
5000
4715
  // Clear page from store when leaving editor
5001
4716
  this.facade.clearPage();
5002
4717
  }
4718
+ onIframeLoad() {
4719
+ const iframe = this.previewFrame()?.nativeElement;
4720
+ if (iframe) {
4721
+ const sfUrl = this.storefrontUrlService.url();
4722
+ const origin = sfUrl ? new URL(sfUrl).origin : '*';
4723
+ this.iframeBridge.connect(iframe, origin);
4724
+ }
4725
+ }
5003
4726
  goBack() {
5004
4727
  if (this.facade.isDirty()) {
5005
4728
  this.navigation
@@ -5064,25 +4787,17 @@ class VisualEditorComponent {
5064
4787
  },
5065
4788
  });
5066
4789
  }
5067
- defaultSectionIcon = '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M3 3h14a1 1 0 011 1v12a1 1 0 01-1 1H3a1 1 0 01-1-1V4a1 1 0 011-1zm1 2v10h12V5H4z"/></svg>';
5068
- defaultBlockIcon = '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2l1.5 3h3.5l-2.5 2.5 1 3.5-3.5-2-3.5 2 1-3.5L4 5h3.5L10 2zm-6 10h12v2H4v-2zm2 4h8v2H6v-2z"/></svg>';
5069
- toSafeIcon(icon) {
5070
- if (icon.trim().startsWith('<')) {
5071
- return this.sanitizer.bypassSecurityTrustHtml(icon);
5072
- }
5073
- return this.sanitizer.bypassSecurityTrustHtml(`<span style="font-family:'Material Icons',sans-serif;font-weight:normal;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility">${icon}</span>`);
5074
- }
5075
4790
  getIcon(def) {
5076
- return this.toSafeIcon(this.sectionIconMap[def.type] ?? def.icon ?? this.defaultSectionIcon);
4791
+ return this.sectionIconMap[def.type] ?? '📦';
5077
4792
  }
5078
4793
  getBlockIcon(def) {
5079
- return this.toSafeIcon(this.blockIconMap[def.type] ?? def.icon ?? this.defaultBlockIcon);
4794
+ return this.blockIconMap[def.type] ?? '🧩';
5080
4795
  }
5081
4796
  getBlockIconByType(type) {
5082
- return this.toSafeIcon(this.blockIconMap[type] ?? this.defaultBlockIcon);
4797
+ return this.blockIconMap[type] ?? '🧩';
5083
4798
  }
5084
4799
  getSectionIcon(type) {
5085
- return this.toSafeIcon(this.sectionIconMap[type] ?? this.defaultSectionIcon);
4800
+ return this.sectionIconMap[type] ?? '📦';
5086
4801
  }
5087
4802
  // Tree expansion
5088
4803
  isSectionExpanded(sectionId) {
@@ -5508,10 +5223,10 @@ class VisualEditorComponent {
5508
5223
  this.closeNestedBlockPicker();
5509
5224
  }
5510
5225
  getSectionPresetIcon(rp) {
5511
- return this.toSafeIcon(rp.displayIcon ?? this.sectionIconMap[rp.definition.type] ?? this.defaultSectionIcon);
5226
+ return rp.displayIcon ?? this.sectionIconMap[rp.definition.type] ?? '📦';
5512
5227
  }
5513
5228
  getBlockPresetIcon(rp) {
5514
- return this.toSafeIcon(rp.displayIcon ?? this.blockIconMap[rp.definition.type] ?? this.defaultBlockIcon);
5229
+ return rp.displayIcon ?? this.blockIconMap[rp.definition.type] ?? '🧩';
5515
5230
  }
5516
5231
  selectSection(section, event) {
5517
5232
  event.stopPropagation();
@@ -5755,15 +5470,15 @@ class VisualEditorComponent {
5755
5470
  return;
5756
5471
  this.facade.updateSectionProps(section.id, { [key]: '' });
5757
5472
  }
5758
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VisualEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5759
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: VisualEditorComponent, isStandalone: true, selector: "lib-visual-editor", viewQueries: [{ propertyName: "canvasEl", first: true, predicate: ["canvasEl"], descendants: true, isSignal: true }, { propertyName: "nameInput", first: true, predicate: ["nameInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
5473
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5474
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: VisualEditorComponent, isStandalone: true, selector: "lib-visual-editor", providers: [IframeBridgeService], viewQueries: [{ propertyName: "canvasEl", first: true, predicate: ["canvasEl"], descendants: true, isSignal: true }, { propertyName: "previewFrame", first: true, predicate: ["previewFrame"], descendants: true, isSignal: true }, { propertyName: "nameInput", first: true, predicate: ["nameInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
5760
5475
  <div class="editor-layout">
5761
5476
  <!-- Sidebar: Hierarchical Tree -->
5762
5477
  <aside class="sidebar">
5763
5478
  <div class="sidebar-header">
5764
5479
  <h2>Page Structure</h2>
5765
5480
  <div class="search-box">
5766
- <span class="search-icon"><svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor"><path d="M8 16a8 8 0 116.32-3.1l5.39 5.38a1 1 0 01-1.42 1.42l-5.38-5.39A8 8 0 018 16zm0-2A6 6 0 108 2a6 6 0 000 12z"/></svg></span>
5481
+ <span class="search-icon">🔍</span>
5767
5482
  <input
5768
5483
  type="text"
5769
5484
  class="search-input"
@@ -5811,7 +5526,7 @@ class VisualEditorComponent {
5811
5526
  <span class="expand-icon">&#9654;</span>
5812
5527
  </button>
5813
5528
  <span class="icon-drag-wrapper">
5814
- <span class="section-icon" [innerHTML]="getSectionIcon(section.type)"></span>
5529
+ <span class="section-icon">{{ getSectionIcon(section.type) }}</span>
5815
5530
  <span
5816
5531
  class="drag-handle section-drag-handle"
5817
5532
  role="button"
@@ -5913,7 +5628,7 @@ class VisualEditorComponent {
5913
5628
  }
5914
5629
  @for (rp of group.presets; track rp.displayName + '-' + rp.definition.type) {
5915
5630
  <button class="picker-item" (click)="addSectionFromPreset(rp)">
5916
- <span class="picker-icon" [innerHTML]="getSectionPresetIcon(rp)"></span>
5631
+ <span class="picker-icon">{{ getSectionPresetIcon(rp) }}</span>
5917
5632
  <div class="picker-info">
5918
5633
  <span class="picker-name">{{ rp.displayName }}</span>
5919
5634
  @if (rp.displayDescription) {
@@ -5951,7 +5666,7 @@ class VisualEditorComponent {
5951
5666
  <div class="picker-content">
5952
5667
  @for (rp of filteredBlockPresetsForSection(); track rp.displayName + '-' + rp.definition.type) {
5953
5668
  <button class="picker-item" (click)="addBlockToSectionFromPreset(blockPickerSection()!, rp)">
5954
- <span class="picker-icon" [innerHTML]="getBlockPresetIcon(rp)"></span>
5669
+ <span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
5955
5670
  <div class="picker-info">
5956
5671
  <span class="picker-name">{{ rp.displayName }}</span>
5957
5672
  @if (rp.displayDescription) {
@@ -5994,7 +5709,7 @@ class VisualEditorComponent {
5994
5709
  <div class="picker-content">
5995
5710
  @for (rp of filteredBlockPresetsForNestedSlot(); track rp.displayName + '-' + rp.definition.type) {
5996
5711
  <button class="picker-item" (click)="addNestedBlockFromPreset(rp)">
5997
- <span class="picker-icon" [innerHTML]="getBlockPresetIcon(rp)"></span>
5712
+ <span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
5998
5713
  <div class="picker-info">
5999
5714
  <span class="picker-name">{{ rp.displayName }}</span>
6000
5715
  @if (rp.displayDescription) {
@@ -6024,7 +5739,7 @@ class VisualEditorComponent {
6024
5739
  <div class="toolbar">
6025
5740
  <div class="toolbar-left">
6026
5741
  <button class="toolbar-btn back-btn" (click)="goBack()" title="Back to pages">
6027
- <svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" style="vertical-align: middle"><path d="M17 9H5.414l3.293-3.293a1 1 0 00-1.414-1.414l-5 5a1 1 0 000 1.414l5 5a1 1 0 001.414-1.414L5.414 11H17a1 1 0 100-2z"/></svg> Back
5742
+ Back
6028
5743
  </button>
6029
5744
  <button
6030
5745
  class="toolbar-btn"
@@ -6032,7 +5747,7 @@ class VisualEditorComponent {
6032
5747
  (click)="facade.undo()"
6033
5748
  title="Undo"
6034
5749
  >
6035
- <svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" style="vertical-align: middle"><path d="M7.5 4.5a1 1 0 00-1.7-.7l-4 4a1 1 0 000 1.4l4 4a1 1 0 001.7-.7V11h5a4 4 0 010 8H9a1 1 0 110-2h3.5a2 2 0 000-4h-5v1.5a1 1 0 01-1.7.7l-4-4a1 1 0 010-1.4l4-4a1 1 0 011.7.7V6h5a6 6 0 010 12H9a3 3 0 010-6h-1.5V4.5z"/></svg> Undo
5750
+ Undo
6036
5751
  </button>
6037
5752
  <button
6038
5753
  class="toolbar-btn"
@@ -6040,7 +5755,7 @@ class VisualEditorComponent {
6040
5755
  (click)="facade.redo()"
6041
5756
  title="Redo"
6042
5757
  >
6043
- <svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" style="vertical-align: middle; transform: scaleX(-1)"><path d="M7.5 4.5a1 1 0 00-1.7-.7l-4 4a1 1 0 000 1.4l4 4a1 1 0 001.7-.7V11h5a4 4 0 010 8H9a1 1 0 110-2h3.5a2 2 0 000-4h-5v1.5a1 1 0 01-1.7.7l-4-4a1 1 0 010-1.4l4-4a1 1 0 011.7.7V6h5a6 6 0 010 12H9a3 3 0 010-6h-1.5V4.5z"/></svg> Redo
5758
+ Redo
6044
5759
  </button>
6045
5760
  </div>
6046
5761
  <div class="toolbar-center">
@@ -6073,7 +5788,7 @@ class VisualEditorComponent {
6073
5788
  (click)="savePage()"
6074
5789
  title="Save changes"
6075
5790
  >
6076
- {{ isSaving() ? 'Saving...' : 'Save' }}
5791
+ {{ isSaving() ? 'Saving...' : '💾 Save' }}
6077
5792
  </button>
6078
5793
  }
6079
5794
  @if (facade.currentPage() && showPublishButtons()) {
@@ -6084,7 +5799,7 @@ class VisualEditorComponent {
6084
5799
  (click)="publishPage()"
6085
5800
  title="Publish page"
6086
5801
  >
6087
- {{ isPublishing() ? 'Publishing...' : 'Publish' }}
5802
+ {{ isPublishing() ? 'Publishing...' : '🚀 Publish' }}
6088
5803
  </button>
6089
5804
  } @else {
6090
5805
  <button
@@ -6093,57 +5808,68 @@ class VisualEditorComponent {
6093
5808
  (click)="unpublishPage()"
6094
5809
  title="Unpublish page"
6095
5810
  >
6096
- {{ isPublishing() ? 'Unpublishing...' : 'Unpublish' }}
5811
+ {{ isPublishing() ? 'Unpublishing...' : '📥 Unpublish' }}
6097
5812
  </button>
6098
5813
  }
6099
5814
  }
6100
5815
  <button class="toolbar-btn preview-btn" (click)="togglePreview()">
6101
- {{ isPreviewMode() ? 'Edit' : 'Preview' }}
5816
+ {{ isPreviewMode() ? '✏️ Edit' : '👁 Preview' }}
6102
5817
  </button>
6103
5818
  </div>
6104
5819
  </div>
6105
5820
 
6106
5821
  <!-- Canvas Content -->
6107
5822
  <div class="canvas" #canvasEl [class.preview-mode]="isPreviewMode()">
6108
- @if (facade.sections().length === 0) {
6109
- <div class="empty-state">
6110
- <div class="empty-icon"><svg width="48" height="48" viewBox="0 0 20 20" fill="currentColor"><path d="M6 2a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V7.414a2 2 0 00-.586-1.414l-3.414-3.414A2 2 0 0010.586 2H6zm0 2h4v3a1 1 0 001 1h3v8H6V4z"/></svg></div>
6111
- <h3>Start Building</h3>
6112
- <p>Click on a component from the sidebar to add it to your page</p>
6113
- </div>
5823
+ @if (previewUrl()) {
5824
+ <iframe
5825
+ #previewFrame
5826
+ class="preview-iframe"
5827
+ [src]="previewUrl()!"
5828
+ (load)="onIframeLoad()"
5829
+ allow="clipboard-read; clipboard-write"
5830
+ ></iframe>
6114
5831
  } @else {
6115
- @for (section of facade.sections(); track section.id; let idx = $index) {
6116
- <div
6117
- class="section-outer"
6118
- [class.selected]="facade.selectedSection()?.id === section.id"
6119
- >
6120
- @if (!isPreviewMode()) {
6121
- <div class="section-controls">
6122
- <span class="section-label">{{ getSectionName(section) }}</span>
6123
- <div class="section-actions">
6124
- @if (idx > 0) {
6125
- <button class="action-btn" (click)="moveUp(section.id, idx, $event)" title="Move up">↑</button>
6126
- }
6127
- @if (idx < facade.sections().length - 1) {
6128
- <button class="action-btn" (click)="moveDown(section.id, idx, $event)" title="Move down">↓</button>
6129
- }
6130
- @if (facade.isSectionDuplicable(section.type)) {
6131
- <button class="action-btn" (click)="duplicateSection(section.id, $event)" title="Duplicate">
6132
- <span class="material-icon">content_copy</span>
6133
- </button>
6134
- }
6135
- <button class="action-btn delete" (click)="deleteSection(section.id, $event)" title="Delete">×</button>
6136
- </div>
6137
- </div>
6138
- }
5832
+ <!-- Fallback: direct rendering when no storefront URL configured -->
5833
+ @if (facade.sections().length === 0) {
5834
+ <div class="empty-state">
5835
+ <div class="empty-icon">📄</div>
5836
+ <h3>Start Building</h3>
5837
+ <p>Click on a component from the sidebar to add it to your page</p>
5838
+ </div>
5839
+ } @else {
5840
+ @for (section of facade.sections(); track section.id; let idx = $index) {
6139
5841
  <div
6140
- class="section-wrapper"
6141
- [attr.data-section-id]="section.id"
6142
- (click)="selectSection(section, $event)"
5842
+ class="section-outer"
5843
+ [class.selected]="facade.selectedSection()?.id === section.id"
6143
5844
  >
6144
- <lib-dynamic-renderer [element]="section" [context]="editorContext()" />
5845
+ @if (!isPreviewMode()) {
5846
+ <div class="section-controls">
5847
+ <span class="section-label">{{ getSectionName(section) }}</span>
5848
+ <div class="section-actions">
5849
+ @if (idx > 0) {
5850
+ <button class="action-btn" (click)="moveUp(section.id, idx, $event)" title="Move up">↑</button>
5851
+ }
5852
+ @if (idx < facade.sections().length - 1) {
5853
+ <button class="action-btn" (click)="moveDown(section.id, idx, $event)" title="Move down">↓</button>
5854
+ }
5855
+ @if (facade.isSectionDuplicable(section.type)) {
5856
+ <button class="action-btn" (click)="duplicateSection(section.id, $event)" title="Duplicate">
5857
+ <span class="material-icon">content_copy</span>
5858
+ </button>
5859
+ }
5860
+ <button class="action-btn delete" (click)="deleteSection(section.id, $event)" title="Delete">×</button>
5861
+ </div>
5862
+ </div>
5863
+ }
5864
+ <div
5865
+ class="section-wrapper"
5866
+ [attr.data-section-id]="section.id"
5867
+ (click)="selectSection(section, $event)"
5868
+ >
5869
+ <lib-dynamic-renderer [element]="section" [context]="editorContext()" />
5870
+ </div>
6145
5871
  </div>
6146
- </div>
5872
+ }
6147
5873
  }
6148
5874
  }
6149
5875
  </div>
@@ -6533,7 +6259,7 @@ class VisualEditorComponent {
6533
6259
  [class.selected]="facade.selectedBlock()?.id === block.id"
6534
6260
  (click)="selectBlock(block)"
6535
6261
  >
6536
- <span class="block-icon" [innerHTML]="getBlockIconByType(block.type)"></span>
6262
+ <span class="block-icon">{{ getBlockIconByType(block.type) }}</span>
6537
6263
  <span class="block-name">{{ getBlockName(block) }}</span>
6538
6264
  <div class="block-actions">
6539
6265
  @if (idx > 0) {
@@ -6580,18 +6306,18 @@ class VisualEditorComponent {
6580
6306
  />
6581
6307
  }
6582
6308
  </div>
6583
- `, isInline: true, styles: [":host{display:block;height:calc(100vh - 56px);overflow:hidden;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.editor-layout{display:grid;grid-template-columns:280px 1fr 300px;height:100%;background:#f6f6f7}.sidebar{background:#fff;border-right:1px solid #e3e3e3;display:flex;flex-direction:column}.sidebar-header{padding:1rem;border-bottom:1px solid #e3e3e3}.sidebar-header h2{margin:0;font-size:.8125rem;font-weight:600;color:#303030}.search-box{display:none}.search-box:focus-within{border-color:#005bd3;background:#fff}.search-icon{font-size:.8125rem;opacity:.6}.search-input{flex:1;border:none;background:transparent;font-size:.8125rem;color:#303030;font-family:inherit;outline:none}.search-input::placeholder{color:#8c9196}.search-clear{padding:.125rem .375rem;border:none;background:#e3e3e3;border-radius:4px;cursor:pointer;font-size:.75rem;color:#616161;line-height:1}.search-clear:hover{background:#c9cccf}.search-no-results{padding:2rem 1rem;text-align:center;color:#8c9196}.search-no-results p{margin:0;font-size:.8125rem}.picker-search{padding:.5rem .75rem;border-bottom:1px solid #e3e3e3}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .2s,box-shadow .2s}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-search-input::placeholder{color:#8c9196}.section-tree{flex:1;overflow-y:auto;padding:.25rem 0}.tree-section{border-bottom:none}.tree-section.selected>.tree-section-header{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-section.selected>.tree-section-header .section-name,.tree-section.selected>.tree-section-header .expand-btn,.tree-section.selected>.tree-section-header .section-icon,.tree-section.selected>.tree-section-header .drag-handle,.tree-section.selected>.tree-section-header .tree-btn{color:inherit}.tree-section-header{display:flex;align-items:center;gap:.375rem;padding:.375rem .5rem;cursor:pointer;transition:background .15s}.tree-section-header:hover{background:#f6f6f7;border-radius:8px;margin:0 .375rem}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#8c9196;transition:transform .15s;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .15s}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-section-header:hover .icon-drag-wrapper .section-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .section-icon{visibility:hidden}.section-icon{font-size:.875rem;color:#8c9196;display:flex;align-items:center}.section-name{flex:1;font-size:.8125rem;font-weight:500;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.tree-section-header:hover .tree-actions,.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#8c9196;transition:all .15s}.tree-btn:hover{background:#e3e3e3;color:#303030}.tree-btn.delete:hover{background:#fef2f0;color:#d72c0d}.tree-section.drag-over-before>.tree-section-header{box-shadow:inset 0 2px #005bd3}.tree-section.drag-over-after>.tree-section-header{box-shadow:inset 0 -2px #005bd3}.tree-section.is-section-dragging{opacity:.4}.tree-section.is-section-dragging>.tree-section-header{cursor:grabbing}.drag-handle{cursor:grab;color:#8c9196;opacity:0;transition:opacity .15s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:4px}.drag-handle:hover{color:#303030;background:#e3e3e3}.drag-handle:focus-visible{opacity:1;outline:2px solid #005bd3;outline-offset:1px}.tree-section-header:hover .drag-handle{opacity:1}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.tree-section,.drag-handle,.tree-actions{transition:none}}.tree-section-content{padding-left:.5rem}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.375rem .75rem;cursor:pointer;transition:background .15s}.tree-block:hover{background:#f6f6f7;border-radius:8px;margin:0 .375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name{color:inherit}.block-indent{width:18px;height:1px;background:#e3e3e3;margin-left:10px}.block-name{flex:1;font-size:.8125rem;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1.5rem);margin:.25rem .75rem .375rem 2rem;padding:.375rem .5rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#005bd3;transition:all .15s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.8125rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}.add-section-area{padding:.75rem;border-top:1px solid #e3e3e3;position:relative}.add-section-btn{display:flex;align-items:center;justify-content:center;gap:.375rem;width:100%;padding:.625rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;color:#005bd3;transition:all .15s}.add-section-btn:hover{background:#f0f5ff}.section-picker{position:absolute;bottom:100%;left:.75rem;right:.75rem;background:#fff;border:1px solid #e3e3e3;border-radius:12px;box-shadow:0 -4px 12px #0000001a;max-height:300px;overflow-y:auto;z-index:100}.picker-item{display:flex;align-items:center;gap:.75rem;width:100%;padding:.625rem .75rem;border:none;border-bottom:1px solid #f6f6f7;background:#fff;cursor:pointer;text-align:left;font-family:inherit;transition:background .15s}.picker-item:last-child{border-bottom:none}.picker-item:hover{background:#f6f6f7}.picker-icon{font-size:1.25rem;color:#8c9196;display:flex;align-items:center;flex-shrink:0}.picker-info{display:flex;flex-direction:column;gap:.125rem}.picker-name{font-size:.8125rem;font-weight:500;color:#303030}.picker-desc{font-size:.75rem;color:#8c9196}.block-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.block-picker{background:#fff;border-radius:12px;width:340px;max-height:420px;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e3e3e3}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.picker-content{flex:1;overflow-y:auto;padding:.5rem}.picker-content .picker-item{border:1px solid #e3e3e3;border-radius:8px;margin-bottom:.5rem}.picker-content .picker-item:hover{border-color:#005bd3;background:#f8fafe}.picker-category{border-bottom:1px solid #f6f6f7}.picker-category:last-child{border-bottom:none}.picker-category-label{padding:.5rem .75rem .25rem;font-size:.6875rem;font-weight:600;color:#8c9196;text-transform:uppercase;letter-spacing:.3px}.picker-empty{padding:2rem;text-align:center;color:#8c9196;font-size:.8125rem}.canvas-area{display:flex;flex-direction:column;overflow:hidden}.toolbar{display:flex;align-items:center;justify-content:space-between;padding:0 1rem;height:56px;background:#fff;border-bottom:1px solid #e3e3e3}.toolbar-left,.toolbar-right{display:flex;gap:.5rem;align-items:center}.toolbar-center{display:flex;align-items:center}.toolbar-btn{padding:.375rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;font-weight:500;color:#303030;transition:all .15s}.toolbar-btn:hover:not(:disabled){background:#f6f6f7}.toolbar-btn:disabled{opacity:.5;cursor:not-allowed}.preview-btn,.publish-btn{background:#303030;color:#fff;border-color:#303030;border-radius:8px}.preview-btn:hover,.publish-btn:hover:not(:disabled){background:#1a1a1a!important}.back-btn{border-color:transparent;background:transparent;color:#303030}.back-btn:hover:not(:disabled){background:#f6f6f7}.section-count{font-size:.8125rem;color:#616161}.page-title{font-size:.875rem;font-weight:600;color:#303030;margin-right:.75rem}.page-title-link{text-decoration:none;color:inherit}.page-title-link:hover{text-decoration:underline}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.6875rem;font-weight:500;text-transform:capitalize;background:#ffd6a4;color:#b98900}.status-badge.published{background:#aee9d1;color:#0d542b}.dirty-indicator{margin-left:.75rem;font-size:.75rem;color:#b98900;font-weight:500}.canvas{flex:1;overflow-y:auto;padding:2rem;background:#e8e8e8}.canvas.preview-mode{padding:0;background:#fff}.canvas.preview-mode .section-outer{margin:0}.canvas.preview-mode .section-wrapper{border:none;border-radius:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#616161;text-align:center}.empty-icon{font-size:3rem;margin-bottom:1rem;color:#8c9196}.empty-state h3{margin:0 0 .5rem;color:#303030;font-size:1rem}.empty-state p{margin:0;font-size:.8125rem;color:#8c9196}.section-outer{position:relative;margin-bottom:0}.section-wrapper{border:1px solid transparent;border-radius:0;overflow:hidden;transition:border-color .15s;background:#fff}.section-outer:hover .section-wrapper{border-color:#005bd34d}.section-outer.selected .section-wrapper{border-color:#005bd366}.section-controls{position:absolute;top:-22px;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:.1875rem .75rem;background:#005bd3;color:#fff;font-size:.75rem;border-radius:8px 8px 0 0;opacity:0;pointer-events:none;transition:opacity .15s;z-index:10}.section-outer:hover .section-controls,.section-outer.selected .section-controls{opacity:1;pointer-events:auto}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-top-left-radius:0;border-top-right-radius:0}.section-label{font-weight:500}.section-actions{display:flex;gap:.25rem}.action-btn{padding:.1875rem .5rem;border:none;border-radius:4px;background:#ffffff26;color:#fff;cursor:pointer;font-size:.625rem;font-family:inherit;transition:background .15s}.action-btn:hover{background:#ffffff4d}.action-btn.delete:hover{background:#d72c0d}.material-icon{font-family:Material Icons,sans-serif;font-size:inherit;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.properties-panel{background:#fff;border-left:1px solid #e3e3e3;display:flex;flex-direction:column;overflow:hidden}.properties-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e3e3e3}.properties-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.editable-name{cursor:pointer;display:flex;align-items:center;gap:.375rem;border-radius:8px;padding:.25rem .375rem;margin:-.25rem -.375rem;transition:background .15s}.editable-name:hover{background:#f6f6f7}.edit-icon{opacity:0;color:#8c9196;transition:opacity .15s;display:flex;flex-shrink:0}.editable-name:hover .edit-icon{opacity:1}.name-edit-input{flex:1;padding:.375rem .5rem;border:1px solid #005bd3;border-radius:8px;font-size:.9375rem;font-weight:600;font-family:inherit;color:#303030;outline:none;box-shadow:0 0 0 1px #005bd3}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#8c9196;transition:color .15s}.close-btn:hover{color:#303030}.more-menu-btn{border:none;background:none;cursor:pointer;color:#8c9196;padding:.25rem;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:all .15s}.more-menu-btn:hover{background:#f6f6f7;color:#303030}.properties-content{flex:1;overflow-y:auto;padding:1rem}.property-group{margin-bottom:0;padding:1rem 0;border-top:1px solid #e3e3e3}.property-group:first-child{border-top:none}.group-title{display:flex;align-items:center;gap:.375rem;width:100%;padding:0;margin:0 0 .75rem;border:none;background:none;font-size:.8125rem;font-weight:600;font-family:inherit;color:#303030;cursor:pointer}.group-title:hover{color:#1a1a1a}.group-title:focus-visible{outline:2px solid #005bd3;outline-offset:2px;border-radius:4px}.group-title-text{flex:1;text-align:left}.group-toggle-icon{font-size:.5rem;color:#8c9196;transition:transform .15s;display:inline-block}.group-toggle-icon.collapsed{transform:rotate(-90deg)}.property-field{margin-bottom:1rem}.property-label{display:block;font-size:.8125rem;font-weight:500;margin-bottom:.375rem;color:#303030}.property-input,.property-select,.property-textarea{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;color:#303030;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.property-input:focus,.property-select:focus,.property-textarea:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.property-input::placeholder,.property-textarea::placeholder{color:#8c9196}.property-textarea{resize:vertical;font-family:SF Mono,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:.75rem}.color-field{display:flex;gap:.5rem;align-items:center}.color-picker{width:36px;height:36px;padding:2px;border:1px solid #e3e3e3;border-radius:6px;cursor:pointer}.color-text{flex:1}.no-selection{display:flex;align-items:center;justify-content:center;height:100%;color:#8c9196;text-align:center;padding:2rem}.no-selection p{margin:0;font-size:.8125rem}.panel-tabs{display:flex;border-bottom:1px solid #e3e3e3}.panel-tab{flex:1;padding:.75rem;border:none;border-bottom:2px solid transparent;background:#fff;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;color:#616161;transition:all .15s}.panel-tab:hover{background:#f6f6f7;color:#303030}.panel-tab.active{background:#fff;color:#303030;font-weight:600;border-bottom:2px solid #005bd3}.blocks-list{flex:1;overflow-y:auto;padding:1rem}.slot-group{margin-bottom:1.5rem}.slot-title{font-size:.75rem;font-weight:600;color:#8c9196;margin:0 0 .5rem;text-transform:uppercase;letter-spacing:.3px}.block-item{display:flex;align-items:center;gap:.5rem;padding:.5rem .625rem;border:1px solid #e3e3e3;border-radius:8px;margin-bottom:.375rem;cursor:pointer;transition:all .15s}.block-item:hover{border-color:#005bd3;background:#f8fafe}.block-item.selected{border-color:#005bd3;background:#005bd3;color:#fff}.block-item.selected .block-icon,.block-item.selected .block-name{color:inherit}.block-icon{font-size:.875rem;color:#8c9196;display:flex;align-items:center}.block-name{flex:1;font-size:.8125rem;font-weight:500;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.block-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.block-item:hover .block-actions{opacity:1}.mini-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.75rem;color:#8c9196;transition:all .15s}.mini-btn:hover{background:#e3e3e3;color:#303030}.mini-btn.delete:hover{background:#fef2f0;color:#d72c0d}.empty-slot{padding:.75rem;text-align:center;color:#8c9196;font-size:.8125rem;background:#f6f6f7;border:1px dashed #e3e3e3;border-radius:8px}.no-slots{padding:2rem;text-align:center;color:#8c9196}.no-slots p{margin:0;font-size:.8125rem}.delete-block-btn{width:auto;padding:0;margin-top:1rem;border:none;border-radius:0;background:transparent;color:#d72c0d;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;transition:color .15s}.delete-block-btn:hover{background:transparent;color:#bc2200;text-decoration:underline}.toggle-field{display:flex;align-items:center;justify-content:space-between}.toggle-label{font-size:.8125rem;color:#303030}.toggle-switch{position:relative;display:inline-flex;cursor:pointer}.toggle-input{position:absolute;opacity:0;width:0;height:0}.toggle-track{width:36px;height:20px;background:#8c9196;border-radius:10px;position:relative;transition:background .2s}.toggle-thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-input:checked+.toggle-track{background:#008060}.toggle-input:checked+.toggle-track .toggle-thumb{transform:translate(16px)}.toggle-input:focus-visible+.toggle-track{outline:2px solid #005bd3;outline-offset:2px}.toggle-description{display:block;font-size:.75rem;color:#8c9196;margin-top:.25rem}.media-field{display:flex;flex-direction:column;gap:.5rem}.media-preview{position:relative;border:1px solid #e3e3e3;border-radius:8px;overflow:hidden}.media-thumbnail{display:block;width:100%;max-height:160px;object-fit:cover;background:#f6f6f7}.media-remove-btn{position:absolute;top:.375rem;right:.375rem;width:22px;height:22px;border:none;border-radius:50%;background:#0009;color:#fff;cursor:pointer;font-size:.875rem;line-height:1;display:flex;align-items:center;justify-content:center;transition:background .15s}.media-remove-btn:hover{background:#d72c0d}.media-browse-btn{padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#303030;transition:all .15s}.media-browse-btn:hover{background:#f6f6f7;border-color:#8c9196}.media-url-input{font-size:.75rem;color:#616161}.video-preview{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#1a1a1a}.video-indicator{color:#fff;display:flex;align-items:center;flex-shrink:0}.video-url-text{flex:1;color:#8c9196;font-size:.6875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.video-preview .media-remove-btn{position:static;flex-shrink:0}\n"], dependencies: [{ kind: "component", type: DynamicRendererComponent, selector: "lib-dynamic-renderer", inputs: ["element", "context"] }, { kind: "component", type: BlockTreeItemComponent, selector: "lib-block-tree-item", inputs: ["block", "context", "index", "totalSiblings", "expandAll"], outputs: ["selectBlock", "deleteBlock", "duplicateBlock", "moveBlock", "openBlockPicker"] }, { kind: "component", type: ShopifyFilePickerComponent, selector: "lib-shopify-file-picker", inputs: ["mediaType", "currentValue"], outputs: ["fileSelected", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6309
+ `, isInline: true, styles: [":host{display:block;height:100vh;overflow:hidden}.editor-layout{display:grid;grid-template-columns:280px 1fr 300px;height:100%;background:#f0f2f5}.sidebar{background:#fff;border-right:1px solid #e0e0e0;display:flex;flex-direction:column}.sidebar-header{padding:1rem;border-bottom:1px solid #e0e0e0}.sidebar-header h2{margin:0;font-size:.875rem;font-weight:600;color:#333}.search-box{display:none}.search-box:focus-within{border-color:#005bd3;background:#fff}.search-icon{font-size:.875rem;opacity:.6}.search-input{flex:1;border:none;background:transparent;font-size:.8125rem;color:#333;outline:none}.search-input::placeholder{color:#999}.search-clear{padding:.125rem .375rem;border:none;background:#ddd;border-radius:3px;cursor:pointer;font-size:.75rem;color:#666;line-height:1}.search-clear:hover{background:#ccc}.search-no-results{padding:2rem 1rem;text-align:center;color:#888}.search-no-results p{margin:0;font-size:.875rem}.picker-search{padding:.5rem;border-bottom:1px solid #e0e0e0}.picker-search-input{width:87%;padding:.5rem .75rem;border:1px solid #e0e0e0;border-radius:4px;font-size:.8125rem;outline:none;transition:border-color .2s}.picker-search-input:focus{border-color:#005bd3}.picker-search-input::placeholder{color:#999}.section-tree{flex:1;overflow-y:auto;padding:.25rem 0}.tree-section{border-bottom:none}.tree-section.selected>.tree-section-header{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-section.selected>.tree-section-header .section-name,.tree-section.selected>.tree-section-header .expand-btn,.tree-section.selected>.tree-section-header .section-icon,.tree-section.selected>.tree-section-header .drag-handle,.tree-section.selected>.tree-section-header .tree-btn{color:inherit}.tree-section-header{display:flex;align-items:center;gap:.5rem;padding:.5rem;cursor:pointer;transition:background .2s}.tree-section-header:hover{background:#f5f5f5;border-radius:8px;margin:0 .375rem}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;transition:transform .2s;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.625rem;transition:transform .2s}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-section-header:hover .icon-drag-wrapper .section-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .section-icon{visibility:hidden}.section-icon{font-size:1rem}.section-name{flex:1;font-size:.8125rem;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.tree-section-header:hover .tree-actions,.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.6875rem;color:#666}.tree-btn:hover{background:#d0d0d0}.tree-btn.delete:hover{background:#e74c3c;color:#fff}.tree-section.drag-over-before>.tree-section-header{box-shadow:inset 0 2px #4a90d9}.tree-section.drag-over-after>.tree-section-header{box-shadow:inset 0 -2px #4a90d9}.tree-section.is-section-dragging{opacity:.4}.tree-section.is-section-dragging>.tree-section-header{cursor:grabbing}.drag-handle{cursor:grab;color:#999;opacity:0;transition:opacity .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:2px}.drag-handle:hover{color:#666;background:#e0e0e0}.drag-handle:focus-visible{opacity:1;outline:2px solid #4a90d9;outline-offset:1px}.tree-section-header:hover .drag-handle{opacity:1}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.tree-section,.drag-handle,.tree-actions{transition:none}}.tree-section-content{padding-left:.5rem}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.5rem .75rem;cursor:pointer;transition:background .2s}.tree-block:hover{background:#f5f5f5;border-radius:8px;margin:0 .375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name{color:inherit}.block-indent{width:20px;height:1px;background:#ddd;margin-left:10px}.block-icon{font-size:.875rem}.block-name{flex:1;font-size:.75rem;color:#555;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1.5rem);margin:.375rem .75rem .5rem 2rem;padding:.375rem .5rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.75rem;color:#005bd3;transition:all .2s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.875rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}.add-section-area{padding:.75rem;border-top:1px solid #e0e0e0;position:relative}.add-section-btn{display:flex;align-items:center;justify-content:center;gap:.375rem;width:100%;padding:.625rem;border:none;border-radius:6px;background:transparent;cursor:pointer;font-size:.8125rem;font-weight:500;color:#005bd3;transition:all .2s}.add-section-btn:hover{background:#f0f5ff}.section-picker{position:absolute;bottom:100%;left:.75rem;right:.75rem;background:#fff;border:1px solid #e0e0e0;border-radius:6px;box-shadow:0 -4px 12px #0000001a;max-height:300px;overflow-y:auto;z-index:100}.picker-item{display:flex;align-items:center;gap:.75rem;width:100%;padding:.75rem;border:none;border-bottom:1px solid #f0f0f0;background:#fff;cursor:pointer;text-align:left;transition:background .2s}.picker-item:last-child{border-bottom:none}.picker-item:hover{background:#f5f5f5}.picker-icon{font-family:Material Icons,sans-serif;font-size:1.5rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.picker-info{display:flex;flex-direction:column;gap:.125rem}.picker-name{font-size:.8125rem;font-weight:500;color:#333}.picker-desc{font-size:.6875rem;color:#888}.block-picker-overlay{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.block-picker{background:#fff;border-radius:8px;width:320px;max-height:400px;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600}.picker-content{flex:1;overflow-y:auto;padding:.5rem}.picker-content .picker-item{border:1px solid #e0e0e0;border-radius:6px;margin-bottom:.5rem}.picker-content .picker-item:hover{border-color:#005bd3}.picker-category{border-bottom:1px solid #f0f0f0}.picker-category:last-child{border-bottom:none}.picker-category-label{padding:.5rem .75rem .25rem;font-size:.6875rem;font-weight:600;color:#888}.picker-empty{padding:2rem;text-align:center;color:#888;font-size:.875rem}.canvas-area{display:flex;flex-direction:column;overflow:hidden}.toolbar{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem;background:#fff;border-bottom:1px solid #e0e0e0}.toolbar-left,.toolbar-right{display:flex;gap:.5rem}.toolbar-btn{padding:.375rem .75rem;border:1px solid #e0e0e0;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;transition:all .2s}.toolbar-btn:hover:not(:disabled){background:#f0f2f5}.toolbar-btn:disabled{opacity:.5;cursor:not-allowed}.preview-btn,.publish-btn{background:#303030;color:#fff;border-color:#303030;border-radius:8px}.preview-btn:hover,.publish-btn:hover:not(:disabled){background:#1a1a1a!important}.back-btn{border-color:transparent;background:transparent}.section-count{font-size:.875rem;color:#666}.page-title{font-size:.9375rem;font-weight:600;color:#333;margin-right:.75rem}.page-title-link{text-decoration:none;color:inherit}.page-title-link:hover{text-decoration:underline}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.6875rem;font-weight:500;text-transform:capitalize;background:#ffeaa7;color:#856404}.status-badge.published{background:#b4fed3;color:#0d542b}.dirty-indicator{margin-left:.75rem;font-size:.8125rem;color:#e67e22;font-weight:500}.canvas{flex:1;overflow-y:auto;padding:2rem;position:relative}.canvas.preview-mode{padding:0;background:#fff}.preview-iframe{width:100%;height:100%;border:none;position:absolute;inset:0}.canvas.preview-mode .section-outer{margin:0}.canvas.preview-mode .section-wrapper{border:none;border-radius:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#666;text-align:center}.empty-icon{font-size:4rem;margin-bottom:1rem}.empty-state h3{margin:0 0 .5rem;color:#333}.empty-state p{margin:0;font-size:.875rem}.section-outer{position:relative;margin-bottom:0}.section-wrapper{border:1px solid transparent;border-radius:0;overflow:hidden;transition:border-color .2s;background:#fff}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-color:#005bd366}.section-controls{position:absolute;top:-20px;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:.14rem .75rem;background:#005bd3;color:#fff;font-size:.75rem;border-radius:8px 8px 0 0;opacity:0;pointer-events:none;transition:opacity .2s;z-index:10}.section-outer:hover .section-controls,.section-outer.selected .section-controls{opacity:1;pointer-events:auto}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-top-left-radius:0;border-top-right-radius:0}.section-label{font-weight:500}.section-actions{display:flex;gap:.25rem}.action-btn{padding:.15rem .5rem;border:none;border-radius:4px;background:#ffffff26;color:#fff;cursor:pointer;font-size:.6rem}.action-btn:hover{background:#ffffff4d}.action-btn.delete:hover{background:#e74c3c}.material-icon{font-family:Material Icons,sans-serif;font-size:.75rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.properties-panel{background:#fff;border-left:1px solid #e0e0e0;display:flex;flex-direction:column;overflow:hidden}.properties-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0}.properties-header h3{margin:0;font-size:1rem;font-weight:600}.editable-name{cursor:pointer;display:flex;align-items:center;gap:.375rem;border-radius:4px;padding:.125rem .25rem;margin:-.125rem -.25rem;transition:background .2s}.editable-name:hover{background:#f0f2f5}.edit-icon{opacity:0;color:#999;transition:opacity .2s;display:flex;flex-shrink:0}.editable-name:hover .edit-icon{opacity:1}.name-edit-input{flex:1;padding:.375rem .5rem;border:1px solid #005bd3;border-radius:8px;font-size:1rem;font-weight:600;outline:none}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#666}.more-menu-btn{border:none;background:none;cursor:pointer;color:#616161;padding:.25rem;border-radius:6px;display:flex;align-items:center;justify-content:center;transition:background .2s}.more-menu-btn:hover{background:#f0f2f5;color:#303030}.properties-content{flex:1;overflow-y:auto;padding:1rem}.property-group{margin-bottom:0;padding:1rem 0;border-top:1px solid #e0e0e0}.property-group:first-child{border-top:none}.group-title{display:flex;align-items:center;gap:.375rem;width:100%;padding:0;margin:0 0 .75rem;border:none;background:none;font-size:.8125rem;font-weight:600;color:#303030;cursor:pointer}.group-title:hover{color:#333}.group-title:focus-visible{outline:2px solid #4a90d9;outline-offset:2px;border-radius:2px}.group-title-text{flex:1;text-align:left}.group-toggle-icon{font-size:.5rem;transition:transform .2s;display:inline-block}.group-toggle-icon.collapsed{transform:rotate(-90deg)}.property-field{margin-bottom:1rem}.property-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem;color:#616161}.property-input,.property-select,.property-textarea{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.875rem;transition:border-color .2s,box-shadow .2s}.property-input:focus,.property-select:focus,.property-textarea:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.property-textarea{resize:vertical;font-family:monospace;font-size:.75rem}.color-field{display:flex;gap:.5rem}.color-picker{width:40px;height:36px;padding:2px;border:1px solid #e0e0e0;border-radius:4px;cursor:pointer}.color-text{flex:1}.no-selection{display:flex;align-items:center;justify-content:center;height:100%;color:#666;text-align:center;padding:2rem}.no-selection p{margin:0;font-size:.875rem}.panel-tabs{display:flex;border-bottom:1px solid #e0e0e0}.panel-tab{flex:1;padding:.75rem;border:none;border-bottom:2px solid transparent;background:#fff;cursor:pointer;font-size:.75rem;font-weight:500;color:#666;transition:all .2s}.panel-tab:hover{background:#f8f9fa;color:#303030}.panel-tab.active{background:#fff;color:#303030;border-bottom:2px solid #005bd3}.blocks-list{flex:1;overflow-y:auto;padding:1rem}.slot-group{margin-bottom:1.5rem}.slot-title{font-size:.75rem;font-weight:600;color:#616161;margin:0 0 .5rem}.block-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;border:1px solid #e0e0e0;border-radius:4px;margin-bottom:.375rem;cursor:pointer;transition:all .2s}.block-item:hover{border-color:#005bd3;background:#f8f9fa}.block-item.selected{border-color:#005bd3;background:#005bd3;color:#fff}.block-item.selected .block-icon,.block-item.selected .block-name{color:inherit}.block-icon{font-size:1rem}.block-name{flex:1;font-size:.8rem;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.block-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.block-item:hover .block-actions{opacity:1}.mini-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.75rem;color:#666}.mini-btn:hover{background:#d0d0d0}.mini-btn.delete:hover{background:#e74c3c;color:#fff}.empty-slot{padding:.75rem;text-align:center;color:#999;font-size:.75rem;background:#f8f9fa;border:1px dashed #ddd;border-radius:4px}.no-slots{padding:2rem;text-align:center;color:#666}.no-slots p{margin:0;font-size:.875rem}.delete-block-btn{width:auto;padding:0;margin-top:1rem;border:none;border-radius:0;background:transparent;color:#d72c0d;cursor:pointer;font-size:.875rem;font-weight:500;transition:color .2s}.delete-block-btn:hover{background:transparent;color:#bc2200;text-decoration:underline}.toggle-field{display:flex;align-items:center;justify-content:space-between}.toggle-label{font-size:.875rem;color:#303030}.toggle-switch{position:relative;display:inline-flex;cursor:pointer}.toggle-input{position:absolute;opacity:0;width:0;height:0}.toggle-track{width:36px;height:20px;background:#b5b5b5;border-radius:10px;position:relative;transition:background .2s}.toggle-thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-input:checked+.toggle-track{background:#008060}.toggle-input:checked+.toggle-track .toggle-thumb{transform:translate(16px)}.toggle-input:focus-visible+.toggle-track{outline:2px solid #005bd3;outline-offset:2px}.toggle-description{display:block;font-size:.75rem;color:#616161;margin-top:.25rem}.media-field{display:flex;flex-direction:column;gap:.5rem}.media-preview{position:relative;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}.media-thumbnail{display:block;width:100%;max-height:160px;object-fit:cover;background:#f0f2f5}.media-remove-btn{position:absolute;top:.25rem;right:.25rem;width:22px;height:22px;border:none;border-radius:50%;background:#0009;color:#fff;cursor:pointer;font-size:.875rem;line-height:1;display:flex;align-items:center;justify-content:center}.media-remove-btn:hover{background:#d72c0d}.media-browse-btn{padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;color:#303030;transition:all .15s}.media-browse-btn:hover{background:#f0f2f5;border-color:#999}.media-url-input{font-size:.75rem;color:#616161}.video-preview{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#1a1a1a}.video-indicator{color:#fff;display:flex;align-items:center;flex-shrink:0}.video-url-text{flex:1;color:#ccc;font-size:.6875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.video-preview .media-remove-btn{position:static;flex-shrink:0}\n"], dependencies: [{ kind: "component", type: DynamicRendererComponent, selector: "lib-dynamic-renderer", inputs: ["element", "context"] }, { kind: "component", type: BlockTreeItemComponent, selector: "lib-block-tree-item", inputs: ["block", "context", "index", "totalSiblings", "expandAll"], outputs: ["selectBlock", "deleteBlock", "duplicateBlock", "moveBlock", "openBlockPicker"] }, { kind: "component", type: ShopifyFilePickerComponent, selector: "lib-shopify-file-picker", inputs: ["mediaType", "currentValue"], outputs: ["fileSelected", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6584
6310
  }
6585
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VisualEditorComponent, decorators: [{
6311
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditorComponent, decorators: [{
6586
6312
  type: Component,
6587
- args: [{ selector: 'lib-visual-editor', imports: [DynamicRendererComponent, BlockTreeItemComponent, ShopifyFilePickerComponent], template: `
6313
+ args: [{ selector: 'lib-visual-editor', imports: [DynamicRendererComponent, BlockTreeItemComponent, ShopifyFilePickerComponent], providers: [IframeBridgeService], template: `
6588
6314
  <div class="editor-layout">
6589
6315
  <!-- Sidebar: Hierarchical Tree -->
6590
6316
  <aside class="sidebar">
6591
6317
  <div class="sidebar-header">
6592
6318
  <h2>Page Structure</h2>
6593
6319
  <div class="search-box">
6594
- <span class="search-icon"><svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor"><path d="M8 16a8 8 0 116.32-3.1l5.39 5.38a1 1 0 01-1.42 1.42l-5.38-5.39A8 8 0 018 16zm0-2A6 6 0 108 2a6 6 0 000 12z"/></svg></span>
6320
+ <span class="search-icon">🔍</span>
6595
6321
  <input
6596
6322
  type="text"
6597
6323
  class="search-input"
@@ -6639,7 +6365,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6639
6365
  <span class="expand-icon">&#9654;</span>
6640
6366
  </button>
6641
6367
  <span class="icon-drag-wrapper">
6642
- <span class="section-icon" [innerHTML]="getSectionIcon(section.type)"></span>
6368
+ <span class="section-icon">{{ getSectionIcon(section.type) }}</span>
6643
6369
  <span
6644
6370
  class="drag-handle section-drag-handle"
6645
6371
  role="button"
@@ -6741,7 +6467,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6741
6467
  }
6742
6468
  @for (rp of group.presets; track rp.displayName + '-' + rp.definition.type) {
6743
6469
  <button class="picker-item" (click)="addSectionFromPreset(rp)">
6744
- <span class="picker-icon" [innerHTML]="getSectionPresetIcon(rp)"></span>
6470
+ <span class="picker-icon">{{ getSectionPresetIcon(rp) }}</span>
6745
6471
  <div class="picker-info">
6746
6472
  <span class="picker-name">{{ rp.displayName }}</span>
6747
6473
  @if (rp.displayDescription) {
@@ -6779,7 +6505,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6779
6505
  <div class="picker-content">
6780
6506
  @for (rp of filteredBlockPresetsForSection(); track rp.displayName + '-' + rp.definition.type) {
6781
6507
  <button class="picker-item" (click)="addBlockToSectionFromPreset(blockPickerSection()!, rp)">
6782
- <span class="picker-icon" [innerHTML]="getBlockPresetIcon(rp)"></span>
6508
+ <span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
6783
6509
  <div class="picker-info">
6784
6510
  <span class="picker-name">{{ rp.displayName }}</span>
6785
6511
  @if (rp.displayDescription) {
@@ -6822,7 +6548,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6822
6548
  <div class="picker-content">
6823
6549
  @for (rp of filteredBlockPresetsForNestedSlot(); track rp.displayName + '-' + rp.definition.type) {
6824
6550
  <button class="picker-item" (click)="addNestedBlockFromPreset(rp)">
6825
- <span class="picker-icon" [innerHTML]="getBlockPresetIcon(rp)"></span>
6551
+ <span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
6826
6552
  <div class="picker-info">
6827
6553
  <span class="picker-name">{{ rp.displayName }}</span>
6828
6554
  @if (rp.displayDescription) {
@@ -6852,7 +6578,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6852
6578
  <div class="toolbar">
6853
6579
  <div class="toolbar-left">
6854
6580
  <button class="toolbar-btn back-btn" (click)="goBack()" title="Back to pages">
6855
- <svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" style="vertical-align: middle"><path d="M17 9H5.414l3.293-3.293a1 1 0 00-1.414-1.414l-5 5a1 1 0 000 1.414l5 5a1 1 0 001.414-1.414L5.414 11H17a1 1 0 100-2z"/></svg> Back
6581
+ Back
6856
6582
  </button>
6857
6583
  <button
6858
6584
  class="toolbar-btn"
@@ -6860,7 +6586,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6860
6586
  (click)="facade.undo()"
6861
6587
  title="Undo"
6862
6588
  >
6863
- <svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" style="vertical-align: middle"><path d="M7.5 4.5a1 1 0 00-1.7-.7l-4 4a1 1 0 000 1.4l4 4a1 1 0 001.7-.7V11h5a4 4 0 010 8H9a1 1 0 110-2h3.5a2 2 0 000-4h-5v1.5a1 1 0 01-1.7.7l-4-4a1 1 0 010-1.4l4-4a1 1 0 011.7.7V6h5a6 6 0 010 12H9a3 3 0 010-6h-1.5V4.5z"/></svg> Undo
6589
+ Undo
6864
6590
  </button>
6865
6591
  <button
6866
6592
  class="toolbar-btn"
@@ -6868,7 +6594,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6868
6594
  (click)="facade.redo()"
6869
6595
  title="Redo"
6870
6596
  >
6871
- <svg width="14" height="14" viewBox="0 0 20 20" fill="currentColor" style="vertical-align: middle; transform: scaleX(-1)"><path d="M7.5 4.5a1 1 0 00-1.7-.7l-4 4a1 1 0 000 1.4l4 4a1 1 0 001.7-.7V11h5a4 4 0 010 8H9a1 1 0 110-2h3.5a2 2 0 000-4h-5v1.5a1 1 0 01-1.7.7l-4-4a1 1 0 010-1.4l4-4a1 1 0 011.7.7V6h5a6 6 0 010 12H9a3 3 0 010-6h-1.5V4.5z"/></svg> Redo
6597
+ Redo
6872
6598
  </button>
6873
6599
  </div>
6874
6600
  <div class="toolbar-center">
@@ -6901,7 +6627,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6901
6627
  (click)="savePage()"
6902
6628
  title="Save changes"
6903
6629
  >
6904
- {{ isSaving() ? 'Saving...' : 'Save' }}
6630
+ {{ isSaving() ? 'Saving...' : '💾 Save' }}
6905
6631
  </button>
6906
6632
  }
6907
6633
  @if (facade.currentPage() && showPublishButtons()) {
@@ -6912,7 +6638,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6912
6638
  (click)="publishPage()"
6913
6639
  title="Publish page"
6914
6640
  >
6915
- {{ isPublishing() ? 'Publishing...' : 'Publish' }}
6641
+ {{ isPublishing() ? 'Publishing...' : '🚀 Publish' }}
6916
6642
  </button>
6917
6643
  } @else {
6918
6644
  <button
@@ -6921,57 +6647,68 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
6921
6647
  (click)="unpublishPage()"
6922
6648
  title="Unpublish page"
6923
6649
  >
6924
- {{ isPublishing() ? 'Unpublishing...' : 'Unpublish' }}
6650
+ {{ isPublishing() ? 'Unpublishing...' : '📥 Unpublish' }}
6925
6651
  </button>
6926
6652
  }
6927
6653
  }
6928
6654
  <button class="toolbar-btn preview-btn" (click)="togglePreview()">
6929
- {{ isPreviewMode() ? 'Edit' : 'Preview' }}
6655
+ {{ isPreviewMode() ? '✏️ Edit' : '👁 Preview' }}
6930
6656
  </button>
6931
6657
  </div>
6932
6658
  </div>
6933
6659
 
6934
6660
  <!-- Canvas Content -->
6935
6661
  <div class="canvas" #canvasEl [class.preview-mode]="isPreviewMode()">
6936
- @if (facade.sections().length === 0) {
6937
- <div class="empty-state">
6938
- <div class="empty-icon"><svg width="48" height="48" viewBox="0 0 20 20" fill="currentColor"><path d="M6 2a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V7.414a2 2 0 00-.586-1.414l-3.414-3.414A2 2 0 0010.586 2H6zm0 2h4v3a1 1 0 001 1h3v8H6V4z"/></svg></div>
6939
- <h3>Start Building</h3>
6940
- <p>Click on a component from the sidebar to add it to your page</p>
6941
- </div>
6662
+ @if (previewUrl()) {
6663
+ <iframe
6664
+ #previewFrame
6665
+ class="preview-iframe"
6666
+ [src]="previewUrl()!"
6667
+ (load)="onIframeLoad()"
6668
+ allow="clipboard-read; clipboard-write"
6669
+ ></iframe>
6942
6670
  } @else {
6943
- @for (section of facade.sections(); track section.id; let idx = $index) {
6944
- <div
6945
- class="section-outer"
6946
- [class.selected]="facade.selectedSection()?.id === section.id"
6947
- >
6948
- @if (!isPreviewMode()) {
6949
- <div class="section-controls">
6950
- <span class="section-label">{{ getSectionName(section) }}</span>
6951
- <div class="section-actions">
6952
- @if (idx > 0) {
6953
- <button class="action-btn" (click)="moveUp(section.id, idx, $event)" title="Move up">↑</button>
6954
- }
6955
- @if (idx < facade.sections().length - 1) {
6956
- <button class="action-btn" (click)="moveDown(section.id, idx, $event)" title="Move down">↓</button>
6957
- }
6958
- @if (facade.isSectionDuplicable(section.type)) {
6959
- <button class="action-btn" (click)="duplicateSection(section.id, $event)" title="Duplicate">
6960
- <span class="material-icon">content_copy</span>
6961
- </button>
6962
- }
6963
- <button class="action-btn delete" (click)="deleteSection(section.id, $event)" title="Delete">×</button>
6964
- </div>
6965
- </div>
6966
- }
6671
+ <!-- Fallback: direct rendering when no storefront URL configured -->
6672
+ @if (facade.sections().length === 0) {
6673
+ <div class="empty-state">
6674
+ <div class="empty-icon">📄</div>
6675
+ <h3>Start Building</h3>
6676
+ <p>Click on a component from the sidebar to add it to your page</p>
6677
+ </div>
6678
+ } @else {
6679
+ @for (section of facade.sections(); track section.id; let idx = $index) {
6967
6680
  <div
6968
- class="section-wrapper"
6969
- [attr.data-section-id]="section.id"
6970
- (click)="selectSection(section, $event)"
6681
+ class="section-outer"
6682
+ [class.selected]="facade.selectedSection()?.id === section.id"
6971
6683
  >
6972
- <lib-dynamic-renderer [element]="section" [context]="editorContext()" />
6684
+ @if (!isPreviewMode()) {
6685
+ <div class="section-controls">
6686
+ <span class="section-label">{{ getSectionName(section) }}</span>
6687
+ <div class="section-actions">
6688
+ @if (idx > 0) {
6689
+ <button class="action-btn" (click)="moveUp(section.id, idx, $event)" title="Move up">↑</button>
6690
+ }
6691
+ @if (idx < facade.sections().length - 1) {
6692
+ <button class="action-btn" (click)="moveDown(section.id, idx, $event)" title="Move down">↓</button>
6693
+ }
6694
+ @if (facade.isSectionDuplicable(section.type)) {
6695
+ <button class="action-btn" (click)="duplicateSection(section.id, $event)" title="Duplicate">
6696
+ <span class="material-icon">content_copy</span>
6697
+ </button>
6698
+ }
6699
+ <button class="action-btn delete" (click)="deleteSection(section.id, $event)" title="Delete">×</button>
6700
+ </div>
6701
+ </div>
6702
+ }
6703
+ <div
6704
+ class="section-wrapper"
6705
+ [attr.data-section-id]="section.id"
6706
+ (click)="selectSection(section, $event)"
6707
+ >
6708
+ <lib-dynamic-renderer [element]="section" [context]="editorContext()" />
6709
+ </div>
6973
6710
  </div>
6974
- </div>
6711
+ }
6975
6712
  }
6976
6713
  }
6977
6714
  </div>
@@ -7361,7 +7098,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
7361
7098
  [class.selected]="facade.selectedBlock()?.id === block.id"
7362
7099
  (click)="selectBlock(block)"
7363
7100
  >
7364
- <span class="block-icon" [innerHTML]="getBlockIconByType(block.type)"></span>
7101
+ <span class="block-icon">{{ getBlockIconByType(block.type) }}</span>
7365
7102
  <span class="block-name">{{ getBlockName(block) }}</span>
7366
7103
  <div class="block-actions">
7367
7104
  @if (idx > 0) {
@@ -7408,8 +7145,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
7408
7145
  />
7409
7146
  }
7410
7147
  </div>
7411
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;height:calc(100vh - 56px);overflow:hidden;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.editor-layout{display:grid;grid-template-columns:280px 1fr 300px;height:100%;background:#f6f6f7}.sidebar{background:#fff;border-right:1px solid #e3e3e3;display:flex;flex-direction:column}.sidebar-header{padding:1rem;border-bottom:1px solid #e3e3e3}.sidebar-header h2{margin:0;font-size:.8125rem;font-weight:600;color:#303030}.search-box{display:none}.search-box:focus-within{border-color:#005bd3;background:#fff}.search-icon{font-size:.8125rem;opacity:.6}.search-input{flex:1;border:none;background:transparent;font-size:.8125rem;color:#303030;font-family:inherit;outline:none}.search-input::placeholder{color:#8c9196}.search-clear{padding:.125rem .375rem;border:none;background:#e3e3e3;border-radius:4px;cursor:pointer;font-size:.75rem;color:#616161;line-height:1}.search-clear:hover{background:#c9cccf}.search-no-results{padding:2rem 1rem;text-align:center;color:#8c9196}.search-no-results p{margin:0;font-size:.8125rem}.picker-search{padding:.5rem .75rem;border-bottom:1px solid #e3e3e3}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .2s,box-shadow .2s}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-search-input::placeholder{color:#8c9196}.section-tree{flex:1;overflow-y:auto;padding:.25rem 0}.tree-section{border-bottom:none}.tree-section.selected>.tree-section-header{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-section.selected>.tree-section-header .section-name,.tree-section.selected>.tree-section-header .expand-btn,.tree-section.selected>.tree-section-header .section-icon,.tree-section.selected>.tree-section-header .drag-handle,.tree-section.selected>.tree-section-header .tree-btn{color:inherit}.tree-section-header{display:flex;align-items:center;gap:.375rem;padding:.375rem .5rem;cursor:pointer;transition:background .15s}.tree-section-header:hover{background:#f6f6f7;border-radius:8px;margin:0 .375rem}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#8c9196;transition:transform .15s;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .15s}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-section-header:hover .icon-drag-wrapper .section-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .section-icon{visibility:hidden}.section-icon{font-size:.875rem;color:#8c9196;display:flex;align-items:center}.section-name{flex:1;font-size:.8125rem;font-weight:500;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.tree-section-header:hover .tree-actions,.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#8c9196;transition:all .15s}.tree-btn:hover{background:#e3e3e3;color:#303030}.tree-btn.delete:hover{background:#fef2f0;color:#d72c0d}.tree-section.drag-over-before>.tree-section-header{box-shadow:inset 0 2px #005bd3}.tree-section.drag-over-after>.tree-section-header{box-shadow:inset 0 -2px #005bd3}.tree-section.is-section-dragging{opacity:.4}.tree-section.is-section-dragging>.tree-section-header{cursor:grabbing}.drag-handle{cursor:grab;color:#8c9196;opacity:0;transition:opacity .15s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:4px}.drag-handle:hover{color:#303030;background:#e3e3e3}.drag-handle:focus-visible{opacity:1;outline:2px solid #005bd3;outline-offset:1px}.tree-section-header:hover .drag-handle{opacity:1}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.tree-section,.drag-handle,.tree-actions{transition:none}}.tree-section-content{padding-left:.5rem}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.375rem .75rem;cursor:pointer;transition:background .15s}.tree-block:hover{background:#f6f6f7;border-radius:8px;margin:0 .375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name{color:inherit}.block-indent{width:18px;height:1px;background:#e3e3e3;margin-left:10px}.block-name{flex:1;font-size:.8125rem;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1.5rem);margin:.25rem .75rem .375rem 2rem;padding:.375rem .5rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#005bd3;transition:all .15s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.8125rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}.add-section-area{padding:.75rem;border-top:1px solid #e3e3e3;position:relative}.add-section-btn{display:flex;align-items:center;justify-content:center;gap:.375rem;width:100%;padding:.625rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;color:#005bd3;transition:all .15s}.add-section-btn:hover{background:#f0f5ff}.section-picker{position:absolute;bottom:100%;left:.75rem;right:.75rem;background:#fff;border:1px solid #e3e3e3;border-radius:12px;box-shadow:0 -4px 12px #0000001a;max-height:300px;overflow-y:auto;z-index:100}.picker-item{display:flex;align-items:center;gap:.75rem;width:100%;padding:.625rem .75rem;border:none;border-bottom:1px solid #f6f6f7;background:#fff;cursor:pointer;text-align:left;font-family:inherit;transition:background .15s}.picker-item:last-child{border-bottom:none}.picker-item:hover{background:#f6f6f7}.picker-icon{font-size:1.25rem;color:#8c9196;display:flex;align-items:center;flex-shrink:0}.picker-info{display:flex;flex-direction:column;gap:.125rem}.picker-name{font-size:.8125rem;font-weight:500;color:#303030}.picker-desc{font-size:.75rem;color:#8c9196}.block-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.block-picker{background:#fff;border-radius:12px;width:340px;max-height:420px;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e3e3e3}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.picker-content{flex:1;overflow-y:auto;padding:.5rem}.picker-content .picker-item{border:1px solid #e3e3e3;border-radius:8px;margin-bottom:.5rem}.picker-content .picker-item:hover{border-color:#005bd3;background:#f8fafe}.picker-category{border-bottom:1px solid #f6f6f7}.picker-category:last-child{border-bottom:none}.picker-category-label{padding:.5rem .75rem .25rem;font-size:.6875rem;font-weight:600;color:#8c9196;text-transform:uppercase;letter-spacing:.3px}.picker-empty{padding:2rem;text-align:center;color:#8c9196;font-size:.8125rem}.canvas-area{display:flex;flex-direction:column;overflow:hidden}.toolbar{display:flex;align-items:center;justify-content:space-between;padding:0 1rem;height:56px;background:#fff;border-bottom:1px solid #e3e3e3}.toolbar-left,.toolbar-right{display:flex;gap:.5rem;align-items:center}.toolbar-center{display:flex;align-items:center}.toolbar-btn{padding:.375rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;font-weight:500;color:#303030;transition:all .15s}.toolbar-btn:hover:not(:disabled){background:#f6f6f7}.toolbar-btn:disabled{opacity:.5;cursor:not-allowed}.preview-btn,.publish-btn{background:#303030;color:#fff;border-color:#303030;border-radius:8px}.preview-btn:hover,.publish-btn:hover:not(:disabled){background:#1a1a1a!important}.back-btn{border-color:transparent;background:transparent;color:#303030}.back-btn:hover:not(:disabled){background:#f6f6f7}.section-count{font-size:.8125rem;color:#616161}.page-title{font-size:.875rem;font-weight:600;color:#303030;margin-right:.75rem}.page-title-link{text-decoration:none;color:inherit}.page-title-link:hover{text-decoration:underline}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.6875rem;font-weight:500;text-transform:capitalize;background:#ffd6a4;color:#b98900}.status-badge.published{background:#aee9d1;color:#0d542b}.dirty-indicator{margin-left:.75rem;font-size:.75rem;color:#b98900;font-weight:500}.canvas{flex:1;overflow-y:auto;padding:2rem;background:#e8e8e8}.canvas.preview-mode{padding:0;background:#fff}.canvas.preview-mode .section-outer{margin:0}.canvas.preview-mode .section-wrapper{border:none;border-radius:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#616161;text-align:center}.empty-icon{font-size:3rem;margin-bottom:1rem;color:#8c9196}.empty-state h3{margin:0 0 .5rem;color:#303030;font-size:1rem}.empty-state p{margin:0;font-size:.8125rem;color:#8c9196}.section-outer{position:relative;margin-bottom:0}.section-wrapper{border:1px solid transparent;border-radius:0;overflow:hidden;transition:border-color .15s;background:#fff}.section-outer:hover .section-wrapper{border-color:#005bd34d}.section-outer.selected .section-wrapper{border-color:#005bd366}.section-controls{position:absolute;top:-22px;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:.1875rem .75rem;background:#005bd3;color:#fff;font-size:.75rem;border-radius:8px 8px 0 0;opacity:0;pointer-events:none;transition:opacity .15s;z-index:10}.section-outer:hover .section-controls,.section-outer.selected .section-controls{opacity:1;pointer-events:auto}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-top-left-radius:0;border-top-right-radius:0}.section-label{font-weight:500}.section-actions{display:flex;gap:.25rem}.action-btn{padding:.1875rem .5rem;border:none;border-radius:4px;background:#ffffff26;color:#fff;cursor:pointer;font-size:.625rem;font-family:inherit;transition:background .15s}.action-btn:hover{background:#ffffff4d}.action-btn.delete:hover{background:#d72c0d}.material-icon{font-family:Material Icons,sans-serif;font-size:inherit;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.properties-panel{background:#fff;border-left:1px solid #e3e3e3;display:flex;flex-direction:column;overflow:hidden}.properties-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e3e3e3}.properties-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.editable-name{cursor:pointer;display:flex;align-items:center;gap:.375rem;border-radius:8px;padding:.25rem .375rem;margin:-.25rem -.375rem;transition:background .15s}.editable-name:hover{background:#f6f6f7}.edit-icon{opacity:0;color:#8c9196;transition:opacity .15s;display:flex;flex-shrink:0}.editable-name:hover .edit-icon{opacity:1}.name-edit-input{flex:1;padding:.375rem .5rem;border:1px solid #005bd3;border-radius:8px;font-size:.9375rem;font-weight:600;font-family:inherit;color:#303030;outline:none;box-shadow:0 0 0 1px #005bd3}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#8c9196;transition:color .15s}.close-btn:hover{color:#303030}.more-menu-btn{border:none;background:none;cursor:pointer;color:#8c9196;padding:.25rem;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:all .15s}.more-menu-btn:hover{background:#f6f6f7;color:#303030}.properties-content{flex:1;overflow-y:auto;padding:1rem}.property-group{margin-bottom:0;padding:1rem 0;border-top:1px solid #e3e3e3}.property-group:first-child{border-top:none}.group-title{display:flex;align-items:center;gap:.375rem;width:100%;padding:0;margin:0 0 .75rem;border:none;background:none;font-size:.8125rem;font-weight:600;font-family:inherit;color:#303030;cursor:pointer}.group-title:hover{color:#1a1a1a}.group-title:focus-visible{outline:2px solid #005bd3;outline-offset:2px;border-radius:4px}.group-title-text{flex:1;text-align:left}.group-toggle-icon{font-size:.5rem;color:#8c9196;transition:transform .15s;display:inline-block}.group-toggle-icon.collapsed{transform:rotate(-90deg)}.property-field{margin-bottom:1rem}.property-label{display:block;font-size:.8125rem;font-weight:500;margin-bottom:.375rem;color:#303030}.property-input,.property-select,.property-textarea{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;color:#303030;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.property-input:focus,.property-select:focus,.property-textarea:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.property-input::placeholder,.property-textarea::placeholder{color:#8c9196}.property-textarea{resize:vertical;font-family:SF Mono,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:.75rem}.color-field{display:flex;gap:.5rem;align-items:center}.color-picker{width:36px;height:36px;padding:2px;border:1px solid #e3e3e3;border-radius:6px;cursor:pointer}.color-text{flex:1}.no-selection{display:flex;align-items:center;justify-content:center;height:100%;color:#8c9196;text-align:center;padding:2rem}.no-selection p{margin:0;font-size:.8125rem}.panel-tabs{display:flex;border-bottom:1px solid #e3e3e3}.panel-tab{flex:1;padding:.75rem;border:none;border-bottom:2px solid transparent;background:#fff;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;color:#616161;transition:all .15s}.panel-tab:hover{background:#f6f6f7;color:#303030}.panel-tab.active{background:#fff;color:#303030;font-weight:600;border-bottom:2px solid #005bd3}.blocks-list{flex:1;overflow-y:auto;padding:1rem}.slot-group{margin-bottom:1.5rem}.slot-title{font-size:.75rem;font-weight:600;color:#8c9196;margin:0 0 .5rem;text-transform:uppercase;letter-spacing:.3px}.block-item{display:flex;align-items:center;gap:.5rem;padding:.5rem .625rem;border:1px solid #e3e3e3;border-radius:8px;margin-bottom:.375rem;cursor:pointer;transition:all .15s}.block-item:hover{border-color:#005bd3;background:#f8fafe}.block-item.selected{border-color:#005bd3;background:#005bd3;color:#fff}.block-item.selected .block-icon,.block-item.selected .block-name{color:inherit}.block-icon{font-size:.875rem;color:#8c9196;display:flex;align-items:center}.block-name{flex:1;font-size:.8125rem;font-weight:500;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.block-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.block-item:hover .block-actions{opacity:1}.mini-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.75rem;color:#8c9196;transition:all .15s}.mini-btn:hover{background:#e3e3e3;color:#303030}.mini-btn.delete:hover{background:#fef2f0;color:#d72c0d}.empty-slot{padding:.75rem;text-align:center;color:#8c9196;font-size:.8125rem;background:#f6f6f7;border:1px dashed #e3e3e3;border-radius:8px}.no-slots{padding:2rem;text-align:center;color:#8c9196}.no-slots p{margin:0;font-size:.8125rem}.delete-block-btn{width:auto;padding:0;margin-top:1rem;border:none;border-radius:0;background:transparent;color:#d72c0d;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;transition:color .15s}.delete-block-btn:hover{background:transparent;color:#bc2200;text-decoration:underline}.toggle-field{display:flex;align-items:center;justify-content:space-between}.toggle-label{font-size:.8125rem;color:#303030}.toggle-switch{position:relative;display:inline-flex;cursor:pointer}.toggle-input{position:absolute;opacity:0;width:0;height:0}.toggle-track{width:36px;height:20px;background:#8c9196;border-radius:10px;position:relative;transition:background .2s}.toggle-thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-input:checked+.toggle-track{background:#008060}.toggle-input:checked+.toggle-track .toggle-thumb{transform:translate(16px)}.toggle-input:focus-visible+.toggle-track{outline:2px solid #005bd3;outline-offset:2px}.toggle-description{display:block;font-size:.75rem;color:#8c9196;margin-top:.25rem}.media-field{display:flex;flex-direction:column;gap:.5rem}.media-preview{position:relative;border:1px solid #e3e3e3;border-radius:8px;overflow:hidden}.media-thumbnail{display:block;width:100%;max-height:160px;object-fit:cover;background:#f6f6f7}.media-remove-btn{position:absolute;top:.375rem;right:.375rem;width:22px;height:22px;border:none;border-radius:50%;background:#0009;color:#fff;cursor:pointer;font-size:.875rem;line-height:1;display:flex;align-items:center;justify-content:center;transition:background .15s}.media-remove-btn:hover{background:#d72c0d}.media-browse-btn{padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#303030;transition:all .15s}.media-browse-btn:hover{background:#f6f6f7;border-color:#8c9196}.media-url-input{font-size:.75rem;color:#616161}.video-preview{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#1a1a1a}.video-indicator{color:#fff;display:flex;align-items:center;flex-shrink:0}.video-url-text{flex:1;color:#8c9196;font-size:.6875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.video-preview .media-remove-btn{position:static;flex-shrink:0}\n"] }]
7412
- }], propDecorators: { canvasEl: [{ type: i0.ViewChild, args: ['canvasEl', { isSignal: true }] }], nameInput: [{ type: i0.ViewChild, args: ['nameInput', { isSignal: true }] }] } });
7148
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;height:100vh;overflow:hidden}.editor-layout{display:grid;grid-template-columns:280px 1fr 300px;height:100%;background:#f0f2f5}.sidebar{background:#fff;border-right:1px solid #e0e0e0;display:flex;flex-direction:column}.sidebar-header{padding:1rem;border-bottom:1px solid #e0e0e0}.sidebar-header h2{margin:0;font-size:.875rem;font-weight:600;color:#333}.search-box{display:none}.search-box:focus-within{border-color:#005bd3;background:#fff}.search-icon{font-size:.875rem;opacity:.6}.search-input{flex:1;border:none;background:transparent;font-size:.8125rem;color:#333;outline:none}.search-input::placeholder{color:#999}.search-clear{padding:.125rem .375rem;border:none;background:#ddd;border-radius:3px;cursor:pointer;font-size:.75rem;color:#666;line-height:1}.search-clear:hover{background:#ccc}.search-no-results{padding:2rem 1rem;text-align:center;color:#888}.search-no-results p{margin:0;font-size:.875rem}.picker-search{padding:.5rem;border-bottom:1px solid #e0e0e0}.picker-search-input{width:87%;padding:.5rem .75rem;border:1px solid #e0e0e0;border-radius:4px;font-size:.8125rem;outline:none;transition:border-color .2s}.picker-search-input:focus{border-color:#005bd3}.picker-search-input::placeholder{color:#999}.section-tree{flex:1;overflow-y:auto;padding:.25rem 0}.tree-section{border-bottom:none}.tree-section.selected>.tree-section-header{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-section.selected>.tree-section-header .section-name,.tree-section.selected>.tree-section-header .expand-btn,.tree-section.selected>.tree-section-header .section-icon,.tree-section.selected>.tree-section-header .drag-handle,.tree-section.selected>.tree-section-header .tree-btn{color:inherit}.tree-section-header{display:flex;align-items:center;gap:.5rem;padding:.5rem;cursor:pointer;transition:background .2s}.tree-section-header:hover{background:#f5f5f5;border-radius:8px;margin:0 .375rem}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;transition:transform .2s;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.625rem;transition:transform .2s}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-section-header:hover .icon-drag-wrapper .section-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .section-icon{visibility:hidden}.section-icon{font-size:1rem}.section-name{flex:1;font-size:.8125rem;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.tree-section-header:hover .tree-actions,.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.6875rem;color:#666}.tree-btn:hover{background:#d0d0d0}.tree-btn.delete:hover{background:#e74c3c;color:#fff}.tree-section.drag-over-before>.tree-section-header{box-shadow:inset 0 2px #4a90d9}.tree-section.drag-over-after>.tree-section-header{box-shadow:inset 0 -2px #4a90d9}.tree-section.is-section-dragging{opacity:.4}.tree-section.is-section-dragging>.tree-section-header{cursor:grabbing}.drag-handle{cursor:grab;color:#999;opacity:0;transition:opacity .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:2px}.drag-handle:hover{color:#666;background:#e0e0e0}.drag-handle:focus-visible{opacity:1;outline:2px solid #4a90d9;outline-offset:1px}.tree-section-header:hover .drag-handle{opacity:1}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.tree-section,.drag-handle,.tree-actions{transition:none}}.tree-section-content{padding-left:.5rem}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.5rem .75rem;cursor:pointer;transition:background .2s}.tree-block:hover{background:#f5f5f5;border-radius:8px;margin:0 .375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name{color:inherit}.block-indent{width:20px;height:1px;background:#ddd;margin-left:10px}.block-icon{font-size:.875rem}.block-name{flex:1;font-size:.75rem;color:#555;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1.5rem);margin:.375rem .75rem .5rem 2rem;padding:.375rem .5rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.75rem;color:#005bd3;transition:all .2s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.875rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}.add-section-area{padding:.75rem;border-top:1px solid #e0e0e0;position:relative}.add-section-btn{display:flex;align-items:center;justify-content:center;gap:.375rem;width:100%;padding:.625rem;border:none;border-radius:6px;background:transparent;cursor:pointer;font-size:.8125rem;font-weight:500;color:#005bd3;transition:all .2s}.add-section-btn:hover{background:#f0f5ff}.section-picker{position:absolute;bottom:100%;left:.75rem;right:.75rem;background:#fff;border:1px solid #e0e0e0;border-radius:6px;box-shadow:0 -4px 12px #0000001a;max-height:300px;overflow-y:auto;z-index:100}.picker-item{display:flex;align-items:center;gap:.75rem;width:100%;padding:.75rem;border:none;border-bottom:1px solid #f0f0f0;background:#fff;cursor:pointer;text-align:left;transition:background .2s}.picker-item:last-child{border-bottom:none}.picker-item:hover{background:#f5f5f5}.picker-icon{font-family:Material Icons,sans-serif;font-size:1.5rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.picker-info{display:flex;flex-direction:column;gap:.125rem}.picker-name{font-size:.8125rem;font-weight:500;color:#333}.picker-desc{font-size:.6875rem;color:#888}.block-picker-overlay{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.block-picker{background:#fff;border-radius:8px;width:320px;max-height:400px;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600}.picker-content{flex:1;overflow-y:auto;padding:.5rem}.picker-content .picker-item{border:1px solid #e0e0e0;border-radius:6px;margin-bottom:.5rem}.picker-content .picker-item:hover{border-color:#005bd3}.picker-category{border-bottom:1px solid #f0f0f0}.picker-category:last-child{border-bottom:none}.picker-category-label{padding:.5rem .75rem .25rem;font-size:.6875rem;font-weight:600;color:#888}.picker-empty{padding:2rem;text-align:center;color:#888;font-size:.875rem}.canvas-area{display:flex;flex-direction:column;overflow:hidden}.toolbar{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem;background:#fff;border-bottom:1px solid #e0e0e0}.toolbar-left,.toolbar-right{display:flex;gap:.5rem}.toolbar-btn{padding:.375rem .75rem;border:1px solid #e0e0e0;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;transition:all .2s}.toolbar-btn:hover:not(:disabled){background:#f0f2f5}.toolbar-btn:disabled{opacity:.5;cursor:not-allowed}.preview-btn,.publish-btn{background:#303030;color:#fff;border-color:#303030;border-radius:8px}.preview-btn:hover,.publish-btn:hover:not(:disabled){background:#1a1a1a!important}.back-btn{border-color:transparent;background:transparent}.section-count{font-size:.875rem;color:#666}.page-title{font-size:.9375rem;font-weight:600;color:#333;margin-right:.75rem}.page-title-link{text-decoration:none;color:inherit}.page-title-link:hover{text-decoration:underline}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.6875rem;font-weight:500;text-transform:capitalize;background:#ffeaa7;color:#856404}.status-badge.published{background:#b4fed3;color:#0d542b}.dirty-indicator{margin-left:.75rem;font-size:.8125rem;color:#e67e22;font-weight:500}.canvas{flex:1;overflow-y:auto;padding:2rem;position:relative}.canvas.preview-mode{padding:0;background:#fff}.preview-iframe{width:100%;height:100%;border:none;position:absolute;inset:0}.canvas.preview-mode .section-outer{margin:0}.canvas.preview-mode .section-wrapper{border:none;border-radius:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#666;text-align:center}.empty-icon{font-size:4rem;margin-bottom:1rem}.empty-state h3{margin:0 0 .5rem;color:#333}.empty-state p{margin:0;font-size:.875rem}.section-outer{position:relative;margin-bottom:0}.section-wrapper{border:1px solid transparent;border-radius:0;overflow:hidden;transition:border-color .2s;background:#fff}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-color:#005bd366}.section-controls{position:absolute;top:-20px;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:.14rem .75rem;background:#005bd3;color:#fff;font-size:.75rem;border-radius:8px 8px 0 0;opacity:0;pointer-events:none;transition:opacity .2s;z-index:10}.section-outer:hover .section-controls,.section-outer.selected .section-controls{opacity:1;pointer-events:auto}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-top-left-radius:0;border-top-right-radius:0}.section-label{font-weight:500}.section-actions{display:flex;gap:.25rem}.action-btn{padding:.15rem .5rem;border:none;border-radius:4px;background:#ffffff26;color:#fff;cursor:pointer;font-size:.6rem}.action-btn:hover{background:#ffffff4d}.action-btn.delete:hover{background:#e74c3c}.material-icon{font-family:Material Icons,sans-serif;font-size:.75rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.properties-panel{background:#fff;border-left:1px solid #e0e0e0;display:flex;flex-direction:column;overflow:hidden}.properties-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0}.properties-header h3{margin:0;font-size:1rem;font-weight:600}.editable-name{cursor:pointer;display:flex;align-items:center;gap:.375rem;border-radius:4px;padding:.125rem .25rem;margin:-.125rem -.25rem;transition:background .2s}.editable-name:hover{background:#f0f2f5}.edit-icon{opacity:0;color:#999;transition:opacity .2s;display:flex;flex-shrink:0}.editable-name:hover .edit-icon{opacity:1}.name-edit-input{flex:1;padding:.375rem .5rem;border:1px solid #005bd3;border-radius:8px;font-size:1rem;font-weight:600;outline:none}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#666}.more-menu-btn{border:none;background:none;cursor:pointer;color:#616161;padding:.25rem;border-radius:6px;display:flex;align-items:center;justify-content:center;transition:background .2s}.more-menu-btn:hover{background:#f0f2f5;color:#303030}.properties-content{flex:1;overflow-y:auto;padding:1rem}.property-group{margin-bottom:0;padding:1rem 0;border-top:1px solid #e0e0e0}.property-group:first-child{border-top:none}.group-title{display:flex;align-items:center;gap:.375rem;width:100%;padding:0;margin:0 0 .75rem;border:none;background:none;font-size:.8125rem;font-weight:600;color:#303030;cursor:pointer}.group-title:hover{color:#333}.group-title:focus-visible{outline:2px solid #4a90d9;outline-offset:2px;border-radius:2px}.group-title-text{flex:1;text-align:left}.group-toggle-icon{font-size:.5rem;transition:transform .2s;display:inline-block}.group-toggle-icon.collapsed{transform:rotate(-90deg)}.property-field{margin-bottom:1rem}.property-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem;color:#616161}.property-input,.property-select,.property-textarea{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.875rem;transition:border-color .2s,box-shadow .2s}.property-input:focus,.property-select:focus,.property-textarea:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.property-textarea{resize:vertical;font-family:monospace;font-size:.75rem}.color-field{display:flex;gap:.5rem}.color-picker{width:40px;height:36px;padding:2px;border:1px solid #e0e0e0;border-radius:4px;cursor:pointer}.color-text{flex:1}.no-selection{display:flex;align-items:center;justify-content:center;height:100%;color:#666;text-align:center;padding:2rem}.no-selection p{margin:0;font-size:.875rem}.panel-tabs{display:flex;border-bottom:1px solid #e0e0e0}.panel-tab{flex:1;padding:.75rem;border:none;border-bottom:2px solid transparent;background:#fff;cursor:pointer;font-size:.75rem;font-weight:500;color:#666;transition:all .2s}.panel-tab:hover{background:#f8f9fa;color:#303030}.panel-tab.active{background:#fff;color:#303030;border-bottom:2px solid #005bd3}.blocks-list{flex:1;overflow-y:auto;padding:1rem}.slot-group{margin-bottom:1.5rem}.slot-title{font-size:.75rem;font-weight:600;color:#616161;margin:0 0 .5rem}.block-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;border:1px solid #e0e0e0;border-radius:4px;margin-bottom:.375rem;cursor:pointer;transition:all .2s}.block-item:hover{border-color:#005bd3;background:#f8f9fa}.block-item.selected{border-color:#005bd3;background:#005bd3;color:#fff}.block-item.selected .block-icon,.block-item.selected .block-name{color:inherit}.block-icon{font-size:1rem}.block-name{flex:1;font-size:.8rem;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.block-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.block-item:hover .block-actions{opacity:1}.mini-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.75rem;color:#666}.mini-btn:hover{background:#d0d0d0}.mini-btn.delete:hover{background:#e74c3c;color:#fff}.empty-slot{padding:.75rem;text-align:center;color:#999;font-size:.75rem;background:#f8f9fa;border:1px dashed #ddd;border-radius:4px}.no-slots{padding:2rem;text-align:center;color:#666}.no-slots p{margin:0;font-size:.875rem}.delete-block-btn{width:auto;padding:0;margin-top:1rem;border:none;border-radius:0;background:transparent;color:#d72c0d;cursor:pointer;font-size:.875rem;font-weight:500;transition:color .2s}.delete-block-btn:hover{background:transparent;color:#bc2200;text-decoration:underline}.toggle-field{display:flex;align-items:center;justify-content:space-between}.toggle-label{font-size:.875rem;color:#303030}.toggle-switch{position:relative;display:inline-flex;cursor:pointer}.toggle-input{position:absolute;opacity:0;width:0;height:0}.toggle-track{width:36px;height:20px;background:#b5b5b5;border-radius:10px;position:relative;transition:background .2s}.toggle-thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-input:checked+.toggle-track{background:#008060}.toggle-input:checked+.toggle-track .toggle-thumb{transform:translate(16px)}.toggle-input:focus-visible+.toggle-track{outline:2px solid #005bd3;outline-offset:2px}.toggle-description{display:block;font-size:.75rem;color:#616161;margin-top:.25rem}.media-field{display:flex;flex-direction:column;gap:.5rem}.media-preview{position:relative;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}.media-thumbnail{display:block;width:100%;max-height:160px;object-fit:cover;background:#f0f2f5}.media-remove-btn{position:absolute;top:.25rem;right:.25rem;width:22px;height:22px;border:none;border-radius:50%;background:#0009;color:#fff;cursor:pointer;font-size:.875rem;line-height:1;display:flex;align-items:center;justify-content:center}.media-remove-btn:hover{background:#d72c0d}.media-browse-btn{padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;color:#303030;transition:all .15s}.media-browse-btn:hover{background:#f0f2f5;border-color:#999}.media-url-input{font-size:.75rem;color:#616161}.video-preview{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#1a1a1a}.video-indicator{color:#fff;display:flex;align-items:center;flex-shrink:0}.video-url-text{flex:1;color:#ccc;font-size:.6875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.video-preview .media-remove-btn{position:static;flex-shrink:0}\n"] }]
7149
+ }], ctorParameters: () => [], propDecorators: { canvasEl: [{ type: i0.ViewChild, args: ['canvasEl', { isSignal: true }] }], previewFrame: [{ type: i0.ViewChild, args: ['previewFrame', { isSignal: true }] }], nameInput: [{ type: i0.ViewChild, args: ['nameInput', { isSignal: true }] }] } });
7413
7150
 
7414
7151
  /**
7415
7152
  * Page manager component for listing, creating, and managing pages
@@ -7447,6 +7184,9 @@ class PageManagerComponent {
7447
7184
  },
7448
7185
  });
7449
7186
  }
7187
+ openSetup() {
7188
+ this.navigation.navigate({ type: 'setup' });
7189
+ }
7450
7190
  openCreateDialog() {
7451
7191
  this.showCreateDialog.set(true);
7452
7192
  this.newPageTitle.set('');
@@ -7538,14 +7278,19 @@ class PageManagerComponent {
7538
7278
  year: 'numeric',
7539
7279
  });
7540
7280
  }
7541
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageManagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7542
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: PageManagerComponent, isStandalone: true, selector: "lib-page-manager", ngImport: i0, template: `
7281
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageManagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7282
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: PageManagerComponent, isStandalone: true, selector: "lib-page-manager", ngImport: i0, template: `
7543
7283
  <div class="page-manager">
7544
7284
  <header class="manager-header">
7545
7285
  <h1>Pages</h1>
7546
- <button class="btn-primary" (click)="openCreateDialog()">
7547
- + New Page
7548
- </button>
7286
+ <div class="header-actions">
7287
+ <button class="btn-secondary" (click)="openSetup()">
7288
+ Setup
7289
+ </button>
7290
+ <button class="btn-primary" (click)="openCreateDialog()">
7291
+ + New Page
7292
+ </button>
7293
+ </div>
7549
7294
  </header>
7550
7295
 
7551
7296
  @if (isLoading()) {
@@ -7559,7 +7304,7 @@ class PageManagerComponent {
7559
7304
  </div>
7560
7305
  } @else if (pages().length === 0) {
7561
7306
  <div class="empty-state">
7562
- <div class="empty-icon"><svg width="40" height="40" viewBox="0 0 20 20" fill="currentColor"><path d="M6 2a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V7.414A2 2 0 0015.414 6L12 2.586A2 2 0 0010.586 2H6zm0 2h4v3a1 1 0 001 1h3v8H6V4zm6 .414L14.586 7H12V4.414z"/></svg></div>
7307
+ <div class="empty-icon">📄</div>
7563
7308
  <h2>No pages yet</h2>
7564
7309
  <p>Create your first page to get started</p>
7565
7310
  <button class="btn-primary" (click)="openCreateDialog()">
@@ -7591,21 +7336,21 @@ class PageManagerComponent {
7591
7336
  (click)="editPage(page.id); $event.stopPropagation()"
7592
7337
  title="Edit"
7593
7338
  >
7594
- <svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M14.846 1.403a2.45 2.45 0 013.464 3.464l-1.241 1.24-3.464-3.463 1.241-1.241zm-2.654 2.654L2 14.25V17.5h3.464L15.656 7.308l-3.464-3.25z"/></svg>
7339
+ ✏️
7595
7340
  </button>
7596
7341
  <button
7597
7342
  class="action-btn"
7598
7343
  (click)="duplicatePage(page.id, $event)"
7599
7344
  title="Duplicate"
7600
7345
  >
7601
- <svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M8 2a2 2 0 00-2 2v10a2 2 0 002 2h6a2 2 0 002-2V4a2 2 0 00-2-2H8zm0 2h6v10H8V4zM4 6a2 2 0 00-2 2v8a2 2 0 002 2h6a2 2 0 002-2h-2a4 4 0 01-4-4V6H4z"/></svg>
7346
+ 📋
7602
7347
  </button>
7603
7348
  <button
7604
7349
  class="action-btn delete"
7605
7350
  (click)="confirmDelete(page, $event)"
7606
7351
  title="Delete"
7607
7352
  >
7608
- <svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M8 3a1 1 0 00-1 1H5a1 1 0 000 2h10a1 1 0 100-2h-2a1 1 0 00-1-1H8zm-2 4h8l-.5 8.5a1 1 0 01-1 .5h-5a1 1 0 01-1-.5L6 7z"/></svg>
7353
+ 🗑️
7609
7354
  </button>
7610
7355
  </span>
7611
7356
  </div>
@@ -7686,17 +7431,22 @@ class PageManagerComponent {
7686
7431
  </div>
7687
7432
  }
7688
7433
  </div>
7689
- `, isInline: true, styles: [":host{display:block;min-height:100vh;background:#f6f6f7;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.page-manager{max-width:1200px;margin:0 auto;padding:2rem}.manager-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem}.manager-header h1{margin:0;font-size:1.5rem;font-weight:600;color:#303030}.btn-primary{padding:.625rem 1.25rem;border:none;border-radius:8px;background:#303030;color:#fff;font-size:.8125rem;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s}.btn-primary:hover{background:#1a1a1a}.btn-primary:disabled{opacity:.5;cursor:not-allowed}.btn-secondary{padding:.625rem 1.25rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;color:#303030;font-size:.8125rem;font-weight:500;font-family:inherit;cursor:pointer;transition:all .15s}.btn-secondary:hover{background:#f6f6f7}.btn-danger{padding:.625rem 1.25rem;border:none;border-radius:8px;background:#d72c0d;color:#fff;font-size:.8125rem;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s}.btn-danger:hover{background:#bc2200}.btn-danger:disabled{opacity:.5;cursor:not-allowed}.loading-state,.error-state,.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4rem 2rem;background:#fff;border-radius:12px;text-align:center;box-shadow:0 1px #0000000d;border:1px solid #e3e3e3}.empty-icon{margin-bottom:1rem;color:#8c9196;display:flex;align-items:center;justify-content:center}.empty-state h2{margin:0 0 .5rem;color:#303030;font-size:1.125rem}.empty-state p{margin:0 0 1.5rem;color:#8c9196;font-size:.8125rem}.error-state{color:#d72c0d}.pages-table{background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px #0000000d;border:1px solid #e3e3e3}.table-header,.table-row{display:grid;grid-template-columns:1fr 150px 100px 150px 120px;gap:1rem;padding:.875rem 1.5rem;align-items:center}.table-header{background:#f6f6f7;font-size:.75rem;font-weight:600;text-transform:uppercase;color:#8c9196;letter-spacing:.3px;border-bottom:1px solid #e3e3e3}.table-row{border-top:1px solid #f6f6f7;cursor:pointer;transition:background .15s}.table-row:first-of-type{border-top:none}.table-row:hover{background:#f6f6f7}.col-title{font-weight:500;color:#303030;font-size:.8125rem}.col-slug{font-size:.75rem;color:#8c9196;font-family:SF Mono,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.75rem;font-weight:500;text-transform:capitalize;background:#ffd6a4;color:#b98900}.status-badge.published{background:#aee9d1;color:#0d542b}.col-updated{font-size:.8125rem;color:#8c9196}.col-actions{display:flex;gap:.375rem}.action-btn{padding:.375rem;border:none;border-radius:8px;background:transparent;cursor:pointer;color:#8c9196;display:inline-flex;align-items:center;justify-content:center;transition:all .15s}.action-btn:hover{background:#f6f6f7;color:#303030}.action-btn.delete:hover{background:#fef2f0;color:#d72c0d}.dialog-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog{background:#fff;border-radius:12px;width:100%;max-width:480px;box-shadow:0 8px 32px #0003}.dialog-small{max-width:400px}.dialog-header{display:flex;justify-content:space-between;align-items:center;padding:1.25rem 1.5rem;border-bottom:1px solid #e3e3e3}.dialog-header h2{margin:0;font-size:1.125rem;font-weight:600;color:#303030}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#8c9196;padding:.25rem;line-height:1;border-radius:8px;transition:all .15s}.close-btn:hover{color:#303030;background:#f6f6f7}.dialog-content{padding:1.5rem}.dialog-content p{margin:0 0 .5rem;color:#303030;font-size:.8125rem}.dialog-content .warning{color:#d72c0d;font-size:.8125rem}.form-field{margin-bottom:1.25rem}.form-field label{display:block;margin-bottom:.375rem;font-size:.8125rem;font-weight:500;color:#303030}.form-field input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;color:#303030;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.form-field input:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.form-field input::placeholder{color:#8c9196}.slug-field{display:flex;align-items:center;border:1px solid #c9cccf;border-radius:8px;overflow:hidden;transition:border-color .15s,box-shadow .15s}.slug-field:focus-within{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.slug-prefix{padding:.5rem .75rem;background:#f6f6f7;color:#8c9196;font-size:.8125rem;font-family:SF Mono,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;border-right:1px solid #e3e3e3}.slug-field input{border:none;border-radius:0}.slug-field input:focus{border-color:transparent;box-shadow:none}.form-error{margin-bottom:1rem;padding:.75rem;background:#fef2f0;border-radius:8px;color:#d72c0d;font-size:.8125rem}.dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1.25rem 1.5rem;border-top:1px solid #e3e3e3}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7434
+ `, isInline: true, styles: [":host{display:block;min-height:100vh;background:#f0f2f5}.page-manager{max-width:1200px;margin:0 auto;padding:2rem}.manager-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem}.header-actions{display:flex;gap:.75rem;align-items:center}.manager-header h1{margin:0;font-size:1.75rem;font-weight:600;color:#1a1a2e}.btn-primary{padding:.75rem 1.5rem;border:none;border-radius:6px;background:#1a1a2e;color:#fff;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .2s}.btn-primary:hover{background:#2a2a3e}.btn-primary:disabled{opacity:.6;cursor:not-allowed}.btn-secondary{padding:.75rem 1.5rem;border:1px solid #e0e0e0;border-radius:6px;background:#fff;color:#333;font-size:.875rem;font-weight:500;cursor:pointer;transition:all .2s}.btn-secondary:hover{background:#f5f5f5}.btn-danger{padding:.75rem 1.5rem;border:none;border-radius:6px;background:#e74c3c;color:#fff;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .2s}.btn-danger:hover{background:#c0392b}.btn-danger:disabled{opacity:.6;cursor:not-allowed}.loading-state,.error-state,.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4rem 2rem;background:#fff;border-radius:8px;text-align:center}.empty-icon{font-size:4rem;margin-bottom:1rem}.empty-state h2{margin:0 0 .5rem;color:#333}.empty-state p{margin:0 0 1.5rem;color:#666}.error-state{color:#e74c3c}.pages-table{background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 1px 3px #0000001a}.table-header,.table-row{display:grid;grid-template-columns:1fr 150px 100px 150px 120px;gap:1rem;padding:1rem 1.5rem;align-items:center}.table-header{background:#f8f9fa;font-size:.75rem;font-weight:600;text-transform:uppercase;color:#666;letter-spacing:.5px}.table-row{border-top:1px solid #e0e0e0;cursor:pointer;transition:background .2s}.table-row:hover{background:#f8f9fa}.col-title{font-weight:500;color:#333}.col-slug{font-size:.8125rem;color:#666;font-family:monospace}.status-badge{display:inline-block;padding:.25rem .5rem;border-radius:4px;font-size:.75rem;font-weight:500;text-transform:capitalize;background:#ffeaa7;color:#856404}.status-badge.published{background:#d4edda;color:#155724}.col-updated{font-size:.8125rem;color:#666}.col-actions{display:flex;gap:.5rem}.action-btn{padding:.375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:1rem;transition:background .2s}.action-btn:hover{background:#e0e0e0}.action-btn.delete:hover{background:#fde2e2}.dialog-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog{background:#fff;border-radius:8px;width:100%;max-width:480px;box-shadow:0 8px 32px #0003}.dialog-small{max-width:400px}.dialog-header{display:flex;justify-content:space-between;align-items:center;padding:1.5rem;border-bottom:1px solid #e0e0e0}.dialog-header h2{margin:0;font-size:1.25rem;font-weight:600}.close-btn{border:none;background:none;font-size:1.5rem;cursor:pointer;color:#666;padding:0;line-height:1}.close-btn:hover{color:#333}.dialog-content{padding:1.5rem}.dialog-content p{margin:0 0 .5rem;color:#333}.dialog-content .warning{color:#e74c3c;font-size:.875rem}.form-field{margin-bottom:1.25rem}.form-field label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:#333}.form-field input{width:100%;padding:.75rem;border:1px solid #e0e0e0;border-radius:6px;font-size:.875rem;transition:border-color .2s}.form-field input:focus{outline:none;border-color:#1a1a2e}.slug-field{display:flex;align-items:center;border:1px solid #e0e0e0;border-radius:6px;overflow:hidden}.slug-field:focus-within{border-color:#1a1a2e}.slug-prefix{padding:.75rem;background:#f5f5f5;color:#666;font-size:.875rem;font-family:monospace}.slug-field input{border:none;border-radius:0}.slug-field input:focus{border-color:transparent}.form-error{margin-bottom:1rem;padding:.75rem;background:#fde2e2;border-radius:6px;color:#e74c3c;font-size:.875rem}.dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1.5rem;border-top:1px solid #e0e0e0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7690
7435
  }
7691
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: PageManagerComponent, decorators: [{
7436
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageManagerComponent, decorators: [{
7692
7437
  type: Component,
7693
7438
  args: [{ selector: 'lib-page-manager', template: `
7694
7439
  <div class="page-manager">
7695
7440
  <header class="manager-header">
7696
7441
  <h1>Pages</h1>
7697
- <button class="btn-primary" (click)="openCreateDialog()">
7698
- + New Page
7699
- </button>
7442
+ <div class="header-actions">
7443
+ <button class="btn-secondary" (click)="openSetup()">
7444
+ Setup
7445
+ </button>
7446
+ <button class="btn-primary" (click)="openCreateDialog()">
7447
+ + New Page
7448
+ </button>
7449
+ </div>
7700
7450
  </header>
7701
7451
 
7702
7452
  @if (isLoading()) {
@@ -7710,7 +7460,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
7710
7460
  </div>
7711
7461
  } @else if (pages().length === 0) {
7712
7462
  <div class="empty-state">
7713
- <div class="empty-icon"><svg width="40" height="40" viewBox="0 0 20 20" fill="currentColor"><path d="M6 2a2 2 0 00-2 2v12a2 2 0 002 2h8a2 2 0 002-2V7.414A2 2 0 0015.414 6L12 2.586A2 2 0 0010.586 2H6zm0 2h4v3a1 1 0 001 1h3v8H6V4zm6 .414L14.586 7H12V4.414z"/></svg></div>
7463
+ <div class="empty-icon">📄</div>
7714
7464
  <h2>No pages yet</h2>
7715
7465
  <p>Create your first page to get started</p>
7716
7466
  <button class="btn-primary" (click)="openCreateDialog()">
@@ -7742,21 +7492,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
7742
7492
  (click)="editPage(page.id); $event.stopPropagation()"
7743
7493
  title="Edit"
7744
7494
  >
7745
- <svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M14.846 1.403a2.45 2.45 0 013.464 3.464l-1.241 1.24-3.464-3.463 1.241-1.241zm-2.654 2.654L2 14.25V17.5h3.464L15.656 7.308l-3.464-3.25z"/></svg>
7495
+ ✏️
7746
7496
  </button>
7747
7497
  <button
7748
7498
  class="action-btn"
7749
7499
  (click)="duplicatePage(page.id, $event)"
7750
7500
  title="Duplicate"
7751
7501
  >
7752
- <svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M8 2a2 2 0 00-2 2v10a2 2 0 002 2h6a2 2 0 002-2V4a2 2 0 00-2-2H8zm0 2h6v10H8V4zM4 6a2 2 0 00-2 2v8a2 2 0 002 2h6a2 2 0 002-2h-2a4 4 0 01-4-4V6H4z"/></svg>
7502
+ 📋
7753
7503
  </button>
7754
7504
  <button
7755
7505
  class="action-btn delete"
7756
7506
  (click)="confirmDelete(page, $event)"
7757
7507
  title="Delete"
7758
7508
  >
7759
- <svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M8 3a1 1 0 00-1 1H5a1 1 0 000 2h10a1 1 0 100-2h-2a1 1 0 00-1-1H8zm-2 4h8l-.5 8.5a1 1 0 01-1 .5h-5a1 1 0 01-1-.5L6 7z"/></svg>
7509
+ 🗑️
7760
7510
  </button>
7761
7511
  </span>
7762
7512
  </div>
@@ -7837,1111 +7587,287 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
7837
7587
  </div>
7838
7588
  }
7839
7589
  </div>
7840
- `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;min-height:100vh;background:#f6f6f7;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.page-manager{max-width:1200px;margin:0 auto;padding:2rem}.manager-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem}.manager-header h1{margin:0;font-size:1.5rem;font-weight:600;color:#303030}.btn-primary{padding:.625rem 1.25rem;border:none;border-radius:8px;background:#303030;color:#fff;font-size:.8125rem;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s}.btn-primary:hover{background:#1a1a1a}.btn-primary:disabled{opacity:.5;cursor:not-allowed}.btn-secondary{padding:.625rem 1.25rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;color:#303030;font-size:.8125rem;font-weight:500;font-family:inherit;cursor:pointer;transition:all .15s}.btn-secondary:hover{background:#f6f6f7}.btn-danger{padding:.625rem 1.25rem;border:none;border-radius:8px;background:#d72c0d;color:#fff;font-size:.8125rem;font-weight:500;font-family:inherit;cursor:pointer;transition:background .15s}.btn-danger:hover{background:#bc2200}.btn-danger:disabled{opacity:.5;cursor:not-allowed}.loading-state,.error-state,.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4rem 2rem;background:#fff;border-radius:12px;text-align:center;box-shadow:0 1px #0000000d;border:1px solid #e3e3e3}.empty-icon{margin-bottom:1rem;color:#8c9196;display:flex;align-items:center;justify-content:center}.empty-state h2{margin:0 0 .5rem;color:#303030;font-size:1.125rem}.empty-state p{margin:0 0 1.5rem;color:#8c9196;font-size:.8125rem}.error-state{color:#d72c0d}.pages-table{background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px #0000000d;border:1px solid #e3e3e3}.table-header,.table-row{display:grid;grid-template-columns:1fr 150px 100px 150px 120px;gap:1rem;padding:.875rem 1.5rem;align-items:center}.table-header{background:#f6f6f7;font-size:.75rem;font-weight:600;text-transform:uppercase;color:#8c9196;letter-spacing:.3px;border-bottom:1px solid #e3e3e3}.table-row{border-top:1px solid #f6f6f7;cursor:pointer;transition:background .15s}.table-row:first-of-type{border-top:none}.table-row:hover{background:#f6f6f7}.col-title{font-weight:500;color:#303030;font-size:.8125rem}.col-slug{font-size:.75rem;color:#8c9196;font-family:SF Mono,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.75rem;font-weight:500;text-transform:capitalize;background:#ffd6a4;color:#b98900}.status-badge.published{background:#aee9d1;color:#0d542b}.col-updated{font-size:.8125rem;color:#8c9196}.col-actions{display:flex;gap:.375rem}.action-btn{padding:.375rem;border:none;border-radius:8px;background:transparent;cursor:pointer;color:#8c9196;display:inline-flex;align-items:center;justify-content:center;transition:all .15s}.action-btn:hover{background:#f6f6f7;color:#303030}.action-btn.delete:hover{background:#fef2f0;color:#d72c0d}.dialog-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog{background:#fff;border-radius:12px;width:100%;max-width:480px;box-shadow:0 8px 32px #0003}.dialog-small{max-width:400px}.dialog-header{display:flex;justify-content:space-between;align-items:center;padding:1.25rem 1.5rem;border-bottom:1px solid #e3e3e3}.dialog-header h2{margin:0;font-size:1.125rem;font-weight:600;color:#303030}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#8c9196;padding:.25rem;line-height:1;border-radius:8px;transition:all .15s}.close-btn:hover{color:#303030;background:#f6f6f7}.dialog-content{padding:1.5rem}.dialog-content p{margin:0 0 .5rem;color:#303030;font-size:.8125rem}.dialog-content .warning{color:#d72c0d;font-size:.8125rem}.form-field{margin-bottom:1.25rem}.form-field label{display:block;margin-bottom:.375rem;font-size:.8125rem;font-weight:500;color:#303030}.form-field input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;color:#303030;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.form-field input:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.form-field input::placeholder{color:#8c9196}.slug-field{display:flex;align-items:center;border:1px solid #c9cccf;border-radius:8px;overflow:hidden;transition:border-color .15s,box-shadow .15s}.slug-field:focus-within{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.slug-prefix{padding:.5rem .75rem;background:#f6f6f7;color:#8c9196;font-size:.8125rem;font-family:SF Mono,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;border-right:1px solid #e3e3e3}.slug-field input{border:none;border-radius:0}.slug-field input:focus{border-color:transparent;box-shadow:none}.form-error{margin-bottom:1rem;padding:.75rem;background:#fef2f0;border-radius:8px;color:#d72c0d;font-size:.8125rem}.dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1.25rem 1.5rem;border-top:1px solid #e3e3e3}\n"] }]
7590
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;min-height:100vh;background:#f0f2f5}.page-manager{max-width:1200px;margin:0 auto;padding:2rem}.manager-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem}.header-actions{display:flex;gap:.75rem;align-items:center}.manager-header h1{margin:0;font-size:1.75rem;font-weight:600;color:#1a1a2e}.btn-primary{padding:.75rem 1.5rem;border:none;border-radius:6px;background:#1a1a2e;color:#fff;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .2s}.btn-primary:hover{background:#2a2a3e}.btn-primary:disabled{opacity:.6;cursor:not-allowed}.btn-secondary{padding:.75rem 1.5rem;border:1px solid #e0e0e0;border-radius:6px;background:#fff;color:#333;font-size:.875rem;font-weight:500;cursor:pointer;transition:all .2s}.btn-secondary:hover{background:#f5f5f5}.btn-danger{padding:.75rem 1.5rem;border:none;border-radius:6px;background:#e74c3c;color:#fff;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .2s}.btn-danger:hover{background:#c0392b}.btn-danger:disabled{opacity:.6;cursor:not-allowed}.loading-state,.error-state,.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4rem 2rem;background:#fff;border-radius:8px;text-align:center}.empty-icon{font-size:4rem;margin-bottom:1rem}.empty-state h2{margin:0 0 .5rem;color:#333}.empty-state p{margin:0 0 1.5rem;color:#666}.error-state{color:#e74c3c}.pages-table{background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 1px 3px #0000001a}.table-header,.table-row{display:grid;grid-template-columns:1fr 150px 100px 150px 120px;gap:1rem;padding:1rem 1.5rem;align-items:center}.table-header{background:#f8f9fa;font-size:.75rem;font-weight:600;text-transform:uppercase;color:#666;letter-spacing:.5px}.table-row{border-top:1px solid #e0e0e0;cursor:pointer;transition:background .2s}.table-row:hover{background:#f8f9fa}.col-title{font-weight:500;color:#333}.col-slug{font-size:.8125rem;color:#666;font-family:monospace}.status-badge{display:inline-block;padding:.25rem .5rem;border-radius:4px;font-size:.75rem;font-weight:500;text-transform:capitalize;background:#ffeaa7;color:#856404}.status-badge.published{background:#d4edda;color:#155724}.col-updated{font-size:.8125rem;color:#666}.col-actions{display:flex;gap:.5rem}.action-btn{padding:.375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:1rem;transition:background .2s}.action-btn:hover{background:#e0e0e0}.action-btn.delete:hover{background:#fde2e2}.dialog-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog{background:#fff;border-radius:8px;width:100%;max-width:480px;box-shadow:0 8px 32px #0003}.dialog-small{max-width:400px}.dialog-header{display:flex;justify-content:space-between;align-items:center;padding:1.5rem;border-bottom:1px solid #e0e0e0}.dialog-header h2{margin:0;font-size:1.25rem;font-weight:600}.close-btn{border:none;background:none;font-size:1.5rem;cursor:pointer;color:#666;padding:0;line-height:1}.close-btn:hover{color:#333}.dialog-content{padding:1.5rem}.dialog-content p{margin:0 0 .5rem;color:#333}.dialog-content .warning{color:#e74c3c;font-size:.875rem}.form-field{margin-bottom:1.25rem}.form-field label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:#333}.form-field input{width:100%;padding:.75rem;border:1px solid #e0e0e0;border-radius:6px;font-size:.875rem;transition:border-color .2s}.form-field input:focus{outline:none;border-color:#1a1a2e}.slug-field{display:flex;align-items:center;border:1px solid #e0e0e0;border-radius:6px;overflow:hidden}.slug-field:focus-within{border-color:#1a1a2e}.slug-prefix{padding:.75rem;background:#f5f5f5;color:#666;font-size:.875rem;font-family:monospace}.slug-field input{border:none;border-radius:0}.slug-field input:focus{border-color:transparent}.form-error{margin-bottom:1rem;padding:.75rem;background:#fde2e2;border-radius:6px;color:#e74c3c;font-size:.875rem}.dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1.5rem;border-top:1px solid #e0e0e0}\n"] }]
7841
7591
  }] });
7842
7592
 
7843
- class LicenseGuardComponent {
7844
- shopAuthService = inject(ShopAuthService);
7845
- showExpirationWarning = input(true, ...(ngDevMode ? [{ debugName: "showExpirationWarning" }] : []));
7846
- subscribe = output();
7847
- renew = output();
7848
- reauth = output();
7849
- retry = output();
7850
- onSubscribe() {
7851
- this.subscribe.emit();
7852
- }
7853
- onRenew() {
7854
- this.renew.emit();
7855
- }
7856
- onReauth() {
7857
- this.reauth.emit();
7858
- }
7859
- onRetry() {
7860
- this.retry.emit();
7861
- }
7862
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LicenseGuardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7863
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: LicenseGuardComponent, isStandalone: true, selector: "lib-license-guard", inputs: { showExpirationWarning: { classPropertyName: "showExpirationWarning", publicName: "showExpirationWarning", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { subscribe: "subscribe", renew: "renew", reauth: "reauth", retry: "retry" }, ngImport: i0, template: `
7864
- @if (shopAuthService.loading()) {
7865
- <div class="license-loading">
7866
- <div class="spinner"></div>
7867
- <span>Verificando acceso...</span>
7868
- </div>
7869
- } @else if (!shopAuthService.hasAccess()) {
7870
- <div class="license-paywall">
7871
- <div class="paywall-content">
7872
- <h2>Acceso restringido</h2>
7873
-
7874
- @switch (shopAuthService.error()?.code) {
7875
- @case ('LICENSE_EXPIRED') {
7876
- <p>Tu licencia ha expirado.</p>
7877
- <button class="btn-primary" (click)="onRenew()">Renovar licencia</button>
7878
- }
7879
- @case ('NO_ACCESS') {
7880
- <p>No tienes una licencia activa.</p>
7881
- <button class="btn-primary" (click)="onSubscribe()">Obtener licencia</button>
7882
- }
7883
- @case ('SHOP_NOT_FOUND') {
7884
- <p>Tienda no encontrada.</p>
7885
- <button class="btn-secondary" (click)="onRetry()">Reintentar</button>
7886
- }
7887
- @case ('USER_INACTIVE') {
7888
- <p>No tienes permisos para acceder.</p>
7889
- }
7890
- @case ('NOT_AUTHENTICATED') {
7891
- <p>Sesion no valida.</p>
7892
- <button class="btn-primary" (click)="onReauth()">Iniciar sesion</button>
7893
- }
7894
- @case ('UNKNOWN_ERROR') {
7895
- <p>Error de conexion.</p>
7896
- <button class="btn-secondary" (click)="onRetry()">Reintentar</button>
7897
- }
7898
- @default {
7899
- <p>No tienes acceso a esta funcionalidad.</p>
7900
- <button class="btn-primary" (click)="onSubscribe()">Ver planes</button>
7593
+ /**
7594
+ * Read-only GraphQL client for the Shopify Storefront API.
7595
+ * Uses a Storefront Access Token (public, non-secret) instead of Admin API credentials.
7596
+ */
7597
+ class StorefrontGraphQLClient {
7598
+ http = inject(HttpClient);
7599
+ config = inject(STOREFRONT_CONFIG);
7600
+ query(query, variables) {
7601
+ const url = this.config.proxyUrl
7602
+ ? this.config.proxyUrl
7603
+ : `https://${this.config.shopDomain}/api/${this.config.apiVersion}/graphql.json`;
7604
+ const request = { query, variables };
7605
+ const headers = {
7606
+ 'Content-Type': 'application/json',
7607
+ };
7608
+ if (!this.config.proxyUrl) {
7609
+ headers['X-Shopify-Storefront-Access-Token'] = this.config.storefrontAccessToken;
7610
+ }
7611
+ return this.http.post(url, request, { headers }).pipe(map$1(response => this.handleResponse(response)), catchError(error => this.handleError(error)));
7612
+ }
7613
+ handleResponse(response) {
7614
+ if (response.errors && response.errors.length > 0) {
7615
+ throw {
7616
+ code: 'GRAPHQL_ERROR',
7617
+ message: response.errors[0].message,
7618
+ errors: response.errors,
7619
+ };
7620
+ }
7621
+ if (!response.data) {
7622
+ throw {
7623
+ code: 'EMPTY_RESPONSE',
7624
+ message: 'GraphQL response contains no data',
7625
+ };
7626
+ }
7627
+ return response.data;
7628
+ }
7629
+ handleError(error) {
7630
+ let errorCode = 'UNKNOWN_ERROR';
7631
+ let errorMessage = 'An unknown error occurred';
7632
+ if (typeof ErrorEvent !== 'undefined' && error.error instanceof ErrorEvent) {
7633
+ errorCode = 'NETWORK_ERROR';
7634
+ errorMessage = error.error.message;
7635
+ }
7636
+ else {
7637
+ switch (error.status) {
7638
+ case 401:
7639
+ errorCode = 'UNAUTHORIZED';
7640
+ errorMessage = 'Invalid or expired storefront access token';
7641
+ break;
7642
+ case 402:
7643
+ errorCode = 'PAYMENT_REQUIRED';
7644
+ errorMessage = 'Storefront API access requires a Shopify plan';
7645
+ break;
7646
+ case 429:
7647
+ errorCode = 'RATE_LIMIT';
7648
+ errorMessage = 'Rate limit exceeded';
7649
+ break;
7650
+ case 500:
7651
+ case 502:
7652
+ case 503:
7653
+ case 504:
7654
+ errorCode = 'SERVER_ERROR';
7655
+ errorMessage = 'Shopify server error';
7656
+ break;
7657
+ default:
7658
+ errorCode = 'HTTP_ERROR';
7659
+ errorMessage = `HTTP ${error.status}: ${error.statusText}`;
7901
7660
  }
7902
- }
7903
- </div>
7904
- </div>
7905
- } @else {
7906
- <ng-content></ng-content>
7907
-
7908
- @if (shopAuthService.isLicenseExpiringSoon() && showExpirationWarning()) {
7909
- <div class="expiration-warning">
7910
- <span class="warning-icon">!</span>
7911
- <span>Tu licencia expira en {{ shopAuthService.daysRemaining() }} dias.</span>
7912
- <button class="btn-small" (click)="onRenew()">Renovar</button>
7913
- </div>
7914
- }
7661
+ }
7662
+ return throwError(() => ({
7663
+ code: errorCode,
7664
+ message: errorMessage,
7665
+ status: error.status,
7666
+ original: error,
7667
+ }));
7915
7668
  }
7916
- `, isInline: true, styles: [".license-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px;gap:16px}.spinner{width:32px;height:32px;border:3px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.license-paywall{display:flex;align-items:center;justify-content:center;min-height:300px;padding:24px;background:#f9fafb;border-radius:8px}.paywall-content{text-align:center;max-width:400px}.paywall-content h2{margin:0 0 12px;font-size:24px;font-weight:600;color:#111827}.paywall-content p{margin:0 0 24px;color:#6b7280}.btn-primary{padding:12px 24px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s}.btn-primary:hover{background:#2563eb}.btn-secondary{padding:12px 24px;background:#6b7280;color:#fff;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s}.btn-secondary:hover{background:#4b5563}.btn-small{padding:6px 12px;background:transparent;color:inherit;border:1px solid currentColor;border-radius:4px;font-size:12px;cursor:pointer;transition:background .2s}.btn-small:hover{background:#0000000d}.expiration-warning{position:fixed;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:center;gap:12px;padding:12px 24px;background:#fef3c7;color:#92400e;font-size:14px;z-index:1000}.warning-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;background:#f59e0b;color:#fff;border-radius:50%;font-size:12px;font-weight:700}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
7669
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontGraphQLClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7670
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontGraphQLClient, providedIn: 'root' });
7917
7671
  }
7918
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LicenseGuardComponent, decorators: [{
7919
- type: Component,
7920
- args: [{ selector: 'lib-license-guard', standalone: true, imports: [CommonModule], template: `
7921
- @if (shopAuthService.loading()) {
7922
- <div class="license-loading">
7923
- <div class="spinner"></div>
7924
- <span>Verificando acceso...</span>
7925
- </div>
7926
- } @else if (!shopAuthService.hasAccess()) {
7927
- <div class="license-paywall">
7928
- <div class="paywall-content">
7929
- <h2>Acceso restringido</h2>
7672
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontGraphQLClient, decorators: [{
7673
+ type: Injectable,
7674
+ args: [{ providedIn: 'root' }]
7675
+ }] });
7930
7676
 
7931
- @switch (shopAuthService.error()?.code) {
7932
- @case ('LICENSE_EXPIRED') {
7933
- <p>Tu licencia ha expirado.</p>
7934
- <button class="btn-primary" (click)="onRenew()">Renovar licencia</button>
7935
- }
7936
- @case ('NO_ACCESS') {
7937
- <p>No tienes una licencia activa.</p>
7938
- <button class="btn-primary" (click)="onSubscribe()">Obtener licencia</button>
7677
+ const NAMESPACE = 'kustomizer';
7678
+ const INDEX_KEY = 'pages_index';
7679
+ /**
7680
+ * Storefront API query for reading shop metafields.
7681
+ * Uses `shop.metafield(namespace, key)` — the Storefront API equivalent
7682
+ * of the Admin API's shop metafield query.
7683
+ */
7684
+ const GET_SHOP_METAFIELD_QUERY = `
7685
+ query GetShopMetafield($namespace: String!, $key: String!) {
7686
+ shop {
7687
+ metafield(namespace: $namespace, key: $key) {
7688
+ value
7689
+ type
7690
+ }
7691
+ }
7692
+ }
7693
+ `;
7694
+ /**
7695
+ * Read-only metafield repository using the Shopify Storefront API.
7696
+ * Only works for metafields with MetafieldDefinitions that have `access.storefront: PUBLIC_READ`.
7697
+ */
7698
+ class StorefrontMetafieldRepository {
7699
+ client = inject(StorefrontGraphQLClient);
7700
+ getMetafield(namespace, key) {
7701
+ return this.client.query(GET_SHOP_METAFIELD_QUERY, { namespace, key }).pipe(map$1(response => response.shop.metafield));
7702
+ }
7703
+ getPageIndex() {
7704
+ return this.getMetafield(NAMESPACE, INDEX_KEY).pipe(map$1(metafield => {
7705
+ if (!metafield) {
7706
+ return this.getDefaultIndex();
7939
7707
  }
7940
- @case ('SHOP_NOT_FOUND') {
7941
- <p>Tienda no encontrada.</p>
7942
- <button class="btn-secondary" (click)="onRetry()">Reintentar</button>
7708
+ try {
7709
+ const parsed = JSON.parse(metafield.value);
7710
+ if (!parsed.pages || !Array.isArray(parsed.pages)) {
7711
+ throw new Error('Invalid index structure');
7712
+ }
7713
+ return parsed;
7943
7714
  }
7944
- @case ('USER_INACTIVE') {
7945
- <p>No tienes permisos para acceder.</p>
7715
+ catch (error) {
7716
+ throw {
7717
+ code: 'INDEX_PARSE_ERROR',
7718
+ message: 'Stored index data is corrupted',
7719
+ original: error,
7720
+ };
7946
7721
  }
7947
- @case ('NOT_AUTHENTICATED') {
7948
- <p>Sesion no valida.</p>
7949
- <button class="btn-primary" (click)="onReauth()">Iniciar sesion</button>
7722
+ }));
7723
+ }
7724
+ getPageMetafield(pageId) {
7725
+ const key = `page_${pageId}`;
7726
+ return this.getMetafield(NAMESPACE, key).pipe(map$1(metafield => {
7727
+ if (!metafield) {
7728
+ return null;
7950
7729
  }
7951
- @case ('UNKNOWN_ERROR') {
7952
- <p>Error de conexion.</p>
7953
- <button class="btn-secondary" (click)="onRetry()">Reintentar</button>
7730
+ try {
7731
+ return JSON.parse(metafield.value);
7954
7732
  }
7955
- @default {
7956
- <p>No tienes acceso a esta funcionalidad.</p>
7957
- <button class="btn-primary" (click)="onSubscribe()">Ver planes</button>
7733
+ catch (error) {
7734
+ throw {
7735
+ code: 'PAGE_PARSE_ERROR',
7736
+ message: 'Stored page data is corrupted',
7737
+ original: error,
7738
+ };
7958
7739
  }
7959
- }
7960
- </div>
7961
- </div>
7962
- } @else {
7963
- <ng-content></ng-content>
7964
-
7965
- @if (shopAuthService.isLicenseExpiringSoon() && showExpirationWarning()) {
7966
- <div class="expiration-warning">
7967
- <span class="warning-icon">!</span>
7968
- <span>Tu licencia expira en {{ shopAuthService.daysRemaining() }} dias.</span>
7969
- <button class="btn-small" (click)="onRenew()">Renovar</button>
7970
- </div>
7971
- }
7740
+ }));
7972
7741
  }
7973
- `, styles: [".license-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px;gap:16px}.spinner{width:32px;height:32px;border:3px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:spin .8s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.license-paywall{display:flex;align-items:center;justify-content:center;min-height:300px;padding:24px;background:#f9fafb;border-radius:8px}.paywall-content{text-align:center;max-width:400px}.paywall-content h2{margin:0 0 12px;font-size:24px;font-weight:600;color:#111827}.paywall-content p{margin:0 0 24px;color:#6b7280}.btn-primary{padding:12px 24px;background:#3b82f6;color:#fff;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s}.btn-primary:hover{background:#2563eb}.btn-secondary{padding:12px 24px;background:#6b7280;color:#fff;border:none;border-radius:6px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s}.btn-secondary:hover{background:#4b5563}.btn-small{padding:6px 12px;background:transparent;color:inherit;border:1px solid currentColor;border-radius:4px;font-size:12px;cursor:pointer;transition:background .2s}.btn-small:hover{background:#0000000d}.expiration-warning{position:fixed;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:center;gap:12px;padding:12px 24px;background:#fef3c7;color:#92400e;font-size:14px;z-index:1000}.warning-icon{display:flex;align-items:center;justify-content:center;width:20px;height:20px;background:#f59e0b;color:#fff;border-radius:50%;font-size:12px;font-weight:700}\n"] }]
7974
- }], propDecorators: { showExpirationWarning: [{ type: i0.Input, args: [{ isSignal: true, alias: "showExpirationWarning", required: false }] }], subscribe: [{ type: i0.Output, args: ["subscribe"] }], renew: [{ type: i0.Output, args: ["renew"] }], reauth: [{ type: i0.Output, args: ["reauth"] }], retry: [{ type: i0.Output, args: ["retry"] }] } });
7742
+ getDefaultIndex() {
7743
+ return {
7744
+ version: 0,
7745
+ lastUpdated: new Date().toISOString(),
7746
+ pages: [],
7747
+ };
7748
+ }
7749
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontMetafieldRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7750
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontMetafieldRepository, providedIn: 'root' });
7751
+ }
7752
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontMetafieldRepository, decorators: [{
7753
+ type: Injectable,
7754
+ args: [{ providedIn: 'root' }]
7755
+ }] });
7975
7756
 
7976
- class LoginComponent {
7977
- authService = inject(SupabaseAuthService);
7978
- router = inject(Router);
7979
- loginSuccess = output();
7980
- email = signal('', ...(ngDevMode ? [{ debugName: "email" }] : []));
7981
- password = signal('', ...(ngDevMode ? [{ debugName: "password" }] : []));
7982
- error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
7983
- loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
7984
- isSignUp = signal(false, ...(ngDevMode ? [{ debugName: "isSignUp" }] : []));
7985
- showPassword = signal(false, ...(ngDevMode ? [{ debugName: "showPassword" }] : []));
7986
- toggleMode() {
7987
- this.isSignUp.update(v => !v);
7988
- this.error.set(null);
7757
+ /**
7758
+ * Read-only page repository using the Shopify Storefront API.
7759
+ * Used by the public-storefront to read published pages without Admin API access.
7760
+ */
7761
+ class PageStorefrontRepository {
7762
+ metafieldRepo = inject(StorefrontMetafieldRepository);
7763
+ getPages() {
7764
+ return this.metafieldRepo.getPageIndex().pipe(map$1(index => index.pages
7765
+ .map(entry => ({
7766
+ id: entry.id,
7767
+ slug: entry.slug,
7768
+ title: entry.title,
7769
+ status: entry.status,
7770
+ updatedAt: entry.updatedAt,
7771
+ publishedAt: entry.publishedAt,
7772
+ }))
7773
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))));
7989
7774
  }
7990
- togglePassword() {
7991
- this.showPassword.update(v => !v);
7775
+ getPublishedPages() {
7776
+ return this.getPages().pipe(map$1(pages => pages.filter(p => p.status === 'published')));
7992
7777
  }
7993
- async onSubmit() {
7994
- if (!this.email() || !this.password()) {
7995
- this.error.set('Please fill in all fields');
7996
- return;
7997
- }
7998
- this.loading.set(true);
7999
- this.error.set(null);
8000
- try {
8001
- let result;
8002
- if (this.isSignUp()) {
8003
- result = await this.authService.signUp(this.email(), this.password());
8004
- if (result.success) {
8005
- this.loginSuccess.emit();
8006
- return;
8007
- }
7778
+ getPage(id) {
7779
+ return this.metafieldRepo.getPageMetafield(id).pipe(map$1(page => {
7780
+ if (!page) {
7781
+ throw {
7782
+ code: 'PAGE_NOT_FOUND',
7783
+ message: `Page with id ${id} not found`,
7784
+ };
8008
7785
  }
8009
- else {
8010
- result = await this.authService.signInWithPassword(this.email(), this.password());
8011
- if (result.success) {
8012
- this.loginSuccess.emit();
8013
- return;
8014
- }
7786
+ return page;
7787
+ }));
7788
+ }
7789
+ getPublishedPageBySlug(slug) {
7790
+ return this.metafieldRepo.getPageIndex().pipe(switchMap(index => {
7791
+ const entry = index.pages.find(p => p.slug === slug && p.status === 'published');
7792
+ if (!entry) {
7793
+ return throwError(() => ({
7794
+ code: 'PAGE_NOT_FOUND',
7795
+ message: `Published page with slug '${slug}' not found`,
7796
+ }));
8015
7797
  }
8016
- this.error.set(result.error ?? 'Authentication error');
8017
- }
8018
- finally {
8019
- this.loading.set(false);
8020
- }
7798
+ return this.getPage(entry.id);
7799
+ }));
8021
7800
  }
8022
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8023
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: LoginComponent, isStandalone: true, selector: "lib-login", outputs: { loginSuccess: "loginSuccess" }, ngImport: i0, template: `
8024
- <div class="login-page">
8025
- <div class="login-bg">
8026
- <div class="login-gradient"></div>
8027
- <div class="login-pattern"></div>
8028
- <div class="login-glow login-glow--1"></div>
8029
- <div class="login-glow login-glow--2"></div>
8030
- </div>
8031
-
8032
- <div class="login-container">
8033
- <div class="login-card">
8034
- <header class="login-header">
8035
- <div class="login-logo-wrap">
8036
- <img src="/img/kustomizerlogo.png" alt="Kustomizer" class="login-logo" />
8037
- </div>
8038
- <h1 class="login-title">{{ isSignUp() ? 'Create account' : 'Sign in' }}</h1>
8039
- <p class="login-subtitle">{{ isSignUp() ? 'Create your account to get started' : 'Sign in to your Kustomizer account' }}</p>
8040
- </header>
8041
-
8042
- @if (error()) {
8043
- <div class="login-alert">
8044
- <svg viewBox="0 0 20 20" fill="currentColor" class="login-alert-icon">
8045
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
8046
- </svg>
8047
- <span>{{ error() }}</span>
8048
- </div>
8049
- }
8050
-
8051
- <form (ngSubmit)="onSubmit()" class="login-form">
8052
- <div class="login-field">
8053
- <label for="email" class="login-label">Email</label>
8054
- <input
8055
- id="email"
8056
- type="email"
8057
- [(ngModel)]="email"
8058
- name="email"
8059
- placeholder=""
8060
- class="login-input"
8061
- required
8062
- [disabled]="loading()"
8063
- />
8064
- </div>
8065
-
8066
- <div class="login-field">
8067
- <label for="password" class="login-label">Password</label>
8068
- <div class="login-input-wrap">
8069
- <input
8070
- id="password"
8071
- [type]="showPassword() ? 'text' : 'password'"
8072
- [(ngModel)]="password"
8073
- name="password"
8074
- placeholder="••••••••"
8075
- class="login-input"
8076
- required
8077
- [disabled]="loading()"
8078
- />
8079
- <button type="button" class="login-password-toggle" (click)="togglePassword()" tabindex="-1">
8080
- @if (showPassword()) {
8081
- <svg viewBox="0 0 20 20" fill="currentColor">
8082
- <path fill-rule="evenodd" d="M3.28 2.22a.75.75 0 00-1.06 1.06l14.5 14.5a.75.75 0 101.06-1.06l-1.745-1.745a10.029 10.029 0 003.3-4.38 1.651 1.651 0 000-1.185A10.004 10.004 0 009.999 3a9.956 9.956 0 00-4.744 1.194L3.28 2.22zM7.752 6.69l1.092 1.092a2.5 2.5 0 013.374 3.373l1.091 1.092a4 4 0 00-5.557-5.557z" clip-rule="evenodd" />
8083
- <path d="M10.748 13.93l2.523 2.523a9.987 9.987 0 01-3.27.547c-4.258 0-7.894-2.66-9.337-6.41a1.651 1.651 0 010-1.186A10.007 10.007 0 012.839 6.02L6.07 9.252a4 4 0 004.678 4.678z" />
8084
- </svg>
8085
- } @else {
8086
- <svg viewBox="0 0 20 20" fill="currentColor">
8087
- <path d="M10 12.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z" />
8088
- <path fill-rule="evenodd" d="M.664 10.59a1.651 1.651 0 010-1.186A10.004 10.004 0 0110 3c4.257 0 7.893 2.66 9.336 6.41.147.381.146.804 0 1.186A10.004 10.004 0 0110 17c-4.257 0-7.893-2.66-9.336-6.41zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd" />
8089
- </svg>
8090
- }
8091
- </button>
8092
- </div>
8093
- </div>
8094
-
8095
- <button type="submit" class="login-submit" [disabled]="loading()">
8096
- @if (loading()) {
8097
- <span class="login-spinner"></span>
8098
- {{ isSignUp() ? 'Creating...' : 'Signing in...' }}
8099
- } @else {
8100
- {{ isSignUp() ? 'Create account' : 'Sign in' }}
8101
- }
8102
- </button>
8103
- </form>
8104
-
8105
- <div class="login-divider"><span>or</span></div>
8106
-
8107
- <button type="button" class="login-switch" (click)="toggleMode()">
8108
- {{ isSignUp() ? 'Already have an account? Sign in' : "Don't have an account? Sign up" }}
8109
- </button>
8110
- </div>
8111
-
8112
- <footer class="login-footer">
8113
- <span class="login-footer-text">Kustomizer</span>
8114
- <span class="login-footer-dot">·</span>
8115
- <a href="https://kustomizer.net" target="_blank" rel="noopener noreferrer" class="login-footer-link">kustomizer.net</a>
8116
- </footer>
8117
- </div>
8118
- </div>
8119
- `, isInline: true, styles: [":host{display:block}.login-page{--k-primary: #bdf366;--k-primary-hover: #a8e050;--k-primary-text: #1a1f0e;--k-bg: #0c0f08;--k-card: #ffffff;--k-border: #e5e7eb;--k-border-focus: #bdf366;--k-text: #1f2937;--k-text-secondary: #6b7280;--k-text-muted: #9ca3af;--k-error: #dc2626;--k-error-bg: #fef2f2;--k-error-border: #fecaca;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--k-bg);color:var(--k-text);padding:20px;position:relative;overflow:hidden}.login-bg{position:fixed;inset:0;pointer-events:none;z-index:0}.login-gradient{position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(189,243,102,.15),transparent),radial-gradient(ellipse 60% 40% at 100% 100%,rgba(189,243,102,.08),transparent),linear-gradient(180deg,#0c0f08,#111a0a)}.login-pattern{position:absolute;inset:0;background-image:radial-gradient(rgba(189,243,102,.07) 1px,transparent 1px);background-size:32px 32px;mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent);-webkit-mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent)}.login-glow{position:absolute;border-radius:50%;filter:blur(80px);opacity:.6}.login-glow--1{width:400px;height:400px;background:#bdf3661f;top:-150px;left:50%;transform:translate(-50%);animation:glowPulse 8s ease-in-out infinite}.login-glow--2{width:300px;height:300px;background:#bdf3660f;bottom:-100px;right:-50px;animation:glowPulse2 10s ease-in-out infinite}@keyframes glowPulse{0%,to{opacity:.4;transform:translate(-50%) scale(1)}50%{opacity:.7;transform:translate(-50%) scale(1.1)}}@keyframes glowPulse2{0%,to{opacity:.3;transform:scale(1)}50%{opacity:.5;transform:scale(1.15)}}.login-container{position:relative;z-index:1;width:100%;max-width:400px;animation:fadeIn .5s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.login-card{background:var(--k-card);border-radius:20px;padding:40px 32px;box-shadow:0 0 0 1px #ffffff1a,0 25px 50px -12px #00000080,0 0 100px -20px #bdf36633}.login-header{text-align:center;margin-bottom:32px}.login-logo-wrap{margin-bottom:20px}.login-logo{width:56px;height:56px;border-radius:12px}.login-title{font-size:24px;font-weight:600;color:var(--k-text);margin:0 0 8px;letter-spacing:-.3px}.login-subtitle{font-size:14px;color:var(--k-text-secondary);margin:0;font-weight:400}.login-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;font-size:14px;margin-bottom:20px;background:var(--k-error-bg);border:1px solid var(--k-error-border);color:var(--k-error)}.login-alert-icon{width:18px;height:18px;flex-shrink:0;margin-top:1px}.login-form{display:flex;flex-direction:column;gap:20px}.login-field{display:flex;flex-direction:column;gap:6px}.login-label{font-size:14px;font-weight:500;color:var(--k-text)}.login-input-wrap{position:relative;display:flex;align-items:center}.login-input{width:100%;background:var(--k-card);border:1px solid var(--k-border);border-radius:10px;padding:12px 14px;font-size:15px;font-family:inherit;color:var(--k-text);outline:none;transition:border-color .2s ease,box-shadow .2s ease;box-sizing:border-box}.login-input-wrap .login-input{padding-right:44px}.login-input::placeholder{color:var(--k-text-muted)}.login-input:hover{border-color:#d1d5db}.login-input:focus{border-color:var(--k-border-focus);box-shadow:0 0 0 3px #bdf36626}.login-input:disabled{background:#f3f4f6;cursor:not-allowed;opacity:.7}.login-password-toggle{position:absolute;right:10px;background:none;border:none;padding:6px;cursor:pointer;color:var(--k-text-muted);transition:color .2s ease;display:flex;align-items:center;justify-content:center;border-radius:6px}.login-password-toggle:hover{color:var(--k-primary-text);background:#bdf36626}.login-password-toggle svg{width:18px;height:18px}.login-submit{width:100%;background:var(--k-primary);color:var(--k-primary-text);border:none;border-radius:10px;padding:14px 20px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s ease,transform .1s ease;margin-top:4px;display:flex;align-items:center;justify-content:center;gap:8px}.login-submit:hover:not(:disabled){background:var(--k-primary-hover)}.login-submit:active:not(:disabled){transform:scale(.98)}.login-submit:disabled{opacity:.6;cursor:not-allowed}.login-spinner{width:16px;height:16px;border:2px solid transparent;border-top-color:var(--k-primary-text);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.login-divider{display:flex;align-items:center;gap:14px;margin:24px 0}.login-divider:before,.login-divider:after{content:\"\";flex:1;height:1px;background:var(--k-border)}.login-divider span{font-size:13px;color:var(--k-text-muted);text-transform:lowercase}.login-switch{width:100%;background:transparent;border:1px solid var(--k-border);border-radius:10px;padding:12px 16px;font-size:14px;font-weight:500;font-family:inherit;color:var(--k-text-secondary);cursor:pointer;transition:all .2s ease}.login-switch:hover{background:#bdf36614;border-color:#bdf3664d;color:var(--k-primary-text)}.login-footer{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px}.login-footer-text{font-size:13px;color:#fff6;font-weight:500}.login-footer-link{font-size:13px;color:#ffffff80;text-decoration:none;transition:color .2s ease}.login-footer-link:hover{color:var(--k-primary)}.login-footer-dot{color:#ffffff4d;font-size:12px}@media(max-width:440px){.login-page{padding:16px}.login-card{padding:32px 24px}.login-title{font-size:22px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
8120
- }
8121
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LoginComponent, decorators: [{
8122
- type: Component,
8123
- args: [{ selector: 'lib-login', standalone: true, imports: [CommonModule, FormsModule], template: `
8124
- <div class="login-page">
8125
- <div class="login-bg">
8126
- <div class="login-gradient"></div>
8127
- <div class="login-pattern"></div>
8128
- <div class="login-glow login-glow--1"></div>
8129
- <div class="login-glow login-glow--2"></div>
8130
- </div>
8131
-
8132
- <div class="login-container">
8133
- <div class="login-card">
8134
- <header class="login-header">
8135
- <div class="login-logo-wrap">
8136
- <img src="/img/kustomizerlogo.png" alt="Kustomizer" class="login-logo" />
8137
- </div>
8138
- <h1 class="login-title">{{ isSignUp() ? 'Create account' : 'Sign in' }}</h1>
8139
- <p class="login-subtitle">{{ isSignUp() ? 'Create your account to get started' : 'Sign in to your Kustomizer account' }}</p>
8140
- </header>
8141
-
8142
- @if (error()) {
8143
- <div class="login-alert">
8144
- <svg viewBox="0 0 20 20" fill="currentColor" class="login-alert-icon">
8145
- <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
8146
- </svg>
8147
- <span>{{ error() }}</span>
8148
- </div>
8149
- }
8150
-
8151
- <form (ngSubmit)="onSubmit()" class="login-form">
8152
- <div class="login-field">
8153
- <label for="email" class="login-label">Email</label>
8154
- <input
8155
- id="email"
8156
- type="email"
8157
- [(ngModel)]="email"
8158
- name="email"
8159
- placeholder=""
8160
- class="login-input"
8161
- required
8162
- [disabled]="loading()"
8163
- />
8164
- </div>
8165
-
8166
- <div class="login-field">
8167
- <label for="password" class="login-label">Password</label>
8168
- <div class="login-input-wrap">
8169
- <input
8170
- id="password"
8171
- [type]="showPassword() ? 'text' : 'password'"
8172
- [(ngModel)]="password"
8173
- name="password"
8174
- placeholder="••••••••"
8175
- class="login-input"
8176
- required
8177
- [disabled]="loading()"
8178
- />
8179
- <button type="button" class="login-password-toggle" (click)="togglePassword()" tabindex="-1">
8180
- @if (showPassword()) {
8181
- <svg viewBox="0 0 20 20" fill="currentColor">
8182
- <path fill-rule="evenodd" d="M3.28 2.22a.75.75 0 00-1.06 1.06l14.5 14.5a.75.75 0 101.06-1.06l-1.745-1.745a10.029 10.029 0 003.3-4.38 1.651 1.651 0 000-1.185A10.004 10.004 0 009.999 3a9.956 9.956 0 00-4.744 1.194L3.28 2.22zM7.752 6.69l1.092 1.092a2.5 2.5 0 013.374 3.373l1.091 1.092a4 4 0 00-5.557-5.557z" clip-rule="evenodd" />
8183
- <path d="M10.748 13.93l2.523 2.523a9.987 9.987 0 01-3.27.547c-4.258 0-7.894-2.66-9.337-6.41a1.651 1.651 0 010-1.186A10.007 10.007 0 012.839 6.02L6.07 9.252a4 4 0 004.678 4.678z" />
8184
- </svg>
8185
- } @else {
8186
- <svg viewBox="0 0 20 20" fill="currentColor">
8187
- <path d="M10 12.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z" />
8188
- <path fill-rule="evenodd" d="M.664 10.59a1.651 1.651 0 010-1.186A10.004 10.004 0 0110 3c4.257 0 7.893 2.66 9.336 6.41.147.381.146.804 0 1.186A10.004 10.004 0 0110 17c-4.257 0-7.893-2.66-9.336-6.41zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd" />
8189
- </svg>
8190
- }
8191
- </button>
8192
- </div>
8193
- </div>
8194
-
8195
- <button type="submit" class="login-submit" [disabled]="loading()">
8196
- @if (loading()) {
8197
- <span class="login-spinner"></span>
8198
- {{ isSignUp() ? 'Creating...' : 'Signing in...' }}
8199
- } @else {
8200
- {{ isSignUp() ? 'Create account' : 'Sign in' }}
8201
- }
8202
- </button>
8203
- </form>
8204
-
8205
- <div class="login-divider"><span>or</span></div>
8206
-
8207
- <button type="button" class="login-switch" (click)="toggleMode()">
8208
- {{ isSignUp() ? 'Already have an account? Sign in' : "Don't have an account? Sign up" }}
8209
- </button>
8210
- </div>
8211
-
8212
- <footer class="login-footer">
8213
- <span class="login-footer-text">Kustomizer</span>
8214
- <span class="login-footer-dot">·</span>
8215
- <a href="https://kustomizer.net" target="_blank" rel="noopener noreferrer" class="login-footer-link">kustomizer.net</a>
8216
- </footer>
8217
- </div>
8218
- </div>
8219
- `, styles: [":host{display:block}.login-page{--k-primary: #bdf366;--k-primary-hover: #a8e050;--k-primary-text: #1a1f0e;--k-bg: #0c0f08;--k-card: #ffffff;--k-border: #e5e7eb;--k-border-focus: #bdf366;--k-text: #1f2937;--k-text-secondary: #6b7280;--k-text-muted: #9ca3af;--k-error: #dc2626;--k-error-bg: #fef2f2;--k-error-border: #fecaca;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--k-bg);color:var(--k-text);padding:20px;position:relative;overflow:hidden}.login-bg{position:fixed;inset:0;pointer-events:none;z-index:0}.login-gradient{position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(189,243,102,.15),transparent),radial-gradient(ellipse 60% 40% at 100% 100%,rgba(189,243,102,.08),transparent),linear-gradient(180deg,#0c0f08,#111a0a)}.login-pattern{position:absolute;inset:0;background-image:radial-gradient(rgba(189,243,102,.07) 1px,transparent 1px);background-size:32px 32px;mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent);-webkit-mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent)}.login-glow{position:absolute;border-radius:50%;filter:blur(80px);opacity:.6}.login-glow--1{width:400px;height:400px;background:#bdf3661f;top:-150px;left:50%;transform:translate(-50%);animation:glowPulse 8s ease-in-out infinite}.login-glow--2{width:300px;height:300px;background:#bdf3660f;bottom:-100px;right:-50px;animation:glowPulse2 10s ease-in-out infinite}@keyframes glowPulse{0%,to{opacity:.4;transform:translate(-50%) scale(1)}50%{opacity:.7;transform:translate(-50%) scale(1.1)}}@keyframes glowPulse2{0%,to{opacity:.3;transform:scale(1)}50%{opacity:.5;transform:scale(1.15)}}.login-container{position:relative;z-index:1;width:100%;max-width:400px;animation:fadeIn .5s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.login-card{background:var(--k-card);border-radius:20px;padding:40px 32px;box-shadow:0 0 0 1px #ffffff1a,0 25px 50px -12px #00000080,0 0 100px -20px #bdf36633}.login-header{text-align:center;margin-bottom:32px}.login-logo-wrap{margin-bottom:20px}.login-logo{width:56px;height:56px;border-radius:12px}.login-title{font-size:24px;font-weight:600;color:var(--k-text);margin:0 0 8px;letter-spacing:-.3px}.login-subtitle{font-size:14px;color:var(--k-text-secondary);margin:0;font-weight:400}.login-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;font-size:14px;margin-bottom:20px;background:var(--k-error-bg);border:1px solid var(--k-error-border);color:var(--k-error)}.login-alert-icon{width:18px;height:18px;flex-shrink:0;margin-top:1px}.login-form{display:flex;flex-direction:column;gap:20px}.login-field{display:flex;flex-direction:column;gap:6px}.login-label{font-size:14px;font-weight:500;color:var(--k-text)}.login-input-wrap{position:relative;display:flex;align-items:center}.login-input{width:100%;background:var(--k-card);border:1px solid var(--k-border);border-radius:10px;padding:12px 14px;font-size:15px;font-family:inherit;color:var(--k-text);outline:none;transition:border-color .2s ease,box-shadow .2s ease;box-sizing:border-box}.login-input-wrap .login-input{padding-right:44px}.login-input::placeholder{color:var(--k-text-muted)}.login-input:hover{border-color:#d1d5db}.login-input:focus{border-color:var(--k-border-focus);box-shadow:0 0 0 3px #bdf36626}.login-input:disabled{background:#f3f4f6;cursor:not-allowed;opacity:.7}.login-password-toggle{position:absolute;right:10px;background:none;border:none;padding:6px;cursor:pointer;color:var(--k-text-muted);transition:color .2s ease;display:flex;align-items:center;justify-content:center;border-radius:6px}.login-password-toggle:hover{color:var(--k-primary-text);background:#bdf36626}.login-password-toggle svg{width:18px;height:18px}.login-submit{width:100%;background:var(--k-primary);color:var(--k-primary-text);border:none;border-radius:10px;padding:14px 20px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s ease,transform .1s ease;margin-top:4px;display:flex;align-items:center;justify-content:center;gap:8px}.login-submit:hover:not(:disabled){background:var(--k-primary-hover)}.login-submit:active:not(:disabled){transform:scale(.98)}.login-submit:disabled{opacity:.6;cursor:not-allowed}.login-spinner{width:16px;height:16px;border:2px solid transparent;border-top-color:var(--k-primary-text);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.login-divider{display:flex;align-items:center;gap:14px;margin:24px 0}.login-divider:before,.login-divider:after{content:\"\";flex:1;height:1px;background:var(--k-border)}.login-divider span{font-size:13px;color:var(--k-text-muted);text-transform:lowercase}.login-switch{width:100%;background:transparent;border:1px solid var(--k-border);border-radius:10px;padding:12px 16px;font-size:14px;font-weight:500;font-family:inherit;color:var(--k-text-secondary);cursor:pointer;transition:all .2s ease}.login-switch:hover{background:#bdf36614;border-color:#bdf3664d;color:var(--k-primary-text)}.login-footer{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px}.login-footer-text{font-size:13px;color:#fff6;font-weight:500}.login-footer-link{font-size:13px;color:#ffffff80;text-decoration:none;transition:color .2s ease}.login-footer-link:hover{color:var(--k-primary)}.login-footer-dot{color:#ffffff4d;font-size:12px}@media(max-width:440px){.login-page{padding:16px}.login-card{padding:32px 24px}.login-title{font-size:22px}}\n"] }]
8220
- }], propDecorators: { loginSuccess: [{ type: i0.Output, args: ["loginSuccess"] }] } });
8221
-
8222
- class ActivateLicenseComponent {
8223
- shopAuthService = inject(ShopAuthService);
8224
- viewState = computed(() => {
8225
- if (this.shopAuthService.loading()) {
8226
- return 'loading';
8227
- }
8228
- if (!this.shopAuthService.shop()) {
8229
- return 'no-store';
8230
- }
8231
- return 'license-expired';
8232
- }, ...(ngDevMode ? [{ debugName: "viewState" }] : []));
8233
- ngOnInit() {
8234
- this.shopAuthService.authenticate();
8235
- }
8236
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ActivateLicenseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8237
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ActivateLicenseComponent, isStandalone: true, selector: "lib-activate-license", ngImport: i0, template: `
8238
- <div class="activate-page">
8239
- <div class="activate-bg">
8240
- <div class="activate-glow"></div>
8241
- <div class="activate-grid"></div>
8242
- </div>
8243
-
8244
- <div class="activate-card">
8245
- @switch (viewState()) {
8246
- @case ('loading') {
8247
- <div class="activate-loading">
8248
- <span class="pulse-dot"></span>
8249
- <p>Verificando cuenta...</p>
8250
- </div>
8251
- }
8252
-
8253
- @case ('no-store') {
8254
- <div class="activate-content">
8255
- <p class="store-name">Tienda no encontrada</p>
8256
- <p class="license-status">Licencia expirada</p>
8257
- <a class="renew-button" href="https://kustomizer.net" target="_blank" rel="noopener noreferrer">
8258
- Renovar en kustomizer.net
8259
- </a>
8260
- </div>
8261
- }
8262
-
8263
- @case ('license-expired') {
8264
- <div class="activate-content">
8265
- <p class="store-name">{{ shopAuthService.shopName() }}</p>
8266
- <p class="license-status">Licencia expirada</p>
8267
- <a class="renew-button" href="https://kustomizer.net" target="_blank" rel="noopener noreferrer">
8268
- Renovar en kustomizer.net
8269
- </a>
8270
- </div>
8271
- }
8272
- }
8273
- </div>
8274
- </div>
8275
- `, isInline: true, styles: ["@import\"https://fonts.googleapis.com/css2?family=Newsreader:wght@400;500;600&family=Spline+Sans:wght@400;500;600&display=swap\";:host{display:block}.activate-page{--k-accent: #bdf366;--k-accent-strong: #a8e050;--k-ink: #11140c;--k-ink-muted: rgba(17, 20, 12, .6);--k-ink-faint: rgba(17, 20, 12, .45);--k-ink-ghost: rgba(17, 20, 12, .3);--k-surface: #f7f8f1;--k-surface-strong: #ffffff;--k-outline: rgba(17, 20, 12, .12);--k-shadow: 0 40px 100px -60px rgba(12, 15, 8, .6);min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0c0f08;padding:24px;position:relative;overflow:hidden;font-family:Spline Sans,Segoe UI,sans-serif;color:var(--k-ink)}.activate-bg{position:absolute;inset:0;pointer-events:none}.activate-glow{position:absolute;width:520px;height:520px;left:50%;top:-200px;transform:translate(-50%);background:radial-gradient(circle,rgba(189,243,102,.22),transparent 70%);filter:blur(10px);opacity:.8}.activate-grid{position:absolute;inset:0;background-image:linear-gradient(rgba(189,243,102,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(189,243,102,.08) 1px,transparent 1px);background-size:40px 40px;mask-image:radial-gradient(circle at 50% 10%,black,transparent 70%);-webkit-mask-image:radial-gradient(circle at 50% 10%,black,transparent 70%);opacity:.4}.activate-card{position:relative;z-index:1;width:min(480px,92vw);background:var(--k-surface-strong);border-radius:24px;padding:48px 40px;box-shadow:var(--k-shadow);border:1px solid rgba(255,255,255,.2);animation:rise .5s ease both}@keyframes rise{0%{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}.activate-loading{text-align:center;color:#ffffffb3;font-size:.9rem}.activate-loading p{margin:12px 0 0}.pulse-dot{width:10px;height:10px;border-radius:999px;background:var(--k-accent);display:inline-block;animation:pulse 1.4s ease-in-out infinite}@keyframes pulse{0%,to{transform:scale(1);opacity:.6}50%{transform:scale(1.5);opacity:1}}.activate-content{display:flex;flex-direction:column;gap:16px;align-items:center;text-align:center}.store-name{margin:0;font-family:Newsreader,Times New Roman,serif;font-size:1.6rem;letter-spacing:-.4px;color:var(--k-ink)}.license-status{margin:0;font-size:.95rem;text-transform:uppercase;letter-spacing:.2em;color:var(--k-ink-ghost)}.renew-button{margin-top:16px;padding:12px 22px;border-radius:999px;border:1px solid var(--k-outline);color:var(--k-ink);text-decoration:none;font-size:.9rem;font-weight:600;letter-spacing:.02em;background:var(--k-surface);transition:all .2s ease}.renew-button:hover{border-color:#11140c47;background:#fff;transform:translateY(-1px)}@media(max-width:480px){.activate-card{padding:36px 26px}.store-name{font-size:1.4rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
7801
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageStorefrontRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7802
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageStorefrontRepository, providedIn: 'root' });
8276
7803
  }
8277
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ActivateLicenseComponent, decorators: [{
8278
- type: Component,
8279
- args: [{ selector: 'lib-activate-license', standalone: true, imports: [CommonModule], template: `
8280
- <div class="activate-page">
8281
- <div class="activate-bg">
8282
- <div class="activate-glow"></div>
8283
- <div class="activate-grid"></div>
8284
- </div>
8285
-
8286
- <div class="activate-card">
8287
- @switch (viewState()) {
8288
- @case ('loading') {
8289
- <div class="activate-loading">
8290
- <span class="pulse-dot"></span>
8291
- <p>Verificando cuenta...</p>
8292
- </div>
8293
- }
8294
-
8295
- @case ('no-store') {
8296
- <div class="activate-content">
8297
- <p class="store-name">Tienda no encontrada</p>
8298
- <p class="license-status">Licencia expirada</p>
8299
- <a class="renew-button" href="https://kustomizer.net" target="_blank" rel="noopener noreferrer">
8300
- Renovar en kustomizer.net
8301
- </a>
8302
- </div>
8303
- }
8304
-
8305
- @case ('license-expired') {
8306
- <div class="activate-content">
8307
- <p class="store-name">{{ shopAuthService.shopName() }}</p>
8308
- <p class="license-status">Licencia expirada</p>
8309
- <a class="renew-button" href="https://kustomizer.net" target="_blank" rel="noopener noreferrer">
8310
- Renovar en kustomizer.net
8311
- </a>
8312
- </div>
8313
- }
8314
- }
8315
- </div>
8316
- </div>
8317
- `, styles: ["@import\"https://fonts.googleapis.com/css2?family=Newsreader:wght@400;500;600&family=Spline+Sans:wght@400;500;600&display=swap\";:host{display:block}.activate-page{--k-accent: #bdf366;--k-accent-strong: #a8e050;--k-ink: #11140c;--k-ink-muted: rgba(17, 20, 12, .6);--k-ink-faint: rgba(17, 20, 12, .45);--k-ink-ghost: rgba(17, 20, 12, .3);--k-surface: #f7f8f1;--k-surface-strong: #ffffff;--k-outline: rgba(17, 20, 12, .12);--k-shadow: 0 40px 100px -60px rgba(12, 15, 8, .6);min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0c0f08;padding:24px;position:relative;overflow:hidden;font-family:Spline Sans,Segoe UI,sans-serif;color:var(--k-ink)}.activate-bg{position:absolute;inset:0;pointer-events:none}.activate-glow{position:absolute;width:520px;height:520px;left:50%;top:-200px;transform:translate(-50%);background:radial-gradient(circle,rgba(189,243,102,.22),transparent 70%);filter:blur(10px);opacity:.8}.activate-grid{position:absolute;inset:0;background-image:linear-gradient(rgba(189,243,102,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(189,243,102,.08) 1px,transparent 1px);background-size:40px 40px;mask-image:radial-gradient(circle at 50% 10%,black,transparent 70%);-webkit-mask-image:radial-gradient(circle at 50% 10%,black,transparent 70%);opacity:.4}.activate-card{position:relative;z-index:1;width:min(480px,92vw);background:var(--k-surface-strong);border-radius:24px;padding:48px 40px;box-shadow:var(--k-shadow);border:1px solid rgba(255,255,255,.2);animation:rise .5s ease both}@keyframes rise{0%{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}.activate-loading{text-align:center;color:#ffffffb3;font-size:.9rem}.activate-loading p{margin:12px 0 0}.pulse-dot{width:10px;height:10px;border-radius:999px;background:var(--k-accent);display:inline-block;animation:pulse 1.4s ease-in-out infinite}@keyframes pulse{0%,to{transform:scale(1);opacity:.6}50%{transform:scale(1.5);opacity:1}}.activate-content{display:flex;flex-direction:column;gap:16px;align-items:center;text-align:center}.store-name{margin:0;font-family:Newsreader,Times New Roman,serif;font-size:1.6rem;letter-spacing:-.4px;color:var(--k-ink)}.license-status{margin:0;font-size:.95rem;text-transform:uppercase;letter-spacing:.2em;color:var(--k-ink-ghost)}.renew-button{margin-top:16px;padding:12px 22px;border-radius:999px;border:1px solid var(--k-outline);color:var(--k-ink);text-decoration:none;font-size:.9rem;font-weight:600;letter-spacing:.02em;background:var(--k-surface);transition:all .2s ease}.renew-button:hover{border-color:#11140c47;background:#fff;transform:translateY(-1px)}@media(max-width:480px){.activate-card{padding:36px 26px}.store-name{font-size:1.4rem}}\n"] }]
7804
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageStorefrontRepository, decorators: [{
7805
+ type: Injectable,
7806
+ args: [{ providedIn: 'root' }]
8318
7807
  }] });
8319
7808
 
8320
- class AppShellComponent {
8321
- authService = inject(SupabaseAuthService);
8322
- shopAuthService = inject(ShopAuthService);
8323
- loggedOut = output();
8324
- async logout() {
8325
- await this.authService.signOut();
8326
- this.shopAuthService.clear();
8327
- this.loggedOut.emit();
8328
- }
8329
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AppShellComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8330
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: AppShellComponent, isStandalone: true, selector: "lib-app-shell", outputs: { loggedOut: "loggedOut" }, ngImport: i0, template: `
8331
- <div class="app-shell">
8332
- <header class="app-header">
8333
- <div class="header-left">
8334
- <span class="logo">Kustomizer</span>
8335
- @if (shopAuthService.shopName()) {
8336
- <span class="store-name">{{ shopAuthService.shopName() }}</span>
8337
- }
8338
- </div>
8339
- <div class="header-right">
8340
- @if (shopAuthService.licenseTier()) {
8341
- <span class="license-badge" [class.expiring]="shopAuthService.daysRemaining() !== null && shopAuthService.daysRemaining()! <= 7">
8342
- {{ shopAuthService.licenseTier() | uppercase }}
8343
- @if (shopAuthService.daysRemaining() !== null && shopAuthService.daysRemaining()! <= 7) {
8344
- <span class="days-left">({{ shopAuthService.daysRemaining() }}d)</span>
8345
- }
8346
- </span>
8347
- }
8348
- <div class="user-menu">
8349
- <span class="user-email">{{ authService.userEmail() }}</span>
8350
- <button class="btn-logout" (click)="logout()">Salir</button>
8351
- </div>
8352
- </div>
8353
- </header>
8354
- <main class="app-content">
8355
- <ng-content></ng-content>
8356
- </main>
8357
- </div>
8358
- `, isInline: true, styles: [".app-shell{min-height:100vh;display:flex;flex-direction:column;background:#f6f6f7;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.app-header{display:flex;justify-content:space-between;align-items:center;padding:0 1rem;height:56px;background:#1a1a1a;color:#e3e3e3;box-shadow:0 1px #00000014}.header-left{display:flex;align-items:center;gap:.75rem}.logo{font-size:.9375rem;font-weight:600;letter-spacing:-.3px;color:#fff}.store-name{font-size:.8125rem;color:#8c9196;padding-left:.75rem;border-left:1px solid rgba(255,255,255,.15)}.header-right{display:flex;align-items:center;gap:.75rem}.license-badge{padding:.1875rem .5rem;background:#ffffff1f;border-radius:10px;font-size:.6875rem;font-weight:500;letter-spacing:.3px;color:#b5b5b5}.license-badge.expiring{background:#ffd6a4;color:#b98900}.days-left{font-weight:400;opacity:.8}.user-menu{display:flex;align-items:center;gap:.5rem}.user-email{font-size:.8125rem;color:#8c9196}.btn-logout{padding:.375rem .75rem;background:transparent;border:1px solid rgba(255,255,255,.15);border-radius:8px;color:#e3e3e3;font-size:.8125rem;font-family:inherit;cursor:pointer;transition:all .2s}.btn-logout:hover{background:#ffffff1a;border-color:#ffffff40}.app-content{flex:1}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: i1$1.UpperCasePipe, name: "uppercase" }] });
8359
- }
8360
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: AppShellComponent, decorators: [{
8361
- type: Component,
8362
- args: [{ selector: 'lib-app-shell', standalone: true, imports: [CommonModule], template: `
8363
- <div class="app-shell">
8364
- <header class="app-header">
8365
- <div class="header-left">
8366
- <span class="logo">Kustomizer</span>
8367
- @if (shopAuthService.shopName()) {
8368
- <span class="store-name">{{ shopAuthService.shopName() }}</span>
8369
- }
8370
- </div>
8371
- <div class="header-right">
8372
- @if (shopAuthService.licenseTier()) {
8373
- <span class="license-badge" [class.expiring]="shopAuthService.daysRemaining() !== null && shopAuthService.daysRemaining()! <= 7">
8374
- {{ shopAuthService.licenseTier() | uppercase }}
8375
- @if (shopAuthService.daysRemaining() !== null && shopAuthService.daysRemaining()! <= 7) {
8376
- <span class="days-left">({{ shopAuthService.daysRemaining() }}d)</span>
8377
- }
8378
- </span>
8379
- }
8380
- <div class="user-menu">
8381
- <span class="user-email">{{ authService.userEmail() }}</span>
8382
- <button class="btn-logout" (click)="logout()">Salir</button>
8383
- </div>
8384
- </div>
8385
- </header>
8386
- <main class="app-content">
8387
- <ng-content></ng-content>
8388
- </main>
8389
- </div>
8390
- `, styles: [".app-shell{min-height:100vh;display:flex;flex-direction:column;background:#f6f6f7;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.app-header{display:flex;justify-content:space-between;align-items:center;padding:0 1rem;height:56px;background:#1a1a1a;color:#e3e3e3;box-shadow:0 1px #00000014}.header-left{display:flex;align-items:center;gap:.75rem}.logo{font-size:.9375rem;font-weight:600;letter-spacing:-.3px;color:#fff}.store-name{font-size:.8125rem;color:#8c9196;padding-left:.75rem;border-left:1px solid rgba(255,255,255,.15)}.header-right{display:flex;align-items:center;gap:.75rem}.license-badge{padding:.1875rem .5rem;background:#ffffff1f;border-radius:10px;font-size:.6875rem;font-weight:500;letter-spacing:.3px;color:#b5b5b5}.license-badge.expiring{background:#ffd6a4;color:#b98900}.days-left{font-weight:400;opacity:.8}.user-menu{display:flex;align-items:center;gap:.5rem}.user-email{font-size:.8125rem;color:#8c9196}.btn-logout{padding:.375rem .75rem;background:transparent;border:1px solid rgba(255,255,255,.15);border-radius:8px;color:#e3e3e3;font-size:.8125rem;font-family:inherit;cursor:pointer;transition:all .2s}.btn-logout:hover{background:#ffffff1a;border-color:#ffffff40}.app-content{flex:1}\n"] }]
8391
- }], propDecorators: { loggedOut: [{ type: i0.Output, args: ["loggedOut"] }] } });
8392
-
8393
- class NoAccessComponent {
8394
- backToLogin = output();
8395
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NoAccessComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8396
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: NoAccessComponent, isStandalone: true, selector: "ke-no-access", outputs: { backToLogin: "backToLogin" }, ngImport: i0, template: `
8397
- <div class="noaccess-page">
8398
- <div class="noaccess-bg">
8399
- <div class="noaccess-gradient"></div>
8400
- <div class="noaccess-pattern"></div>
8401
- <div class="noaccess-glow noaccess-glow--1"></div>
8402
- <div class="noaccess-glow noaccess-glow--2"></div>
8403
- </div>
8404
-
8405
- <div class="noaccess-container">
8406
- <div class="noaccess-card">
8407
- <header class="noaccess-header">
8408
- <div class="noaccess-logo-wrap">
8409
- <img src="/img/kustomizerlogo.png" alt="Kustomizer" class="noaccess-logo" />
8410
- </div>
8411
-
8412
- <div class="noaccess-icon-wrap">
8413
- <div class="noaccess-icon-ring"></div>
8414
- <svg class="noaccess-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
8415
- <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
8416
- <path d="M7 11V7a5 5 0 0 1 10 0v4" />
8417
- </svg>
8418
- </div>
8419
-
8420
- <h1 class="noaccess-title">No access</h1>
8421
- <p class="noaccess-subtitle">You don't have permissions to access this store. Contact the store owner to request an invitation.</p>
8422
- </header>
8423
-
8424
- <div class="noaccess-info">
8425
- <div class="noaccess-info-row">
8426
- <svg viewBox="0 0 20 20" fill="currentColor" class="noaccess-info-icon">
8427
- <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
8428
- </svg>
8429
- <span>If you believe this is an error, try signing in with a different account.</span>
8430
- </div>
8431
- </div>
8432
-
8433
- <button type="button" class="noaccess-btn" (click)="backToLogin.emit()">
8434
- <svg viewBox="0 0 20 20" fill="currentColor" class="noaccess-btn-icon">
8435
- <path fill-rule="evenodd" d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z" clip-rule="evenodd" />
8436
- </svg>
8437
- Back to sign in
8438
- </button>
8439
- </div>
8440
-
8441
- <footer class="noaccess-footer">
8442
- <span class="noaccess-footer-text">Kustomizer</span>
8443
- <span class="noaccess-footer-dot">&middot;</span>
8444
- <a href="https://kustomizer.net" target="_blank" rel="noopener noreferrer" class="noaccess-footer-link">kustomizer.net</a>
8445
- </footer>
8446
- </div>
8447
- </div>
8448
- `, isInline: true, styles: [":host{display:block}.noaccess-page{--k-primary: #bdf366;--k-primary-hover: #a8e050;--k-primary-text: #1a1f0e;--k-bg: #0c0f08;--k-card: #ffffff;--k-border: #e5e7eb;--k-text: #1f2937;--k-text-secondary: #6b7280;--k-text-muted: #9ca3af;--k-red: #dc2626;--k-red-light: #fef2f2;--k-red-border: #fecaca;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--k-bg);color:var(--k-text);padding:20px;position:relative;overflow:hidden}.noaccess-bg{position:fixed;inset:0;pointer-events:none;z-index:0}.noaccess-gradient{position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(220,38,38,.1),transparent),radial-gradient(ellipse 60% 40% at 100% 100%,rgba(189,243,102,.06),transparent),linear-gradient(180deg,#0c0f08,#111a0a)}.noaccess-pattern{position:absolute;inset:0;background-image:radial-gradient(rgba(189,243,102,.07) 1px,transparent 1px);background-size:32px 32px;mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent);-webkit-mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent)}.noaccess-glow{position:absolute;border-radius:50%;filter:blur(80px);opacity:.6}.noaccess-glow--1{width:400px;height:400px;background:#dc262614;top:-150px;left:50%;transform:translate(-50%);animation:glowPulse 8s ease-in-out infinite}.noaccess-glow--2{width:300px;height:300px;background:#bdf3660f;bottom:-100px;right:-50px;animation:glowPulse2 10s ease-in-out infinite}@keyframes glowPulse{0%,to{opacity:.4;transform:translate(-50%) scale(1)}50%{opacity:.7;transform:translate(-50%) scale(1.1)}}@keyframes glowPulse2{0%,to{opacity:.3;transform:scale(1)}50%{opacity:.5;transform:scale(1.15)}}.noaccess-container{position:relative;z-index:1;width:100%;max-width:400px;animation:fadeIn .5s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.noaccess-card{background:var(--k-card);border-radius:20px;padding:40px 32px;box-shadow:0 0 0 1px #ffffff1a,0 25px 50px -12px #00000080,0 0 100px -20px #dc26261f}.noaccess-header{text-align:center;margin-bottom:24px}.noaccess-logo-wrap{margin-bottom:24px}.noaccess-logo{width:56px;height:56px;border-radius:12px}.noaccess-icon-wrap{position:relative;display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;margin-bottom:20px}.noaccess-icon-ring{position:absolute;inset:0;border-radius:50%;background:var(--k-red-light);border:1px solid var(--k-red-border)}.noaccess-icon{position:relative;width:28px;height:28px;color:var(--k-red)}.noaccess-title{font-size:24px;font-weight:600;color:var(--k-text);margin:0 0 8px;letter-spacing:-.3px}.noaccess-subtitle{font-size:14px;color:var(--k-text-secondary);margin:0;font-weight:400;line-height:1.5}.noaccess-info{background:var(--k-red-light);border:1px solid var(--k-red-border);border-radius:10px;padding:12px 14px;margin-bottom:24px}.noaccess-info-row{display:flex;align-items:flex-start;gap:10px;font-size:13px;color:#991b1b;line-height:1.4}.noaccess-info-icon{width:16px;height:16px;flex-shrink:0;margin-top:1px;color:var(--k-red)}.noaccess-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;background:var(--k-primary);color:var(--k-primary-text);border:none;border-radius:10px;padding:14px 20px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s ease,transform .1s ease;text-decoration:none;box-sizing:border-box}.noaccess-btn:hover{background:var(--k-primary-hover)}.noaccess-btn:active{transform:scale(.98)}.noaccess-btn-icon{width:16px;height:16px}.noaccess-footer{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px}.noaccess-footer-text{font-size:13px;color:#fff6;font-weight:500}.noaccess-footer-link{font-size:13px;color:#ffffff80;text-decoration:none;transition:color .2s ease}.noaccess-footer-link:hover{color:var(--k-primary)}.noaccess-footer-dot{color:#ffffff4d;font-size:12px}@media(max-width:440px){.noaccess-page{padding:16px}.noaccess-card{padding:32px 24px}.noaccess-title{font-size:22px}}\n"] });
8449
- }
8450
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NoAccessComponent, decorators: [{
8451
- type: Component,
8452
- args: [{ selector: 'ke-no-access', standalone: true, template: `
8453
- <div class="noaccess-page">
8454
- <div class="noaccess-bg">
8455
- <div class="noaccess-gradient"></div>
8456
- <div class="noaccess-pattern"></div>
8457
- <div class="noaccess-glow noaccess-glow--1"></div>
8458
- <div class="noaccess-glow noaccess-glow--2"></div>
8459
- </div>
8460
-
8461
- <div class="noaccess-container">
8462
- <div class="noaccess-card">
8463
- <header class="noaccess-header">
8464
- <div class="noaccess-logo-wrap">
8465
- <img src="/img/kustomizerlogo.png" alt="Kustomizer" class="noaccess-logo" />
8466
- </div>
8467
-
8468
- <div class="noaccess-icon-wrap">
8469
- <div class="noaccess-icon-ring"></div>
8470
- <svg class="noaccess-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
8471
- <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
8472
- <path d="M7 11V7a5 5 0 0 1 10 0v4" />
8473
- </svg>
8474
- </div>
8475
-
8476
- <h1 class="noaccess-title">No access</h1>
8477
- <p class="noaccess-subtitle">You don't have permissions to access this store. Contact the store owner to request an invitation.</p>
8478
- </header>
8479
-
8480
- <div class="noaccess-info">
8481
- <div class="noaccess-info-row">
8482
- <svg viewBox="0 0 20 20" fill="currentColor" class="noaccess-info-icon">
8483
- <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
8484
- </svg>
8485
- <span>If you believe this is an error, try signing in with a different account.</span>
8486
- </div>
8487
- </div>
8488
-
8489
- <button type="button" class="noaccess-btn" (click)="backToLogin.emit()">
8490
- <svg viewBox="0 0 20 20" fill="currentColor" class="noaccess-btn-icon">
8491
- <path fill-rule="evenodd" d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z" clip-rule="evenodd" />
8492
- </svg>
8493
- Back to sign in
8494
- </button>
8495
- </div>
8496
-
8497
- <footer class="noaccess-footer">
8498
- <span class="noaccess-footer-text">Kustomizer</span>
8499
- <span class="noaccess-footer-dot">&middot;</span>
8500
- <a href="https://kustomizer.net" target="_blank" rel="noopener noreferrer" class="noaccess-footer-link">kustomizer.net</a>
8501
- </footer>
8502
- </div>
8503
- </div>
8504
- `, styles: [":host{display:block}.noaccess-page{--k-primary: #bdf366;--k-primary-hover: #a8e050;--k-primary-text: #1a1f0e;--k-bg: #0c0f08;--k-card: #ffffff;--k-border: #e5e7eb;--k-text: #1f2937;--k-text-secondary: #6b7280;--k-text-muted: #9ca3af;--k-red: #dc2626;--k-red-light: #fef2f2;--k-red-border: #fecaca;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--k-bg);color:var(--k-text);padding:20px;position:relative;overflow:hidden}.noaccess-bg{position:fixed;inset:0;pointer-events:none;z-index:0}.noaccess-gradient{position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(220,38,38,.1),transparent),radial-gradient(ellipse 60% 40% at 100% 100%,rgba(189,243,102,.06),transparent),linear-gradient(180deg,#0c0f08,#111a0a)}.noaccess-pattern{position:absolute;inset:0;background-image:radial-gradient(rgba(189,243,102,.07) 1px,transparent 1px);background-size:32px 32px;mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent);-webkit-mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent)}.noaccess-glow{position:absolute;border-radius:50%;filter:blur(80px);opacity:.6}.noaccess-glow--1{width:400px;height:400px;background:#dc262614;top:-150px;left:50%;transform:translate(-50%);animation:glowPulse 8s ease-in-out infinite}.noaccess-glow--2{width:300px;height:300px;background:#bdf3660f;bottom:-100px;right:-50px;animation:glowPulse2 10s ease-in-out infinite}@keyframes glowPulse{0%,to{opacity:.4;transform:translate(-50%) scale(1)}50%{opacity:.7;transform:translate(-50%) scale(1.1)}}@keyframes glowPulse2{0%,to{opacity:.3;transform:scale(1)}50%{opacity:.5;transform:scale(1.15)}}.noaccess-container{position:relative;z-index:1;width:100%;max-width:400px;animation:fadeIn .5s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.noaccess-card{background:var(--k-card);border-radius:20px;padding:40px 32px;box-shadow:0 0 0 1px #ffffff1a,0 25px 50px -12px #00000080,0 0 100px -20px #dc26261f}.noaccess-header{text-align:center;margin-bottom:24px}.noaccess-logo-wrap{margin-bottom:24px}.noaccess-logo{width:56px;height:56px;border-radius:12px}.noaccess-icon-wrap{position:relative;display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;margin-bottom:20px}.noaccess-icon-ring{position:absolute;inset:0;border-radius:50%;background:var(--k-red-light);border:1px solid var(--k-red-border)}.noaccess-icon{position:relative;width:28px;height:28px;color:var(--k-red)}.noaccess-title{font-size:24px;font-weight:600;color:var(--k-text);margin:0 0 8px;letter-spacing:-.3px}.noaccess-subtitle{font-size:14px;color:var(--k-text-secondary);margin:0;font-weight:400;line-height:1.5}.noaccess-info{background:var(--k-red-light);border:1px solid var(--k-red-border);border-radius:10px;padding:12px 14px;margin-bottom:24px}.noaccess-info-row{display:flex;align-items:flex-start;gap:10px;font-size:13px;color:#991b1b;line-height:1.4}.noaccess-info-icon{width:16px;height:16px;flex-shrink:0;margin-top:1px;color:var(--k-red)}.noaccess-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;background:var(--k-primary);color:var(--k-primary-text);border:none;border-radius:10px;padding:14px 20px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s ease,transform .1s ease;text-decoration:none;box-sizing:border-box}.noaccess-btn:hover{background:var(--k-primary-hover)}.noaccess-btn:active{transform:scale(.98)}.noaccess-btn-icon{width:16px;height:16px}.noaccess-footer{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px}.noaccess-footer-text{font-size:13px;color:#fff6;font-weight:500}.noaccess-footer-link{font-size:13px;color:#ffffff80;text-decoration:none;transition:color .2s ease}.noaccess-footer-link:hover{color:var(--k-primary)}.noaccess-footer-dot{color:#ffffff4d;font-size:12px}@media(max-width:440px){.noaccess-page{padding:16px}.noaccess-card{padding:32px 24px}.noaccess-title{font-size:22px}}\n"] }]
8505
- }], propDecorators: { backToLogin: [{ type: i0.Output, args: ["backToLogin"] }] } });
8506
-
8507
- class LicenseExpiredComponent {
8508
- backToLogin = output();
8509
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LicenseExpiredComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8510
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: LicenseExpiredComponent, isStandalone: true, selector: "ke-license-expired", outputs: { backToLogin: "backToLogin" }, ngImport: i0, template: `
8511
- <div class="expired-page">
8512
- <div class="expired-bg">
8513
- <div class="expired-gradient"></div>
8514
- <div class="expired-pattern"></div>
8515
- <div class="expired-glow expired-glow--1"></div>
8516
- <div class="expired-glow expired-glow--2"></div>
8517
- </div>
8518
-
8519
- <div class="expired-container">
8520
- <div class="expired-card">
8521
- <header class="expired-header">
8522
- <div class="expired-logo-wrap">
8523
- <img src="/img/kustomizerlogo.png" alt="Kustomizer" class="expired-logo" />
8524
- </div>
8525
-
8526
- <div class="expired-icon-wrap">
8527
- <div class="expired-icon-ring"></div>
8528
- <svg class="expired-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
8529
- <circle cx="12" cy="12" r="10" />
8530
- <polyline points="12 6 12 12 16 14" />
8531
- </svg>
8532
- </div>
8533
-
8534
- <h1 class="expired-title">License expired</h1>
8535
- <p class="expired-subtitle">The license for this store's owner has expired. Contact the owner to renew the subscription.</p>
8536
- </header>
8537
-
8538
- <div class="expired-info">
8539
- <div class="expired-info-row">
8540
- <svg viewBox="0 0 20 20" fill="currentColor" class="expired-info-icon">
8541
- <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
8542
- </svg>
8543
- <span>All editing features are disabled until the license is renewed.</span>
8544
- </div>
8545
- </div>
8546
-
8547
- <button type="button" class="expired-btn" (click)="backToLogin.emit()">
8548
- <svg viewBox="0 0 20 20" fill="currentColor" class="expired-btn-icon">
8549
- <path fill-rule="evenodd" d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z" clip-rule="evenodd" />
8550
- </svg>
8551
- Back to sign in
8552
- </button>
8553
- </div>
8554
-
8555
- <footer class="expired-footer">
8556
- <span class="expired-footer-text">Kustomizer</span>
8557
- <span class="expired-footer-dot">&middot;</span>
8558
- <a href="https://kustomizer.net" target="_blank" rel="noopener noreferrer" class="expired-footer-link">kustomizer.net</a>
8559
- </footer>
8560
- </div>
8561
- </div>
8562
- `, isInline: true, styles: [":host{display:block}.expired-page{--k-primary: #bdf366;--k-primary-hover: #a8e050;--k-primary-text: #1a1f0e;--k-bg: #0c0f08;--k-card: #ffffff;--k-border: #e5e7eb;--k-text: #1f2937;--k-text-secondary: #6b7280;--k-text-muted: #9ca3af;--k-amber: #f59e0b;--k-amber-light: #fef3c7;--k-amber-border: #fde68a;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--k-bg);color:var(--k-text);padding:20px;position:relative;overflow:hidden}.expired-bg{position:fixed;inset:0;pointer-events:none;z-index:0}.expired-gradient{position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(245,158,11,.12),transparent),radial-gradient(ellipse 60% 40% at 100% 100%,rgba(189,243,102,.06),transparent),linear-gradient(180deg,#0c0f08,#111a0a)}.expired-pattern{position:absolute;inset:0;background-image:radial-gradient(rgba(189,243,102,.07) 1px,transparent 1px);background-size:32px 32px;mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent);-webkit-mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent)}.expired-glow{position:absolute;border-radius:50%;filter:blur(80px);opacity:.6}.expired-glow--1{width:400px;height:400px;background:#f59e0b1a;top:-150px;left:50%;transform:translate(-50%);animation:glowPulse 8s ease-in-out infinite}.expired-glow--2{width:300px;height:300px;background:#bdf3660f;bottom:-100px;right:-50px;animation:glowPulse2 10s ease-in-out infinite}@keyframes glowPulse{0%,to{opacity:.4;transform:translate(-50%) scale(1)}50%{opacity:.7;transform:translate(-50%) scale(1.1)}}@keyframes glowPulse2{0%,to{opacity:.3;transform:scale(1)}50%{opacity:.5;transform:scale(1.15)}}.expired-container{position:relative;z-index:1;width:100%;max-width:400px;animation:fadeIn .5s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.expired-card{background:var(--k-card);border-radius:20px;padding:40px 32px;box-shadow:0 0 0 1px #ffffff1a,0 25px 50px -12px #00000080,0 0 100px -20px #f59e0b26}.expired-header{text-align:center;margin-bottom:24px}.expired-logo-wrap{margin-bottom:24px}.expired-logo{width:56px;height:56px;border-radius:12px}.expired-icon-wrap{position:relative;display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;margin-bottom:20px}.expired-icon-ring{position:absolute;inset:0;border-radius:50%;background:var(--k-amber-light);border:1px solid var(--k-amber-border);animation:ringPulse 3s ease-in-out infinite}@keyframes ringPulse{0%,to{transform:scale(1);opacity:1}50%{transform:scale(1.08);opacity:.8}}.expired-icon{position:relative;width:28px;height:28px;color:var(--k-amber)}.expired-title{font-size:24px;font-weight:600;color:var(--k-text);margin:0 0 8px;letter-spacing:-.3px}.expired-subtitle{font-size:14px;color:var(--k-text-secondary);margin:0;font-weight:400;line-height:1.5}.expired-info{background:var(--k-amber-light);border:1px solid var(--k-amber-border);border-radius:10px;padding:12px 14px;margin-bottom:24px}.expired-info-row{display:flex;align-items:flex-start;gap:10px;font-size:13px;color:#92400e;line-height:1.4}.expired-info-icon{width:16px;height:16px;flex-shrink:0;margin-top:1px;color:var(--k-amber)}.expired-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;background:var(--k-primary);color:var(--k-primary-text);border:none;border-radius:10px;padding:14px 20px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s ease,transform .1s ease;text-decoration:none;box-sizing:border-box}.expired-btn:hover{background:var(--k-primary-hover)}.expired-btn:active{transform:scale(.98)}.expired-btn-icon{width:16px;height:16px}.expired-footer{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px}.expired-footer-text{font-size:13px;color:#fff6;font-weight:500}.expired-footer-link{font-size:13px;color:#ffffff80;text-decoration:none;transition:color .2s ease}.expired-footer-link:hover{color:var(--k-primary)}.expired-footer-dot{color:#ffffff4d;font-size:12px}@media(max-width:440px){.expired-page{padding:16px}.expired-card{padding:32px 24px}.expired-title{font-size:22px}}\n"] });
8563
- }
8564
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LicenseExpiredComponent, decorators: [{
8565
- type: Component,
8566
- args: [{ selector: 'ke-license-expired', standalone: true, template: `
8567
- <div class="expired-page">
8568
- <div class="expired-bg">
8569
- <div class="expired-gradient"></div>
8570
- <div class="expired-pattern"></div>
8571
- <div class="expired-glow expired-glow--1"></div>
8572
- <div class="expired-glow expired-glow--2"></div>
8573
- </div>
8574
-
8575
- <div class="expired-container">
8576
- <div class="expired-card">
8577
- <header class="expired-header">
8578
- <div class="expired-logo-wrap">
8579
- <img src="/img/kustomizerlogo.png" alt="Kustomizer" class="expired-logo" />
8580
- </div>
8581
-
8582
- <div class="expired-icon-wrap">
8583
- <div class="expired-icon-ring"></div>
8584
- <svg class="expired-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
8585
- <circle cx="12" cy="12" r="10" />
8586
- <polyline points="12 6 12 12 16 14" />
8587
- </svg>
8588
- </div>
8589
-
8590
- <h1 class="expired-title">License expired</h1>
8591
- <p class="expired-subtitle">The license for this store's owner has expired. Contact the owner to renew the subscription.</p>
8592
- </header>
8593
-
8594
- <div class="expired-info">
8595
- <div class="expired-info-row">
8596
- <svg viewBox="0 0 20 20" fill="currentColor" class="expired-info-icon">
8597
- <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd" />
8598
- </svg>
8599
- <span>All editing features are disabled until the license is renewed.</span>
8600
- </div>
8601
- </div>
8602
-
8603
- <button type="button" class="expired-btn" (click)="backToLogin.emit()">
8604
- <svg viewBox="0 0 20 20" fill="currentColor" class="expired-btn-icon">
8605
- <path fill-rule="evenodd" d="M17 10a.75.75 0 01-.75.75H5.612l4.158 3.96a.75.75 0 11-1.04 1.08l-5.5-5.25a.75.75 0 010-1.08l5.5-5.25a.75.75 0 111.04 1.08L5.612 9.25H16.25A.75.75 0 0117 10z" clip-rule="evenodd" />
8606
- </svg>
8607
- Back to sign in
8608
- </button>
8609
- </div>
8610
-
8611
- <footer class="expired-footer">
8612
- <span class="expired-footer-text">Kustomizer</span>
8613
- <span class="expired-footer-dot">&middot;</span>
8614
- <a href="https://kustomizer.net" target="_blank" rel="noopener noreferrer" class="expired-footer-link">kustomizer.net</a>
8615
- </footer>
8616
- </div>
8617
- </div>
8618
- `, styles: [":host{display:block}.expired-page{--k-primary: #bdf366;--k-primary-hover: #a8e050;--k-primary-text: #1a1f0e;--k-bg: #0c0f08;--k-card: #ffffff;--k-border: #e5e7eb;--k-text: #1f2937;--k-text-secondary: #6b7280;--k-text-muted: #9ca3af;--k-amber: #f59e0b;--k-amber-light: #fef3c7;--k-amber-border: #fde68a;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--k-bg);color:var(--k-text);padding:20px;position:relative;overflow:hidden}.expired-bg{position:fixed;inset:0;pointer-events:none;z-index:0}.expired-gradient{position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(245,158,11,.12),transparent),radial-gradient(ellipse 60% 40% at 100% 100%,rgba(189,243,102,.06),transparent),linear-gradient(180deg,#0c0f08,#111a0a)}.expired-pattern{position:absolute;inset:0;background-image:radial-gradient(rgba(189,243,102,.07) 1px,transparent 1px);background-size:32px 32px;mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent);-webkit-mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent)}.expired-glow{position:absolute;border-radius:50%;filter:blur(80px);opacity:.6}.expired-glow--1{width:400px;height:400px;background:#f59e0b1a;top:-150px;left:50%;transform:translate(-50%);animation:glowPulse 8s ease-in-out infinite}.expired-glow--2{width:300px;height:300px;background:#bdf3660f;bottom:-100px;right:-50px;animation:glowPulse2 10s ease-in-out infinite}@keyframes glowPulse{0%,to{opacity:.4;transform:translate(-50%) scale(1)}50%{opacity:.7;transform:translate(-50%) scale(1.1)}}@keyframes glowPulse2{0%,to{opacity:.3;transform:scale(1)}50%{opacity:.5;transform:scale(1.15)}}.expired-container{position:relative;z-index:1;width:100%;max-width:400px;animation:fadeIn .5s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.expired-card{background:var(--k-card);border-radius:20px;padding:40px 32px;box-shadow:0 0 0 1px #ffffff1a,0 25px 50px -12px #00000080,0 0 100px -20px #f59e0b26}.expired-header{text-align:center;margin-bottom:24px}.expired-logo-wrap{margin-bottom:24px}.expired-logo{width:56px;height:56px;border-radius:12px}.expired-icon-wrap{position:relative;display:inline-flex;align-items:center;justify-content:center;width:56px;height:56px;margin-bottom:20px}.expired-icon-ring{position:absolute;inset:0;border-radius:50%;background:var(--k-amber-light);border:1px solid var(--k-amber-border);animation:ringPulse 3s ease-in-out infinite}@keyframes ringPulse{0%,to{transform:scale(1);opacity:1}50%{transform:scale(1.08);opacity:.8}}.expired-icon{position:relative;width:28px;height:28px;color:var(--k-amber)}.expired-title{font-size:24px;font-weight:600;color:var(--k-text);margin:0 0 8px;letter-spacing:-.3px}.expired-subtitle{font-size:14px;color:var(--k-text-secondary);margin:0;font-weight:400;line-height:1.5}.expired-info{background:var(--k-amber-light);border:1px solid var(--k-amber-border);border-radius:10px;padding:12px 14px;margin-bottom:24px}.expired-info-row{display:flex;align-items:flex-start;gap:10px;font-size:13px;color:#92400e;line-height:1.4}.expired-info-icon{width:16px;height:16px;flex-shrink:0;margin-top:1px;color:var(--k-amber)}.expired-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;background:var(--k-primary);color:var(--k-primary-text);border:none;border-radius:10px;padding:14px 20px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s ease,transform .1s ease;text-decoration:none;box-sizing:border-box}.expired-btn:hover{background:var(--k-primary-hover)}.expired-btn:active{transform:scale(.98)}.expired-btn-icon{width:16px;height:16px}.expired-footer{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px}.expired-footer-text{font-size:13px;color:#fff6;font-weight:500}.expired-footer-link{font-size:13px;color:#ffffff80;text-decoration:none;transition:color .2s ease}.expired-footer-link:hover{color:var(--k-primary)}.expired-footer-dot{color:#ffffff4d;font-size:12px}@media(max-width:440px){.expired-page{padding:16px}.expired-card{padding:32px 24px}.expired-title{font-size:22px}}\n"] }]
8619
- }], propDecorators: { backToLogin: [{ type: i0.Output, args: ["backToLogin"] }] } });
8620
-
8621
- class ShopTeamService {
7809
+ /**
7810
+ * Loads a component manifest from a remote storefront URL and registers
7811
+ * the entries in the ComponentRegistryService as "virtual" definitions
7812
+ * (no Angular component class — only metadata for palette and property panel).
7813
+ */
7814
+ class ManifestLoaderService {
8622
7815
  http = inject(HttpClient);
8623
- config = inject(SUPABASE_CONFIG, { optional: true });
8624
- authService = inject(SupabaseAuthService);
8625
- shopAuthService = inject(ShopAuthService);
8626
- // Estado
8627
- _team = signal(null, ...(ngDevMode ? [{ debugName: "_team" }] : []));
8628
- _loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : []));
8629
- _error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
8630
- team = this._team.asReadonly();
8631
- loading = this._loading.asReadonly();
8632
- error = this._error.asReadonly();
8633
- // Computed
8634
- members = computed(() => this._team()?.members ?? [], ...(ngDevMode ? [{ debugName: "members" }] : []));
8635
- invitations = computed(() => this._team()?.invitations ?? [], ...(ngDevMode ? [{ debugName: "invitations" }] : []));
8636
- currentUser = computed(() => this._team()?.current_user ?? null, ...(ngDevMode ? [{ debugName: "currentUser" }] : []));
8637
- activeMembers = computed(() => this.members().filter((m) => m.status === 'active'), ...(ngDevMode ? [{ debugName: "activeMembers" }] : []));
8638
- pendingInvitations = computed(() => this.invitations().filter((i) => !i.is_expired), ...(ngDevMode ? [{ debugName: "pendingInvitations" }] : []));
8639
- canInvite = computed(() => this._team()?.current_user?.can_invite ?? false, ...(ngDevMode ? [{ debugName: "canInvite" }] : []));
8640
- /**
8641
- * Carga el equipo de la shop actual
8642
- */
8643
- async loadTeam() {
8644
- const shopId = this.shopAuthService.shopId();
8645
- if (!shopId) {
8646
- this._error.set('No shop selected');
8647
- return null;
8648
- }
8649
- const token = this.authService.accessToken();
8650
- if (!token) {
8651
- this._error.set('Not authenticated');
8652
- return null;
8653
- }
8654
- this._loading.set(true);
8655
- this._error.set(null);
8656
- try {
8657
- const url = this.config
8658
- ? `${this.config.supabaseUrl}/functions/v1/shop_team`
8659
- : '/functions/v1/shop_team';
8660
- const response = await firstValueFrom(this.http.post(url, { shop_id: shopId }, {
8661
- headers: {
8662
- 'Authorization': `Bearer ${token}`,
8663
- 'Content-Type': 'application/json',
8664
- },
8665
- }));
8666
- this._team.set(response);
8667
- this._loading.set(false);
8668
- return response;
8669
- }
8670
- catch (err) {
8671
- console.error('[ShopTeamService] Load team failed:', err);
8672
- const httpError = err;
8673
- this._error.set(httpError.error?.error || 'Failed to load team');
8674
- this._loading.set(false);
8675
- return null;
8676
- }
8677
- }
7816
+ registry = inject(ComponentRegistryService);
8678
7817
  /**
8679
- * Invita a un usuario a la shop
7818
+ * Fetch the manifest from the given URL and register all components.
8680
7819
  */
8681
- async inviteUser(email, role, message) {
8682
- const shopId = this.shopAuthService.shopId();
8683
- if (!shopId) {
8684
- this._error.set('No shop selected');
8685
- return null;
8686
- }
8687
- const token = this.authService.accessToken();
8688
- if (!token) {
8689
- this._error.set('Not authenticated');
8690
- return null;
8691
- }
8692
- this._loading.set(true);
8693
- this._error.set(null);
8694
- try {
8695
- const url = this.config
8696
- ? `${this.config.supabaseUrl}/functions/v1/invite_user`
8697
- : '/functions/v1/invite_user';
8698
- const response = await firstValueFrom(this.http.post(url, {
8699
- shop_id: shopId,
8700
- email: email.toLowerCase().trim(),
8701
- role,
8702
- message: message?.trim() || undefined,
8703
- }, {
8704
- headers: {
8705
- 'Authorization': `Bearer ${token}`,
8706
- 'Content-Type': 'application/json',
8707
- },
8708
- }));
8709
- // Recargar equipo para mostrar la invitación
8710
- await this.loadTeam();
8711
- this._loading.set(false);
8712
- return response;
8713
- }
8714
- catch (err) {
8715
- console.error('[ShopTeamService] Invite user failed:', err);
8716
- const httpError = err;
8717
- this._error.set(httpError.error?.error || 'Failed to invite user');
8718
- this._loading.set(false);
8719
- return null;
8720
- }
7820
+ loadManifest(url) {
7821
+ return this.http.get(url).pipe(tap((manifest) => this.registerManifestComponents(manifest)));
8721
7822
  }
8722
7823
  /**
8723
- * Acepta una invitación (usado cuando un usuario hace click en el link de invitación)
7824
+ * Register manifest entries in the ComponentRegistryService.
7825
+ * Each entry becomes a ComponentDefinition without a `component` field.
8724
7826
  */
8725
- async acceptInvitation(invitationToken) {
8726
- const authToken = this.authService.accessToken();
8727
- if (!authToken) {
8728
- this._error.set('Not authenticated');
8729
- return null;
7827
+ registerManifestComponents(manifest) {
7828
+ for (const entry of manifest.components) {
7829
+ const definition = this.manifestEntryToDefinition(entry);
7830
+ this.registry.register(definition);
8730
7831
  }
8731
- this._loading.set(true);
8732
- this._error.set(null);
8733
- try {
8734
- const url = this.config
8735
- ? `${this.config.supabaseUrl}/functions/v1/accept_invitation`
8736
- : '/functions/v1/accept_invitation';
8737
- const response = await firstValueFrom(this.http.post(url, {
8738
- token: invitationToken,
8739
- }, {
8740
- headers: {
8741
- 'Authorization': `Bearer ${authToken}`,
8742
- 'Content-Type': 'application/json',
8743
- },
8744
- }));
8745
- this._loading.set(false);
8746
- return response;
8747
- }
8748
- catch (err) {
8749
- console.error('[ShopTeamService] Accept invitation failed:', err);
8750
- const httpError = err;
8751
- this._error.set(httpError.error?.error || 'Failed to accept invitation');
8752
- this._loading.set(false);
8753
- return null;
8754
- }
8755
- }
8756
- /**
8757
- * Genera el URL de invitación (para copiar/compartir)
8758
- */
8759
- getInvitationUrl(token, baseUrl) {
8760
- const base = baseUrl || (typeof window !== 'undefined' ? window.location.origin : '');
8761
- return `${base}/accept-invitation?token=${token}`;
8762
7832
  }
8763
- /**
8764
- * Limpia el estado
8765
- */
8766
- clear() {
8767
- this._team.set(null);
8768
- this._error.set(null);
7833
+ manifestEntryToDefinition(entry) {
7834
+ return {
7835
+ type: entry.type,
7836
+ name: entry.name,
7837
+ description: entry.description,
7838
+ category: entry.category,
7839
+ icon: entry.icon,
7840
+ props: entry.props,
7841
+ slots: entry.slots,
7842
+ isSection: entry.isSection,
7843
+ isBlock: entry.isBlock,
7844
+ blockScope: entry.blockScope,
7845
+ sectionTypes: entry.sectionTypes,
7846
+ draggable: entry.draggable,
7847
+ deletable: entry.deletable,
7848
+ duplicable: entry.duplicable,
7849
+ tags: entry.tags,
7850
+ order: entry.order,
7851
+ // No `component` — rendering happens in the iframe
7852
+ };
8769
7853
  }
8770
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopTeamService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8771
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopTeamService, providedIn: 'root' });
7854
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ManifestLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
7855
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ManifestLoaderService, providedIn: 'root' });
8772
7856
  }
8773
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ShopTeamService, decorators: [{
7857
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ManifestLoaderService, decorators: [{
8774
7858
  type: Injectable,
8775
7859
  args: [{ providedIn: 'root' }]
8776
7860
  }] });
8777
7861
 
8778
- const SHOPIFY_API_VERSION = '2026-01';
8779
- const SHOPIFY_CONFIG = new InjectionToken('SHOPIFY_CONFIG');
8780
-
8781
- const authGuard = async () => {
8782
- const authService = inject(SupabaseAuthService);
8783
- const router = inject(Router);
8784
- await authService.ensureInitialized();
8785
- if (authService.isAuthenticated()) {
8786
- return true;
8787
- }
8788
- router.navigate(['/login']);
8789
- return false;
8790
- };
8791
- const noAuthGuard = async () => {
8792
- const authService = inject(SupabaseAuthService);
8793
- const router = inject(Router);
8794
- await authService.ensureInitialized();
8795
- if (!authService.isAuthenticated()) {
8796
- return true;
8797
- }
8798
- router.navigate(['/admin']);
8799
- return false;
8800
- };
8801
-
8802
- const licenseGuard = async () => {
8803
- const shopAuthService = inject(ShopAuthService);
8804
- const router = inject(Router);
8805
- await shopAuthService.authenticate();
8806
- if (shopAuthService.hasAccess()) {
8807
- return true;
8808
- }
8809
- router.navigate(['/activate']);
8810
- return false;
8811
- };
8812
- const noLicenseGuard = async () => {
8813
- const shopAuthService = inject(ShopAuthService);
8814
- const router = inject(Router);
8815
- await shopAuthService.authenticate();
8816
- if (!shopAuthService.hasAccess()) {
8817
- return true;
8818
- }
8819
- router.navigate(['/admin']);
8820
- return false;
8821
- };
8822
-
8823
- /**
8824
- * Guard que verifica:
8825
- * 1. Usuario autenticado
8826
- * 2. Usuario tiene acceso a la shop del dominio actual
8827
- * 3. Licencia activa
8828
- *
8829
- * Redirige a:
8830
- * - /login si no está autenticado
8831
- * - /no-access si no tiene acceso a la shop
8832
- * - /license-expired si la licencia expiró
8833
- */
8834
- const shopAuthGuard = async () => {
8835
- const authService = inject(SupabaseAuthService);
8836
- const shopAuthService = inject(ShopAuthService);
8837
- const router = inject(Router);
8838
- // Esperar inicialización del auth
8839
- await authService.ensureInitialized();
8840
- // Verificar autenticación básica
8841
- if (!authService.isAuthenticated()) {
8842
- router.navigate(['/login']);
8843
- return false;
8844
- }
8845
- // Autenticar contra la shop
8846
- await shopAuthService.authenticate();
8847
- // Verificar acceso
8848
- if (shopAuthService.hasAccess()) {
8849
- return true;
8850
- }
8851
- // Manejar diferentes tipos de error
8852
- const error = shopAuthService.error();
8853
- if (error?.code === 'NOT_AUTHENTICATED') {
8854
- router.navigate(['/login']);
8855
- return false;
8856
- }
8857
- if (error?.code === 'LICENSE_EXPIRED') {
8858
- router.navigate(['/license-expired']);
8859
- return false;
8860
- }
8861
- if (error?.code === 'NO_ACCESS' || error?.code === 'SHOP_NOT_FOUND') {
8862
- router.navigate(['/no-access']);
8863
- return false;
8864
- }
8865
- if (error?.code === 'LOCALHOST_NO_SHOP') {
8866
- alert('Add ?shop=your-store.myshopify.com to the URL');
8867
- return false;
8868
- }
8869
- if (error?.code === 'USER_INACTIVE') {
8870
- router.navigate(['/account-inactive']);
8871
- return false;
8872
- }
8873
- // Error desconocido, redirigir a no-access
8874
- router.navigate(['/no-access']);
8875
- return false;
8876
- };
8877
- /**
8878
- * Guard que solo permite acceso si el usuario NO tiene acceso a una shop.
8879
- * Útil para páginas como /login que no deberían ser accesibles si ya tienes acceso.
8880
- */
8881
- const noShopAuthGuard = async () => {
8882
- const authService = inject(SupabaseAuthService);
8883
- const shopAuthService = inject(ShopAuthService);
8884
- const router = inject(Router);
8885
- await authService.ensureInitialized();
8886
- // Si no está autenticado, permitir (puede ver login)
8887
- if (!authService.isAuthenticated()) {
8888
- return true;
8889
- }
8890
- // Verificar acceso a shop
8891
- await shopAuthService.authenticate();
8892
- // Si tiene acceso completo, redirigir al admin
8893
- if (shopAuthService.hasAccess()) {
8894
- router.navigate(['/admin']);
8895
- return false;
8896
- }
8897
- // No tiene acceso completo, permitir ver la página
8898
- return true;
8899
- };
8900
- /**
8901
- * Guard que verifica que el usuario tenga rol de admin o owner.
8902
- * Requiere que shopAuthGuard se haya ejecutado primero.
8903
- */
8904
- const shopAdminGuard = async () => {
8905
- const shopAuthService = inject(ShopAuthService);
8906
- const router = inject(Router);
8907
- // Asegurar que ya se autenticó
8908
- if (!shopAuthService.initialized()) {
8909
- await shopAuthService.authenticate();
8910
- }
8911
- if (shopAuthService.isAdmin()) {
8912
- return true;
8913
- }
8914
- // No es admin, redirigir a admin (sin permisos de gestión)
8915
- router.navigate(['/admin']);
8916
- return false;
8917
- };
8918
- /**
8919
- * Guard que verifica que el usuario pueda editar (owner o admin).
8920
- * Los readers solo pueden ver, no editar.
8921
- */
8922
- const shopEditorGuard = async () => {
8923
- const shopAuthService = inject(ShopAuthService);
8924
- const router = inject(Router);
8925
- if (!shopAuthService.initialized()) {
8926
- await shopAuthService.authenticate();
8927
- }
8928
- if (shopAuthService.canEdit()) {
8929
- return true;
8930
- }
8931
- // Es reader, mostrar página de solo lectura o redirigir
8932
- router.navigate(['/admin']);
8933
- return false;
8934
- };
8935
-
8936
7862
  class VisualEditor {
8937
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VisualEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
8938
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.6", type: VisualEditor, isStandalone: true, selector: "lib-visual-editor", ngImport: i0, template: `
7863
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
7864
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: VisualEditor, isStandalone: true, selector: "lib-visual-editor", ngImport: i0, template: `
8939
7865
  <p>
8940
7866
  visual-editor works!
8941
7867
  </p>
8942
7868
  `, isInline: true, styles: [""] });
8943
7869
  }
8944
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: VisualEditor, decorators: [{
7870
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditor, decorators: [{
8945
7871
  type: Component,
8946
7872
  args: [{ selector: 'lib-visual-editor', imports: [], template: `
8947
7873
  <p>
@@ -8959,5 +7885,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
8959
7885
  * Generated bundle index. Do not edit.
8960
7886
  */
8961
7887
 
8962
- export { ActivateLicenseComponent, AppShellComponent, BlockTreeItemComponent, ComponentRegistryService, DEFAULT_ROUTER_NAVIGATION_CONFIG, DEFAULT_VISUAL_EDITOR_CONFIG, DELETE_METAFIELDS_MUTATION, DefaultRouterNavigationService, DragDropService, DynamicRendererComponent, EDITOR_COMPONENT_DEFINITIONS, FILES_QUERY, GET_SHOP_ID_QUERY, GET_SHOP_METAFIELD_QUERY, INDEX_KEY, InputPageLoadingStrategy, LicenseExpiredComponent, LicenseGuardComponent, LoginComponent, MAX_METAFIELD_SIZE, NAMESPACE, NoAccessComponent, PAGE_ENVIRONMENT, PageLoadingStrategy, PageManagerComponent, PageService, PageShopifyRepository, ROUTER_NAVIGATION_CONFIG, RoutePageLoadingStrategy, SET_METAFIELD_MUTATION, SHOPIFY_API_VERSION, SHOPIFY_APP_CONFIG, SHOPIFY_CONFIG, STORE_DOMAIN, SUPABASE_CONFIG, ShopAuthService, ShopTeamService, ShopifyFilePickerComponent, ShopifyFilesService, ShopifyGraphQLClient, ShopifyMetafieldRepository, ShopifyTokenService, SlotRendererComponent, SupabaseAuthService, USE_IN_MEMORY_PAGES, VISUAL_EDITOR_CONFIG, VISUAL_EDITOR_FEATURE_KEY, VisualEditor, VisualEditorActions, VisualEditorComponent, VisualEditorFacade, VisualEditorNavigation, authGuard, initialVisualEditorState, licenseGuard, noAuthGuard, noLicenseGuard, noShopAuthGuard, provideEditorComponents, provideVisualEditor, provideVisualEditorStore, selectBlocksForSection, selectBlocksForSlot, selectCanRedo, selectCanUndo, selectCurrentPage, selectCurrentPageId, selectCurrentPageSlug, selectCurrentPageStatus, selectCurrentPageTitle, selectCurrentPageVersion, selectDraggedElementId, selectElementById, selectHistory, selectHistoryIndex, selectHistoryLength, selectIsDirty, selectIsDragging, selectIsPageLoaded, selectLastAction, selectSectionById, selectSections, selectSelectedBlock, selectSelectedBlockSlotName, selectSelectedElement, selectSelectedElementId, selectSelectedElementType, selectSelectedSection, selectSelectedSectionId, selectSelectedSectionType, selectVisualEditorState, shopAdminGuard, shopAuthGuard, shopEditorGuard, visualEditorReducer };
7888
+ export { BlockTreeItemComponent, CREATE_METAFIELD_DEFINITION_MUTATION, ComponentRegistryService, DEFAULT_ROUTER_NAVIGATION_CONFIG, DEFAULT_VISUAL_EDITOR_CONFIG, DELETE_METAFIELDS_MUTATION, DELETE_METAFIELD_DEFINITION_MUTATION, DefaultRouterNavigationService, DragDropService, DynamicRendererComponent, EDITOR_COMPONENT_DEFINITIONS, FILES_QUERY, GET_METAFIELD_DEFINITION_QUERY, GET_SHOP_ID_QUERY, GET_SHOP_METAFIELD_QUERY$1 as GET_SHOP_METAFIELD_QUERY, INDEX_KEY$1 as INDEX_KEY, IframeBridgeService, InputPageLoadingStrategy, MAX_METAFIELD_SIZE, ManifestLoaderService, NAMESPACE$1 as NAMESPACE, PageLoadingStrategy, PageManagerComponent, PageService, PageShopifyRepository, PageStorefrontRepository, ROUTER_NAVIGATION_CONFIG, RoutePageLoadingStrategy, SET_METAFIELD_MUTATION, SHOPIFY_CONFIG, STOREFRONT_CONFIG, STOREFRONT_URL, ShopifyFilePickerComponent, ShopifyFilesService, ShopifyGraphQLClient, ShopifyMetafieldRepository, SlotRendererComponent, StorefrontGraphQLClient, StorefrontMetafieldRepository, StorefrontUrlService, USE_IN_MEMORY_PAGES, VISUAL_EDITOR_CONFIG, VISUAL_EDITOR_FEATURE_KEY, VisualEditor, VisualEditorActions, VisualEditorComponent, VisualEditorFacade, VisualEditorNavigation, initialVisualEditorState, provideEditorComponents, provideVisualEditor, provideVisualEditorStore, selectBlocksForSection, selectBlocksForSlot, selectCanRedo, selectCanUndo, selectCurrentPage, selectCurrentPageId, selectCurrentPageSlug, selectCurrentPageStatus, selectCurrentPageTitle, selectCurrentPageVersion, selectDraggedElementId, selectElementById, selectHistory, selectHistoryIndex, selectHistoryLength, selectIsDirty, selectIsDragging, selectIsPageLoaded, selectLastAction, selectSectionById, selectSections, selectSelectedBlock, selectSelectedBlockSlotName, selectSelectedElement, selectSelectedElementId, selectSelectedElementType, selectSelectedSection, selectSelectedSectionId, selectSelectedSectionType, selectVisualEditorState, visualEditorReducer };
8963
7889
  //# sourceMappingURL=kustomizer-visual-editor.mjs.map