@kustomizer/visual-editor 0.0.1 → 0.1.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.
- package/LICENSE +12 -0
- package/README.md +25 -15
- package/fesm2022/kustomizer-visual-editor.mjs +955 -2075
- package/fesm2022/kustomizer-visual-editor.mjs.map +1 -1
- package/package.json +22 -13
- package/types/kustomizer-visual-editor.d.ts +263 -450
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, Injectable,
|
|
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,
|
|
5
|
-
import {
|
|
4
|
+
import { of, startWith, map, delay, throwError, isObservable, Observable, BehaviorSubject, tap, Subject, filter, 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
|
|
86
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
376
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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
|
-
*
|
|
558
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
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
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
-
|
|
444
|
+
throw {
|
|
964
445
|
code: 'GRAPHQL_ERROR',
|
|
965
446
|
message: response.errors[0].message,
|
|
966
|
-
errors: response.errors
|
|
447
|
+
errors: response.errors,
|
|
967
448
|
};
|
|
968
|
-
throw error;
|
|
969
449
|
}
|
|
970
450
|
if (!response.data) {
|
|
971
|
-
|
|
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
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
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(() =>
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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
|
|
1019
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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(
|
|
1168
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1201
|
-
const key =
|
|
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
|
|
1219
|
-
const key =
|
|
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
|
|
1225
|
-
const key =
|
|
1226
|
-
return this.deleteMetafield(NAMESPACE, key);
|
|
723
|
+
deletePageMetafield(pageId) {
|
|
724
|
+
const key = `page_${pageId}`;
|
|
725
|
+
return this.deleteMetafield(NAMESPACE$1, key);
|
|
1227
726
|
}
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
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
|
|
1253
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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
|
-
|
|
1263
|
-
|
|
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
|
|
1275
|
-
|
|
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
|
|
1287
|
-
|
|
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
|
|
848
|
+
return this.getPage(entry.id);
|
|
1297
849
|
}));
|
|
1298
850
|
}
|
|
1299
|
-
createPage(request
|
|
1300
|
-
|
|
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
|
|
1325
|
-
|
|
1326
|
-
return this.metafieldRepo.
|
|
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
|
|
1331
|
-
|
|
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(
|
|
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
|
|
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
|
|
918
|
+
return this.metafieldRepo.updatePageIndex(updatedIndex);
|
|
1366
919
|
}), map$1(() => updatedPage));
|
|
1367
920
|
}));
|
|
1368
921
|
}));
|
|
1369
922
|
}
|
|
1370
|
-
deletePage(id
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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
|
|
1378
|
-
|
|
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
|
|
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
|
|
957
|
+
return this.metafieldRepo.updatePageIndex(updatedIndex);
|
|
1398
958
|
}), map$1(() => publishedPage));
|
|
1399
959
|
}));
|
|
1400
960
|
}
|
|
1401
|
-
unpublishPage(id, request
|
|
1402
|
-
|
|
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
|
|
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
|
|
986
|
+
return this.metafieldRepo.updatePageIndex(updatedIndex);
|
|
1428
987
|
}), map$1(() => unpublishedPage));
|
|
1429
988
|
}));
|
|
1430
989
|
}
|
|
1431
|
-
duplicatePage(id
|
|
1432
|
-
|
|
1433
|
-
|
|
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
|
|
1458
|
-
|
|
1459
|
-
|
|
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
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
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
|
|
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
|
|
1544
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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(
|
|
1580
|
-
const env = this.resolveEnvironment(environment);
|
|
1147
|
+
getPages() {
|
|
1581
1148
|
if (this.useInMemory) {
|
|
1582
|
-
return this.memoryRepo.getPages(
|
|
1149
|
+
return this.memoryRepo.getPages();
|
|
1583
1150
|
}
|
|
1584
|
-
return this.shopifyRepo.getPages(
|
|
1151
|
+
return this.shopifyRepo.getPages();
|
|
1585
1152
|
}
|
|
1586
1153
|
/**
|
|
1587
1154
|
* Get a single page by ID (full content)
|
|
1588
1155
|
*/
|
|
1589
|
-
getPage(id
|
|
1590
|
-
const env = this.resolveEnvironment(environment);
|
|
1156
|
+
getPage(id) {
|
|
1591
1157
|
if (this.useInMemory) {
|
|
1592
|
-
return this.memoryRepo.getPage(id
|
|
1158
|
+
return this.memoryRepo.getPage(id);
|
|
1593
1159
|
}
|
|
1594
|
-
return this.shopifyRepo.getPage(id
|
|
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
|
|
1600
|
-
const env = this.resolveEnvironment(environment);
|
|
1165
|
+
getPublishedPageBySlug(slug) {
|
|
1601
1166
|
if (this.useInMemory) {
|
|
1602
|
-
return this.memoryRepo.getPublishedPageBySlug(slug
|
|
1167
|
+
return this.memoryRepo.getPublishedPageBySlug(slug);
|
|
1603
1168
|
}
|
|
1604
|
-
return this.shopifyRepo.getPublishedPageBySlug(slug
|
|
1169
|
+
return this.shopifyRepo.getPublishedPageBySlug(slug);
|
|
1605
1170
|
}
|
|
1606
1171
|
/**
|
|
1607
1172
|
* Create a new page
|
|
1608
1173
|
*/
|
|
1609
|
-
createPage(request
|
|
1610
|
-
const env = this.resolveEnvironment(environment);
|
|
1174
|
+
createPage(request) {
|
|
1611
1175
|
if (this.useInMemory) {
|
|
1612
|
-
return this.memoryRepo.createPage(request
|
|
1176
|
+
return this.memoryRepo.createPage(request);
|
|
1613
1177
|
}
|
|
1614
|
-
return this.shopifyRepo.createPage(request
|
|
1178
|
+
return this.shopifyRepo.createPage(request);
|
|
1615
1179
|
}
|
|
1616
1180
|
/**
|
|
1617
1181
|
* Update an existing page
|
|
1618
1182
|
*/
|
|
1619
|
-
updatePage(id, request
|
|
1620
|
-
const env = this.resolveEnvironment(environment);
|
|
1183
|
+
updatePage(id, request) {
|
|
1621
1184
|
if (this.useInMemory) {
|
|
1622
|
-
return this.memoryRepo.updatePage(id, request
|
|
1185
|
+
return this.memoryRepo.updatePage(id, request);
|
|
1623
1186
|
}
|
|
1624
|
-
return this.shopifyRepo.updatePage(id, request
|
|
1187
|
+
return this.shopifyRepo.updatePage(id, request);
|
|
1625
1188
|
}
|
|
1626
1189
|
/**
|
|
1627
1190
|
* Delete a page
|
|
1628
1191
|
*/
|
|
1629
|
-
deletePage(id
|
|
1630
|
-
const env = this.resolveEnvironment(environment);
|
|
1192
|
+
deletePage(id) {
|
|
1631
1193
|
if (this.useInMemory) {
|
|
1632
|
-
return this.memoryRepo.deletePage(id
|
|
1194
|
+
return this.memoryRepo.deletePage(id);
|
|
1633
1195
|
}
|
|
1634
|
-
return this.shopifyRepo.deletePage(id
|
|
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
|
|
1640
|
-
const env = this.resolveEnvironment(environment);
|
|
1201
|
+
publishPage(id, request) {
|
|
1641
1202
|
if (this.useInMemory) {
|
|
1642
|
-
return this.memoryRepo.publishPage(id, request
|
|
1203
|
+
return this.memoryRepo.publishPage(id, request);
|
|
1643
1204
|
}
|
|
1644
|
-
return this.shopifyRepo.publishPage(id, request
|
|
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
|
|
1650
|
-
const env = this.resolveEnvironment(environment);
|
|
1210
|
+
unpublishPage(id, request) {
|
|
1651
1211
|
if (this.useInMemory) {
|
|
1652
|
-
return this.memoryRepo.unpublishPage(id, request
|
|
1212
|
+
return this.memoryRepo.unpublishPage(id, request);
|
|
1653
1213
|
}
|
|
1654
|
-
return this.shopifyRepo.unpublishPage(id, request
|
|
1214
|
+
return this.shopifyRepo.unpublishPage(id, request);
|
|
1655
1215
|
}
|
|
1656
1216
|
/**
|
|
1657
1217
|
* Duplicate an existing page
|
|
1658
1218
|
*/
|
|
1659
|
-
duplicatePage(id
|
|
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.
|
|
1221
|
+
return this.memoryRepo.duplicatePage(id);
|
|
1673
1222
|
}
|
|
1674
|
-
return this.shopifyRepo.
|
|
1223
|
+
return this.shopifyRepo.duplicatePage(id);
|
|
1675
1224
|
}
|
|
1676
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
1677
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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
|
|
1703
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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
|
|
1729
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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
|
|
2799
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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
|
|
2908
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
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
|
|
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
|
|
2965
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,108 @@ 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
|
|
3624
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
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
|
|
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
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
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
|
+
const MAX_DEPTH = 12;
|
|
3264
|
+
class DragDropService {
|
|
3265
|
+
store = inject(Store);
|
|
3266
|
+
registry = inject(ComponentRegistryService);
|
|
3267
|
+
sections = toSignal(this.store.select(selectSections), { initialValue: [] });
|
|
3268
|
+
dragItem = signal(null, ...(ngDevMode ? [{ debugName: "dragItem" }] : []));
|
|
3269
|
+
dropTarget = signal(null, ...(ngDevMode ? [{ debugName: "dropTarget" }] : []));
|
|
3270
|
+
isDragging = computed(() => this.dragItem() !== null, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
|
|
3271
|
+
startDrag(item, event) {
|
|
3272
|
+
this.dragItem.set(item);
|
|
3273
|
+
this.store.dispatch(VisualEditorActions.startDrag({ elementId: item.elementId }));
|
|
3642
3274
|
if (event.dataTransfer) {
|
|
3643
3275
|
event.dataTransfer.effectAllowed = 'move';
|
|
3644
3276
|
event.dataTransfer.setData('text/plain', item.elementId);
|
|
@@ -3870,10 +3502,10 @@ class DragDropService {
|
|
|
3870
3502
|
return false;
|
|
3871
3503
|
return a.every((id, i) => id === b[i]);
|
|
3872
3504
|
}
|
|
3873
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
3874
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
3505
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DragDropService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3506
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DragDropService, providedIn: 'root' });
|
|
3875
3507
|
}
|
|
3876
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
3508
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DragDropService, decorators: [{
|
|
3877
3509
|
type: Injectable,
|
|
3878
3510
|
args: [{ providedIn: 'root' }]
|
|
3879
3511
|
}] });
|
|
@@ -3885,7 +3517,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
3885
3517
|
class BlockTreeItemComponent {
|
|
3886
3518
|
facade = inject(VisualEditorFacade);
|
|
3887
3519
|
dndService = inject(DragDropService);
|
|
3888
|
-
sanitizer = inject(DomSanitizer);
|
|
3889
3520
|
treeBlock = viewChild('treeBlock', ...(ngDevMode ? [{ debugName: "treeBlock" }] : []));
|
|
3890
3521
|
block = input.required(...(ngDevMode ? [{ debugName: "block" }] : []));
|
|
3891
3522
|
context = input.required(...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
@@ -3913,20 +3544,14 @@ class BlockTreeItemComponent {
|
|
|
3913
3544
|
}), ...(ngDevMode ? [{ debugName: "childContext" }] : []));
|
|
3914
3545
|
blockIcon = computed(() => {
|
|
3915
3546
|
const iconMap = {
|
|
3916
|
-
'text-block': '
|
|
3917
|
-
'button-block': '
|
|
3918
|
-
'image-block': '
|
|
3919
|
-
'icon-block': '
|
|
3920
|
-
'feature-item': '
|
|
3921
|
-
'card-block': '
|
|
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>',
|
|
3547
|
+
'text-block': '📝',
|
|
3548
|
+
'button-block': '🔘',
|
|
3549
|
+
'image-block': '🖼️',
|
|
3550
|
+
'icon-block': '⭐',
|
|
3551
|
+
'feature-item': '⭐',
|
|
3552
|
+
'card-block': '🃏',
|
|
3925
3553
|
};
|
|
3926
|
-
|
|
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);
|
|
3554
|
+
return iconMap[this.block().type] ?? '🧩';
|
|
3930
3555
|
}, ...(ngDevMode ? [{ debugName: "blockIcon" }] : []));
|
|
3931
3556
|
blockName = computed(() => {
|
|
3932
3557
|
const block = this.block();
|
|
@@ -4088,8 +3713,8 @@ class BlockTreeItemComponent {
|
|
|
4088
3713
|
zone,
|
|
4089
3714
|
};
|
|
4090
3715
|
}
|
|
4091
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
4092
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
3716
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BlockTreeItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3717
|
+
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
3718
|
<div
|
|
4094
3719
|
class="tree-block"
|
|
4095
3720
|
#treeBlock
|
|
@@ -4123,7 +3748,7 @@ class BlockTreeItemComponent {
|
|
|
4123
3748
|
<span class="block-indent"></span>
|
|
4124
3749
|
}
|
|
4125
3750
|
<span class="icon-drag-wrapper">
|
|
4126
|
-
<span class="block-icon"
|
|
3751
|
+
<span class="block-icon">{{ blockIcon() }}</span>
|
|
4127
3752
|
<span
|
|
4128
3753
|
class="drag-handle"
|
|
4129
3754
|
role="button"
|
|
@@ -4192,9 +3817,9 @@ class BlockTreeItemComponent {
|
|
|
4192
3817
|
}
|
|
4193
3818
|
</div>
|
|
4194
3819
|
}
|
|
4195
|
-
`, isInline: true, styles: [":host{display:block
|
|
3820
|
+
`, 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
3821
|
}
|
|
4197
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
3822
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BlockTreeItemComponent, decorators: [{
|
|
4198
3823
|
type: Component,
|
|
4199
3824
|
args: [{ selector: 'lib-block-tree-item', imports: [BlockTreeItemComponent], template: `
|
|
4200
3825
|
<div
|
|
@@ -4230,7 +3855,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
4230
3855
|
<span class="block-indent"></span>
|
|
4231
3856
|
}
|
|
4232
3857
|
<span class="icon-drag-wrapper">
|
|
4233
|
-
<span class="block-icon"
|
|
3858
|
+
<span class="block-icon">{{ blockIcon() }}</span>
|
|
4234
3859
|
<span
|
|
4235
3860
|
class="drag-handle"
|
|
4236
3861
|
role="button"
|
|
@@ -4299,7 +3924,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
4299
3924
|
}
|
|
4300
3925
|
</div>
|
|
4301
3926
|
}
|
|
4302
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block
|
|
3927
|
+
`, 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
3928
|
}], 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
3929
|
|
|
4305
3930
|
const FILES_QUERY = `
|
|
@@ -4414,10 +4039,10 @@ class ShopifyFilesService {
|
|
|
4414
4039
|
return 'unknown';
|
|
4415
4040
|
}
|
|
4416
4041
|
}
|
|
4417
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
4418
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
4042
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4043
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilesService, providedIn: 'root' });
|
|
4419
4044
|
}
|
|
4420
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
4045
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilesService, decorators: [{
|
|
4421
4046
|
type: Injectable,
|
|
4422
4047
|
args: [{ providedIn: 'root' }]
|
|
4423
4048
|
}] });
|
|
@@ -4440,7 +4065,7 @@ class ShopifyFilePickerComponent {
|
|
|
4440
4065
|
endCursor = null;
|
|
4441
4066
|
hasNextPage = false;
|
|
4442
4067
|
ngOnInit() {
|
|
4443
|
-
this.searchSubject.pipe(debounceTime(300), tap
|
|
4068
|
+
this.searchSubject.pipe(debounceTime(300), tap(() => {
|
|
4444
4069
|
this.files.set([]);
|
|
4445
4070
|
this.endCursor = null;
|
|
4446
4071
|
this.hasNextPage = false;
|
|
@@ -4553,8 +4178,8 @@ class ShopifyFilePickerComponent {
|
|
|
4553
4178
|
},
|
|
4554
4179
|
});
|
|
4555
4180
|
}
|
|
4556
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
4557
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
4181
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4182
|
+
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
4183
|
<div
|
|
4559
4184
|
class="file-picker-overlay"
|
|
4560
4185
|
role="dialog"
|
|
@@ -4649,9 +4274,9 @@ class ShopifyFilePickerComponent {
|
|
|
4649
4274
|
</div>
|
|
4650
4275
|
</div>
|
|
4651
4276
|
</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
|
|
4277
|
+
`, 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
4278
|
}
|
|
4654
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
4279
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilePickerComponent, decorators: [{
|
|
4655
4280
|
type: Component,
|
|
4656
4281
|
args: [{ selector: 'lib-shopify-file-picker', template: `
|
|
4657
4282
|
<div
|
|
@@ -4748,7 +4373,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
4748
4373
|
</div>
|
|
4749
4374
|
</div>
|
|
4750
4375
|
</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
|
|
4376
|
+
`, 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
4377
|
}], 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
4378
|
|
|
4754
4379
|
/**
|
|
@@ -4765,6 +4390,8 @@ class VisualEditorComponent {
|
|
|
4765
4390
|
loadingStrategy = inject(PageLoadingStrategy);
|
|
4766
4391
|
config = inject(VISUAL_EDITOR_CONFIG);
|
|
4767
4392
|
dndService = inject(DragDropService);
|
|
4393
|
+
iframeBridge = inject(IframeBridgeService);
|
|
4394
|
+
storefrontUrl = inject(STOREFRONT_URL);
|
|
4768
4395
|
sanitizer = inject(DomSanitizer);
|
|
4769
4396
|
destroy$ = new Subject();
|
|
4770
4397
|
// Configuration-driven UI options
|
|
@@ -4774,6 +4401,14 @@ class VisualEditorComponent {
|
|
|
4774
4401
|
isPreviewMode = signal(false, ...(ngDevMode ? [{ debugName: "isPreviewMode" }] : []));
|
|
4775
4402
|
editorContext = computed(() => ({ isEditor: !this.isPreviewMode() }), ...(ngDevMode ? [{ debugName: "editorContext" }] : []));
|
|
4776
4403
|
canvasEl = viewChild('canvasEl', ...(ngDevMode ? [{ debugName: "canvasEl" }] : []));
|
|
4404
|
+
// Iframe preview
|
|
4405
|
+
previewFrame = viewChild('previewFrame', ...(ngDevMode ? [{ debugName: "previewFrame" }] : []));
|
|
4406
|
+
previewUrl = computed(() => {
|
|
4407
|
+
if (!this.storefrontUrl)
|
|
4408
|
+
return null;
|
|
4409
|
+
return this.sanitizer.bypassSecurityTrustResourceUrl(`${this.storefrontUrl}/kustomizer/editor`);
|
|
4410
|
+
}, ...(ngDevMode ? [{ debugName: "previewUrl" }] : []));
|
|
4411
|
+
iframeReady = false;
|
|
4777
4412
|
propertiesTab = signal('props', ...(ngDevMode ? [{ debugName: "propertiesTab" }] : []));
|
|
4778
4413
|
expandedGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedGroups" }] : []));
|
|
4779
4414
|
expandedSections = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedSections" }] : []));
|
|
@@ -4898,23 +4533,19 @@ class VisualEditorComponent {
|
|
|
4898
4533
|
});
|
|
4899
4534
|
}, ...(ngDevMode ? [{ debugName: "filteredBlockPresetsForNestedSlot" }] : []));
|
|
4900
4535
|
sectionIconMap = {
|
|
4901
|
-
'hero-section': '
|
|
4902
|
-
'features-grid': '
|
|
4903
|
-
'testimonials': '
|
|
4904
|
-
'cta-section': '
|
|
4905
|
-
'footer-section': '
|
|
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>',
|
|
4536
|
+
'hero-section': '🎯',
|
|
4537
|
+
'features-grid': '⚡',
|
|
4538
|
+
'testimonials': '💬',
|
|
4539
|
+
'cta-section': '📢',
|
|
4540
|
+
'footer-section': '📋',
|
|
4907
4541
|
};
|
|
4908
4542
|
blockIconMap = {
|
|
4909
|
-
'text-block': '
|
|
4910
|
-
'button-block': '
|
|
4911
|
-
'image-block': '
|
|
4912
|
-
'icon-block': '
|
|
4913
|
-
'feature-item': '
|
|
4914
|
-
'card-block': '
|
|
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>',
|
|
4543
|
+
'text-block': '📝',
|
|
4544
|
+
'button-block': '🔘',
|
|
4545
|
+
'image-block': '🖼️',
|
|
4546
|
+
'icon-block': '⭐',
|
|
4547
|
+
'feature-item': '⭐',
|
|
4548
|
+
'card-block': '🃏',
|
|
4918
4549
|
};
|
|
4919
4550
|
selectedBlockDefinition = computed(() => this.facade.selectedBlockDefinition(), ...(ngDevMode ? [{ debugName: "selectedBlockDefinition" }] : []));
|
|
4920
4551
|
// Inline name editing
|
|
@@ -4974,6 +4605,27 @@ class VisualEditorComponent {
|
|
|
4974
4605
|
cancelEditingName() {
|
|
4975
4606
|
this.isEditingName.set(false);
|
|
4976
4607
|
}
|
|
4608
|
+
constructor() {
|
|
4609
|
+
// Sync sections to iframe whenever they change
|
|
4610
|
+
effect(() => {
|
|
4611
|
+
const sections = this.facade.sections();
|
|
4612
|
+
if (this.iframeReady && this.previewUrl()) {
|
|
4613
|
+
this.iframeBridge.sendPageUpdate(sections);
|
|
4614
|
+
}
|
|
4615
|
+
});
|
|
4616
|
+
// Sync selection to iframe
|
|
4617
|
+
effect(() => {
|
|
4618
|
+
const selected = this.facade.selectedElement()?.id ?? null;
|
|
4619
|
+
if (!this.iframeReady || !this.previewUrl())
|
|
4620
|
+
return;
|
|
4621
|
+
if (selected) {
|
|
4622
|
+
this.iframeBridge.sendSelectElement(selected);
|
|
4623
|
+
}
|
|
4624
|
+
else {
|
|
4625
|
+
this.iframeBridge.sendDeselect();
|
|
4626
|
+
}
|
|
4627
|
+
});
|
|
4628
|
+
}
|
|
4977
4629
|
ngOnInit() {
|
|
4978
4630
|
// Load page using the configured loading strategy
|
|
4979
4631
|
this.loadingStrategy
|
|
@@ -4993,13 +4645,38 @@ class VisualEditorComponent {
|
|
|
4993
4645
|
this.navigation.navigate({ type: 'page-list' });
|
|
4994
4646
|
},
|
|
4995
4647
|
});
|
|
4648
|
+
// Listen for iframe events
|
|
4649
|
+
if (this.previewUrl()) {
|
|
4650
|
+
this.iframeBridge
|
|
4651
|
+
.onReady()
|
|
4652
|
+
.pipe(takeUntil(this.destroy$))
|
|
4653
|
+
.subscribe(() => {
|
|
4654
|
+
this.iframeReady = true;
|
|
4655
|
+
// Send initial page state
|
|
4656
|
+
this.iframeBridge.sendPageUpdate(this.facade.sections());
|
|
4657
|
+
});
|
|
4658
|
+
this.iframeBridge
|
|
4659
|
+
.onElementClicked()
|
|
4660
|
+
.pipe(takeUntil(this.destroy$))
|
|
4661
|
+
.subscribe(({ elementId, sectionId }) => {
|
|
4662
|
+
this.facade.selectElement(sectionId, elementId);
|
|
4663
|
+
});
|
|
4664
|
+
}
|
|
4996
4665
|
}
|
|
4997
4666
|
ngOnDestroy() {
|
|
4998
4667
|
this.destroy$.next();
|
|
4999
4668
|
this.destroy$.complete();
|
|
4669
|
+
this.iframeBridge.disconnect();
|
|
5000
4670
|
// Clear page from store when leaving editor
|
|
5001
4671
|
this.facade.clearPage();
|
|
5002
4672
|
}
|
|
4673
|
+
onIframeLoad() {
|
|
4674
|
+
const iframe = this.previewFrame()?.nativeElement;
|
|
4675
|
+
if (iframe) {
|
|
4676
|
+
const origin = this.storefrontUrl ? new URL(this.storefrontUrl).origin : '*';
|
|
4677
|
+
this.iframeBridge.connect(iframe, origin);
|
|
4678
|
+
}
|
|
4679
|
+
}
|
|
5003
4680
|
goBack() {
|
|
5004
4681
|
if (this.facade.isDirty()) {
|
|
5005
4682
|
this.navigation
|
|
@@ -5064,25 +4741,17 @@ class VisualEditorComponent {
|
|
|
5064
4741
|
},
|
|
5065
4742
|
});
|
|
5066
4743
|
}
|
|
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
4744
|
getIcon(def) {
|
|
5076
|
-
return this.
|
|
4745
|
+
return this.sectionIconMap[def.type] ?? '📦';
|
|
5077
4746
|
}
|
|
5078
4747
|
getBlockIcon(def) {
|
|
5079
|
-
return this.
|
|
4748
|
+
return this.blockIconMap[def.type] ?? '🧩';
|
|
5080
4749
|
}
|
|
5081
4750
|
getBlockIconByType(type) {
|
|
5082
|
-
return this.
|
|
4751
|
+
return this.blockIconMap[type] ?? '🧩';
|
|
5083
4752
|
}
|
|
5084
4753
|
getSectionIcon(type) {
|
|
5085
|
-
return this.
|
|
4754
|
+
return this.sectionIconMap[type] ?? '📦';
|
|
5086
4755
|
}
|
|
5087
4756
|
// Tree expansion
|
|
5088
4757
|
isSectionExpanded(sectionId) {
|
|
@@ -5508,10 +5177,10 @@ class VisualEditorComponent {
|
|
|
5508
5177
|
this.closeNestedBlockPicker();
|
|
5509
5178
|
}
|
|
5510
5179
|
getSectionPresetIcon(rp) {
|
|
5511
|
-
return
|
|
5180
|
+
return rp.displayIcon ?? this.sectionIconMap[rp.definition.type] ?? '📦';
|
|
5512
5181
|
}
|
|
5513
5182
|
getBlockPresetIcon(rp) {
|
|
5514
|
-
return
|
|
5183
|
+
return rp.displayIcon ?? this.blockIconMap[rp.definition.type] ?? '🧩';
|
|
5515
5184
|
}
|
|
5516
5185
|
selectSection(section, event) {
|
|
5517
5186
|
event.stopPropagation();
|
|
@@ -5755,15 +5424,15 @@ class VisualEditorComponent {
|
|
|
5755
5424
|
return;
|
|
5756
5425
|
this.facade.updateSectionProps(section.id, { [key]: '' });
|
|
5757
5426
|
}
|
|
5758
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
5759
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
5427
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5428
|
+
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
5429
|
<div class="editor-layout">
|
|
5761
5430
|
<!-- Sidebar: Hierarchical Tree -->
|
|
5762
5431
|
<aside class="sidebar">
|
|
5763
5432
|
<div class="sidebar-header">
|
|
5764
5433
|
<h2>Page Structure</h2>
|
|
5765
5434
|
<div class="search-box">
|
|
5766
|
-
<span class="search-icon"
|
|
5435
|
+
<span class="search-icon">🔍</span>
|
|
5767
5436
|
<input
|
|
5768
5437
|
type="text"
|
|
5769
5438
|
class="search-input"
|
|
@@ -5811,7 +5480,7 @@ class VisualEditorComponent {
|
|
|
5811
5480
|
<span class="expand-icon">▶</span>
|
|
5812
5481
|
</button>
|
|
5813
5482
|
<span class="icon-drag-wrapper">
|
|
5814
|
-
<span class="section-icon"
|
|
5483
|
+
<span class="section-icon">{{ getSectionIcon(section.type) }}</span>
|
|
5815
5484
|
<span
|
|
5816
5485
|
class="drag-handle section-drag-handle"
|
|
5817
5486
|
role="button"
|
|
@@ -5913,7 +5582,7 @@ class VisualEditorComponent {
|
|
|
5913
5582
|
}
|
|
5914
5583
|
@for (rp of group.presets; track rp.displayName + '-' + rp.definition.type) {
|
|
5915
5584
|
<button class="picker-item" (click)="addSectionFromPreset(rp)">
|
|
5916
|
-
<span class="picker-icon"
|
|
5585
|
+
<span class="picker-icon">{{ getSectionPresetIcon(rp) }}</span>
|
|
5917
5586
|
<div class="picker-info">
|
|
5918
5587
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
5919
5588
|
@if (rp.displayDescription) {
|
|
@@ -5951,7 +5620,7 @@ class VisualEditorComponent {
|
|
|
5951
5620
|
<div class="picker-content">
|
|
5952
5621
|
@for (rp of filteredBlockPresetsForSection(); track rp.displayName + '-' + rp.definition.type) {
|
|
5953
5622
|
<button class="picker-item" (click)="addBlockToSectionFromPreset(blockPickerSection()!, rp)">
|
|
5954
|
-
<span class="picker-icon"
|
|
5623
|
+
<span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
|
|
5955
5624
|
<div class="picker-info">
|
|
5956
5625
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
5957
5626
|
@if (rp.displayDescription) {
|
|
@@ -5994,7 +5663,7 @@ class VisualEditorComponent {
|
|
|
5994
5663
|
<div class="picker-content">
|
|
5995
5664
|
@for (rp of filteredBlockPresetsForNestedSlot(); track rp.displayName + '-' + rp.definition.type) {
|
|
5996
5665
|
<button class="picker-item" (click)="addNestedBlockFromPreset(rp)">
|
|
5997
|
-
<span class="picker-icon"
|
|
5666
|
+
<span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
|
|
5998
5667
|
<div class="picker-info">
|
|
5999
5668
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
6000
5669
|
@if (rp.displayDescription) {
|
|
@@ -6024,7 +5693,7 @@ class VisualEditorComponent {
|
|
|
6024
5693
|
<div class="toolbar">
|
|
6025
5694
|
<div class="toolbar-left">
|
|
6026
5695
|
<button class="toolbar-btn back-btn" (click)="goBack()" title="Back to pages">
|
|
6027
|
-
|
|
5696
|
+
← Back
|
|
6028
5697
|
</button>
|
|
6029
5698
|
<button
|
|
6030
5699
|
class="toolbar-btn"
|
|
@@ -6032,7 +5701,7 @@ class VisualEditorComponent {
|
|
|
6032
5701
|
(click)="facade.undo()"
|
|
6033
5702
|
title="Undo"
|
|
6034
5703
|
>
|
|
6035
|
-
|
|
5704
|
+
↩ Undo
|
|
6036
5705
|
</button>
|
|
6037
5706
|
<button
|
|
6038
5707
|
class="toolbar-btn"
|
|
@@ -6040,7 +5709,7 @@ class VisualEditorComponent {
|
|
|
6040
5709
|
(click)="facade.redo()"
|
|
6041
5710
|
title="Redo"
|
|
6042
5711
|
>
|
|
6043
|
-
|
|
5712
|
+
↪ Redo
|
|
6044
5713
|
</button>
|
|
6045
5714
|
</div>
|
|
6046
5715
|
<div class="toolbar-center">
|
|
@@ -6073,7 +5742,7 @@ class VisualEditorComponent {
|
|
|
6073
5742
|
(click)="savePage()"
|
|
6074
5743
|
title="Save changes"
|
|
6075
5744
|
>
|
|
6076
|
-
{{ isSaving() ? 'Saving...' : 'Save' }}
|
|
5745
|
+
{{ isSaving() ? 'Saving...' : '💾 Save' }}
|
|
6077
5746
|
</button>
|
|
6078
5747
|
}
|
|
6079
5748
|
@if (facade.currentPage() && showPublishButtons()) {
|
|
@@ -6084,7 +5753,7 @@ class VisualEditorComponent {
|
|
|
6084
5753
|
(click)="publishPage()"
|
|
6085
5754
|
title="Publish page"
|
|
6086
5755
|
>
|
|
6087
|
-
{{ isPublishing() ? 'Publishing...' : 'Publish' }}
|
|
5756
|
+
{{ isPublishing() ? 'Publishing...' : '🚀 Publish' }}
|
|
6088
5757
|
</button>
|
|
6089
5758
|
} @else {
|
|
6090
5759
|
<button
|
|
@@ -6093,57 +5762,68 @@ class VisualEditorComponent {
|
|
|
6093
5762
|
(click)="unpublishPage()"
|
|
6094
5763
|
title="Unpublish page"
|
|
6095
5764
|
>
|
|
6096
|
-
{{ isPublishing() ? 'Unpublishing...' : 'Unpublish' }}
|
|
5765
|
+
{{ isPublishing() ? 'Unpublishing...' : '📥 Unpublish' }}
|
|
6097
5766
|
</button>
|
|
6098
5767
|
}
|
|
6099
5768
|
}
|
|
6100
5769
|
<button class="toolbar-btn preview-btn" (click)="togglePreview()">
|
|
6101
|
-
{{ isPreviewMode() ? 'Edit' : 'Preview' }}
|
|
5770
|
+
{{ isPreviewMode() ? '✏️ Edit' : '👁 Preview' }}
|
|
6102
5771
|
</button>
|
|
6103
5772
|
</div>
|
|
6104
5773
|
</div>
|
|
6105
5774
|
|
|
6106
5775
|
<!-- Canvas Content -->
|
|
6107
5776
|
<div class="canvas" #canvasEl [class.preview-mode]="isPreviewMode()">
|
|
6108
|
-
@if (
|
|
6109
|
-
<
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
5777
|
+
@if (previewUrl()) {
|
|
5778
|
+
<iframe
|
|
5779
|
+
#previewFrame
|
|
5780
|
+
class="preview-iframe"
|
|
5781
|
+
[src]="previewUrl()!"
|
|
5782
|
+
(load)="onIframeLoad()"
|
|
5783
|
+
allow="clipboard-read; clipboard-write"
|
|
5784
|
+
></iframe>
|
|
6114
5785
|
} @else {
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
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
|
-
}
|
|
5786
|
+
<!-- Fallback: direct rendering when no storefront URL configured -->
|
|
5787
|
+
@if (facade.sections().length === 0) {
|
|
5788
|
+
<div class="empty-state">
|
|
5789
|
+
<div class="empty-icon">📄</div>
|
|
5790
|
+
<h3>Start Building</h3>
|
|
5791
|
+
<p>Click on a component from the sidebar to add it to your page</p>
|
|
5792
|
+
</div>
|
|
5793
|
+
} @else {
|
|
5794
|
+
@for (section of facade.sections(); track section.id; let idx = $index) {
|
|
6139
5795
|
<div
|
|
6140
|
-
class="section-
|
|
6141
|
-
[
|
|
6142
|
-
(click)="selectSection(section, $event)"
|
|
5796
|
+
class="section-outer"
|
|
5797
|
+
[class.selected]="facade.selectedSection()?.id === section.id"
|
|
6143
5798
|
>
|
|
6144
|
-
|
|
5799
|
+
@if (!isPreviewMode()) {
|
|
5800
|
+
<div class="section-controls">
|
|
5801
|
+
<span class="section-label">{{ getSectionName(section) }}</span>
|
|
5802
|
+
<div class="section-actions">
|
|
5803
|
+
@if (idx > 0) {
|
|
5804
|
+
<button class="action-btn" (click)="moveUp(section.id, idx, $event)" title="Move up">↑</button>
|
|
5805
|
+
}
|
|
5806
|
+
@if (idx < facade.sections().length - 1) {
|
|
5807
|
+
<button class="action-btn" (click)="moveDown(section.id, idx, $event)" title="Move down">↓</button>
|
|
5808
|
+
}
|
|
5809
|
+
@if (facade.isSectionDuplicable(section.type)) {
|
|
5810
|
+
<button class="action-btn" (click)="duplicateSection(section.id, $event)" title="Duplicate">
|
|
5811
|
+
<span class="material-icon">content_copy</span>
|
|
5812
|
+
</button>
|
|
5813
|
+
}
|
|
5814
|
+
<button class="action-btn delete" (click)="deleteSection(section.id, $event)" title="Delete">×</button>
|
|
5815
|
+
</div>
|
|
5816
|
+
</div>
|
|
5817
|
+
}
|
|
5818
|
+
<div
|
|
5819
|
+
class="section-wrapper"
|
|
5820
|
+
[attr.data-section-id]="section.id"
|
|
5821
|
+
(click)="selectSection(section, $event)"
|
|
5822
|
+
>
|
|
5823
|
+
<lib-dynamic-renderer [element]="section" [context]="editorContext()" />
|
|
5824
|
+
</div>
|
|
6145
5825
|
</div>
|
|
6146
|
-
|
|
5826
|
+
}
|
|
6147
5827
|
}
|
|
6148
5828
|
}
|
|
6149
5829
|
</div>
|
|
@@ -6533,7 +6213,7 @@ class VisualEditorComponent {
|
|
|
6533
6213
|
[class.selected]="facade.selectedBlock()?.id === block.id"
|
|
6534
6214
|
(click)="selectBlock(block)"
|
|
6535
6215
|
>
|
|
6536
|
-
<span class="block-icon"
|
|
6216
|
+
<span class="block-icon">{{ getBlockIconByType(block.type) }}</span>
|
|
6537
6217
|
<span class="block-name">{{ getBlockName(block) }}</span>
|
|
6538
6218
|
<div class="block-actions">
|
|
6539
6219
|
@if (idx > 0) {
|
|
@@ -6580,18 +6260,18 @@ class VisualEditorComponent {
|
|
|
6580
6260
|
/>
|
|
6581
6261
|
}
|
|
6582
6262
|
</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 });
|
|
6263
|
+
`, 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
6264
|
}
|
|
6585
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
6265
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditorComponent, decorators: [{
|
|
6586
6266
|
type: Component,
|
|
6587
|
-
args: [{ selector: 'lib-visual-editor', imports: [DynamicRendererComponent, BlockTreeItemComponent, ShopifyFilePickerComponent], template: `
|
|
6267
|
+
args: [{ selector: 'lib-visual-editor', imports: [DynamicRendererComponent, BlockTreeItemComponent, ShopifyFilePickerComponent], providers: [IframeBridgeService], template: `
|
|
6588
6268
|
<div class="editor-layout">
|
|
6589
6269
|
<!-- Sidebar: Hierarchical Tree -->
|
|
6590
6270
|
<aside class="sidebar">
|
|
6591
6271
|
<div class="sidebar-header">
|
|
6592
6272
|
<h2>Page Structure</h2>
|
|
6593
6273
|
<div class="search-box">
|
|
6594
|
-
<span class="search-icon"
|
|
6274
|
+
<span class="search-icon">🔍</span>
|
|
6595
6275
|
<input
|
|
6596
6276
|
type="text"
|
|
6597
6277
|
class="search-input"
|
|
@@ -6639,7 +6319,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6639
6319
|
<span class="expand-icon">▶</span>
|
|
6640
6320
|
</button>
|
|
6641
6321
|
<span class="icon-drag-wrapper">
|
|
6642
|
-
<span class="section-icon"
|
|
6322
|
+
<span class="section-icon">{{ getSectionIcon(section.type) }}</span>
|
|
6643
6323
|
<span
|
|
6644
6324
|
class="drag-handle section-drag-handle"
|
|
6645
6325
|
role="button"
|
|
@@ -6741,7 +6421,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6741
6421
|
}
|
|
6742
6422
|
@for (rp of group.presets; track rp.displayName + '-' + rp.definition.type) {
|
|
6743
6423
|
<button class="picker-item" (click)="addSectionFromPreset(rp)">
|
|
6744
|
-
<span class="picker-icon"
|
|
6424
|
+
<span class="picker-icon">{{ getSectionPresetIcon(rp) }}</span>
|
|
6745
6425
|
<div class="picker-info">
|
|
6746
6426
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
6747
6427
|
@if (rp.displayDescription) {
|
|
@@ -6779,7 +6459,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6779
6459
|
<div class="picker-content">
|
|
6780
6460
|
@for (rp of filteredBlockPresetsForSection(); track rp.displayName + '-' + rp.definition.type) {
|
|
6781
6461
|
<button class="picker-item" (click)="addBlockToSectionFromPreset(blockPickerSection()!, rp)">
|
|
6782
|
-
<span class="picker-icon"
|
|
6462
|
+
<span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
|
|
6783
6463
|
<div class="picker-info">
|
|
6784
6464
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
6785
6465
|
@if (rp.displayDescription) {
|
|
@@ -6822,7 +6502,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6822
6502
|
<div class="picker-content">
|
|
6823
6503
|
@for (rp of filteredBlockPresetsForNestedSlot(); track rp.displayName + '-' + rp.definition.type) {
|
|
6824
6504
|
<button class="picker-item" (click)="addNestedBlockFromPreset(rp)">
|
|
6825
|
-
<span class="picker-icon"
|
|
6505
|
+
<span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
|
|
6826
6506
|
<div class="picker-info">
|
|
6827
6507
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
6828
6508
|
@if (rp.displayDescription) {
|
|
@@ -6852,7 +6532,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6852
6532
|
<div class="toolbar">
|
|
6853
6533
|
<div class="toolbar-left">
|
|
6854
6534
|
<button class="toolbar-btn back-btn" (click)="goBack()" title="Back to pages">
|
|
6855
|
-
|
|
6535
|
+
← Back
|
|
6856
6536
|
</button>
|
|
6857
6537
|
<button
|
|
6858
6538
|
class="toolbar-btn"
|
|
@@ -6860,7 +6540,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6860
6540
|
(click)="facade.undo()"
|
|
6861
6541
|
title="Undo"
|
|
6862
6542
|
>
|
|
6863
|
-
|
|
6543
|
+
↩ Undo
|
|
6864
6544
|
</button>
|
|
6865
6545
|
<button
|
|
6866
6546
|
class="toolbar-btn"
|
|
@@ -6868,7 +6548,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6868
6548
|
(click)="facade.redo()"
|
|
6869
6549
|
title="Redo"
|
|
6870
6550
|
>
|
|
6871
|
-
|
|
6551
|
+
↪ Redo
|
|
6872
6552
|
</button>
|
|
6873
6553
|
</div>
|
|
6874
6554
|
<div class="toolbar-center">
|
|
@@ -6901,7 +6581,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6901
6581
|
(click)="savePage()"
|
|
6902
6582
|
title="Save changes"
|
|
6903
6583
|
>
|
|
6904
|
-
{{ isSaving() ? 'Saving...' : 'Save' }}
|
|
6584
|
+
{{ isSaving() ? 'Saving...' : '💾 Save' }}
|
|
6905
6585
|
</button>
|
|
6906
6586
|
}
|
|
6907
6587
|
@if (facade.currentPage() && showPublishButtons()) {
|
|
@@ -6912,7 +6592,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6912
6592
|
(click)="publishPage()"
|
|
6913
6593
|
title="Publish page"
|
|
6914
6594
|
>
|
|
6915
|
-
{{ isPublishing() ? 'Publishing...' : 'Publish' }}
|
|
6595
|
+
{{ isPublishing() ? 'Publishing...' : '🚀 Publish' }}
|
|
6916
6596
|
</button>
|
|
6917
6597
|
} @else {
|
|
6918
6598
|
<button
|
|
@@ -6921,57 +6601,68 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6921
6601
|
(click)="unpublishPage()"
|
|
6922
6602
|
title="Unpublish page"
|
|
6923
6603
|
>
|
|
6924
|
-
{{ isPublishing() ? 'Unpublishing...' : 'Unpublish' }}
|
|
6604
|
+
{{ isPublishing() ? 'Unpublishing...' : '📥 Unpublish' }}
|
|
6925
6605
|
</button>
|
|
6926
6606
|
}
|
|
6927
6607
|
}
|
|
6928
6608
|
<button class="toolbar-btn preview-btn" (click)="togglePreview()">
|
|
6929
|
-
{{ isPreviewMode() ? 'Edit' : 'Preview' }}
|
|
6609
|
+
{{ isPreviewMode() ? '✏️ Edit' : '👁 Preview' }}
|
|
6930
6610
|
</button>
|
|
6931
6611
|
</div>
|
|
6932
6612
|
</div>
|
|
6933
6613
|
|
|
6934
6614
|
<!-- Canvas Content -->
|
|
6935
6615
|
<div class="canvas" #canvasEl [class.preview-mode]="isPreviewMode()">
|
|
6936
|
-
@if (
|
|
6937
|
-
<
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6616
|
+
@if (previewUrl()) {
|
|
6617
|
+
<iframe
|
|
6618
|
+
#previewFrame
|
|
6619
|
+
class="preview-iframe"
|
|
6620
|
+
[src]="previewUrl()!"
|
|
6621
|
+
(load)="onIframeLoad()"
|
|
6622
|
+
allow="clipboard-read; clipboard-write"
|
|
6623
|
+
></iframe>
|
|
6942
6624
|
} @else {
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
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
|
-
}
|
|
6625
|
+
<!-- Fallback: direct rendering when no storefront URL configured -->
|
|
6626
|
+
@if (facade.sections().length === 0) {
|
|
6627
|
+
<div class="empty-state">
|
|
6628
|
+
<div class="empty-icon">📄</div>
|
|
6629
|
+
<h3>Start Building</h3>
|
|
6630
|
+
<p>Click on a component from the sidebar to add it to your page</p>
|
|
6631
|
+
</div>
|
|
6632
|
+
} @else {
|
|
6633
|
+
@for (section of facade.sections(); track section.id; let idx = $index) {
|
|
6967
6634
|
<div
|
|
6968
|
-
class="section-
|
|
6969
|
-
[
|
|
6970
|
-
(click)="selectSection(section, $event)"
|
|
6635
|
+
class="section-outer"
|
|
6636
|
+
[class.selected]="facade.selectedSection()?.id === section.id"
|
|
6971
6637
|
>
|
|
6972
|
-
|
|
6638
|
+
@if (!isPreviewMode()) {
|
|
6639
|
+
<div class="section-controls">
|
|
6640
|
+
<span class="section-label">{{ getSectionName(section) }}</span>
|
|
6641
|
+
<div class="section-actions">
|
|
6642
|
+
@if (idx > 0) {
|
|
6643
|
+
<button class="action-btn" (click)="moveUp(section.id, idx, $event)" title="Move up">↑</button>
|
|
6644
|
+
}
|
|
6645
|
+
@if (idx < facade.sections().length - 1) {
|
|
6646
|
+
<button class="action-btn" (click)="moveDown(section.id, idx, $event)" title="Move down">↓</button>
|
|
6647
|
+
}
|
|
6648
|
+
@if (facade.isSectionDuplicable(section.type)) {
|
|
6649
|
+
<button class="action-btn" (click)="duplicateSection(section.id, $event)" title="Duplicate">
|
|
6650
|
+
<span class="material-icon">content_copy</span>
|
|
6651
|
+
</button>
|
|
6652
|
+
}
|
|
6653
|
+
<button class="action-btn delete" (click)="deleteSection(section.id, $event)" title="Delete">×</button>
|
|
6654
|
+
</div>
|
|
6655
|
+
</div>
|
|
6656
|
+
}
|
|
6657
|
+
<div
|
|
6658
|
+
class="section-wrapper"
|
|
6659
|
+
[attr.data-section-id]="section.id"
|
|
6660
|
+
(click)="selectSection(section, $event)"
|
|
6661
|
+
>
|
|
6662
|
+
<lib-dynamic-renderer [element]="section" [context]="editorContext()" />
|
|
6663
|
+
</div>
|
|
6973
6664
|
</div>
|
|
6974
|
-
|
|
6665
|
+
}
|
|
6975
6666
|
}
|
|
6976
6667
|
}
|
|
6977
6668
|
</div>
|
|
@@ -7361,7 +7052,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7361
7052
|
[class.selected]="facade.selectedBlock()?.id === block.id"
|
|
7362
7053
|
(click)="selectBlock(block)"
|
|
7363
7054
|
>
|
|
7364
|
-
<span class="block-icon"
|
|
7055
|
+
<span class="block-icon">{{ getBlockIconByType(block.type) }}</span>
|
|
7365
7056
|
<span class="block-name">{{ getBlockName(block) }}</span>
|
|
7366
7057
|
<div class="block-actions">
|
|
7367
7058
|
@if (idx > 0) {
|
|
@@ -7408,8 +7099,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7408
7099
|
/>
|
|
7409
7100
|
}
|
|
7410
7101
|
</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 }] }] } });
|
|
7102
|
+
`, 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"] }]
|
|
7103
|
+
}], 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
7104
|
|
|
7414
7105
|
/**
|
|
7415
7106
|
* Page manager component for listing, creating, and managing pages
|
|
@@ -7447,6 +7138,9 @@ class PageManagerComponent {
|
|
|
7447
7138
|
},
|
|
7448
7139
|
});
|
|
7449
7140
|
}
|
|
7141
|
+
openSetup() {
|
|
7142
|
+
this.navigation.navigate({ type: 'setup' });
|
|
7143
|
+
}
|
|
7450
7144
|
openCreateDialog() {
|
|
7451
7145
|
this.showCreateDialog.set(true);
|
|
7452
7146
|
this.newPageTitle.set('');
|
|
@@ -7538,14 +7232,19 @@ class PageManagerComponent {
|
|
|
7538
7232
|
year: 'numeric',
|
|
7539
7233
|
});
|
|
7540
7234
|
}
|
|
7541
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
7542
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
7235
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageManagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7236
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: PageManagerComponent, isStandalone: true, selector: "lib-page-manager", ngImport: i0, template: `
|
|
7543
7237
|
<div class="page-manager">
|
|
7544
7238
|
<header class="manager-header">
|
|
7545
7239
|
<h1>Pages</h1>
|
|
7546
|
-
<
|
|
7547
|
-
|
|
7548
|
-
|
|
7240
|
+
<div class="header-actions">
|
|
7241
|
+
<button class="btn-secondary" (click)="openSetup()">
|
|
7242
|
+
Setup
|
|
7243
|
+
</button>
|
|
7244
|
+
<button class="btn-primary" (click)="openCreateDialog()">
|
|
7245
|
+
+ New Page
|
|
7246
|
+
</button>
|
|
7247
|
+
</div>
|
|
7549
7248
|
</header>
|
|
7550
7249
|
|
|
7551
7250
|
@if (isLoading()) {
|
|
@@ -7559,7 +7258,7 @@ class PageManagerComponent {
|
|
|
7559
7258
|
</div>
|
|
7560
7259
|
} @else if (pages().length === 0) {
|
|
7561
7260
|
<div class="empty-state">
|
|
7562
|
-
<div class="empty-icon"
|
|
7261
|
+
<div class="empty-icon">📄</div>
|
|
7563
7262
|
<h2>No pages yet</h2>
|
|
7564
7263
|
<p>Create your first page to get started</p>
|
|
7565
7264
|
<button class="btn-primary" (click)="openCreateDialog()">
|
|
@@ -7591,21 +7290,21 @@ class PageManagerComponent {
|
|
|
7591
7290
|
(click)="editPage(page.id); $event.stopPropagation()"
|
|
7592
7291
|
title="Edit"
|
|
7593
7292
|
>
|
|
7594
|
-
|
|
7293
|
+
✏️
|
|
7595
7294
|
</button>
|
|
7596
7295
|
<button
|
|
7597
7296
|
class="action-btn"
|
|
7598
7297
|
(click)="duplicatePage(page.id, $event)"
|
|
7599
7298
|
title="Duplicate"
|
|
7600
7299
|
>
|
|
7601
|
-
|
|
7300
|
+
📋
|
|
7602
7301
|
</button>
|
|
7603
7302
|
<button
|
|
7604
7303
|
class="action-btn delete"
|
|
7605
7304
|
(click)="confirmDelete(page, $event)"
|
|
7606
7305
|
title="Delete"
|
|
7607
7306
|
>
|
|
7608
|
-
|
|
7307
|
+
🗑️
|
|
7609
7308
|
</button>
|
|
7610
7309
|
</span>
|
|
7611
7310
|
</div>
|
|
@@ -7686,17 +7385,22 @@ class PageManagerComponent {
|
|
|
7686
7385
|
</div>
|
|
7687
7386
|
}
|
|
7688
7387
|
</div>
|
|
7689
|
-
`, isInline: true, styles: [":host{display:block;min-height:100vh;background:#
|
|
7388
|
+
`, 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
7389
|
}
|
|
7691
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
7390
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageManagerComponent, decorators: [{
|
|
7692
7391
|
type: Component,
|
|
7693
7392
|
args: [{ selector: 'lib-page-manager', template: `
|
|
7694
7393
|
<div class="page-manager">
|
|
7695
7394
|
<header class="manager-header">
|
|
7696
7395
|
<h1>Pages</h1>
|
|
7697
|
-
<
|
|
7698
|
-
|
|
7699
|
-
|
|
7396
|
+
<div class="header-actions">
|
|
7397
|
+
<button class="btn-secondary" (click)="openSetup()">
|
|
7398
|
+
Setup
|
|
7399
|
+
</button>
|
|
7400
|
+
<button class="btn-primary" (click)="openCreateDialog()">
|
|
7401
|
+
+ New Page
|
|
7402
|
+
</button>
|
|
7403
|
+
</div>
|
|
7700
7404
|
</header>
|
|
7701
7405
|
|
|
7702
7406
|
@if (isLoading()) {
|
|
@@ -7710,7 +7414,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7710
7414
|
</div>
|
|
7711
7415
|
} @else if (pages().length === 0) {
|
|
7712
7416
|
<div class="empty-state">
|
|
7713
|
-
<div class="empty-icon"
|
|
7417
|
+
<div class="empty-icon">📄</div>
|
|
7714
7418
|
<h2>No pages yet</h2>
|
|
7715
7419
|
<p>Create your first page to get started</p>
|
|
7716
7420
|
<button class="btn-primary" (click)="openCreateDialog()">
|
|
@@ -7742,21 +7446,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7742
7446
|
(click)="editPage(page.id); $event.stopPropagation()"
|
|
7743
7447
|
title="Edit"
|
|
7744
7448
|
>
|
|
7745
|
-
|
|
7449
|
+
✏️
|
|
7746
7450
|
</button>
|
|
7747
7451
|
<button
|
|
7748
7452
|
class="action-btn"
|
|
7749
7453
|
(click)="duplicatePage(page.id, $event)"
|
|
7750
7454
|
title="Duplicate"
|
|
7751
7455
|
>
|
|
7752
|
-
|
|
7456
|
+
📋
|
|
7753
7457
|
</button>
|
|
7754
7458
|
<button
|
|
7755
7459
|
class="action-btn delete"
|
|
7756
7460
|
(click)="confirmDelete(page, $event)"
|
|
7757
7461
|
title="Delete"
|
|
7758
7462
|
>
|
|
7759
|
-
|
|
7463
|
+
🗑️
|
|
7760
7464
|
</button>
|
|
7761
7465
|
</span>
|
|
7762
7466
|
</div>
|
|
@@ -7837,1111 +7541,287 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7837
7541
|
</div>
|
|
7838
7542
|
}
|
|
7839
7543
|
</div>
|
|
7840
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;min-height:100vh;background:#
|
|
7544
|
+
`, 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
7545
|
}] });
|
|
7842
7546
|
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
this.
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7547
|
+
/**
|
|
7548
|
+
* Read-only GraphQL client for the Shopify Storefront API.
|
|
7549
|
+
* Uses a Storefront Access Token (public, non-secret) instead of Admin API credentials.
|
|
7550
|
+
*/
|
|
7551
|
+
class StorefrontGraphQLClient {
|
|
7552
|
+
http = inject(HttpClient);
|
|
7553
|
+
config = inject(STOREFRONT_CONFIG);
|
|
7554
|
+
query(query, variables) {
|
|
7555
|
+
const url = this.config.proxyUrl
|
|
7556
|
+
? this.config.proxyUrl
|
|
7557
|
+
: `https://${this.config.shopDomain}/api/${this.config.apiVersion}/graphql.json`;
|
|
7558
|
+
const request = { query, variables };
|
|
7559
|
+
const headers = {
|
|
7560
|
+
'Content-Type': 'application/json',
|
|
7561
|
+
};
|
|
7562
|
+
if (!this.config.proxyUrl) {
|
|
7563
|
+
headers['X-Shopify-Storefront-Access-Token'] = this.config.storefrontAccessToken;
|
|
7564
|
+
}
|
|
7565
|
+
return this.http.post(url, request, { headers }).pipe(map$1(response => this.handleResponse(response)), catchError(error => this.handleError(error)));
|
|
7566
|
+
}
|
|
7567
|
+
handleResponse(response) {
|
|
7568
|
+
if (response.errors && response.errors.length > 0) {
|
|
7569
|
+
throw {
|
|
7570
|
+
code: 'GRAPHQL_ERROR',
|
|
7571
|
+
message: response.errors[0].message,
|
|
7572
|
+
errors: response.errors,
|
|
7573
|
+
};
|
|
7574
|
+
}
|
|
7575
|
+
if (!response.data) {
|
|
7576
|
+
throw {
|
|
7577
|
+
code: 'EMPTY_RESPONSE',
|
|
7578
|
+
message: 'GraphQL response contains no data',
|
|
7579
|
+
};
|
|
7580
|
+
}
|
|
7581
|
+
return response.data;
|
|
7582
|
+
}
|
|
7583
|
+
handleError(error) {
|
|
7584
|
+
let errorCode = 'UNKNOWN_ERROR';
|
|
7585
|
+
let errorMessage = 'An unknown error occurred';
|
|
7586
|
+
if (typeof ErrorEvent !== 'undefined' && error.error instanceof ErrorEvent) {
|
|
7587
|
+
errorCode = 'NETWORK_ERROR';
|
|
7588
|
+
errorMessage = error.error.message;
|
|
7589
|
+
}
|
|
7590
|
+
else {
|
|
7591
|
+
switch (error.status) {
|
|
7592
|
+
case 401:
|
|
7593
|
+
errorCode = 'UNAUTHORIZED';
|
|
7594
|
+
errorMessage = 'Invalid or expired storefront access token';
|
|
7595
|
+
break;
|
|
7596
|
+
case 402:
|
|
7597
|
+
errorCode = 'PAYMENT_REQUIRED';
|
|
7598
|
+
errorMessage = 'Storefront API access requires a Shopify plan';
|
|
7599
|
+
break;
|
|
7600
|
+
case 429:
|
|
7601
|
+
errorCode = 'RATE_LIMIT';
|
|
7602
|
+
errorMessage = 'Rate limit exceeded';
|
|
7603
|
+
break;
|
|
7604
|
+
case 500:
|
|
7605
|
+
case 502:
|
|
7606
|
+
case 503:
|
|
7607
|
+
case 504:
|
|
7608
|
+
errorCode = 'SERVER_ERROR';
|
|
7609
|
+
errorMessage = 'Shopify server error';
|
|
7610
|
+
break;
|
|
7611
|
+
default:
|
|
7612
|
+
errorCode = 'HTTP_ERROR';
|
|
7613
|
+
errorMessage = `HTTP ${error.status}: ${error.statusText}`;
|
|
7901
7614
|
}
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
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
|
-
}
|
|
7615
|
+
}
|
|
7616
|
+
return throwError(() => ({
|
|
7617
|
+
code: errorCode,
|
|
7618
|
+
message: errorMessage,
|
|
7619
|
+
status: error.status,
|
|
7620
|
+
original: error,
|
|
7621
|
+
}));
|
|
7915
7622
|
}
|
|
7916
|
-
|
|
7623
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontGraphQLClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7624
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontGraphQLClient, providedIn: 'root' });
|
|
7917
7625
|
}
|
|
7918
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
7919
|
-
type:
|
|
7920
|
-
args: [{
|
|
7921
|
-
|
|
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>
|
|
7626
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontGraphQLClient, decorators: [{
|
|
7627
|
+
type: Injectable,
|
|
7628
|
+
args: [{ providedIn: 'root' }]
|
|
7629
|
+
}] });
|
|
7930
7630
|
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7631
|
+
const NAMESPACE = 'kustomizer';
|
|
7632
|
+
const INDEX_KEY = 'pages_index';
|
|
7633
|
+
/**
|
|
7634
|
+
* Storefront API query for reading shop metafields.
|
|
7635
|
+
* Uses `shop.metafield(namespace, key)` — the Storefront API equivalent
|
|
7636
|
+
* of the Admin API's shop metafield query.
|
|
7637
|
+
*/
|
|
7638
|
+
const GET_SHOP_METAFIELD_QUERY = `
|
|
7639
|
+
query GetShopMetafield($namespace: String!, $key: String!) {
|
|
7640
|
+
shop {
|
|
7641
|
+
metafield(namespace: $namespace, key: $key) {
|
|
7642
|
+
value
|
|
7643
|
+
type
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7646
|
+
}
|
|
7647
|
+
`;
|
|
7648
|
+
/**
|
|
7649
|
+
* Read-only metafield repository using the Shopify Storefront API.
|
|
7650
|
+
* Only works for metafields with MetafieldDefinitions that have `access.storefront: PUBLIC_READ`.
|
|
7651
|
+
*/
|
|
7652
|
+
class StorefrontMetafieldRepository {
|
|
7653
|
+
client = inject(StorefrontGraphQLClient);
|
|
7654
|
+
getMetafield(namespace, key) {
|
|
7655
|
+
return this.client.query(GET_SHOP_METAFIELD_QUERY, { namespace, key }).pipe(map$1(response => response.shop.metafield));
|
|
7656
|
+
}
|
|
7657
|
+
getPageIndex() {
|
|
7658
|
+
return this.getMetafield(NAMESPACE, INDEX_KEY).pipe(map$1(metafield => {
|
|
7659
|
+
if (!metafield) {
|
|
7660
|
+
return this.getDefaultIndex();
|
|
7939
7661
|
}
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7662
|
+
try {
|
|
7663
|
+
const parsed = JSON.parse(metafield.value);
|
|
7664
|
+
if (!parsed.pages || !Array.isArray(parsed.pages)) {
|
|
7665
|
+
throw new Error('Invalid index structure');
|
|
7666
|
+
}
|
|
7667
|
+
return parsed;
|
|
7943
7668
|
}
|
|
7944
|
-
|
|
7945
|
-
|
|
7669
|
+
catch (error) {
|
|
7670
|
+
throw {
|
|
7671
|
+
code: 'INDEX_PARSE_ERROR',
|
|
7672
|
+
message: 'Stored index data is corrupted',
|
|
7673
|
+
original: error,
|
|
7674
|
+
};
|
|
7946
7675
|
}
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7676
|
+
}));
|
|
7677
|
+
}
|
|
7678
|
+
getPageMetafield(pageId) {
|
|
7679
|
+
const key = `page_${pageId}`;
|
|
7680
|
+
return this.getMetafield(NAMESPACE, key).pipe(map$1(metafield => {
|
|
7681
|
+
if (!metafield) {
|
|
7682
|
+
return null;
|
|
7950
7683
|
}
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
<button class="btn-secondary" (click)="onRetry()">Reintentar</button>
|
|
7684
|
+
try {
|
|
7685
|
+
return JSON.parse(metafield.value);
|
|
7954
7686
|
}
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7687
|
+
catch (error) {
|
|
7688
|
+
throw {
|
|
7689
|
+
code: 'PAGE_PARSE_ERROR',
|
|
7690
|
+
message: 'Stored page data is corrupted',
|
|
7691
|
+
original: error,
|
|
7692
|
+
};
|
|
7958
7693
|
}
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
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
|
-
}
|
|
7694
|
+
}));
|
|
7695
|
+
}
|
|
7696
|
+
getDefaultIndex() {
|
|
7697
|
+
return {
|
|
7698
|
+
version: 0,
|
|
7699
|
+
lastUpdated: new Date().toISOString(),
|
|
7700
|
+
pages: [],
|
|
7701
|
+
};
|
|
7972
7702
|
}
|
|
7973
|
-
|
|
7974
|
-
|
|
7703
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontMetafieldRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7704
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontMetafieldRepository, providedIn: 'root' });
|
|
7705
|
+
}
|
|
7706
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontMetafieldRepository, decorators: [{
|
|
7707
|
+
type: Injectable,
|
|
7708
|
+
args: [{ providedIn: 'root' }]
|
|
7709
|
+
}] });
|
|
7975
7710
|
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7711
|
+
/**
|
|
7712
|
+
* Read-only page repository using the Shopify Storefront API.
|
|
7713
|
+
* Used by the public-storefront to read published pages without Admin API access.
|
|
7714
|
+
*/
|
|
7715
|
+
class PageStorefrontRepository {
|
|
7716
|
+
metafieldRepo = inject(StorefrontMetafieldRepository);
|
|
7717
|
+
getPages() {
|
|
7718
|
+
return this.metafieldRepo.getPageIndex().pipe(map$1(index => index.pages
|
|
7719
|
+
.map(entry => ({
|
|
7720
|
+
id: entry.id,
|
|
7721
|
+
slug: entry.slug,
|
|
7722
|
+
title: entry.title,
|
|
7723
|
+
status: entry.status,
|
|
7724
|
+
updatedAt: entry.updatedAt,
|
|
7725
|
+
publishedAt: entry.publishedAt,
|
|
7726
|
+
}))
|
|
7727
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))));
|
|
7989
7728
|
}
|
|
7990
|
-
|
|
7991
|
-
this.
|
|
7729
|
+
getPublishedPages() {
|
|
7730
|
+
return this.getPages().pipe(map$1(pages => pages.filter(p => p.status === 'published')));
|
|
7992
7731
|
}
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
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
|
-
}
|
|
8008
|
-
}
|
|
8009
|
-
else {
|
|
8010
|
-
result = await this.authService.signInWithPassword(this.email(), this.password());
|
|
8011
|
-
if (result.success) {
|
|
8012
|
-
this.loginSuccess.emit();
|
|
8013
|
-
return;
|
|
8014
|
-
}
|
|
7732
|
+
getPage(id) {
|
|
7733
|
+
return this.metafieldRepo.getPageMetafield(id).pipe(map$1(page => {
|
|
7734
|
+
if (!page) {
|
|
7735
|
+
throw {
|
|
7736
|
+
code: 'PAGE_NOT_FOUND',
|
|
7737
|
+
message: `Page with id ${id} not found`,
|
|
7738
|
+
};
|
|
8015
7739
|
}
|
|
8016
|
-
|
|
8017
|
-
}
|
|
8018
|
-
finally {
|
|
8019
|
-
this.loading.set(false);
|
|
8020
|
-
}
|
|
7740
|
+
return page;
|
|
7741
|
+
}));
|
|
8021
7742
|
}
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
|
|
8034
|
-
|
|
8035
|
-
|
|
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 }] });
|
|
7743
|
+
getPublishedPageBySlug(slug) {
|
|
7744
|
+
return this.metafieldRepo.getPageIndex().pipe(switchMap(index => {
|
|
7745
|
+
const entry = index.pages.find(p => p.slug === slug && p.status === 'published');
|
|
7746
|
+
if (!entry) {
|
|
7747
|
+
return throwError(() => ({
|
|
7748
|
+
code: 'PAGE_NOT_FOUND',
|
|
7749
|
+
message: `Published page with slug '${slug}' not found`,
|
|
7750
|
+
}));
|
|
7751
|
+
}
|
|
7752
|
+
return this.getPage(entry.id);
|
|
7753
|
+
}));
|
|
7754
|
+
}
|
|
7755
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageStorefrontRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7756
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageStorefrontRepository, providedIn: 'root' });
|
|
8276
7757
|
}
|
|
8277
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
8278
|
-
type:
|
|
8279
|
-
args: [{
|
|
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"] }]
|
|
7758
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageStorefrontRepository, decorators: [{
|
|
7759
|
+
type: Injectable,
|
|
7760
|
+
args: [{ providedIn: 'root' }]
|
|
8318
7761
|
}] });
|
|
8319
7762
|
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
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">·</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">·</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">·</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">·</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 {
|
|
7763
|
+
/**
|
|
7764
|
+
* Loads a component manifest from a remote storefront URL and registers
|
|
7765
|
+
* the entries in the ComponentRegistryService as "virtual" definitions
|
|
7766
|
+
* (no Angular component class — only metadata for palette and property panel).
|
|
7767
|
+
*/
|
|
7768
|
+
class ManifestLoaderService {
|
|
8622
7769
|
http = inject(HttpClient);
|
|
8623
|
-
|
|
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" }] : []));
|
|
7770
|
+
registry = inject(ComponentRegistryService);
|
|
8640
7771
|
/**
|
|
8641
|
-
*
|
|
7772
|
+
* Fetch the manifest from the given URL and register all components.
|
|
8642
7773
|
*/
|
|
8643
|
-
|
|
8644
|
-
|
|
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
|
-
}
|
|
7774
|
+
loadManifest(url) {
|
|
7775
|
+
return this.http.get(url).pipe(tap((manifest) => this.registerManifestComponents(manifest)));
|
|
8677
7776
|
}
|
|
8678
7777
|
/**
|
|
8679
|
-
*
|
|
7778
|
+
* Register manifest entries in the ComponentRegistryService.
|
|
7779
|
+
* Each entry becomes a ComponentDefinition without a `component` field.
|
|
8680
7780
|
*/
|
|
8681
|
-
|
|
8682
|
-
const
|
|
8683
|
-
|
|
8684
|
-
this.
|
|
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
|
-
}
|
|
8721
|
-
}
|
|
8722
|
-
/**
|
|
8723
|
-
* Acepta una invitación (usado cuando un usuario hace click en el link de invitación)
|
|
8724
|
-
*/
|
|
8725
|
-
async acceptInvitation(invitationToken) {
|
|
8726
|
-
const authToken = this.authService.accessToken();
|
|
8727
|
-
if (!authToken) {
|
|
8728
|
-
this._error.set('Not authenticated');
|
|
8729
|
-
return null;
|
|
8730
|
-
}
|
|
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;
|
|
7781
|
+
registerManifestComponents(manifest) {
|
|
7782
|
+
for (const entry of manifest.components) {
|
|
7783
|
+
const definition = this.manifestEntryToDefinition(entry);
|
|
7784
|
+
this.registry.register(definition);
|
|
8754
7785
|
}
|
|
8755
7786
|
}
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
7787
|
+
manifestEntryToDefinition(entry) {
|
|
7788
|
+
return {
|
|
7789
|
+
type: entry.type,
|
|
7790
|
+
name: entry.name,
|
|
7791
|
+
description: entry.description,
|
|
7792
|
+
category: entry.category,
|
|
7793
|
+
icon: entry.icon,
|
|
7794
|
+
props: entry.props,
|
|
7795
|
+
slots: entry.slots,
|
|
7796
|
+
isSection: entry.isSection,
|
|
7797
|
+
isBlock: entry.isBlock,
|
|
7798
|
+
blockScope: entry.blockScope,
|
|
7799
|
+
sectionTypes: entry.sectionTypes,
|
|
7800
|
+
draggable: entry.draggable,
|
|
7801
|
+
deletable: entry.deletable,
|
|
7802
|
+
duplicable: entry.duplicable,
|
|
7803
|
+
tags: entry.tags,
|
|
7804
|
+
order: entry.order,
|
|
7805
|
+
// No `component` — rendering happens in the iframe
|
|
7806
|
+
};
|
|
8769
7807
|
}
|
|
8770
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
8771
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
7808
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ManifestLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7809
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ManifestLoaderService, providedIn: 'root' });
|
|
8772
7810
|
}
|
|
8773
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
7811
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ManifestLoaderService, decorators: [{
|
|
8774
7812
|
type: Injectable,
|
|
8775
7813
|
args: [{ providedIn: 'root' }]
|
|
8776
7814
|
}] });
|
|
8777
7815
|
|
|
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
7816
|
class VisualEditor {
|
|
8937
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
8938
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0
|
|
7817
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7818
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: VisualEditor, isStandalone: true, selector: "lib-visual-editor", ngImport: i0, template: `
|
|
8939
7819
|
<p>
|
|
8940
7820
|
visual-editor works!
|
|
8941
7821
|
</p>
|
|
8942
7822
|
`, isInline: true, styles: [""] });
|
|
8943
7823
|
}
|
|
8944
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
7824
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditor, decorators: [{
|
|
8945
7825
|
type: Component,
|
|
8946
7826
|
args: [{ selector: 'lib-visual-editor', imports: [], template: `
|
|
8947
7827
|
<p>
|
|
@@ -8959,5 +7839,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
8959
7839
|
* Generated bundle index. Do not edit.
|
|
8960
7840
|
*/
|
|
8961
7841
|
|
|
8962
|
-
export {
|
|
7842
|
+
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, 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
7843
|
//# sourceMappingURL=kustomizer-visual-editor.mjs.map
|