@kustomizer/visual-editor 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +12 -0
- package/README.md +25 -15
- package/fesm2022/kustomizer-visual-editor.mjs +1000 -2074
- package/fesm2022/kustomizer-visual-editor.mjs.map +1 -1
- package/package.json +22 -13
- package/types/kustomizer-visual-editor.d.ts +285 -447
|
@@ -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, firstValueFrom, debounceTime, switchMap as switchMap$1, takeUntil } from 'rxjs';
|
|
5
|
+
import { switchMap, map as map$1, catchError } from 'rxjs/operators';
|
|
6
6
|
import { HttpClient } from '@angular/common/http';
|
|
7
7
|
import { createActionGroup, emptyProps, props, createReducer, on, createFeatureSelector, createSelector, provideState, Store } from '@ngrx/store';
|
|
8
8
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
9
9
|
import { toSignal } from '@angular/core/rxjs-interop';
|
|
10
|
-
import * as i1$1 from '@angular/common';
|
|
11
|
-
import { CommonModule } from '@angular/common';
|
|
12
|
-
import * as i1 from '@angular/forms';
|
|
13
|
-
import { FormsModule } from '@angular/forms';
|
|
14
10
|
|
|
15
11
|
/**
|
|
16
12
|
* Abstract navigation service that consumers can implement
|
|
@@ -57,6 +53,9 @@ class DefaultRouterNavigationService extends VisualEditorNavigation {
|
|
|
57
53
|
this.router.navigate([previewPath]);
|
|
58
54
|
}
|
|
59
55
|
break;
|
|
56
|
+
case 'setup':
|
|
57
|
+
this.router.navigate(['/setup']);
|
|
58
|
+
break;
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
confirm(options) {
|
|
@@ -82,10 +81,10 @@ class DefaultRouterNavigationService extends VisualEditorNavigation {
|
|
|
82
81
|
return this.router.events.pipe(startWith(null), // Trigger initial emission
|
|
83
82
|
map(() => extractPageIdFromUrl()));
|
|
84
83
|
}
|
|
85
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
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,152 @@ class VisualEditorFacade {
|
|
|
3620
3169
|
markClean() {
|
|
3621
3170
|
this.store.dispatch(VisualEditorActions.markClean());
|
|
3622
3171
|
}
|
|
3623
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
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
|
+
/**
|
|
3264
|
+
* Service that holds and dynamically loads the per-merchant storefront URL.
|
|
3265
|
+
*
|
|
3266
|
+
* Resolution order:
|
|
3267
|
+
* 1. Metafield value loaded from GET /api/storefront-url (per-shop)
|
|
3268
|
+
* 2. Static STOREFRONT_URL injection token (env var / window global fallback)
|
|
3269
|
+
* 3. Empty string (editor works with local components only)
|
|
3270
|
+
*/
|
|
3271
|
+
class StorefrontUrlService {
|
|
3272
|
+
http = inject(HttpClient);
|
|
3273
|
+
staticUrl = inject(STOREFRONT_URL);
|
|
3274
|
+
/** Current storefront URL — reactive signal used by the editor and iframe. */
|
|
3275
|
+
url = signal('', ...(ngDevMode ? [{ debugName: "url" }] : []));
|
|
3276
|
+
/**
|
|
3277
|
+
* Load the storefront URL from the backend metafield.
|
|
3278
|
+
* Falls back to the static STOREFRONT_URL token if the endpoint
|
|
3279
|
+
* returns no value or fails.
|
|
3280
|
+
*
|
|
3281
|
+
* Called during APP_INITIALIZER.
|
|
3282
|
+
*/
|
|
3283
|
+
async load() {
|
|
3284
|
+
try {
|
|
3285
|
+
const res = await firstValueFrom(this.http.get('/api/storefront-url'));
|
|
3286
|
+
if (res.url) {
|
|
3287
|
+
this.url.set(res.url);
|
|
3288
|
+
return;
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
catch {
|
|
3292
|
+
// Non-fatal — fall back to static token
|
|
3293
|
+
}
|
|
3294
|
+
// Fallback: use the static injection token (env var / window global)
|
|
3295
|
+
if (this.staticUrl) {
|
|
3296
|
+
this.url.set(this.staticUrl);
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3300
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, providedIn: 'root' });
|
|
3301
|
+
}
|
|
3302
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontUrlService, decorators: [{
|
|
3303
|
+
type: Injectable,
|
|
3304
|
+
args: [{ providedIn: 'root' }]
|
|
3305
|
+
}] });
|
|
3306
|
+
|
|
3307
|
+
const MAX_DEPTH = 12;
|
|
3308
|
+
class DragDropService {
|
|
3309
|
+
store = inject(Store);
|
|
3310
|
+
registry = inject(ComponentRegistryService);
|
|
3311
|
+
sections = toSignal(this.store.select(selectSections), { initialValue: [] });
|
|
3312
|
+
dragItem = signal(null, ...(ngDevMode ? [{ debugName: "dragItem" }] : []));
|
|
3313
|
+
dropTarget = signal(null, ...(ngDevMode ? [{ debugName: "dropTarget" }] : []));
|
|
3314
|
+
isDragging = computed(() => this.dragItem() !== null, ...(ngDevMode ? [{ debugName: "isDragging" }] : []));
|
|
3315
|
+
startDrag(item, event) {
|
|
3316
|
+
this.dragItem.set(item);
|
|
3317
|
+
this.store.dispatch(VisualEditorActions.startDrag({ elementId: item.elementId }));
|
|
3642
3318
|
if (event.dataTransfer) {
|
|
3643
3319
|
event.dataTransfer.effectAllowed = 'move';
|
|
3644
3320
|
event.dataTransfer.setData('text/plain', item.elementId);
|
|
@@ -3870,10 +3546,10 @@ class DragDropService {
|
|
|
3870
3546
|
return false;
|
|
3871
3547
|
return a.every((id, i) => id === b[i]);
|
|
3872
3548
|
}
|
|
3873
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
3874
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
3549
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DragDropService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3550
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DragDropService, providedIn: 'root' });
|
|
3875
3551
|
}
|
|
3876
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
3552
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DragDropService, decorators: [{
|
|
3877
3553
|
type: Injectable,
|
|
3878
3554
|
args: [{ providedIn: 'root' }]
|
|
3879
3555
|
}] });
|
|
@@ -3885,7 +3561,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
3885
3561
|
class BlockTreeItemComponent {
|
|
3886
3562
|
facade = inject(VisualEditorFacade);
|
|
3887
3563
|
dndService = inject(DragDropService);
|
|
3888
|
-
sanitizer = inject(DomSanitizer);
|
|
3889
3564
|
treeBlock = viewChild('treeBlock', ...(ngDevMode ? [{ debugName: "treeBlock" }] : []));
|
|
3890
3565
|
block = input.required(...(ngDevMode ? [{ debugName: "block" }] : []));
|
|
3891
3566
|
context = input.required(...(ngDevMode ? [{ debugName: "context" }] : []));
|
|
@@ -3913,20 +3588,14 @@ class BlockTreeItemComponent {
|
|
|
3913
3588
|
}), ...(ngDevMode ? [{ debugName: "childContext" }] : []));
|
|
3914
3589
|
blockIcon = computed(() => {
|
|
3915
3590
|
const iconMap = {
|
|
3916
|
-
'text-block': '
|
|
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>',
|
|
3591
|
+
'text-block': '📝',
|
|
3592
|
+
'button-block': '🔘',
|
|
3593
|
+
'image-block': '🖼️',
|
|
3594
|
+
'icon-block': '⭐',
|
|
3595
|
+
'feature-item': '⭐',
|
|
3596
|
+
'card-block': '🃏',
|
|
3925
3597
|
};
|
|
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);
|
|
3598
|
+
return iconMap[this.block().type] ?? '🧩';
|
|
3930
3599
|
}, ...(ngDevMode ? [{ debugName: "blockIcon" }] : []));
|
|
3931
3600
|
blockName = computed(() => {
|
|
3932
3601
|
const block = this.block();
|
|
@@ -4088,8 +3757,8 @@ class BlockTreeItemComponent {
|
|
|
4088
3757
|
zone,
|
|
4089
3758
|
};
|
|
4090
3759
|
}
|
|
4091
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
4092
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
3760
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BlockTreeItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3761
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: BlockTreeItemComponent, isStandalone: true, selector: "lib-block-tree-item", inputs: { block: { classPropertyName: "block", publicName: "block", isSignal: true, isRequired: true, transformFunction: null }, context: { classPropertyName: "context", publicName: "context", isSignal: true, isRequired: true, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: true, transformFunction: null }, totalSiblings: { classPropertyName: "totalSiblings", publicName: "totalSiblings", isSignal: true, isRequired: true, transformFunction: null }, expandAll: { classPropertyName: "expandAll", publicName: "expandAll", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectBlock: "selectBlock", deleteBlock: "deleteBlock", duplicateBlock: "duplicateBlock", moveBlock: "moveBlock", openBlockPicker: "openBlockPicker" }, viewQueries: [{ propertyName: "treeBlock", first: true, predicate: ["treeBlock"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
4093
3762
|
<div
|
|
4094
3763
|
class="tree-block"
|
|
4095
3764
|
#treeBlock
|
|
@@ -4123,7 +3792,7 @@ class BlockTreeItemComponent {
|
|
|
4123
3792
|
<span class="block-indent"></span>
|
|
4124
3793
|
}
|
|
4125
3794
|
<span class="icon-drag-wrapper">
|
|
4126
|
-
<span class="block-icon"
|
|
3795
|
+
<span class="block-icon">{{ blockIcon() }}</span>
|
|
4127
3796
|
<span
|
|
4128
3797
|
class="drag-handle"
|
|
4129
3798
|
role="button"
|
|
@@ -4192,9 +3861,9 @@ class BlockTreeItemComponent {
|
|
|
4192
3861
|
}
|
|
4193
3862
|
</div>
|
|
4194
3863
|
}
|
|
4195
|
-
`, isInline: true, styles: [":host{display:block
|
|
3864
|
+
`, isInline: true, styles: [":host{display:block}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.5rem .75rem .5rem .5rem;cursor:pointer;transition:background .2s;position:relative}.tree-block:hover{background:#f5f5f5;border-radius:8px;margin-right:.375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin-right:.375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name,.tree-block.selected .expand-btn,.tree-block.selected .drag-handle,.tree-block.selected .tree-btn{color:inherit}.tree-block.drag-over-before{box-shadow:inset 0 2px #4a90d9}.tree-block.drag-over-inside{background:#4a90d91a;box-shadow:inset 0 0 0 2px #4a90d9}.tree-block.drag-over-after{box-shadow:inset 0 -2px #4a90d9}.tree-block.is-dragging{opacity:.4;cursor:grabbing}.drag-handle{cursor:grab;color:#999;opacity:0;transition:opacity .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:2px}.drag-handle:hover{color:#666;background:#e0e0e0}.drag-handle:focus-visible{opacity:1;outline:2px solid #4a90d9;outline-offset:1px}.tree-block:hover .drag-handle{opacity:1}@media(prefers-reduced-motion:reduce){.tree-block,.drag-handle,.tree-actions,.expand-icon{transition:none}}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .2s}.block-indent{width:18px;height:1px;background:#ddd;flex-shrink:0}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-block:hover .icon-drag-wrapper .block-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .block-icon{visibility:hidden}.block-icon{font-size:.875rem;flex-shrink:0}.block-name{flex:1;font-size:.75rem;color:#555;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.6875rem;color:#666}.tree-btn:hover{background:#d0d0d0}.tree-btn.delete:hover{background:#e74c3c;color:#fff}.material-icon{font-family:Material Icons,sans-serif;font-size:.75rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.nested-content{border-left:1px solid #e0e0e0;margin-left:1.25rem}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1rem);margin:.375rem .5rem;padding:.375rem .5rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#005bd3;transition:all .2s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.75rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}\n"], dependencies: [{ kind: "component", type: BlockTreeItemComponent, selector: "lib-block-tree-item", inputs: ["block", "context", "index", "totalSiblings", "expandAll"], outputs: ["selectBlock", "deleteBlock", "duplicateBlock", "moveBlock", "openBlockPicker"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4196
3865
|
}
|
|
4197
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
3866
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: BlockTreeItemComponent, decorators: [{
|
|
4198
3867
|
type: Component,
|
|
4199
3868
|
args: [{ selector: 'lib-block-tree-item', imports: [BlockTreeItemComponent], template: `
|
|
4200
3869
|
<div
|
|
@@ -4230,7 +3899,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
4230
3899
|
<span class="block-indent"></span>
|
|
4231
3900
|
}
|
|
4232
3901
|
<span class="icon-drag-wrapper">
|
|
4233
|
-
<span class="block-icon"
|
|
3902
|
+
<span class="block-icon">{{ blockIcon() }}</span>
|
|
4234
3903
|
<span
|
|
4235
3904
|
class="drag-handle"
|
|
4236
3905
|
role="button"
|
|
@@ -4299,7 +3968,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
4299
3968
|
}
|
|
4300
3969
|
</div>
|
|
4301
3970
|
}
|
|
4302
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block
|
|
3971
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.5rem .75rem .5rem .5rem;cursor:pointer;transition:background .2s;position:relative}.tree-block:hover{background:#f5f5f5;border-radius:8px;margin-right:.375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin-right:.375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name,.tree-block.selected .expand-btn,.tree-block.selected .drag-handle,.tree-block.selected .tree-btn{color:inherit}.tree-block.drag-over-before{box-shadow:inset 0 2px #4a90d9}.tree-block.drag-over-inside{background:#4a90d91a;box-shadow:inset 0 0 0 2px #4a90d9}.tree-block.drag-over-after{box-shadow:inset 0 -2px #4a90d9}.tree-block.is-dragging{opacity:.4;cursor:grabbing}.drag-handle{cursor:grab;color:#999;opacity:0;transition:opacity .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:2px}.drag-handle:hover{color:#666;background:#e0e0e0}.drag-handle:focus-visible{opacity:1;outline:2px solid #4a90d9;outline-offset:1px}.tree-block:hover .drag-handle{opacity:1}@media(prefers-reduced-motion:reduce){.tree-block,.drag-handle,.tree-actions,.expand-icon{transition:none}}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .2s}.block-indent{width:18px;height:1px;background:#ddd;flex-shrink:0}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-block:hover .icon-drag-wrapper .block-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .block-icon{visibility:hidden}.block-icon{font-size:.875rem;flex-shrink:0}.block-name{flex:1;font-size:.75rem;color:#555;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.6875rem;color:#666}.tree-btn:hover{background:#d0d0d0}.tree-btn.delete:hover{background:#e74c3c;color:#fff}.material-icon{font-family:Material Icons,sans-serif;font-size:.75rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.nested-content{border-left:1px solid #e0e0e0;margin-left:1.25rem}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1rem);margin:.375rem .5rem;padding:.375rem .5rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#005bd3;transition:all .2s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.75rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}\n"] }]
|
|
4303
3972
|
}], propDecorators: { treeBlock: [{ type: i0.ViewChild, args: ['treeBlock', { isSignal: true }] }], block: [{ type: i0.Input, args: [{ isSignal: true, alias: "block", required: true }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "context", required: true }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: true }] }], totalSiblings: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalSiblings", required: true }] }], expandAll: [{ type: i0.Input, args: [{ isSignal: true, alias: "expandAll", required: false }] }], selectBlock: [{ type: i0.Output, args: ["selectBlock"] }], deleteBlock: [{ type: i0.Output, args: ["deleteBlock"] }], duplicateBlock: [{ type: i0.Output, args: ["duplicateBlock"] }], moveBlock: [{ type: i0.Output, args: ["moveBlock"] }], openBlockPicker: [{ type: i0.Output, args: ["openBlockPicker"] }] } });
|
|
4304
3973
|
|
|
4305
3974
|
const FILES_QUERY = `
|
|
@@ -4414,10 +4083,10 @@ class ShopifyFilesService {
|
|
|
4414
4083
|
return 'unknown';
|
|
4415
4084
|
}
|
|
4416
4085
|
}
|
|
4417
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
4418
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
4086
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
4087
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilesService, providedIn: 'root' });
|
|
4419
4088
|
}
|
|
4420
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
4089
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilesService, decorators: [{
|
|
4421
4090
|
type: Injectable,
|
|
4422
4091
|
args: [{ providedIn: 'root' }]
|
|
4423
4092
|
}] });
|
|
@@ -4440,7 +4109,7 @@ class ShopifyFilePickerComponent {
|
|
|
4440
4109
|
endCursor = null;
|
|
4441
4110
|
hasNextPage = false;
|
|
4442
4111
|
ngOnInit() {
|
|
4443
|
-
this.searchSubject.pipe(debounceTime(300), tap
|
|
4112
|
+
this.searchSubject.pipe(debounceTime(300), tap(() => {
|
|
4444
4113
|
this.files.set([]);
|
|
4445
4114
|
this.endCursor = null;
|
|
4446
4115
|
this.hasNextPage = false;
|
|
@@ -4553,8 +4222,8 @@ class ShopifyFilePickerComponent {
|
|
|
4553
4222
|
},
|
|
4554
4223
|
});
|
|
4555
4224
|
}
|
|
4556
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
4557
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
4225
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4226
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ShopifyFilePickerComponent, isStandalone: true, selector: "lib-shopify-file-picker", inputs: { mediaType: { classPropertyName: "mediaType", publicName: "mediaType", isSignal: true, isRequired: true, transformFunction: null }, currentValue: { classPropertyName: "currentValue", publicName: "currentValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileSelected: "fileSelected", closed: "closed" }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "scrollContainer", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
4558
4227
|
<div
|
|
4559
4228
|
class="file-picker-overlay"
|
|
4560
4229
|
role="dialog"
|
|
@@ -4649,9 +4318,9 @@ class ShopifyFilePickerComponent {
|
|
|
4649
4318
|
</div>
|
|
4650
4319
|
</div>
|
|
4651
4320
|
</div>
|
|
4652
|
-
`, isInline: true, styles: [".file-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1100
|
|
4321
|
+
`, isInline: true, styles: [".file-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1100}.file-picker{background:#fff;border-radius:12px;width:560px;max-height:70vh;display:flex;flex-direction:column;box-shadow:0 12px 40px #00000040}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e0e0e0}.picker-header h3{margin:0;font-size:1rem;font-weight:600;color:#303030}.close-btn{border:none;background:none;font-size:1.5rem;cursor:pointer;color:#666;line-height:1;padding:.25rem;border-radius:4px}.close-btn:hover{background:#f0f2f5;color:#303030}.picker-search{padding:.75rem 1.25rem;border-bottom:1px solid #e0e0e0}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.875rem;outline:none;transition:border-color .2s;box-sizing:border-box}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-content{flex:1;overflow-y:auto;padding:1rem 1.25rem;min-height:200px}.file-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem}.file-card{display:flex;flex-direction:column;align-items:center;padding:.5rem;border:2px solid #e0e0e0;border-radius:8px;background:#fff;cursor:pointer;transition:all .15s;position:relative;text-align:center}.file-card:hover{border-color:#005bd3;background:#f8fafe}.file-card:focus-visible{outline:2px solid #005bd3;outline-offset:2px}.file-card.selected{border-color:#005bd3;background:#f0f5ff;box-shadow:0 0 0 1px #005bd3}.file-card.current{border-color:#008060}.file-thumbnail{width:100%;aspect-ratio:1;object-fit:cover;border-radius:4px;background:#f0f2f5}.video-thumbnail{width:100%;aspect-ratio:1;display:flex;align-items:center;justify-content:center;background:#1a1a1a;border-radius:4px;color:#fff}.video-icon{opacity:.8}.file-name{display:block;margin-top:.375rem;font-size:.6875rem;color:#616161;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.current-badge{position:absolute;top:.25rem;right:.25rem;padding:.125rem .375rem;background:#008060;color:#fff;font-size:.625rem;font-weight:600;border-radius:4px}.picker-loading{padding:2rem;text-align:center;color:#616161;font-size:.875rem}.picker-error{padding:2rem;text-align:center;color:#d72c0d}.picker-error p{margin:0 0 .75rem;font-size:.875rem}.retry-btn{padding:.375rem .75rem;border:1px solid #d72c0d;border-radius:6px;background:#fff;color:#d72c0d;cursor:pointer;font-size:.8125rem}.retry-btn:hover{background:#fef2f0}.picker-empty{padding:2rem;text-align:center;color:#616161;font-size:.875rem}.picker-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:.75rem 1.25rem;border-top:1px solid #e0e0e0}.cancel-btn{padding:.5rem 1rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;color:#303030}.cancel-btn:hover{background:#f0f2f5}.select-btn{padding:.5rem 1rem;border:none;border-radius:8px;background:#005bd3;color:#fff;cursor:pointer;font-size:.8125rem;font-weight:500}.select-btn:hover:not(:disabled){background:#004299}.select-btn:disabled{opacity:.5;cursor:not-allowed}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4653
4322
|
}
|
|
4654
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
4323
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ShopifyFilePickerComponent, decorators: [{
|
|
4655
4324
|
type: Component,
|
|
4656
4325
|
args: [{ selector: 'lib-shopify-file-picker', template: `
|
|
4657
4326
|
<div
|
|
@@ -4748,7 +4417,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
4748
4417
|
</div>
|
|
4749
4418
|
</div>
|
|
4750
4419
|
</div>
|
|
4751
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".file-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1100
|
|
4420
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".file-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1100}.file-picker{background:#fff;border-radius:12px;width:560px;max-height:70vh;display:flex;flex-direction:column;box-shadow:0 12px 40px #00000040}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e0e0e0}.picker-header h3{margin:0;font-size:1rem;font-weight:600;color:#303030}.close-btn{border:none;background:none;font-size:1.5rem;cursor:pointer;color:#666;line-height:1;padding:.25rem;border-radius:4px}.close-btn:hover{background:#f0f2f5;color:#303030}.picker-search{padding:.75rem 1.25rem;border-bottom:1px solid #e0e0e0}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.875rem;outline:none;transition:border-color .2s;box-sizing:border-box}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-content{flex:1;overflow-y:auto;padding:1rem 1.25rem;min-height:200px}.file-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem}.file-card{display:flex;flex-direction:column;align-items:center;padding:.5rem;border:2px solid #e0e0e0;border-radius:8px;background:#fff;cursor:pointer;transition:all .15s;position:relative;text-align:center}.file-card:hover{border-color:#005bd3;background:#f8fafe}.file-card:focus-visible{outline:2px solid #005bd3;outline-offset:2px}.file-card.selected{border-color:#005bd3;background:#f0f5ff;box-shadow:0 0 0 1px #005bd3}.file-card.current{border-color:#008060}.file-thumbnail{width:100%;aspect-ratio:1;object-fit:cover;border-radius:4px;background:#f0f2f5}.video-thumbnail{width:100%;aspect-ratio:1;display:flex;align-items:center;justify-content:center;background:#1a1a1a;border-radius:4px;color:#fff}.video-icon{opacity:.8}.file-name{display:block;margin-top:.375rem;font-size:.6875rem;color:#616161;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.current-badge{position:absolute;top:.25rem;right:.25rem;padding:.125rem .375rem;background:#008060;color:#fff;font-size:.625rem;font-weight:600;border-radius:4px}.picker-loading{padding:2rem;text-align:center;color:#616161;font-size:.875rem}.picker-error{padding:2rem;text-align:center;color:#d72c0d}.picker-error p{margin:0 0 .75rem;font-size:.875rem}.retry-btn{padding:.375rem .75rem;border:1px solid #d72c0d;border-radius:6px;background:#fff;color:#d72c0d;cursor:pointer;font-size:.8125rem}.retry-btn:hover{background:#fef2f0}.picker-empty{padding:2rem;text-align:center;color:#616161;font-size:.875rem}.picker-footer{display:flex;justify-content:flex-end;gap:.5rem;padding:.75rem 1.25rem;border-top:1px solid #e0e0e0}.cancel-btn{padding:.5rem 1rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;color:#303030}.cancel-btn:hover{background:#f0f2f5}.select-btn{padding:.5rem 1rem;border:none;border-radius:8px;background:#005bd3;color:#fff;cursor:pointer;font-size:.8125rem;font-weight:500}.select-btn:hover:not(:disabled){background:#004299}.select-btn:disabled{opacity:.5;cursor:not-allowed}\n"] }]
|
|
4752
4421
|
}], propDecorators: { mediaType: [{ type: i0.Input, args: [{ isSignal: true, alias: "mediaType", required: true }] }], currentValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentValue", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], closed: [{ type: i0.Output, args: ["closed"] }], searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }], scrollContainer: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }] } });
|
|
4753
4422
|
|
|
4754
4423
|
/**
|
|
@@ -4765,6 +4434,8 @@ class VisualEditorComponent {
|
|
|
4765
4434
|
loadingStrategy = inject(PageLoadingStrategy);
|
|
4766
4435
|
config = inject(VISUAL_EDITOR_CONFIG);
|
|
4767
4436
|
dndService = inject(DragDropService);
|
|
4437
|
+
iframeBridge = inject(IframeBridgeService);
|
|
4438
|
+
storefrontUrlService = inject(StorefrontUrlService);
|
|
4768
4439
|
sanitizer = inject(DomSanitizer);
|
|
4769
4440
|
destroy$ = new Subject();
|
|
4770
4441
|
// Configuration-driven UI options
|
|
@@ -4774,6 +4445,15 @@ class VisualEditorComponent {
|
|
|
4774
4445
|
isPreviewMode = signal(false, ...(ngDevMode ? [{ debugName: "isPreviewMode" }] : []));
|
|
4775
4446
|
editorContext = computed(() => ({ isEditor: !this.isPreviewMode() }), ...(ngDevMode ? [{ debugName: "editorContext" }] : []));
|
|
4776
4447
|
canvasEl = viewChild('canvasEl', ...(ngDevMode ? [{ debugName: "canvasEl" }] : []));
|
|
4448
|
+
// Iframe preview
|
|
4449
|
+
previewFrame = viewChild('previewFrame', ...(ngDevMode ? [{ debugName: "previewFrame" }] : []));
|
|
4450
|
+
previewUrl = computed(() => {
|
|
4451
|
+
const url = this.storefrontUrlService.url();
|
|
4452
|
+
if (!url)
|
|
4453
|
+
return null;
|
|
4454
|
+
return this.sanitizer.bypassSecurityTrustResourceUrl(`${url}/kustomizer/editor`);
|
|
4455
|
+
}, ...(ngDevMode ? [{ debugName: "previewUrl" }] : []));
|
|
4456
|
+
iframeReady = false;
|
|
4777
4457
|
propertiesTab = signal('props', ...(ngDevMode ? [{ debugName: "propertiesTab" }] : []));
|
|
4778
4458
|
expandedGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedGroups" }] : []));
|
|
4779
4459
|
expandedSections = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedSections" }] : []));
|
|
@@ -4898,23 +4578,19 @@ class VisualEditorComponent {
|
|
|
4898
4578
|
});
|
|
4899
4579
|
}, ...(ngDevMode ? [{ debugName: "filteredBlockPresetsForNestedSlot" }] : []));
|
|
4900
4580
|
sectionIconMap = {
|
|
4901
|
-
'hero-section': '
|
|
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>',
|
|
4581
|
+
'hero-section': '🎯',
|
|
4582
|
+
'features-grid': '⚡',
|
|
4583
|
+
'testimonials': '💬',
|
|
4584
|
+
'cta-section': '📢',
|
|
4585
|
+
'footer-section': '📋',
|
|
4907
4586
|
};
|
|
4908
4587
|
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>',
|
|
4588
|
+
'text-block': '📝',
|
|
4589
|
+
'button-block': '🔘',
|
|
4590
|
+
'image-block': '🖼️',
|
|
4591
|
+
'icon-block': '⭐',
|
|
4592
|
+
'feature-item': '⭐',
|
|
4593
|
+
'card-block': '🃏',
|
|
4918
4594
|
};
|
|
4919
4595
|
selectedBlockDefinition = computed(() => this.facade.selectedBlockDefinition(), ...(ngDevMode ? [{ debugName: "selectedBlockDefinition" }] : []));
|
|
4920
4596
|
// Inline name editing
|
|
@@ -4974,6 +4650,27 @@ class VisualEditorComponent {
|
|
|
4974
4650
|
cancelEditingName() {
|
|
4975
4651
|
this.isEditingName.set(false);
|
|
4976
4652
|
}
|
|
4653
|
+
constructor() {
|
|
4654
|
+
// Sync sections to iframe whenever they change
|
|
4655
|
+
effect(() => {
|
|
4656
|
+
const sections = this.facade.sections();
|
|
4657
|
+
if (this.iframeReady && this.previewUrl()) {
|
|
4658
|
+
this.iframeBridge.sendPageUpdate(sections);
|
|
4659
|
+
}
|
|
4660
|
+
});
|
|
4661
|
+
// Sync selection to iframe
|
|
4662
|
+
effect(() => {
|
|
4663
|
+
const selected = this.facade.selectedElement()?.id ?? null;
|
|
4664
|
+
if (!this.iframeReady || !this.previewUrl())
|
|
4665
|
+
return;
|
|
4666
|
+
if (selected) {
|
|
4667
|
+
this.iframeBridge.sendSelectElement(selected);
|
|
4668
|
+
}
|
|
4669
|
+
else {
|
|
4670
|
+
this.iframeBridge.sendDeselect();
|
|
4671
|
+
}
|
|
4672
|
+
});
|
|
4673
|
+
}
|
|
4977
4674
|
ngOnInit() {
|
|
4978
4675
|
// Load page using the configured loading strategy
|
|
4979
4676
|
this.loadingStrategy
|
|
@@ -4993,13 +4690,39 @@ class VisualEditorComponent {
|
|
|
4993
4690
|
this.navigation.navigate({ type: 'page-list' });
|
|
4994
4691
|
},
|
|
4995
4692
|
});
|
|
4693
|
+
// Listen for iframe events
|
|
4694
|
+
if (this.previewUrl()) {
|
|
4695
|
+
this.iframeBridge
|
|
4696
|
+
.onReady()
|
|
4697
|
+
.pipe(takeUntil(this.destroy$))
|
|
4698
|
+
.subscribe(() => {
|
|
4699
|
+
this.iframeReady = true;
|
|
4700
|
+
// Send initial page state
|
|
4701
|
+
this.iframeBridge.sendPageUpdate(this.facade.sections());
|
|
4702
|
+
});
|
|
4703
|
+
this.iframeBridge
|
|
4704
|
+
.onElementClicked()
|
|
4705
|
+
.pipe(takeUntil(this.destroy$))
|
|
4706
|
+
.subscribe(({ elementId, sectionId }) => {
|
|
4707
|
+
this.facade.selectElement(sectionId, elementId);
|
|
4708
|
+
});
|
|
4709
|
+
}
|
|
4996
4710
|
}
|
|
4997
4711
|
ngOnDestroy() {
|
|
4998
4712
|
this.destroy$.next();
|
|
4999
4713
|
this.destroy$.complete();
|
|
4714
|
+
this.iframeBridge.disconnect();
|
|
5000
4715
|
// Clear page from store when leaving editor
|
|
5001
4716
|
this.facade.clearPage();
|
|
5002
4717
|
}
|
|
4718
|
+
onIframeLoad() {
|
|
4719
|
+
const iframe = this.previewFrame()?.nativeElement;
|
|
4720
|
+
if (iframe) {
|
|
4721
|
+
const sfUrl = this.storefrontUrlService.url();
|
|
4722
|
+
const origin = sfUrl ? new URL(sfUrl).origin : '*';
|
|
4723
|
+
this.iframeBridge.connect(iframe, origin);
|
|
4724
|
+
}
|
|
4725
|
+
}
|
|
5003
4726
|
goBack() {
|
|
5004
4727
|
if (this.facade.isDirty()) {
|
|
5005
4728
|
this.navigation
|
|
@@ -5064,25 +4787,17 @@ class VisualEditorComponent {
|
|
|
5064
4787
|
},
|
|
5065
4788
|
});
|
|
5066
4789
|
}
|
|
5067
|
-
defaultSectionIcon = '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M3 3h14a1 1 0 011 1v12a1 1 0 01-1 1H3a1 1 0 01-1-1V4a1 1 0 011-1zm1 2v10h12V5H4z"/></svg>';
|
|
5068
|
-
defaultBlockIcon = '<svg width="16" height="16" viewBox="0 0 20 20" fill="currentColor"><path d="M10 2l1.5 3h3.5l-2.5 2.5 1 3.5-3.5-2-3.5 2 1-3.5L4 5h3.5L10 2zm-6 10h12v2H4v-2zm2 4h8v2H6v-2z"/></svg>';
|
|
5069
|
-
toSafeIcon(icon) {
|
|
5070
|
-
if (icon.trim().startsWith('<')) {
|
|
5071
|
-
return this.sanitizer.bypassSecurityTrustHtml(icon);
|
|
5072
|
-
}
|
|
5073
|
-
return this.sanitizer.bypassSecurityTrustHtml(`<span style="font-family:'Material Icons',sans-serif;font-weight:normal;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility">${icon}</span>`);
|
|
5074
|
-
}
|
|
5075
4790
|
getIcon(def) {
|
|
5076
|
-
return this.
|
|
4791
|
+
return this.sectionIconMap[def.type] ?? '📦';
|
|
5077
4792
|
}
|
|
5078
4793
|
getBlockIcon(def) {
|
|
5079
|
-
return this.
|
|
4794
|
+
return this.blockIconMap[def.type] ?? '🧩';
|
|
5080
4795
|
}
|
|
5081
4796
|
getBlockIconByType(type) {
|
|
5082
|
-
return this.
|
|
4797
|
+
return this.blockIconMap[type] ?? '🧩';
|
|
5083
4798
|
}
|
|
5084
4799
|
getSectionIcon(type) {
|
|
5085
|
-
return this.
|
|
4800
|
+
return this.sectionIconMap[type] ?? '📦';
|
|
5086
4801
|
}
|
|
5087
4802
|
// Tree expansion
|
|
5088
4803
|
isSectionExpanded(sectionId) {
|
|
@@ -5508,10 +5223,10 @@ class VisualEditorComponent {
|
|
|
5508
5223
|
this.closeNestedBlockPicker();
|
|
5509
5224
|
}
|
|
5510
5225
|
getSectionPresetIcon(rp) {
|
|
5511
|
-
return
|
|
5226
|
+
return rp.displayIcon ?? this.sectionIconMap[rp.definition.type] ?? '📦';
|
|
5512
5227
|
}
|
|
5513
5228
|
getBlockPresetIcon(rp) {
|
|
5514
|
-
return
|
|
5229
|
+
return rp.displayIcon ?? this.blockIconMap[rp.definition.type] ?? '🧩';
|
|
5515
5230
|
}
|
|
5516
5231
|
selectSection(section, event) {
|
|
5517
5232
|
event.stopPropagation();
|
|
@@ -5755,15 +5470,15 @@ class VisualEditorComponent {
|
|
|
5755
5470
|
return;
|
|
5756
5471
|
this.facade.updateSectionProps(section.id, { [key]: '' });
|
|
5757
5472
|
}
|
|
5758
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
5759
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
5473
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5474
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: VisualEditorComponent, isStandalone: true, selector: "lib-visual-editor", providers: [IframeBridgeService], viewQueries: [{ propertyName: "canvasEl", first: true, predicate: ["canvasEl"], descendants: true, isSignal: true }, { propertyName: "previewFrame", first: true, predicate: ["previewFrame"], descendants: true, isSignal: true }, { propertyName: "nameInput", first: true, predicate: ["nameInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
5760
5475
|
<div class="editor-layout">
|
|
5761
5476
|
<!-- Sidebar: Hierarchical Tree -->
|
|
5762
5477
|
<aside class="sidebar">
|
|
5763
5478
|
<div class="sidebar-header">
|
|
5764
5479
|
<h2>Page Structure</h2>
|
|
5765
5480
|
<div class="search-box">
|
|
5766
|
-
<span class="search-icon"
|
|
5481
|
+
<span class="search-icon">🔍</span>
|
|
5767
5482
|
<input
|
|
5768
5483
|
type="text"
|
|
5769
5484
|
class="search-input"
|
|
@@ -5811,7 +5526,7 @@ class VisualEditorComponent {
|
|
|
5811
5526
|
<span class="expand-icon">▶</span>
|
|
5812
5527
|
</button>
|
|
5813
5528
|
<span class="icon-drag-wrapper">
|
|
5814
|
-
<span class="section-icon"
|
|
5529
|
+
<span class="section-icon">{{ getSectionIcon(section.type) }}</span>
|
|
5815
5530
|
<span
|
|
5816
5531
|
class="drag-handle section-drag-handle"
|
|
5817
5532
|
role="button"
|
|
@@ -5913,7 +5628,7 @@ class VisualEditorComponent {
|
|
|
5913
5628
|
}
|
|
5914
5629
|
@for (rp of group.presets; track rp.displayName + '-' + rp.definition.type) {
|
|
5915
5630
|
<button class="picker-item" (click)="addSectionFromPreset(rp)">
|
|
5916
|
-
<span class="picker-icon"
|
|
5631
|
+
<span class="picker-icon">{{ getSectionPresetIcon(rp) }}</span>
|
|
5917
5632
|
<div class="picker-info">
|
|
5918
5633
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
5919
5634
|
@if (rp.displayDescription) {
|
|
@@ -5951,7 +5666,7 @@ class VisualEditorComponent {
|
|
|
5951
5666
|
<div class="picker-content">
|
|
5952
5667
|
@for (rp of filteredBlockPresetsForSection(); track rp.displayName + '-' + rp.definition.type) {
|
|
5953
5668
|
<button class="picker-item" (click)="addBlockToSectionFromPreset(blockPickerSection()!, rp)">
|
|
5954
|
-
<span class="picker-icon"
|
|
5669
|
+
<span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
|
|
5955
5670
|
<div class="picker-info">
|
|
5956
5671
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
5957
5672
|
@if (rp.displayDescription) {
|
|
@@ -5994,7 +5709,7 @@ class VisualEditorComponent {
|
|
|
5994
5709
|
<div class="picker-content">
|
|
5995
5710
|
@for (rp of filteredBlockPresetsForNestedSlot(); track rp.displayName + '-' + rp.definition.type) {
|
|
5996
5711
|
<button class="picker-item" (click)="addNestedBlockFromPreset(rp)">
|
|
5997
|
-
<span class="picker-icon"
|
|
5712
|
+
<span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
|
|
5998
5713
|
<div class="picker-info">
|
|
5999
5714
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
6000
5715
|
@if (rp.displayDescription) {
|
|
@@ -6024,7 +5739,7 @@ class VisualEditorComponent {
|
|
|
6024
5739
|
<div class="toolbar">
|
|
6025
5740
|
<div class="toolbar-left">
|
|
6026
5741
|
<button class="toolbar-btn back-btn" (click)="goBack()" title="Back to pages">
|
|
6027
|
-
|
|
5742
|
+
← Back
|
|
6028
5743
|
</button>
|
|
6029
5744
|
<button
|
|
6030
5745
|
class="toolbar-btn"
|
|
@@ -6032,7 +5747,7 @@ class VisualEditorComponent {
|
|
|
6032
5747
|
(click)="facade.undo()"
|
|
6033
5748
|
title="Undo"
|
|
6034
5749
|
>
|
|
6035
|
-
|
|
5750
|
+
↩ Undo
|
|
6036
5751
|
</button>
|
|
6037
5752
|
<button
|
|
6038
5753
|
class="toolbar-btn"
|
|
@@ -6040,7 +5755,7 @@ class VisualEditorComponent {
|
|
|
6040
5755
|
(click)="facade.redo()"
|
|
6041
5756
|
title="Redo"
|
|
6042
5757
|
>
|
|
6043
|
-
|
|
5758
|
+
↪ Redo
|
|
6044
5759
|
</button>
|
|
6045
5760
|
</div>
|
|
6046
5761
|
<div class="toolbar-center">
|
|
@@ -6073,7 +5788,7 @@ class VisualEditorComponent {
|
|
|
6073
5788
|
(click)="savePage()"
|
|
6074
5789
|
title="Save changes"
|
|
6075
5790
|
>
|
|
6076
|
-
{{ isSaving() ? 'Saving...' : 'Save' }}
|
|
5791
|
+
{{ isSaving() ? 'Saving...' : '💾 Save' }}
|
|
6077
5792
|
</button>
|
|
6078
5793
|
}
|
|
6079
5794
|
@if (facade.currentPage() && showPublishButtons()) {
|
|
@@ -6084,7 +5799,7 @@ class VisualEditorComponent {
|
|
|
6084
5799
|
(click)="publishPage()"
|
|
6085
5800
|
title="Publish page"
|
|
6086
5801
|
>
|
|
6087
|
-
{{ isPublishing() ? 'Publishing...' : 'Publish' }}
|
|
5802
|
+
{{ isPublishing() ? 'Publishing...' : '🚀 Publish' }}
|
|
6088
5803
|
</button>
|
|
6089
5804
|
} @else {
|
|
6090
5805
|
<button
|
|
@@ -6093,57 +5808,68 @@ class VisualEditorComponent {
|
|
|
6093
5808
|
(click)="unpublishPage()"
|
|
6094
5809
|
title="Unpublish page"
|
|
6095
5810
|
>
|
|
6096
|
-
{{ isPublishing() ? 'Unpublishing...' : 'Unpublish' }}
|
|
5811
|
+
{{ isPublishing() ? 'Unpublishing...' : '📥 Unpublish' }}
|
|
6097
5812
|
</button>
|
|
6098
5813
|
}
|
|
6099
5814
|
}
|
|
6100
5815
|
<button class="toolbar-btn preview-btn" (click)="togglePreview()">
|
|
6101
|
-
{{ isPreviewMode() ? 'Edit' : 'Preview' }}
|
|
5816
|
+
{{ isPreviewMode() ? '✏️ Edit' : '👁 Preview' }}
|
|
6102
5817
|
</button>
|
|
6103
5818
|
</div>
|
|
6104
5819
|
</div>
|
|
6105
5820
|
|
|
6106
5821
|
<!-- Canvas Content -->
|
|
6107
5822
|
<div class="canvas" #canvasEl [class.preview-mode]="isPreviewMode()">
|
|
6108
|
-
@if (
|
|
6109
|
-
<
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
5823
|
+
@if (previewUrl()) {
|
|
5824
|
+
<iframe
|
|
5825
|
+
#previewFrame
|
|
5826
|
+
class="preview-iframe"
|
|
5827
|
+
[src]="previewUrl()!"
|
|
5828
|
+
(load)="onIframeLoad()"
|
|
5829
|
+
allow="clipboard-read; clipboard-write"
|
|
5830
|
+
></iframe>
|
|
6114
5831
|
} @else {
|
|
6115
|
-
|
|
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
|
-
}
|
|
5832
|
+
<!-- Fallback: direct rendering when no storefront URL configured -->
|
|
5833
|
+
@if (facade.sections().length === 0) {
|
|
5834
|
+
<div class="empty-state">
|
|
5835
|
+
<div class="empty-icon">📄</div>
|
|
5836
|
+
<h3>Start Building</h3>
|
|
5837
|
+
<p>Click on a component from the sidebar to add it to your page</p>
|
|
5838
|
+
</div>
|
|
5839
|
+
} @else {
|
|
5840
|
+
@for (section of facade.sections(); track section.id; let idx = $index) {
|
|
6139
5841
|
<div
|
|
6140
|
-
class="section-
|
|
6141
|
-
[
|
|
6142
|
-
(click)="selectSection(section, $event)"
|
|
5842
|
+
class="section-outer"
|
|
5843
|
+
[class.selected]="facade.selectedSection()?.id === section.id"
|
|
6143
5844
|
>
|
|
6144
|
-
|
|
5845
|
+
@if (!isPreviewMode()) {
|
|
5846
|
+
<div class="section-controls">
|
|
5847
|
+
<span class="section-label">{{ getSectionName(section) }}</span>
|
|
5848
|
+
<div class="section-actions">
|
|
5849
|
+
@if (idx > 0) {
|
|
5850
|
+
<button class="action-btn" (click)="moveUp(section.id, idx, $event)" title="Move up">↑</button>
|
|
5851
|
+
}
|
|
5852
|
+
@if (idx < facade.sections().length - 1) {
|
|
5853
|
+
<button class="action-btn" (click)="moveDown(section.id, idx, $event)" title="Move down">↓</button>
|
|
5854
|
+
}
|
|
5855
|
+
@if (facade.isSectionDuplicable(section.type)) {
|
|
5856
|
+
<button class="action-btn" (click)="duplicateSection(section.id, $event)" title="Duplicate">
|
|
5857
|
+
<span class="material-icon">content_copy</span>
|
|
5858
|
+
</button>
|
|
5859
|
+
}
|
|
5860
|
+
<button class="action-btn delete" (click)="deleteSection(section.id, $event)" title="Delete">×</button>
|
|
5861
|
+
</div>
|
|
5862
|
+
</div>
|
|
5863
|
+
}
|
|
5864
|
+
<div
|
|
5865
|
+
class="section-wrapper"
|
|
5866
|
+
[attr.data-section-id]="section.id"
|
|
5867
|
+
(click)="selectSection(section, $event)"
|
|
5868
|
+
>
|
|
5869
|
+
<lib-dynamic-renderer [element]="section" [context]="editorContext()" />
|
|
5870
|
+
</div>
|
|
6145
5871
|
</div>
|
|
6146
|
-
|
|
5872
|
+
}
|
|
6147
5873
|
}
|
|
6148
5874
|
}
|
|
6149
5875
|
</div>
|
|
@@ -6533,7 +6259,7 @@ class VisualEditorComponent {
|
|
|
6533
6259
|
[class.selected]="facade.selectedBlock()?.id === block.id"
|
|
6534
6260
|
(click)="selectBlock(block)"
|
|
6535
6261
|
>
|
|
6536
|
-
<span class="block-icon"
|
|
6262
|
+
<span class="block-icon">{{ getBlockIconByType(block.type) }}</span>
|
|
6537
6263
|
<span class="block-name">{{ getBlockName(block) }}</span>
|
|
6538
6264
|
<div class="block-actions">
|
|
6539
6265
|
@if (idx > 0) {
|
|
@@ -6580,18 +6306,18 @@ class VisualEditorComponent {
|
|
|
6580
6306
|
/>
|
|
6581
6307
|
}
|
|
6582
6308
|
</div>
|
|
6583
|
-
`, isInline: true, styles: [":host{display:block;height:calc(100vh - 56px);overflow:hidden;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.editor-layout{display:grid;grid-template-columns:280px 1fr 300px;height:100%;background:#f6f6f7}.sidebar{background:#fff;border-right:1px solid #e3e3e3;display:flex;flex-direction:column}.sidebar-header{padding:1rem;border-bottom:1px solid #e3e3e3}.sidebar-header h2{margin:0;font-size:.8125rem;font-weight:600;color:#303030}.search-box{display:none}.search-box:focus-within{border-color:#005bd3;background:#fff}.search-icon{font-size:.8125rem;opacity:.6}.search-input{flex:1;border:none;background:transparent;font-size:.8125rem;color:#303030;font-family:inherit;outline:none}.search-input::placeholder{color:#8c9196}.search-clear{padding:.125rem .375rem;border:none;background:#e3e3e3;border-radius:4px;cursor:pointer;font-size:.75rem;color:#616161;line-height:1}.search-clear:hover{background:#c9cccf}.search-no-results{padding:2rem 1rem;text-align:center;color:#8c9196}.search-no-results p{margin:0;font-size:.8125rem}.picker-search{padding:.5rem .75rem;border-bottom:1px solid #e3e3e3}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .2s,box-shadow .2s}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-search-input::placeholder{color:#8c9196}.section-tree{flex:1;overflow-y:auto;padding:.25rem 0}.tree-section{border-bottom:none}.tree-section.selected>.tree-section-header{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-section.selected>.tree-section-header .section-name,.tree-section.selected>.tree-section-header .expand-btn,.tree-section.selected>.tree-section-header .section-icon,.tree-section.selected>.tree-section-header .drag-handle,.tree-section.selected>.tree-section-header .tree-btn{color:inherit}.tree-section-header{display:flex;align-items:center;gap:.375rem;padding:.375rem .5rem;cursor:pointer;transition:background .15s}.tree-section-header:hover{background:#f6f6f7;border-radius:8px;margin:0 .375rem}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#8c9196;transition:transform .15s;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .15s}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-section-header:hover .icon-drag-wrapper .section-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .section-icon{visibility:hidden}.section-icon{font-size:.875rem;color:#8c9196;display:flex;align-items:center}.section-name{flex:1;font-size:.8125rem;font-weight:500;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.tree-section-header:hover .tree-actions,.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#8c9196;transition:all .15s}.tree-btn:hover{background:#e3e3e3;color:#303030}.tree-btn.delete:hover{background:#fef2f0;color:#d72c0d}.tree-section.drag-over-before>.tree-section-header{box-shadow:inset 0 2px #005bd3}.tree-section.drag-over-after>.tree-section-header{box-shadow:inset 0 -2px #005bd3}.tree-section.is-section-dragging{opacity:.4}.tree-section.is-section-dragging>.tree-section-header{cursor:grabbing}.drag-handle{cursor:grab;color:#8c9196;opacity:0;transition:opacity .15s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:4px}.drag-handle:hover{color:#303030;background:#e3e3e3}.drag-handle:focus-visible{opacity:1;outline:2px solid #005bd3;outline-offset:1px}.tree-section-header:hover .drag-handle{opacity:1}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.tree-section,.drag-handle,.tree-actions{transition:none}}.tree-section-content{padding-left:.5rem}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.375rem .75rem;cursor:pointer;transition:background .15s}.tree-block:hover{background:#f6f6f7;border-radius:8px;margin:0 .375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name{color:inherit}.block-indent{width:18px;height:1px;background:#e3e3e3;margin-left:10px}.block-name{flex:1;font-size:.8125rem;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1.5rem);margin:.25rem .75rem .375rem 2rem;padding:.375rem .5rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#005bd3;transition:all .15s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.8125rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}.add-section-area{padding:.75rem;border-top:1px solid #e3e3e3;position:relative}.add-section-btn{display:flex;align-items:center;justify-content:center;gap:.375rem;width:100%;padding:.625rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;color:#005bd3;transition:all .15s}.add-section-btn:hover{background:#f0f5ff}.section-picker{position:absolute;bottom:100%;left:.75rem;right:.75rem;background:#fff;border:1px solid #e3e3e3;border-radius:12px;box-shadow:0 -4px 12px #0000001a;max-height:300px;overflow-y:auto;z-index:100}.picker-item{display:flex;align-items:center;gap:.75rem;width:100%;padding:.625rem .75rem;border:none;border-bottom:1px solid #f6f6f7;background:#fff;cursor:pointer;text-align:left;font-family:inherit;transition:background .15s}.picker-item:last-child{border-bottom:none}.picker-item:hover{background:#f6f6f7}.picker-icon{font-size:1.25rem;color:#8c9196;display:flex;align-items:center;flex-shrink:0}.picker-info{display:flex;flex-direction:column;gap:.125rem}.picker-name{font-size:.8125rem;font-weight:500;color:#303030}.picker-desc{font-size:.75rem;color:#8c9196}.block-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.block-picker{background:#fff;border-radius:12px;width:340px;max-height:420px;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e3e3e3}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.picker-content{flex:1;overflow-y:auto;padding:.5rem}.picker-content .picker-item{border:1px solid #e3e3e3;border-radius:8px;margin-bottom:.5rem}.picker-content .picker-item:hover{border-color:#005bd3;background:#f8fafe}.picker-category{border-bottom:1px solid #f6f6f7}.picker-category:last-child{border-bottom:none}.picker-category-label{padding:.5rem .75rem .25rem;font-size:.6875rem;font-weight:600;color:#8c9196;text-transform:uppercase;letter-spacing:.3px}.picker-empty{padding:2rem;text-align:center;color:#8c9196;font-size:.8125rem}.canvas-area{display:flex;flex-direction:column;overflow:hidden}.toolbar{display:flex;align-items:center;justify-content:space-between;padding:0 1rem;height:56px;background:#fff;border-bottom:1px solid #e3e3e3}.toolbar-left,.toolbar-right{display:flex;gap:.5rem;align-items:center}.toolbar-center{display:flex;align-items:center}.toolbar-btn{padding:.375rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;font-weight:500;color:#303030;transition:all .15s}.toolbar-btn:hover:not(:disabled){background:#f6f6f7}.toolbar-btn:disabled{opacity:.5;cursor:not-allowed}.preview-btn,.publish-btn{background:#303030;color:#fff;border-color:#303030;border-radius:8px}.preview-btn:hover,.publish-btn:hover:not(:disabled){background:#1a1a1a!important}.back-btn{border-color:transparent;background:transparent;color:#303030}.back-btn:hover:not(:disabled){background:#f6f6f7}.section-count{font-size:.8125rem;color:#616161}.page-title{font-size:.875rem;font-weight:600;color:#303030;margin-right:.75rem}.page-title-link{text-decoration:none;color:inherit}.page-title-link:hover{text-decoration:underline}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.6875rem;font-weight:500;text-transform:capitalize;background:#ffd6a4;color:#b98900}.status-badge.published{background:#aee9d1;color:#0d542b}.dirty-indicator{margin-left:.75rem;font-size:.75rem;color:#b98900;font-weight:500}.canvas{flex:1;overflow-y:auto;padding:2rem;background:#e8e8e8}.canvas.preview-mode{padding:0;background:#fff}.canvas.preview-mode .section-outer{margin:0}.canvas.preview-mode .section-wrapper{border:none;border-radius:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#616161;text-align:center}.empty-icon{font-size:3rem;margin-bottom:1rem;color:#8c9196}.empty-state h3{margin:0 0 .5rem;color:#303030;font-size:1rem}.empty-state p{margin:0;font-size:.8125rem;color:#8c9196}.section-outer{position:relative;margin-bottom:0}.section-wrapper{border:1px solid transparent;border-radius:0;overflow:hidden;transition:border-color .15s;background:#fff}.section-outer:hover .section-wrapper{border-color:#005bd34d}.section-outer.selected .section-wrapper{border-color:#005bd366}.section-controls{position:absolute;top:-22px;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:.1875rem .75rem;background:#005bd3;color:#fff;font-size:.75rem;border-radius:8px 8px 0 0;opacity:0;pointer-events:none;transition:opacity .15s;z-index:10}.section-outer:hover .section-controls,.section-outer.selected .section-controls{opacity:1;pointer-events:auto}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-top-left-radius:0;border-top-right-radius:0}.section-label{font-weight:500}.section-actions{display:flex;gap:.25rem}.action-btn{padding:.1875rem .5rem;border:none;border-radius:4px;background:#ffffff26;color:#fff;cursor:pointer;font-size:.625rem;font-family:inherit;transition:background .15s}.action-btn:hover{background:#ffffff4d}.action-btn.delete:hover{background:#d72c0d}.material-icon{font-family:Material Icons,sans-serif;font-size:inherit;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.properties-panel{background:#fff;border-left:1px solid #e3e3e3;display:flex;flex-direction:column;overflow:hidden}.properties-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e3e3e3}.properties-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.editable-name{cursor:pointer;display:flex;align-items:center;gap:.375rem;border-radius:8px;padding:.25rem .375rem;margin:-.25rem -.375rem;transition:background .15s}.editable-name:hover{background:#f6f6f7}.edit-icon{opacity:0;color:#8c9196;transition:opacity .15s;display:flex;flex-shrink:0}.editable-name:hover .edit-icon{opacity:1}.name-edit-input{flex:1;padding:.375rem .5rem;border:1px solid #005bd3;border-radius:8px;font-size:.9375rem;font-weight:600;font-family:inherit;color:#303030;outline:none;box-shadow:0 0 0 1px #005bd3}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#8c9196;transition:color .15s}.close-btn:hover{color:#303030}.more-menu-btn{border:none;background:none;cursor:pointer;color:#8c9196;padding:.25rem;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:all .15s}.more-menu-btn:hover{background:#f6f6f7;color:#303030}.properties-content{flex:1;overflow-y:auto;padding:1rem}.property-group{margin-bottom:0;padding:1rem 0;border-top:1px solid #e3e3e3}.property-group:first-child{border-top:none}.group-title{display:flex;align-items:center;gap:.375rem;width:100%;padding:0;margin:0 0 .75rem;border:none;background:none;font-size:.8125rem;font-weight:600;font-family:inherit;color:#303030;cursor:pointer}.group-title:hover{color:#1a1a1a}.group-title:focus-visible{outline:2px solid #005bd3;outline-offset:2px;border-radius:4px}.group-title-text{flex:1;text-align:left}.group-toggle-icon{font-size:.5rem;color:#8c9196;transition:transform .15s;display:inline-block}.group-toggle-icon.collapsed{transform:rotate(-90deg)}.property-field{margin-bottom:1rem}.property-label{display:block;font-size:.8125rem;font-weight:500;margin-bottom:.375rem;color:#303030}.property-input,.property-select,.property-textarea{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;color:#303030;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.property-input:focus,.property-select:focus,.property-textarea:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.property-input::placeholder,.property-textarea::placeholder{color:#8c9196}.property-textarea{resize:vertical;font-family:SF Mono,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:.75rem}.color-field{display:flex;gap:.5rem;align-items:center}.color-picker{width:36px;height:36px;padding:2px;border:1px solid #e3e3e3;border-radius:6px;cursor:pointer}.color-text{flex:1}.no-selection{display:flex;align-items:center;justify-content:center;height:100%;color:#8c9196;text-align:center;padding:2rem}.no-selection p{margin:0;font-size:.8125rem}.panel-tabs{display:flex;border-bottom:1px solid #e3e3e3}.panel-tab{flex:1;padding:.75rem;border:none;border-bottom:2px solid transparent;background:#fff;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;color:#616161;transition:all .15s}.panel-tab:hover{background:#f6f6f7;color:#303030}.panel-tab.active{background:#fff;color:#303030;font-weight:600;border-bottom:2px solid #005bd3}.blocks-list{flex:1;overflow-y:auto;padding:1rem}.slot-group{margin-bottom:1.5rem}.slot-title{font-size:.75rem;font-weight:600;color:#8c9196;margin:0 0 .5rem;text-transform:uppercase;letter-spacing:.3px}.block-item{display:flex;align-items:center;gap:.5rem;padding:.5rem .625rem;border:1px solid #e3e3e3;border-radius:8px;margin-bottom:.375rem;cursor:pointer;transition:all .15s}.block-item:hover{border-color:#005bd3;background:#f8fafe}.block-item.selected{border-color:#005bd3;background:#005bd3;color:#fff}.block-item.selected .block-icon,.block-item.selected .block-name{color:inherit}.block-icon{font-size:.875rem;color:#8c9196;display:flex;align-items:center}.block-name{flex:1;font-size:.8125rem;font-weight:500;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.block-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.block-item:hover .block-actions{opacity:1}.mini-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.75rem;color:#8c9196;transition:all .15s}.mini-btn:hover{background:#e3e3e3;color:#303030}.mini-btn.delete:hover{background:#fef2f0;color:#d72c0d}.empty-slot{padding:.75rem;text-align:center;color:#8c9196;font-size:.8125rem;background:#f6f6f7;border:1px dashed #e3e3e3;border-radius:8px}.no-slots{padding:2rem;text-align:center;color:#8c9196}.no-slots p{margin:0;font-size:.8125rem}.delete-block-btn{width:auto;padding:0;margin-top:1rem;border:none;border-radius:0;background:transparent;color:#d72c0d;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;transition:color .15s}.delete-block-btn:hover{background:transparent;color:#bc2200;text-decoration:underline}.toggle-field{display:flex;align-items:center;justify-content:space-between}.toggle-label{font-size:.8125rem;color:#303030}.toggle-switch{position:relative;display:inline-flex;cursor:pointer}.toggle-input{position:absolute;opacity:0;width:0;height:0}.toggle-track{width:36px;height:20px;background:#8c9196;border-radius:10px;position:relative;transition:background .2s}.toggle-thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-input:checked+.toggle-track{background:#008060}.toggle-input:checked+.toggle-track .toggle-thumb{transform:translate(16px)}.toggle-input:focus-visible+.toggle-track{outline:2px solid #005bd3;outline-offset:2px}.toggle-description{display:block;font-size:.75rem;color:#8c9196;margin-top:.25rem}.media-field{display:flex;flex-direction:column;gap:.5rem}.media-preview{position:relative;border:1px solid #e3e3e3;border-radius:8px;overflow:hidden}.media-thumbnail{display:block;width:100%;max-height:160px;object-fit:cover;background:#f6f6f7}.media-remove-btn{position:absolute;top:.375rem;right:.375rem;width:22px;height:22px;border:none;border-radius:50%;background:#0009;color:#fff;cursor:pointer;font-size:.875rem;line-height:1;display:flex;align-items:center;justify-content:center;transition:background .15s}.media-remove-btn:hover{background:#d72c0d}.media-browse-btn{padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#303030;transition:all .15s}.media-browse-btn:hover{background:#f6f6f7;border-color:#8c9196}.media-url-input{font-size:.75rem;color:#616161}.video-preview{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#1a1a1a}.video-indicator{color:#fff;display:flex;align-items:center;flex-shrink:0}.video-url-text{flex:1;color:#8c9196;font-size:.6875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.video-preview .media-remove-btn{position:static;flex-shrink:0}\n"], dependencies: [{ kind: "component", type: DynamicRendererComponent, selector: "lib-dynamic-renderer", inputs: ["element", "context"] }, { kind: "component", type: BlockTreeItemComponent, selector: "lib-block-tree-item", inputs: ["block", "context", "index", "totalSiblings", "expandAll"], outputs: ["selectBlock", "deleteBlock", "duplicateBlock", "moveBlock", "openBlockPicker"] }, { kind: "component", type: ShopifyFilePickerComponent, selector: "lib-shopify-file-picker", inputs: ["mediaType", "currentValue"], outputs: ["fileSelected", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6309
|
+
`, isInline: true, styles: [":host{display:block;height:100vh;overflow:hidden}.editor-layout{display:grid;grid-template-columns:280px 1fr 300px;height:100%;background:#f0f2f5}.sidebar{background:#fff;border-right:1px solid #e0e0e0;display:flex;flex-direction:column}.sidebar-header{padding:1rem;border-bottom:1px solid #e0e0e0}.sidebar-header h2{margin:0;font-size:.875rem;font-weight:600;color:#333}.search-box{display:none}.search-box:focus-within{border-color:#005bd3;background:#fff}.search-icon{font-size:.875rem;opacity:.6}.search-input{flex:1;border:none;background:transparent;font-size:.8125rem;color:#333;outline:none}.search-input::placeholder{color:#999}.search-clear{padding:.125rem .375rem;border:none;background:#ddd;border-radius:3px;cursor:pointer;font-size:.75rem;color:#666;line-height:1}.search-clear:hover{background:#ccc}.search-no-results{padding:2rem 1rem;text-align:center;color:#888}.search-no-results p{margin:0;font-size:.875rem}.picker-search{padding:.5rem;border-bottom:1px solid #e0e0e0}.picker-search-input{width:87%;padding:.5rem .75rem;border:1px solid #e0e0e0;border-radius:4px;font-size:.8125rem;outline:none;transition:border-color .2s}.picker-search-input:focus{border-color:#005bd3}.picker-search-input::placeholder{color:#999}.section-tree{flex:1;overflow-y:auto;padding:.25rem 0}.tree-section{border-bottom:none}.tree-section.selected>.tree-section-header{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-section.selected>.tree-section-header .section-name,.tree-section.selected>.tree-section-header .expand-btn,.tree-section.selected>.tree-section-header .section-icon,.tree-section.selected>.tree-section-header .drag-handle,.tree-section.selected>.tree-section-header .tree-btn{color:inherit}.tree-section-header{display:flex;align-items:center;gap:.5rem;padding:.5rem;cursor:pointer;transition:background .2s}.tree-section-header:hover{background:#f5f5f5;border-radius:8px;margin:0 .375rem}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;transition:transform .2s;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.625rem;transition:transform .2s}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-section-header:hover .icon-drag-wrapper .section-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .section-icon{visibility:hidden}.section-icon{font-size:1rem}.section-name{flex:1;font-size:.8125rem;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.tree-section-header:hover .tree-actions,.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.6875rem;color:#666}.tree-btn:hover{background:#d0d0d0}.tree-btn.delete:hover{background:#e74c3c;color:#fff}.tree-section.drag-over-before>.tree-section-header{box-shadow:inset 0 2px #4a90d9}.tree-section.drag-over-after>.tree-section-header{box-shadow:inset 0 -2px #4a90d9}.tree-section.is-section-dragging{opacity:.4}.tree-section.is-section-dragging>.tree-section-header{cursor:grabbing}.drag-handle{cursor:grab;color:#999;opacity:0;transition:opacity .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:2px}.drag-handle:hover{color:#666;background:#e0e0e0}.drag-handle:focus-visible{opacity:1;outline:2px solid #4a90d9;outline-offset:1px}.tree-section-header:hover .drag-handle{opacity:1}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.tree-section,.drag-handle,.tree-actions{transition:none}}.tree-section-content{padding-left:.5rem}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.5rem .75rem;cursor:pointer;transition:background .2s}.tree-block:hover{background:#f5f5f5;border-radius:8px;margin:0 .375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name{color:inherit}.block-indent{width:20px;height:1px;background:#ddd;margin-left:10px}.block-icon{font-size:.875rem}.block-name{flex:1;font-size:.75rem;color:#555;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1.5rem);margin:.375rem .75rem .5rem 2rem;padding:.375rem .5rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.75rem;color:#005bd3;transition:all .2s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.875rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}.add-section-area{padding:.75rem;border-top:1px solid #e0e0e0;position:relative}.add-section-btn{display:flex;align-items:center;justify-content:center;gap:.375rem;width:100%;padding:.625rem;border:none;border-radius:6px;background:transparent;cursor:pointer;font-size:.8125rem;font-weight:500;color:#005bd3;transition:all .2s}.add-section-btn:hover{background:#f0f5ff}.section-picker{position:absolute;bottom:100%;left:.75rem;right:.75rem;background:#fff;border:1px solid #e0e0e0;border-radius:6px;box-shadow:0 -4px 12px #0000001a;max-height:300px;overflow-y:auto;z-index:100}.picker-item{display:flex;align-items:center;gap:.75rem;width:100%;padding:.75rem;border:none;border-bottom:1px solid #f0f0f0;background:#fff;cursor:pointer;text-align:left;transition:background .2s}.picker-item:last-child{border-bottom:none}.picker-item:hover{background:#f5f5f5}.picker-icon{font-family:Material Icons,sans-serif;font-size:1.5rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.picker-info{display:flex;flex-direction:column;gap:.125rem}.picker-name{font-size:.8125rem;font-weight:500;color:#333}.picker-desc{font-size:.6875rem;color:#888}.block-picker-overlay{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.block-picker{background:#fff;border-radius:8px;width:320px;max-height:400px;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600}.picker-content{flex:1;overflow-y:auto;padding:.5rem}.picker-content .picker-item{border:1px solid #e0e0e0;border-radius:6px;margin-bottom:.5rem}.picker-content .picker-item:hover{border-color:#005bd3}.picker-category{border-bottom:1px solid #f0f0f0}.picker-category:last-child{border-bottom:none}.picker-category-label{padding:.5rem .75rem .25rem;font-size:.6875rem;font-weight:600;color:#888}.picker-empty{padding:2rem;text-align:center;color:#888;font-size:.875rem}.canvas-area{display:flex;flex-direction:column;overflow:hidden}.toolbar{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem;background:#fff;border-bottom:1px solid #e0e0e0}.toolbar-left,.toolbar-right{display:flex;gap:.5rem}.toolbar-btn{padding:.375rem .75rem;border:1px solid #e0e0e0;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;transition:all .2s}.toolbar-btn:hover:not(:disabled){background:#f0f2f5}.toolbar-btn:disabled{opacity:.5;cursor:not-allowed}.preview-btn,.publish-btn{background:#303030;color:#fff;border-color:#303030;border-radius:8px}.preview-btn:hover,.publish-btn:hover:not(:disabled){background:#1a1a1a!important}.back-btn{border-color:transparent;background:transparent}.section-count{font-size:.875rem;color:#666}.page-title{font-size:.9375rem;font-weight:600;color:#333;margin-right:.75rem}.page-title-link{text-decoration:none;color:inherit}.page-title-link:hover{text-decoration:underline}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.6875rem;font-weight:500;text-transform:capitalize;background:#ffeaa7;color:#856404}.status-badge.published{background:#b4fed3;color:#0d542b}.dirty-indicator{margin-left:.75rem;font-size:.8125rem;color:#e67e22;font-weight:500}.canvas{flex:1;overflow-y:auto;padding:2rem;position:relative}.canvas.preview-mode{padding:0;background:#fff}.preview-iframe{width:100%;height:100%;border:none;position:absolute;inset:0}.canvas.preview-mode .section-outer{margin:0}.canvas.preview-mode .section-wrapper{border:none;border-radius:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#666;text-align:center}.empty-icon{font-size:4rem;margin-bottom:1rem}.empty-state h3{margin:0 0 .5rem;color:#333}.empty-state p{margin:0;font-size:.875rem}.section-outer{position:relative;margin-bottom:0}.section-wrapper{border:1px solid transparent;border-radius:0;overflow:hidden;transition:border-color .2s;background:#fff}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-color:#005bd366}.section-controls{position:absolute;top:-20px;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:.14rem .75rem;background:#005bd3;color:#fff;font-size:.75rem;border-radius:8px 8px 0 0;opacity:0;pointer-events:none;transition:opacity .2s;z-index:10}.section-outer:hover .section-controls,.section-outer.selected .section-controls{opacity:1;pointer-events:auto}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-top-left-radius:0;border-top-right-radius:0}.section-label{font-weight:500}.section-actions{display:flex;gap:.25rem}.action-btn{padding:.15rem .5rem;border:none;border-radius:4px;background:#ffffff26;color:#fff;cursor:pointer;font-size:.6rem}.action-btn:hover{background:#ffffff4d}.action-btn.delete:hover{background:#e74c3c}.material-icon{font-family:Material Icons,sans-serif;font-size:.75rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.properties-panel{background:#fff;border-left:1px solid #e0e0e0;display:flex;flex-direction:column;overflow:hidden}.properties-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0}.properties-header h3{margin:0;font-size:1rem;font-weight:600}.editable-name{cursor:pointer;display:flex;align-items:center;gap:.375rem;border-radius:4px;padding:.125rem .25rem;margin:-.125rem -.25rem;transition:background .2s}.editable-name:hover{background:#f0f2f5}.edit-icon{opacity:0;color:#999;transition:opacity .2s;display:flex;flex-shrink:0}.editable-name:hover .edit-icon{opacity:1}.name-edit-input{flex:1;padding:.375rem .5rem;border:1px solid #005bd3;border-radius:8px;font-size:1rem;font-weight:600;outline:none}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#666}.more-menu-btn{border:none;background:none;cursor:pointer;color:#616161;padding:.25rem;border-radius:6px;display:flex;align-items:center;justify-content:center;transition:background .2s}.more-menu-btn:hover{background:#f0f2f5;color:#303030}.properties-content{flex:1;overflow-y:auto;padding:1rem}.property-group{margin-bottom:0;padding:1rem 0;border-top:1px solid #e0e0e0}.property-group:first-child{border-top:none}.group-title{display:flex;align-items:center;gap:.375rem;width:100%;padding:0;margin:0 0 .75rem;border:none;background:none;font-size:.8125rem;font-weight:600;color:#303030;cursor:pointer}.group-title:hover{color:#333}.group-title:focus-visible{outline:2px solid #4a90d9;outline-offset:2px;border-radius:2px}.group-title-text{flex:1;text-align:left}.group-toggle-icon{font-size:.5rem;transition:transform .2s;display:inline-block}.group-toggle-icon.collapsed{transform:rotate(-90deg)}.property-field{margin-bottom:1rem}.property-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem;color:#616161}.property-input,.property-select,.property-textarea{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.875rem;transition:border-color .2s,box-shadow .2s}.property-input:focus,.property-select:focus,.property-textarea:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.property-textarea{resize:vertical;font-family:monospace;font-size:.75rem}.color-field{display:flex;gap:.5rem}.color-picker{width:40px;height:36px;padding:2px;border:1px solid #e0e0e0;border-radius:4px;cursor:pointer}.color-text{flex:1}.no-selection{display:flex;align-items:center;justify-content:center;height:100%;color:#666;text-align:center;padding:2rem}.no-selection p{margin:0;font-size:.875rem}.panel-tabs{display:flex;border-bottom:1px solid #e0e0e0}.panel-tab{flex:1;padding:.75rem;border:none;border-bottom:2px solid transparent;background:#fff;cursor:pointer;font-size:.75rem;font-weight:500;color:#666;transition:all .2s}.panel-tab:hover{background:#f8f9fa;color:#303030}.panel-tab.active{background:#fff;color:#303030;border-bottom:2px solid #005bd3}.blocks-list{flex:1;overflow-y:auto;padding:1rem}.slot-group{margin-bottom:1.5rem}.slot-title{font-size:.75rem;font-weight:600;color:#616161;margin:0 0 .5rem}.block-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;border:1px solid #e0e0e0;border-radius:4px;margin-bottom:.375rem;cursor:pointer;transition:all .2s}.block-item:hover{border-color:#005bd3;background:#f8f9fa}.block-item.selected{border-color:#005bd3;background:#005bd3;color:#fff}.block-item.selected .block-icon,.block-item.selected .block-name{color:inherit}.block-icon{font-size:1rem}.block-name{flex:1;font-size:.8rem;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.block-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.block-item:hover .block-actions{opacity:1}.mini-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.75rem;color:#666}.mini-btn:hover{background:#d0d0d0}.mini-btn.delete:hover{background:#e74c3c;color:#fff}.empty-slot{padding:.75rem;text-align:center;color:#999;font-size:.75rem;background:#f8f9fa;border:1px dashed #ddd;border-radius:4px}.no-slots{padding:2rem;text-align:center;color:#666}.no-slots p{margin:0;font-size:.875rem}.delete-block-btn{width:auto;padding:0;margin-top:1rem;border:none;border-radius:0;background:transparent;color:#d72c0d;cursor:pointer;font-size:.875rem;font-weight:500;transition:color .2s}.delete-block-btn:hover{background:transparent;color:#bc2200;text-decoration:underline}.toggle-field{display:flex;align-items:center;justify-content:space-between}.toggle-label{font-size:.875rem;color:#303030}.toggle-switch{position:relative;display:inline-flex;cursor:pointer}.toggle-input{position:absolute;opacity:0;width:0;height:0}.toggle-track{width:36px;height:20px;background:#b5b5b5;border-radius:10px;position:relative;transition:background .2s}.toggle-thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-input:checked+.toggle-track{background:#008060}.toggle-input:checked+.toggle-track .toggle-thumb{transform:translate(16px)}.toggle-input:focus-visible+.toggle-track{outline:2px solid #005bd3;outline-offset:2px}.toggle-description{display:block;font-size:.75rem;color:#616161;margin-top:.25rem}.media-field{display:flex;flex-direction:column;gap:.5rem}.media-preview{position:relative;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}.media-thumbnail{display:block;width:100%;max-height:160px;object-fit:cover;background:#f0f2f5}.media-remove-btn{position:absolute;top:.25rem;right:.25rem;width:22px;height:22px;border:none;border-radius:50%;background:#0009;color:#fff;cursor:pointer;font-size:.875rem;line-height:1;display:flex;align-items:center;justify-content:center}.media-remove-btn:hover{background:#d72c0d}.media-browse-btn{padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;color:#303030;transition:all .15s}.media-browse-btn:hover{background:#f0f2f5;border-color:#999}.media-url-input{font-size:.75rem;color:#616161}.video-preview{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#1a1a1a}.video-indicator{color:#fff;display:flex;align-items:center;flex-shrink:0}.video-url-text{flex:1;color:#ccc;font-size:.6875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.video-preview .media-remove-btn{position:static;flex-shrink:0}\n"], dependencies: [{ kind: "component", type: DynamicRendererComponent, selector: "lib-dynamic-renderer", inputs: ["element", "context"] }, { kind: "component", type: BlockTreeItemComponent, selector: "lib-block-tree-item", inputs: ["block", "context", "index", "totalSiblings", "expandAll"], outputs: ["selectBlock", "deleteBlock", "duplicateBlock", "moveBlock", "openBlockPicker"] }, { kind: "component", type: ShopifyFilePickerComponent, selector: "lib-shopify-file-picker", inputs: ["mediaType", "currentValue"], outputs: ["fileSelected", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6584
6310
|
}
|
|
6585
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
6311
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditorComponent, decorators: [{
|
|
6586
6312
|
type: Component,
|
|
6587
|
-
args: [{ selector: 'lib-visual-editor', imports: [DynamicRendererComponent, BlockTreeItemComponent, ShopifyFilePickerComponent], template: `
|
|
6313
|
+
args: [{ selector: 'lib-visual-editor', imports: [DynamicRendererComponent, BlockTreeItemComponent, ShopifyFilePickerComponent], providers: [IframeBridgeService], template: `
|
|
6588
6314
|
<div class="editor-layout">
|
|
6589
6315
|
<!-- Sidebar: Hierarchical Tree -->
|
|
6590
6316
|
<aside class="sidebar">
|
|
6591
6317
|
<div class="sidebar-header">
|
|
6592
6318
|
<h2>Page Structure</h2>
|
|
6593
6319
|
<div class="search-box">
|
|
6594
|
-
<span class="search-icon"
|
|
6320
|
+
<span class="search-icon">🔍</span>
|
|
6595
6321
|
<input
|
|
6596
6322
|
type="text"
|
|
6597
6323
|
class="search-input"
|
|
@@ -6639,7 +6365,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6639
6365
|
<span class="expand-icon">▶</span>
|
|
6640
6366
|
</button>
|
|
6641
6367
|
<span class="icon-drag-wrapper">
|
|
6642
|
-
<span class="section-icon"
|
|
6368
|
+
<span class="section-icon">{{ getSectionIcon(section.type) }}</span>
|
|
6643
6369
|
<span
|
|
6644
6370
|
class="drag-handle section-drag-handle"
|
|
6645
6371
|
role="button"
|
|
@@ -6741,7 +6467,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6741
6467
|
}
|
|
6742
6468
|
@for (rp of group.presets; track rp.displayName + '-' + rp.definition.type) {
|
|
6743
6469
|
<button class="picker-item" (click)="addSectionFromPreset(rp)">
|
|
6744
|
-
<span class="picker-icon"
|
|
6470
|
+
<span class="picker-icon">{{ getSectionPresetIcon(rp) }}</span>
|
|
6745
6471
|
<div class="picker-info">
|
|
6746
6472
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
6747
6473
|
@if (rp.displayDescription) {
|
|
@@ -6779,7 +6505,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6779
6505
|
<div class="picker-content">
|
|
6780
6506
|
@for (rp of filteredBlockPresetsForSection(); track rp.displayName + '-' + rp.definition.type) {
|
|
6781
6507
|
<button class="picker-item" (click)="addBlockToSectionFromPreset(blockPickerSection()!, rp)">
|
|
6782
|
-
<span class="picker-icon"
|
|
6508
|
+
<span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
|
|
6783
6509
|
<div class="picker-info">
|
|
6784
6510
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
6785
6511
|
@if (rp.displayDescription) {
|
|
@@ -6822,7 +6548,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6822
6548
|
<div class="picker-content">
|
|
6823
6549
|
@for (rp of filteredBlockPresetsForNestedSlot(); track rp.displayName + '-' + rp.definition.type) {
|
|
6824
6550
|
<button class="picker-item" (click)="addNestedBlockFromPreset(rp)">
|
|
6825
|
-
<span class="picker-icon"
|
|
6551
|
+
<span class="picker-icon">{{ getBlockPresetIcon(rp) }}</span>
|
|
6826
6552
|
<div class="picker-info">
|
|
6827
6553
|
<span class="picker-name">{{ rp.displayName }}</span>
|
|
6828
6554
|
@if (rp.displayDescription) {
|
|
@@ -6852,7 +6578,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6852
6578
|
<div class="toolbar">
|
|
6853
6579
|
<div class="toolbar-left">
|
|
6854
6580
|
<button class="toolbar-btn back-btn" (click)="goBack()" title="Back to pages">
|
|
6855
|
-
|
|
6581
|
+
← Back
|
|
6856
6582
|
</button>
|
|
6857
6583
|
<button
|
|
6858
6584
|
class="toolbar-btn"
|
|
@@ -6860,7 +6586,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6860
6586
|
(click)="facade.undo()"
|
|
6861
6587
|
title="Undo"
|
|
6862
6588
|
>
|
|
6863
|
-
|
|
6589
|
+
↩ Undo
|
|
6864
6590
|
</button>
|
|
6865
6591
|
<button
|
|
6866
6592
|
class="toolbar-btn"
|
|
@@ -6868,7 +6594,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6868
6594
|
(click)="facade.redo()"
|
|
6869
6595
|
title="Redo"
|
|
6870
6596
|
>
|
|
6871
|
-
|
|
6597
|
+
↪ Redo
|
|
6872
6598
|
</button>
|
|
6873
6599
|
</div>
|
|
6874
6600
|
<div class="toolbar-center">
|
|
@@ -6901,7 +6627,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6901
6627
|
(click)="savePage()"
|
|
6902
6628
|
title="Save changes"
|
|
6903
6629
|
>
|
|
6904
|
-
{{ isSaving() ? 'Saving...' : 'Save' }}
|
|
6630
|
+
{{ isSaving() ? 'Saving...' : '💾 Save' }}
|
|
6905
6631
|
</button>
|
|
6906
6632
|
}
|
|
6907
6633
|
@if (facade.currentPage() && showPublishButtons()) {
|
|
@@ -6912,7 +6638,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6912
6638
|
(click)="publishPage()"
|
|
6913
6639
|
title="Publish page"
|
|
6914
6640
|
>
|
|
6915
|
-
{{ isPublishing() ? 'Publishing...' : 'Publish' }}
|
|
6641
|
+
{{ isPublishing() ? 'Publishing...' : '🚀 Publish' }}
|
|
6916
6642
|
</button>
|
|
6917
6643
|
} @else {
|
|
6918
6644
|
<button
|
|
@@ -6921,57 +6647,68 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
6921
6647
|
(click)="unpublishPage()"
|
|
6922
6648
|
title="Unpublish page"
|
|
6923
6649
|
>
|
|
6924
|
-
{{ isPublishing() ? 'Unpublishing...' : 'Unpublish' }}
|
|
6650
|
+
{{ isPublishing() ? 'Unpublishing...' : '📥 Unpublish' }}
|
|
6925
6651
|
</button>
|
|
6926
6652
|
}
|
|
6927
6653
|
}
|
|
6928
6654
|
<button class="toolbar-btn preview-btn" (click)="togglePreview()">
|
|
6929
|
-
{{ isPreviewMode() ? 'Edit' : 'Preview' }}
|
|
6655
|
+
{{ isPreviewMode() ? '✏️ Edit' : '👁 Preview' }}
|
|
6930
6656
|
</button>
|
|
6931
6657
|
</div>
|
|
6932
6658
|
</div>
|
|
6933
6659
|
|
|
6934
6660
|
<!-- Canvas Content -->
|
|
6935
6661
|
<div class="canvas" #canvasEl [class.preview-mode]="isPreviewMode()">
|
|
6936
|
-
@if (
|
|
6937
|
-
<
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6662
|
+
@if (previewUrl()) {
|
|
6663
|
+
<iframe
|
|
6664
|
+
#previewFrame
|
|
6665
|
+
class="preview-iframe"
|
|
6666
|
+
[src]="previewUrl()!"
|
|
6667
|
+
(load)="onIframeLoad()"
|
|
6668
|
+
allow="clipboard-read; clipboard-write"
|
|
6669
|
+
></iframe>
|
|
6942
6670
|
} @else {
|
|
6943
|
-
|
|
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
|
-
}
|
|
6671
|
+
<!-- Fallback: direct rendering when no storefront URL configured -->
|
|
6672
|
+
@if (facade.sections().length === 0) {
|
|
6673
|
+
<div class="empty-state">
|
|
6674
|
+
<div class="empty-icon">📄</div>
|
|
6675
|
+
<h3>Start Building</h3>
|
|
6676
|
+
<p>Click on a component from the sidebar to add it to your page</p>
|
|
6677
|
+
</div>
|
|
6678
|
+
} @else {
|
|
6679
|
+
@for (section of facade.sections(); track section.id; let idx = $index) {
|
|
6967
6680
|
<div
|
|
6968
|
-
class="section-
|
|
6969
|
-
[
|
|
6970
|
-
(click)="selectSection(section, $event)"
|
|
6681
|
+
class="section-outer"
|
|
6682
|
+
[class.selected]="facade.selectedSection()?.id === section.id"
|
|
6971
6683
|
>
|
|
6972
|
-
|
|
6684
|
+
@if (!isPreviewMode()) {
|
|
6685
|
+
<div class="section-controls">
|
|
6686
|
+
<span class="section-label">{{ getSectionName(section) }}</span>
|
|
6687
|
+
<div class="section-actions">
|
|
6688
|
+
@if (idx > 0) {
|
|
6689
|
+
<button class="action-btn" (click)="moveUp(section.id, idx, $event)" title="Move up">↑</button>
|
|
6690
|
+
}
|
|
6691
|
+
@if (idx < facade.sections().length - 1) {
|
|
6692
|
+
<button class="action-btn" (click)="moveDown(section.id, idx, $event)" title="Move down">↓</button>
|
|
6693
|
+
}
|
|
6694
|
+
@if (facade.isSectionDuplicable(section.type)) {
|
|
6695
|
+
<button class="action-btn" (click)="duplicateSection(section.id, $event)" title="Duplicate">
|
|
6696
|
+
<span class="material-icon">content_copy</span>
|
|
6697
|
+
</button>
|
|
6698
|
+
}
|
|
6699
|
+
<button class="action-btn delete" (click)="deleteSection(section.id, $event)" title="Delete">×</button>
|
|
6700
|
+
</div>
|
|
6701
|
+
</div>
|
|
6702
|
+
}
|
|
6703
|
+
<div
|
|
6704
|
+
class="section-wrapper"
|
|
6705
|
+
[attr.data-section-id]="section.id"
|
|
6706
|
+
(click)="selectSection(section, $event)"
|
|
6707
|
+
>
|
|
6708
|
+
<lib-dynamic-renderer [element]="section" [context]="editorContext()" />
|
|
6709
|
+
</div>
|
|
6973
6710
|
</div>
|
|
6974
|
-
|
|
6711
|
+
}
|
|
6975
6712
|
}
|
|
6976
6713
|
}
|
|
6977
6714
|
</div>
|
|
@@ -7361,7 +7098,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7361
7098
|
[class.selected]="facade.selectedBlock()?.id === block.id"
|
|
7362
7099
|
(click)="selectBlock(block)"
|
|
7363
7100
|
>
|
|
7364
|
-
<span class="block-icon"
|
|
7101
|
+
<span class="block-icon">{{ getBlockIconByType(block.type) }}</span>
|
|
7365
7102
|
<span class="block-name">{{ getBlockName(block) }}</span>
|
|
7366
7103
|
<div class="block-actions">
|
|
7367
7104
|
@if (idx > 0) {
|
|
@@ -7408,8 +7145,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7408
7145
|
/>
|
|
7409
7146
|
}
|
|
7410
7147
|
</div>
|
|
7411
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;height:calc(100vh - 56px);overflow:hidden;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.editor-layout{display:grid;grid-template-columns:280px 1fr 300px;height:100%;background:#f6f6f7}.sidebar{background:#fff;border-right:1px solid #e3e3e3;display:flex;flex-direction:column}.sidebar-header{padding:1rem;border-bottom:1px solid #e3e3e3}.sidebar-header h2{margin:0;font-size:.8125rem;font-weight:600;color:#303030}.search-box{display:none}.search-box:focus-within{border-color:#005bd3;background:#fff}.search-icon{font-size:.8125rem;opacity:.6}.search-input{flex:1;border:none;background:transparent;font-size:.8125rem;color:#303030;font-family:inherit;outline:none}.search-input::placeholder{color:#8c9196}.search-clear{padding:.125rem .375rem;border:none;background:#e3e3e3;border-radius:4px;cursor:pointer;font-size:.75rem;color:#616161;line-height:1}.search-clear:hover{background:#c9cccf}.search-no-results{padding:2rem 1rem;text-align:center;color:#8c9196}.search-no-results p{margin:0;font-size:.8125rem}.picker-search{padding:.5rem .75rem;border-bottom:1px solid #e3e3e3}.picker-search-input{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;outline:none;box-sizing:border-box;transition:border-color .2s,box-shadow .2s}.picker-search-input:focus{border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.picker-search-input::placeholder{color:#8c9196}.section-tree{flex:1;overflow-y:auto;padding:.25rem 0}.tree-section{border-bottom:none}.tree-section.selected>.tree-section-header{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-section.selected>.tree-section-header .section-name,.tree-section.selected>.tree-section-header .expand-btn,.tree-section.selected>.tree-section-header .section-icon,.tree-section.selected>.tree-section-header .drag-handle,.tree-section.selected>.tree-section-header .tree-btn{color:inherit}.tree-section-header{display:flex;align-items:center;gap:.375rem;padding:.375rem .5rem;cursor:pointer;transition:background .15s}.tree-section-header:hover{background:#f6f6f7;border-radius:8px;margin:0 .375rem}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#8c9196;transition:transform .15s;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.5rem;transition:transform .15s}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-section-header:hover .icon-drag-wrapper .section-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .section-icon{visibility:hidden}.section-icon{font-size:.875rem;color:#8c9196;display:flex;align-items:center}.section-name{flex:1;font-size:.8125rem;font-weight:500;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.tree-section-header:hover .tree-actions,.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.6875rem;color:#8c9196;transition:all .15s}.tree-btn:hover{background:#e3e3e3;color:#303030}.tree-btn.delete:hover{background:#fef2f0;color:#d72c0d}.tree-section.drag-over-before>.tree-section-header{box-shadow:inset 0 2px #005bd3}.tree-section.drag-over-after>.tree-section-header{box-shadow:inset 0 -2px #005bd3}.tree-section.is-section-dragging{opacity:.4}.tree-section.is-section-dragging>.tree-section-header{cursor:grabbing}.drag-handle{cursor:grab;color:#8c9196;opacity:0;transition:opacity .15s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:4px}.drag-handle:hover{color:#303030;background:#e3e3e3}.drag-handle:focus-visible{opacity:1;outline:2px solid #005bd3;outline-offset:1px}.tree-section-header:hover .drag-handle{opacity:1}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.tree-section,.drag-handle,.tree-actions{transition:none}}.tree-section-content{padding-left:.5rem}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.375rem .75rem;cursor:pointer;transition:background .15s}.tree-block:hover{background:#f6f6f7;border-radius:8px;margin:0 .375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name{color:inherit}.block-indent{width:18px;height:1px;background:#e3e3e3;margin-left:10px}.block-name{flex:1;font-size:.8125rem;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1.5rem);margin:.25rem .75rem .375rem 2rem;padding:.375rem .5rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#005bd3;transition:all .15s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.8125rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}.add-section-area{padding:.75rem;border-top:1px solid #e3e3e3;position:relative}.add-section-btn{display:flex;align-items:center;justify-content:center;gap:.375rem;width:100%;padding:.625rem;border:none;border-radius:8px;background:transparent;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;color:#005bd3;transition:all .15s}.add-section-btn:hover{background:#f0f5ff}.section-picker{position:absolute;bottom:100%;left:.75rem;right:.75rem;background:#fff;border:1px solid #e3e3e3;border-radius:12px;box-shadow:0 -4px 12px #0000001a;max-height:300px;overflow-y:auto;z-index:100}.picker-item{display:flex;align-items:center;gap:.75rem;width:100%;padding:.625rem .75rem;border:none;border-bottom:1px solid #f6f6f7;background:#fff;cursor:pointer;text-align:left;font-family:inherit;transition:background .15s}.picker-item:last-child{border-bottom:none}.picker-item:hover{background:#f6f6f7}.picker-icon{font-size:1.25rem;color:#8c9196;display:flex;align-items:center;flex-shrink:0}.picker-info{display:flex;flex-direction:column;gap:.125rem}.picker-name{font-size:.8125rem;font-weight:500;color:#303030}.picker-desc{font-size:.75rem;color:#8c9196}.block-picker-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.block-picker{background:#fff;border-radius:12px;width:340px;max-height:420px;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem 1.25rem;border-bottom:1px solid #e3e3e3}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.picker-content{flex:1;overflow-y:auto;padding:.5rem}.picker-content .picker-item{border:1px solid #e3e3e3;border-radius:8px;margin-bottom:.5rem}.picker-content .picker-item:hover{border-color:#005bd3;background:#f8fafe}.picker-category{border-bottom:1px solid #f6f6f7}.picker-category:last-child{border-bottom:none}.picker-category-label{padding:.5rem .75rem .25rem;font-size:.6875rem;font-weight:600;color:#8c9196;text-transform:uppercase;letter-spacing:.3px}.picker-empty{padding:2rem;text-align:center;color:#8c9196;font-size:.8125rem}.canvas-area{display:flex;flex-direction:column;overflow:hidden}.toolbar{display:flex;align-items:center;justify-content:space-between;padding:0 1rem;height:56px;background:#fff;border-bottom:1px solid #e3e3e3}.toolbar-left,.toolbar-right{display:flex;gap:.5rem;align-items:center}.toolbar-center{display:flex;align-items:center}.toolbar-btn{padding:.375rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;font-weight:500;color:#303030;transition:all .15s}.toolbar-btn:hover:not(:disabled){background:#f6f6f7}.toolbar-btn:disabled{opacity:.5;cursor:not-allowed}.preview-btn,.publish-btn{background:#303030;color:#fff;border-color:#303030;border-radius:8px}.preview-btn:hover,.publish-btn:hover:not(:disabled){background:#1a1a1a!important}.back-btn{border-color:transparent;background:transparent;color:#303030}.back-btn:hover:not(:disabled){background:#f6f6f7}.section-count{font-size:.8125rem;color:#616161}.page-title{font-size:.875rem;font-weight:600;color:#303030;margin-right:.75rem}.page-title-link{text-decoration:none;color:inherit}.page-title-link:hover{text-decoration:underline}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.6875rem;font-weight:500;text-transform:capitalize;background:#ffd6a4;color:#b98900}.status-badge.published{background:#aee9d1;color:#0d542b}.dirty-indicator{margin-left:.75rem;font-size:.75rem;color:#b98900;font-weight:500}.canvas{flex:1;overflow-y:auto;padding:2rem;background:#e8e8e8}.canvas.preview-mode{padding:0;background:#fff}.canvas.preview-mode .section-outer{margin:0}.canvas.preview-mode .section-wrapper{border:none;border-radius:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#616161;text-align:center}.empty-icon{font-size:3rem;margin-bottom:1rem;color:#8c9196}.empty-state h3{margin:0 0 .5rem;color:#303030;font-size:1rem}.empty-state p{margin:0;font-size:.8125rem;color:#8c9196}.section-outer{position:relative;margin-bottom:0}.section-wrapper{border:1px solid transparent;border-radius:0;overflow:hidden;transition:border-color .15s;background:#fff}.section-outer:hover .section-wrapper{border-color:#005bd34d}.section-outer.selected .section-wrapper{border-color:#005bd366}.section-controls{position:absolute;top:-22px;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:.1875rem .75rem;background:#005bd3;color:#fff;font-size:.75rem;border-radius:8px 8px 0 0;opacity:0;pointer-events:none;transition:opacity .15s;z-index:10}.section-outer:hover .section-controls,.section-outer.selected .section-controls{opacity:1;pointer-events:auto}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-top-left-radius:0;border-top-right-radius:0}.section-label{font-weight:500}.section-actions{display:flex;gap:.25rem}.action-btn{padding:.1875rem .5rem;border:none;border-radius:4px;background:#ffffff26;color:#fff;cursor:pointer;font-size:.625rem;font-family:inherit;transition:background .15s}.action-btn:hover{background:#ffffff4d}.action-btn.delete:hover{background:#d72c0d}.material-icon{font-family:Material Icons,sans-serif;font-size:inherit;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.properties-panel{background:#fff;border-left:1px solid #e3e3e3;display:flex;flex-direction:column;overflow:hidden}.properties-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e3e3e3}.properties-header h3{margin:0;font-size:.9375rem;font-weight:600;color:#303030}.editable-name{cursor:pointer;display:flex;align-items:center;gap:.375rem;border-radius:8px;padding:.25rem .375rem;margin:-.25rem -.375rem;transition:background .15s}.editable-name:hover{background:#f6f6f7}.edit-icon{opacity:0;color:#8c9196;transition:opacity .15s;display:flex;flex-shrink:0}.editable-name:hover .edit-icon{opacity:1}.name-edit-input{flex:1;padding:.375rem .5rem;border:1px solid #005bd3;border-radius:8px;font-size:.9375rem;font-weight:600;font-family:inherit;color:#303030;outline:none;box-shadow:0 0 0 1px #005bd3}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#8c9196;transition:color .15s}.close-btn:hover{color:#303030}.more-menu-btn{border:none;background:none;cursor:pointer;color:#8c9196;padding:.25rem;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:all .15s}.more-menu-btn:hover{background:#f6f6f7;color:#303030}.properties-content{flex:1;overflow-y:auto;padding:1rem}.property-group{margin-bottom:0;padding:1rem 0;border-top:1px solid #e3e3e3}.property-group:first-child{border-top:none}.group-title{display:flex;align-items:center;gap:.375rem;width:100%;padding:0;margin:0 0 .75rem;border:none;background:none;font-size:.8125rem;font-weight:600;font-family:inherit;color:#303030;cursor:pointer}.group-title:hover{color:#1a1a1a}.group-title:focus-visible{outline:2px solid #005bd3;outline-offset:2px;border-radius:4px}.group-title-text{flex:1;text-align:left}.group-toggle-icon{font-size:.5rem;color:#8c9196;transition:transform .15s;display:inline-block}.group-toggle-icon.collapsed{transform:rotate(-90deg)}.property-field{margin-bottom:1rem}.property-label{display:block;font-size:.8125rem;font-weight:500;margin-bottom:.375rem;color:#303030}.property-input,.property-select,.property-textarea{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.8125rem;font-family:inherit;color:#303030;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.property-input:focus,.property-select:focus,.property-textarea:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.property-input::placeholder,.property-textarea::placeholder{color:#8c9196}.property-textarea{resize:vertical;font-family:SF Mono,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:.75rem}.color-field{display:flex;gap:.5rem;align-items:center}.color-picker{width:36px;height:36px;padding:2px;border:1px solid #e3e3e3;border-radius:6px;cursor:pointer}.color-text{flex:1}.no-selection{display:flex;align-items:center;justify-content:center;height:100%;color:#8c9196;text-align:center;padding:2rem}.no-selection p{margin:0;font-size:.8125rem}.panel-tabs{display:flex;border-bottom:1px solid #e3e3e3}.panel-tab{flex:1;padding:.75rem;border:none;border-bottom:2px solid transparent;background:#fff;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;color:#616161;transition:all .15s}.panel-tab:hover{background:#f6f6f7;color:#303030}.panel-tab.active{background:#fff;color:#303030;font-weight:600;border-bottom:2px solid #005bd3}.blocks-list{flex:1;overflow-y:auto;padding:1rem}.slot-group{margin-bottom:1.5rem}.slot-title{font-size:.75rem;font-weight:600;color:#8c9196;margin:0 0 .5rem;text-transform:uppercase;letter-spacing:.3px}.block-item{display:flex;align-items:center;gap:.5rem;padding:.5rem .625rem;border:1px solid #e3e3e3;border-radius:8px;margin-bottom:.375rem;cursor:pointer;transition:all .15s}.block-item:hover{border-color:#005bd3;background:#f8fafe}.block-item.selected{border-color:#005bd3;background:#005bd3;color:#fff}.block-item.selected .block-icon,.block-item.selected .block-name{color:inherit}.block-icon{font-size:.875rem;color:#8c9196;display:flex;align-items:center}.block-name{flex:1;font-size:.8125rem;font-weight:500;color:#303030;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.block-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .15s}.block-item:hover .block-actions{opacity:1}.mini-btn{padding:.125rem .375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.75rem;color:#8c9196;transition:all .15s}.mini-btn:hover{background:#e3e3e3;color:#303030}.mini-btn.delete:hover{background:#fef2f0;color:#d72c0d}.empty-slot{padding:.75rem;text-align:center;color:#8c9196;font-size:.8125rem;background:#f6f6f7;border:1px dashed #e3e3e3;border-radius:8px}.no-slots{padding:2rem;text-align:center;color:#8c9196}.no-slots p{margin:0;font-size:.8125rem}.delete-block-btn{width:auto;padding:0;margin-top:1rem;border:none;border-radius:0;background:transparent;color:#d72c0d;cursor:pointer;font-size:.8125rem;font-weight:500;font-family:inherit;transition:color .15s}.delete-block-btn:hover{background:transparent;color:#bc2200;text-decoration:underline}.toggle-field{display:flex;align-items:center;justify-content:space-between}.toggle-label{font-size:.8125rem;color:#303030}.toggle-switch{position:relative;display:inline-flex;cursor:pointer}.toggle-input{position:absolute;opacity:0;width:0;height:0}.toggle-track{width:36px;height:20px;background:#8c9196;border-radius:10px;position:relative;transition:background .2s}.toggle-thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-input:checked+.toggle-track{background:#008060}.toggle-input:checked+.toggle-track .toggle-thumb{transform:translate(16px)}.toggle-input:focus-visible+.toggle-track{outline:2px solid #005bd3;outline-offset:2px}.toggle-description{display:block;font-size:.75rem;color:#8c9196;margin-top:.25rem}.media-field{display:flex;flex-direction:column;gap:.5rem}.media-preview{position:relative;border:1px solid #e3e3e3;border-radius:8px;overflow:hidden}.media-thumbnail{display:block;width:100%;max-height:160px;object-fit:cover;background:#f6f6f7}.media-remove-btn{position:absolute;top:.375rem;right:.375rem;width:22px;height:22px;border:none;border-radius:50%;background:#0009;color:#fff;cursor:pointer;font-size:.875rem;line-height:1;display:flex;align-items:center;justify-content:center;transition:background .15s}.media-remove-btn:hover{background:#d72c0d}.media-browse-btn{padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;font-family:inherit;color:#303030;transition:all .15s}.media-browse-btn:hover{background:#f6f6f7;border-color:#8c9196}.media-url-input{font-size:.75rem;color:#616161}.video-preview{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#1a1a1a}.video-indicator{color:#fff;display:flex;align-items:center;flex-shrink:0}.video-url-text{flex:1;color:#8c9196;font-size:.6875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.video-preview .media-remove-btn{position:static;flex-shrink:0}\n"] }]
|
|
7412
|
-
}], propDecorators: { canvasEl: [{ type: i0.ViewChild, args: ['canvasEl', { isSignal: true }] }], nameInput: [{ type: i0.ViewChild, args: ['nameInput', { isSignal: true }] }] } });
|
|
7148
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;height:100vh;overflow:hidden}.editor-layout{display:grid;grid-template-columns:280px 1fr 300px;height:100%;background:#f0f2f5}.sidebar{background:#fff;border-right:1px solid #e0e0e0;display:flex;flex-direction:column}.sidebar-header{padding:1rem;border-bottom:1px solid #e0e0e0}.sidebar-header h2{margin:0;font-size:.875rem;font-weight:600;color:#333}.search-box{display:none}.search-box:focus-within{border-color:#005bd3;background:#fff}.search-icon{font-size:.875rem;opacity:.6}.search-input{flex:1;border:none;background:transparent;font-size:.8125rem;color:#333;outline:none}.search-input::placeholder{color:#999}.search-clear{padding:.125rem .375rem;border:none;background:#ddd;border-radius:3px;cursor:pointer;font-size:.75rem;color:#666;line-height:1}.search-clear:hover{background:#ccc}.search-no-results{padding:2rem 1rem;text-align:center;color:#888}.search-no-results p{margin:0;font-size:.875rem}.picker-search{padding:.5rem;border-bottom:1px solid #e0e0e0}.picker-search-input{width:87%;padding:.5rem .75rem;border:1px solid #e0e0e0;border-radius:4px;font-size:.8125rem;outline:none;transition:border-color .2s}.picker-search-input:focus{border-color:#005bd3}.picker-search-input::placeholder{color:#999}.section-tree{flex:1;overflow-y:auto;padding:.25rem 0}.tree-section{border-bottom:none}.tree-section.selected>.tree-section-header{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-section.selected>.tree-section-header .section-name,.tree-section.selected>.tree-section-header .expand-btn,.tree-section.selected>.tree-section-header .section-icon,.tree-section.selected>.tree-section-header .drag-handle,.tree-section.selected>.tree-section-header .tree-btn{color:inherit}.tree-section-header{display:flex;align-items:center;gap:.5rem;padding:.5rem;cursor:pointer;transition:background .2s}.tree-section-header:hover{background:#f5f5f5;border-radius:8px;margin:0 .375rem}.expand-btn{width:16px;height:16px;padding:0;border:none;background:none;cursor:pointer;display:flex;align-items:center;justify-content:center;color:#666;transition:transform .2s;flex-shrink:0}.expand-btn.expanded .expand-icon{transform:rotate(90deg)}.expand-icon{font-size:.625rem;transition:transform .2s}.icon-drag-wrapper{position:relative;display:flex;align-items:center;justify-content:center;flex-shrink:0}.icon-drag-wrapper .drag-handle{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.tree-section-header:hover .icon-drag-wrapper .section-icon,.icon-drag-wrapper:has(.drag-handle:focus-visible) .section-icon{visibility:hidden}.section-icon{font-size:1rem}.section-name{flex:1;font-size:.8125rem;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tree-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.tree-section-header:hover .tree-actions,.tree-block:hover .tree-actions{opacity:1}.tree-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.6875rem;color:#666}.tree-btn:hover{background:#d0d0d0}.tree-btn.delete:hover{background:#e74c3c;color:#fff}.tree-section.drag-over-before>.tree-section-header{box-shadow:inset 0 2px #4a90d9}.tree-section.drag-over-after>.tree-section-header{box-shadow:inset 0 -2px #4a90d9}.tree-section.is-section-dragging{opacity:.4}.tree-section.is-section-dragging>.tree-section-header{cursor:grabbing}.drag-handle{cursor:grab;color:#999;opacity:0;transition:opacity .2s;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:.125rem;border:none;background:none;border-radius:2px}.drag-handle:hover{color:#666;background:#e0e0e0}.drag-handle:focus-visible{opacity:1;outline:2px solid #4a90d9;outline-offset:1px}.tree-section-header:hover .drag-handle{opacity:1}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.tree-section,.drag-handle,.tree-actions{transition:none}}.tree-section-content{padding-left:.5rem}.tree-block{display:flex;align-items:center;gap:.375rem;padding:.5rem .75rem;cursor:pointer;transition:background .2s}.tree-block:hover{background:#f5f5f5;border-radius:8px;margin:0 .375rem}.tree-block.selected{background:#005bd3;color:#fff;border-radius:8px;margin:0 .375rem}.tree-block.selected .block-icon,.tree-block.selected .block-name{color:inherit}.block-indent{width:20px;height:1px;background:#ddd;margin-left:10px}.block-icon{font-size:.875rem}.block-name{flex:1;font-size:.75rem;color:#555;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.add-block-btn{display:flex;align-items:center;gap:.375rem;width:calc(100% - 1.5rem);margin:.375rem .75rem .5rem 2rem;padding:.375rem .5rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:.75rem;color:#005bd3;transition:all .2s}.add-block-btn:hover{background:#f0f5ff;color:#004299}.add-icon{font-size:.875rem;font-weight:600}.add-icon-circle{display:flex;align-items:center;justify-content:center}.add-section-area{padding:.75rem;border-top:1px solid #e0e0e0;position:relative}.add-section-btn{display:flex;align-items:center;justify-content:center;gap:.375rem;width:100%;padding:.625rem;border:none;border-radius:6px;background:transparent;cursor:pointer;font-size:.8125rem;font-weight:500;color:#005bd3;transition:all .2s}.add-section-btn:hover{background:#f0f5ff}.section-picker{position:absolute;bottom:100%;left:.75rem;right:.75rem;background:#fff;border:1px solid #e0e0e0;border-radius:6px;box-shadow:0 -4px 12px #0000001a;max-height:300px;overflow-y:auto;z-index:100}.picker-item{display:flex;align-items:center;gap:.75rem;width:100%;padding:.75rem;border:none;border-bottom:1px solid #f0f0f0;background:#fff;cursor:pointer;text-align:left;transition:background .2s}.picker-item:last-child{border-bottom:none}.picker-item:hover{background:#f5f5f5}.picker-icon{font-family:Material Icons,sans-serif;font-size:1.5rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.picker-info{display:flex;flex-direction:column;gap:.125rem}.picker-name{font-size:.8125rem;font-weight:500;color:#333}.picker-desc{font-size:.6875rem;color:#888}.block-picker-overlay{position:fixed;inset:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:1000}.block-picker{background:#fff;border-radius:8px;width:320px;max-height:400px;display:flex;flex-direction:column;box-shadow:0 8px 32px #0003}.picker-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0}.picker-header h3{margin:0;font-size:.9375rem;font-weight:600}.picker-content{flex:1;overflow-y:auto;padding:.5rem}.picker-content .picker-item{border:1px solid #e0e0e0;border-radius:6px;margin-bottom:.5rem}.picker-content .picker-item:hover{border-color:#005bd3}.picker-category{border-bottom:1px solid #f0f0f0}.picker-category:last-child{border-bottom:none}.picker-category-label{padding:.5rem .75rem .25rem;font-size:.6875rem;font-weight:600;color:#888}.picker-empty{padding:2rem;text-align:center;color:#888;font-size:.875rem}.canvas-area{display:flex;flex-direction:column;overflow:hidden}.toolbar{display:flex;align-items:center;justify-content:space-between;padding:.5rem 1rem;background:#fff;border-bottom:1px solid #e0e0e0}.toolbar-left,.toolbar-right{display:flex;gap:.5rem}.toolbar-btn{padding:.375rem .75rem;border:1px solid #e0e0e0;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;transition:all .2s}.toolbar-btn:hover:not(:disabled){background:#f0f2f5}.toolbar-btn:disabled{opacity:.5;cursor:not-allowed}.preview-btn,.publish-btn{background:#303030;color:#fff;border-color:#303030;border-radius:8px}.preview-btn:hover,.publish-btn:hover:not(:disabled){background:#1a1a1a!important}.back-btn{border-color:transparent;background:transparent}.section-count{font-size:.875rem;color:#666}.page-title{font-size:.9375rem;font-weight:600;color:#333;margin-right:.75rem}.page-title-link{text-decoration:none;color:inherit}.page-title-link:hover{text-decoration:underline}.status-badge{display:inline-block;padding:.125rem .5rem;border-radius:10px;font-size:.6875rem;font-weight:500;text-transform:capitalize;background:#ffeaa7;color:#856404}.status-badge.published{background:#b4fed3;color:#0d542b}.dirty-indicator{margin-left:.75rem;font-size:.8125rem;color:#e67e22;font-weight:500}.canvas{flex:1;overflow-y:auto;padding:2rem;position:relative}.canvas.preview-mode{padding:0;background:#fff}.preview-iframe{width:100%;height:100%;border:none;position:absolute;inset:0}.canvas.preview-mode .section-outer{margin:0}.canvas.preview-mode .section-wrapper{border:none;border-radius:0}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:#666;text-align:center}.empty-icon{font-size:4rem;margin-bottom:1rem}.empty-state h3{margin:0 0 .5rem;color:#333}.empty-state p{margin:0;font-size:.875rem}.section-outer{position:relative;margin-bottom:0}.section-wrapper{border:1px solid transparent;border-radius:0;overflow:hidden;transition:border-color .2s;background:#fff}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-color:#005bd366}.section-controls{position:absolute;top:-20px;left:0;right:0;display:flex;justify-content:space-between;align-items:center;padding:.14rem .75rem;background:#005bd3;color:#fff;font-size:.75rem;border-radius:8px 8px 0 0;opacity:0;pointer-events:none;transition:opacity .2s;z-index:10}.section-outer:hover .section-controls,.section-outer.selected .section-controls{opacity:1;pointer-events:auto}.section-outer:hover .section-wrapper,.section-outer.selected .section-wrapper{border-top-left-radius:0;border-top-right-radius:0}.section-label{font-weight:500}.section-actions{display:flex;gap:.25rem}.action-btn{padding:.15rem .5rem;border:none;border-radius:4px;background:#ffffff26;color:#fff;cursor:pointer;font-size:.6rem}.action-btn:hover{background:#ffffff4d}.action-btn.delete:hover{background:#e74c3c}.material-icon{font-family:Material Icons,sans-serif;font-size:.75rem;font-weight:400;font-style:normal;line-height:1;letter-spacing:normal;text-transform:none;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}.properties-panel{background:#fff;border-left:1px solid #e0e0e0;display:flex;flex-direction:column;overflow:hidden}.properties-header{display:flex;justify-content:space-between;align-items:center;padding:1rem;border-bottom:1px solid #e0e0e0}.properties-header h3{margin:0;font-size:1rem;font-weight:600}.editable-name{cursor:pointer;display:flex;align-items:center;gap:.375rem;border-radius:4px;padding:.125rem .25rem;margin:-.125rem -.25rem;transition:background .2s}.editable-name:hover{background:#f0f2f5}.edit-icon{opacity:0;color:#999;transition:opacity .2s;display:flex;flex-shrink:0}.editable-name:hover .edit-icon{opacity:1}.name-edit-input{flex:1;padding:.375rem .5rem;border:1px solid #005bd3;border-radius:8px;font-size:1rem;font-weight:600;outline:none}.close-btn{border:none;background:none;font-size:1.25rem;cursor:pointer;color:#666}.more-menu-btn{border:none;background:none;cursor:pointer;color:#616161;padding:.25rem;border-radius:6px;display:flex;align-items:center;justify-content:center;transition:background .2s}.more-menu-btn:hover{background:#f0f2f5;color:#303030}.properties-content{flex:1;overflow-y:auto;padding:1rem}.property-group{margin-bottom:0;padding:1rem 0;border-top:1px solid #e0e0e0}.property-group:first-child{border-top:none}.group-title{display:flex;align-items:center;gap:.375rem;width:100%;padding:0;margin:0 0 .75rem;border:none;background:none;font-size:.8125rem;font-weight:600;color:#303030;cursor:pointer}.group-title:hover{color:#333}.group-title:focus-visible{outline:2px solid #4a90d9;outline-offset:2px;border-radius:2px}.group-title-text{flex:1;text-align:left}.group-toggle-icon{font-size:.5rem;transition:transform .2s;display:inline-block}.group-toggle-icon.collapsed{transform:rotate(-90deg)}.property-field{margin-bottom:1rem}.property-label{display:block;font-size:.875rem;font-weight:500;margin-bottom:.375rem;color:#616161}.property-input,.property-select,.property-textarea{width:100%;padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;font-size:.875rem;transition:border-color .2s,box-shadow .2s}.property-input:focus,.property-select:focus,.property-textarea:focus{outline:none;border-color:#005bd3;box-shadow:0 0 0 1px #005bd3}.property-textarea{resize:vertical;font-family:monospace;font-size:.75rem}.color-field{display:flex;gap:.5rem}.color-picker{width:40px;height:36px;padding:2px;border:1px solid #e0e0e0;border-radius:4px;cursor:pointer}.color-text{flex:1}.no-selection{display:flex;align-items:center;justify-content:center;height:100%;color:#666;text-align:center;padding:2rem}.no-selection p{margin:0;font-size:.875rem}.panel-tabs{display:flex;border-bottom:1px solid #e0e0e0}.panel-tab{flex:1;padding:.75rem;border:none;border-bottom:2px solid transparent;background:#fff;cursor:pointer;font-size:.75rem;font-weight:500;color:#666;transition:all .2s}.panel-tab:hover{background:#f8f9fa;color:#303030}.panel-tab.active{background:#fff;color:#303030;border-bottom:2px solid #005bd3}.blocks-list{flex:1;overflow-y:auto;padding:1rem}.slot-group{margin-bottom:1.5rem}.slot-title{font-size:.75rem;font-weight:600;color:#616161;margin:0 0 .5rem}.block-item{display:flex;align-items:center;gap:.5rem;padding:.5rem;border:1px solid #e0e0e0;border-radius:4px;margin-bottom:.375rem;cursor:pointer;transition:all .2s}.block-item:hover{border-color:#005bd3;background:#f8f9fa}.block-item.selected{border-color:#005bd3;background:#005bd3;color:#fff}.block-item.selected .block-icon,.block-item.selected .block-name{color:inherit}.block-icon{font-size:1rem}.block-name{flex:1;font-size:.8rem;font-weight:500;color:#333;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.block-actions{display:flex;gap:.125rem;opacity:0;transition:opacity .2s}.block-item:hover .block-actions{opacity:1}.mini-btn{padding:.125rem .375rem;border:none;border-radius:3px;background:#e0e0e0;cursor:pointer;font-size:.75rem;color:#666}.mini-btn:hover{background:#d0d0d0}.mini-btn.delete:hover{background:#e74c3c;color:#fff}.empty-slot{padding:.75rem;text-align:center;color:#999;font-size:.75rem;background:#f8f9fa;border:1px dashed #ddd;border-radius:4px}.no-slots{padding:2rem;text-align:center;color:#666}.no-slots p{margin:0;font-size:.875rem}.delete-block-btn{width:auto;padding:0;margin-top:1rem;border:none;border-radius:0;background:transparent;color:#d72c0d;cursor:pointer;font-size:.875rem;font-weight:500;transition:color .2s}.delete-block-btn:hover{background:transparent;color:#bc2200;text-decoration:underline}.toggle-field{display:flex;align-items:center;justify-content:space-between}.toggle-label{font-size:.875rem;color:#303030}.toggle-switch{position:relative;display:inline-flex;cursor:pointer}.toggle-input{position:absolute;opacity:0;width:0;height:0}.toggle-track{width:36px;height:20px;background:#b5b5b5;border-radius:10px;position:relative;transition:background .2s}.toggle-thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;background:#fff;border-radius:50%;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-input:checked+.toggle-track{background:#008060}.toggle-input:checked+.toggle-track .toggle-thumb{transform:translate(16px)}.toggle-input:focus-visible+.toggle-track{outline:2px solid #005bd3;outline-offset:2px}.toggle-description{display:block;font-size:.75rem;color:#616161;margin-top:.25rem}.media-field{display:flex;flex-direction:column;gap:.5rem}.media-preview{position:relative;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}.media-thumbnail{display:block;width:100%;max-height:160px;object-fit:cover;background:#f0f2f5}.media-remove-btn{position:absolute;top:.25rem;right:.25rem;width:22px;height:22px;border:none;border-radius:50%;background:#0009;color:#fff;cursor:pointer;font-size:.875rem;line-height:1;display:flex;align-items:center;justify-content:center}.media-remove-btn:hover{background:#d72c0d}.media-browse-btn{padding:.5rem .75rem;border:1px solid #c9cccf;border-radius:8px;background:#fff;cursor:pointer;font-size:.8125rem;color:#303030;transition:all .15s}.media-browse-btn:hover{background:#f0f2f5;border-color:#999}.media-url-input{font-size:.75rem;color:#616161}.video-preview{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#1a1a1a}.video-indicator{color:#fff;display:flex;align-items:center;flex-shrink:0}.video-url-text{flex:1;color:#ccc;font-size:.6875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.video-preview .media-remove-btn{position:static;flex-shrink:0}\n"] }]
|
|
7149
|
+
}], ctorParameters: () => [], propDecorators: { canvasEl: [{ type: i0.ViewChild, args: ['canvasEl', { isSignal: true }] }], previewFrame: [{ type: i0.ViewChild, args: ['previewFrame', { isSignal: true }] }], nameInput: [{ type: i0.ViewChild, args: ['nameInput', { isSignal: true }] }] } });
|
|
7413
7150
|
|
|
7414
7151
|
/**
|
|
7415
7152
|
* Page manager component for listing, creating, and managing pages
|
|
@@ -7447,6 +7184,9 @@ class PageManagerComponent {
|
|
|
7447
7184
|
},
|
|
7448
7185
|
});
|
|
7449
7186
|
}
|
|
7187
|
+
openSetup() {
|
|
7188
|
+
this.navigation.navigate({ type: 'setup' });
|
|
7189
|
+
}
|
|
7450
7190
|
openCreateDialog() {
|
|
7451
7191
|
this.showCreateDialog.set(true);
|
|
7452
7192
|
this.newPageTitle.set('');
|
|
@@ -7538,14 +7278,19 @@ class PageManagerComponent {
|
|
|
7538
7278
|
year: 'numeric',
|
|
7539
7279
|
});
|
|
7540
7280
|
}
|
|
7541
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
7542
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0
|
|
7281
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageManagerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7282
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: PageManagerComponent, isStandalone: true, selector: "lib-page-manager", ngImport: i0, template: `
|
|
7543
7283
|
<div class="page-manager">
|
|
7544
7284
|
<header class="manager-header">
|
|
7545
7285
|
<h1>Pages</h1>
|
|
7546
|
-
<
|
|
7547
|
-
|
|
7548
|
-
|
|
7286
|
+
<div class="header-actions">
|
|
7287
|
+
<button class="btn-secondary" (click)="openSetup()">
|
|
7288
|
+
Setup
|
|
7289
|
+
</button>
|
|
7290
|
+
<button class="btn-primary" (click)="openCreateDialog()">
|
|
7291
|
+
+ New Page
|
|
7292
|
+
</button>
|
|
7293
|
+
</div>
|
|
7549
7294
|
</header>
|
|
7550
7295
|
|
|
7551
7296
|
@if (isLoading()) {
|
|
@@ -7559,7 +7304,7 @@ class PageManagerComponent {
|
|
|
7559
7304
|
</div>
|
|
7560
7305
|
} @else if (pages().length === 0) {
|
|
7561
7306
|
<div class="empty-state">
|
|
7562
|
-
<div class="empty-icon"
|
|
7307
|
+
<div class="empty-icon">📄</div>
|
|
7563
7308
|
<h2>No pages yet</h2>
|
|
7564
7309
|
<p>Create your first page to get started</p>
|
|
7565
7310
|
<button class="btn-primary" (click)="openCreateDialog()">
|
|
@@ -7591,21 +7336,21 @@ class PageManagerComponent {
|
|
|
7591
7336
|
(click)="editPage(page.id); $event.stopPropagation()"
|
|
7592
7337
|
title="Edit"
|
|
7593
7338
|
>
|
|
7594
|
-
|
|
7339
|
+
✏️
|
|
7595
7340
|
</button>
|
|
7596
7341
|
<button
|
|
7597
7342
|
class="action-btn"
|
|
7598
7343
|
(click)="duplicatePage(page.id, $event)"
|
|
7599
7344
|
title="Duplicate"
|
|
7600
7345
|
>
|
|
7601
|
-
|
|
7346
|
+
📋
|
|
7602
7347
|
</button>
|
|
7603
7348
|
<button
|
|
7604
7349
|
class="action-btn delete"
|
|
7605
7350
|
(click)="confirmDelete(page, $event)"
|
|
7606
7351
|
title="Delete"
|
|
7607
7352
|
>
|
|
7608
|
-
|
|
7353
|
+
🗑️
|
|
7609
7354
|
</button>
|
|
7610
7355
|
</span>
|
|
7611
7356
|
</div>
|
|
@@ -7686,17 +7431,22 @@ class PageManagerComponent {
|
|
|
7686
7431
|
</div>
|
|
7687
7432
|
}
|
|
7688
7433
|
</div>
|
|
7689
|
-
`, isInline: true, styles: [":host{display:block;min-height:100vh;background:#
|
|
7434
|
+
`, isInline: true, styles: [":host{display:block;min-height:100vh;background:#f0f2f5}.page-manager{max-width:1200px;margin:0 auto;padding:2rem}.manager-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem}.header-actions{display:flex;gap:.75rem;align-items:center}.manager-header h1{margin:0;font-size:1.75rem;font-weight:600;color:#1a1a2e}.btn-primary{padding:.75rem 1.5rem;border:none;border-radius:6px;background:#1a1a2e;color:#fff;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .2s}.btn-primary:hover{background:#2a2a3e}.btn-primary:disabled{opacity:.6;cursor:not-allowed}.btn-secondary{padding:.75rem 1.5rem;border:1px solid #e0e0e0;border-radius:6px;background:#fff;color:#333;font-size:.875rem;font-weight:500;cursor:pointer;transition:all .2s}.btn-secondary:hover{background:#f5f5f5}.btn-danger{padding:.75rem 1.5rem;border:none;border-radius:6px;background:#e74c3c;color:#fff;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .2s}.btn-danger:hover{background:#c0392b}.btn-danger:disabled{opacity:.6;cursor:not-allowed}.loading-state,.error-state,.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4rem 2rem;background:#fff;border-radius:8px;text-align:center}.empty-icon{font-size:4rem;margin-bottom:1rem}.empty-state h2{margin:0 0 .5rem;color:#333}.empty-state p{margin:0 0 1.5rem;color:#666}.error-state{color:#e74c3c}.pages-table{background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 1px 3px #0000001a}.table-header,.table-row{display:grid;grid-template-columns:1fr 150px 100px 150px 120px;gap:1rem;padding:1rem 1.5rem;align-items:center}.table-header{background:#f8f9fa;font-size:.75rem;font-weight:600;text-transform:uppercase;color:#666;letter-spacing:.5px}.table-row{border-top:1px solid #e0e0e0;cursor:pointer;transition:background .2s}.table-row:hover{background:#f8f9fa}.col-title{font-weight:500;color:#333}.col-slug{font-size:.8125rem;color:#666;font-family:monospace}.status-badge{display:inline-block;padding:.25rem .5rem;border-radius:4px;font-size:.75rem;font-weight:500;text-transform:capitalize;background:#ffeaa7;color:#856404}.status-badge.published{background:#d4edda;color:#155724}.col-updated{font-size:.8125rem;color:#666}.col-actions{display:flex;gap:.5rem}.action-btn{padding:.375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:1rem;transition:background .2s}.action-btn:hover{background:#e0e0e0}.action-btn.delete:hover{background:#fde2e2}.dialog-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog{background:#fff;border-radius:8px;width:100%;max-width:480px;box-shadow:0 8px 32px #0003}.dialog-small{max-width:400px}.dialog-header{display:flex;justify-content:space-between;align-items:center;padding:1.5rem;border-bottom:1px solid #e0e0e0}.dialog-header h2{margin:0;font-size:1.25rem;font-weight:600}.close-btn{border:none;background:none;font-size:1.5rem;cursor:pointer;color:#666;padding:0;line-height:1}.close-btn:hover{color:#333}.dialog-content{padding:1.5rem}.dialog-content p{margin:0 0 .5rem;color:#333}.dialog-content .warning{color:#e74c3c;font-size:.875rem}.form-field{margin-bottom:1.25rem}.form-field label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:#333}.form-field input{width:100%;padding:.75rem;border:1px solid #e0e0e0;border-radius:6px;font-size:.875rem;transition:border-color .2s}.form-field input:focus{outline:none;border-color:#1a1a2e}.slug-field{display:flex;align-items:center;border:1px solid #e0e0e0;border-radius:6px;overflow:hidden}.slug-field:focus-within{border-color:#1a1a2e}.slug-prefix{padding:.75rem;background:#f5f5f5;color:#666;font-size:.875rem;font-family:monospace}.slug-field input{border:none;border-radius:0}.slug-field input:focus{border-color:transparent}.form-error{margin-bottom:1rem;padding:.75rem;background:#fde2e2;border-radius:6px;color:#e74c3c;font-size:.875rem}.dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1.5rem;border-top:1px solid #e0e0e0}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7690
7435
|
}
|
|
7691
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
7436
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageManagerComponent, decorators: [{
|
|
7692
7437
|
type: Component,
|
|
7693
7438
|
args: [{ selector: 'lib-page-manager', template: `
|
|
7694
7439
|
<div class="page-manager">
|
|
7695
7440
|
<header class="manager-header">
|
|
7696
7441
|
<h1>Pages</h1>
|
|
7697
|
-
<
|
|
7698
|
-
|
|
7699
|
-
|
|
7442
|
+
<div class="header-actions">
|
|
7443
|
+
<button class="btn-secondary" (click)="openSetup()">
|
|
7444
|
+
Setup
|
|
7445
|
+
</button>
|
|
7446
|
+
<button class="btn-primary" (click)="openCreateDialog()">
|
|
7447
|
+
+ New Page
|
|
7448
|
+
</button>
|
|
7449
|
+
</div>
|
|
7700
7450
|
</header>
|
|
7701
7451
|
|
|
7702
7452
|
@if (isLoading()) {
|
|
@@ -7710,7 +7460,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7710
7460
|
</div>
|
|
7711
7461
|
} @else if (pages().length === 0) {
|
|
7712
7462
|
<div class="empty-state">
|
|
7713
|
-
<div class="empty-icon"
|
|
7463
|
+
<div class="empty-icon">📄</div>
|
|
7714
7464
|
<h2>No pages yet</h2>
|
|
7715
7465
|
<p>Create your first page to get started</p>
|
|
7716
7466
|
<button class="btn-primary" (click)="openCreateDialog()">
|
|
@@ -7742,21 +7492,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7742
7492
|
(click)="editPage(page.id); $event.stopPropagation()"
|
|
7743
7493
|
title="Edit"
|
|
7744
7494
|
>
|
|
7745
|
-
|
|
7495
|
+
✏️
|
|
7746
7496
|
</button>
|
|
7747
7497
|
<button
|
|
7748
7498
|
class="action-btn"
|
|
7749
7499
|
(click)="duplicatePage(page.id, $event)"
|
|
7750
7500
|
title="Duplicate"
|
|
7751
7501
|
>
|
|
7752
|
-
|
|
7502
|
+
📋
|
|
7753
7503
|
</button>
|
|
7754
7504
|
<button
|
|
7755
7505
|
class="action-btn delete"
|
|
7756
7506
|
(click)="confirmDelete(page, $event)"
|
|
7757
7507
|
title="Delete"
|
|
7758
7508
|
>
|
|
7759
|
-
|
|
7509
|
+
🗑️
|
|
7760
7510
|
</button>
|
|
7761
7511
|
</span>
|
|
7762
7512
|
</div>
|
|
@@ -7837,1111 +7587,287 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
7837
7587
|
</div>
|
|
7838
7588
|
}
|
|
7839
7589
|
</div>
|
|
7840
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;min-height:100vh;background:#
|
|
7590
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;min-height:100vh;background:#f0f2f5}.page-manager{max-width:1200px;margin:0 auto;padding:2rem}.manager-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:2rem}.header-actions{display:flex;gap:.75rem;align-items:center}.manager-header h1{margin:0;font-size:1.75rem;font-weight:600;color:#1a1a2e}.btn-primary{padding:.75rem 1.5rem;border:none;border-radius:6px;background:#1a1a2e;color:#fff;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .2s}.btn-primary:hover{background:#2a2a3e}.btn-primary:disabled{opacity:.6;cursor:not-allowed}.btn-secondary{padding:.75rem 1.5rem;border:1px solid #e0e0e0;border-radius:6px;background:#fff;color:#333;font-size:.875rem;font-weight:500;cursor:pointer;transition:all .2s}.btn-secondary:hover{background:#f5f5f5}.btn-danger{padding:.75rem 1.5rem;border:none;border-radius:6px;background:#e74c3c;color:#fff;font-size:.875rem;font-weight:500;cursor:pointer;transition:background .2s}.btn-danger:hover{background:#c0392b}.btn-danger:disabled{opacity:.6;cursor:not-allowed}.loading-state,.error-state,.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:4rem 2rem;background:#fff;border-radius:8px;text-align:center}.empty-icon{font-size:4rem;margin-bottom:1rem}.empty-state h2{margin:0 0 .5rem;color:#333}.empty-state p{margin:0 0 1.5rem;color:#666}.error-state{color:#e74c3c}.pages-table{background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 1px 3px #0000001a}.table-header,.table-row{display:grid;grid-template-columns:1fr 150px 100px 150px 120px;gap:1rem;padding:1rem 1.5rem;align-items:center}.table-header{background:#f8f9fa;font-size:.75rem;font-weight:600;text-transform:uppercase;color:#666;letter-spacing:.5px}.table-row{border-top:1px solid #e0e0e0;cursor:pointer;transition:background .2s}.table-row:hover{background:#f8f9fa}.col-title{font-weight:500;color:#333}.col-slug{font-size:.8125rem;color:#666;font-family:monospace}.status-badge{display:inline-block;padding:.25rem .5rem;border-radius:4px;font-size:.75rem;font-weight:500;text-transform:capitalize;background:#ffeaa7;color:#856404}.status-badge.published{background:#d4edda;color:#155724}.col-updated{font-size:.8125rem;color:#666}.col-actions{display:flex;gap:.5rem}.action-btn{padding:.375rem;border:none;border-radius:4px;background:transparent;cursor:pointer;font-size:1rem;transition:background .2s}.action-btn:hover{background:#e0e0e0}.action-btn.delete:hover{background:#fde2e2}.dialog-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1000}.dialog{background:#fff;border-radius:8px;width:100%;max-width:480px;box-shadow:0 8px 32px #0003}.dialog-small{max-width:400px}.dialog-header{display:flex;justify-content:space-between;align-items:center;padding:1.5rem;border-bottom:1px solid #e0e0e0}.dialog-header h2{margin:0;font-size:1.25rem;font-weight:600}.close-btn{border:none;background:none;font-size:1.5rem;cursor:pointer;color:#666;padding:0;line-height:1}.close-btn:hover{color:#333}.dialog-content{padding:1.5rem}.dialog-content p{margin:0 0 .5rem;color:#333}.dialog-content .warning{color:#e74c3c;font-size:.875rem}.form-field{margin-bottom:1.25rem}.form-field label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:#333}.form-field input{width:100%;padding:.75rem;border:1px solid #e0e0e0;border-radius:6px;font-size:.875rem;transition:border-color .2s}.form-field input:focus{outline:none;border-color:#1a1a2e}.slug-field{display:flex;align-items:center;border:1px solid #e0e0e0;border-radius:6px;overflow:hidden}.slug-field:focus-within{border-color:#1a1a2e}.slug-prefix{padding:.75rem;background:#f5f5f5;color:#666;font-size:.875rem;font-family:monospace}.slug-field input{border:none;border-radius:0}.slug-field input:focus{border-color:transparent}.form-error{margin-bottom:1rem;padding:.75rem;background:#fde2e2;border-radius:6px;color:#e74c3c;font-size:.875rem}.dialog-actions{display:flex;justify-content:flex-end;gap:.75rem;padding:1.5rem;border-top:1px solid #e0e0e0}\n"] }]
|
|
7841
7591
|
}] });
|
|
7842
7592
|
|
|
7843
|
-
|
|
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
|
-
|
|
7593
|
+
/**
|
|
7594
|
+
* Read-only GraphQL client for the Shopify Storefront API.
|
|
7595
|
+
* Uses a Storefront Access Token (public, non-secret) instead of Admin API credentials.
|
|
7596
|
+
*/
|
|
7597
|
+
class StorefrontGraphQLClient {
|
|
7598
|
+
http = inject(HttpClient);
|
|
7599
|
+
config = inject(STOREFRONT_CONFIG);
|
|
7600
|
+
query(query, variables) {
|
|
7601
|
+
const url = this.config.proxyUrl
|
|
7602
|
+
? this.config.proxyUrl
|
|
7603
|
+
: `https://${this.config.shopDomain}/api/${this.config.apiVersion}/graphql.json`;
|
|
7604
|
+
const request = { query, variables };
|
|
7605
|
+
const headers = {
|
|
7606
|
+
'Content-Type': 'application/json',
|
|
7607
|
+
};
|
|
7608
|
+
if (!this.config.proxyUrl) {
|
|
7609
|
+
headers['X-Shopify-Storefront-Access-Token'] = this.config.storefrontAccessToken;
|
|
7610
|
+
}
|
|
7611
|
+
return this.http.post(url, request, { headers }).pipe(map$1(response => this.handleResponse(response)), catchError(error => this.handleError(error)));
|
|
7612
|
+
}
|
|
7613
|
+
handleResponse(response) {
|
|
7614
|
+
if (response.errors && response.errors.length > 0) {
|
|
7615
|
+
throw {
|
|
7616
|
+
code: 'GRAPHQL_ERROR',
|
|
7617
|
+
message: response.errors[0].message,
|
|
7618
|
+
errors: response.errors,
|
|
7619
|
+
};
|
|
7620
|
+
}
|
|
7621
|
+
if (!response.data) {
|
|
7622
|
+
throw {
|
|
7623
|
+
code: 'EMPTY_RESPONSE',
|
|
7624
|
+
message: 'GraphQL response contains no data',
|
|
7625
|
+
};
|
|
7626
|
+
}
|
|
7627
|
+
return response.data;
|
|
7628
|
+
}
|
|
7629
|
+
handleError(error) {
|
|
7630
|
+
let errorCode = 'UNKNOWN_ERROR';
|
|
7631
|
+
let errorMessage = 'An unknown error occurred';
|
|
7632
|
+
if (typeof ErrorEvent !== 'undefined' && error.error instanceof ErrorEvent) {
|
|
7633
|
+
errorCode = 'NETWORK_ERROR';
|
|
7634
|
+
errorMessage = error.error.message;
|
|
7635
|
+
}
|
|
7636
|
+
else {
|
|
7637
|
+
switch (error.status) {
|
|
7638
|
+
case 401:
|
|
7639
|
+
errorCode = 'UNAUTHORIZED';
|
|
7640
|
+
errorMessage = 'Invalid or expired storefront access token';
|
|
7641
|
+
break;
|
|
7642
|
+
case 402:
|
|
7643
|
+
errorCode = 'PAYMENT_REQUIRED';
|
|
7644
|
+
errorMessage = 'Storefront API access requires a Shopify plan';
|
|
7645
|
+
break;
|
|
7646
|
+
case 429:
|
|
7647
|
+
errorCode = 'RATE_LIMIT';
|
|
7648
|
+
errorMessage = 'Rate limit exceeded';
|
|
7649
|
+
break;
|
|
7650
|
+
case 500:
|
|
7651
|
+
case 502:
|
|
7652
|
+
case 503:
|
|
7653
|
+
case 504:
|
|
7654
|
+
errorCode = 'SERVER_ERROR';
|
|
7655
|
+
errorMessage = 'Shopify server error';
|
|
7656
|
+
break;
|
|
7657
|
+
default:
|
|
7658
|
+
errorCode = 'HTTP_ERROR';
|
|
7659
|
+
errorMessage = `HTTP ${error.status}: ${error.statusText}`;
|
|
7901
7660
|
}
|
|
7902
|
-
|
|
7903
|
-
|
|
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
|
-
}
|
|
7661
|
+
}
|
|
7662
|
+
return throwError(() => ({
|
|
7663
|
+
code: errorCode,
|
|
7664
|
+
message: errorMessage,
|
|
7665
|
+
status: error.status,
|
|
7666
|
+
original: error,
|
|
7667
|
+
}));
|
|
7915
7668
|
}
|
|
7916
|
-
|
|
7669
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontGraphQLClient, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7670
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontGraphQLClient, providedIn: 'root' });
|
|
7917
7671
|
}
|
|
7918
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
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>
|
|
7672
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontGraphQLClient, decorators: [{
|
|
7673
|
+
type: Injectable,
|
|
7674
|
+
args: [{ providedIn: 'root' }]
|
|
7675
|
+
}] });
|
|
7930
7676
|
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7677
|
+
const NAMESPACE = 'kustomizer';
|
|
7678
|
+
const INDEX_KEY = 'pages_index';
|
|
7679
|
+
/**
|
|
7680
|
+
* Storefront API query for reading shop metafields.
|
|
7681
|
+
* Uses `shop.metafield(namespace, key)` — the Storefront API equivalent
|
|
7682
|
+
* of the Admin API's shop metafield query.
|
|
7683
|
+
*/
|
|
7684
|
+
const GET_SHOP_METAFIELD_QUERY = `
|
|
7685
|
+
query GetShopMetafield($namespace: String!, $key: String!) {
|
|
7686
|
+
shop {
|
|
7687
|
+
metafield(namespace: $namespace, key: $key) {
|
|
7688
|
+
value
|
|
7689
|
+
type
|
|
7690
|
+
}
|
|
7691
|
+
}
|
|
7692
|
+
}
|
|
7693
|
+
`;
|
|
7694
|
+
/**
|
|
7695
|
+
* Read-only metafield repository using the Shopify Storefront API.
|
|
7696
|
+
* Only works for metafields with MetafieldDefinitions that have `access.storefront: PUBLIC_READ`.
|
|
7697
|
+
*/
|
|
7698
|
+
class StorefrontMetafieldRepository {
|
|
7699
|
+
client = inject(StorefrontGraphQLClient);
|
|
7700
|
+
getMetafield(namespace, key) {
|
|
7701
|
+
return this.client.query(GET_SHOP_METAFIELD_QUERY, { namespace, key }).pipe(map$1(response => response.shop.metafield));
|
|
7702
|
+
}
|
|
7703
|
+
getPageIndex() {
|
|
7704
|
+
return this.getMetafield(NAMESPACE, INDEX_KEY).pipe(map$1(metafield => {
|
|
7705
|
+
if (!metafield) {
|
|
7706
|
+
return this.getDefaultIndex();
|
|
7939
7707
|
}
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7708
|
+
try {
|
|
7709
|
+
const parsed = JSON.parse(metafield.value);
|
|
7710
|
+
if (!parsed.pages || !Array.isArray(parsed.pages)) {
|
|
7711
|
+
throw new Error('Invalid index structure');
|
|
7712
|
+
}
|
|
7713
|
+
return parsed;
|
|
7943
7714
|
}
|
|
7944
|
-
|
|
7945
|
-
|
|
7715
|
+
catch (error) {
|
|
7716
|
+
throw {
|
|
7717
|
+
code: 'INDEX_PARSE_ERROR',
|
|
7718
|
+
message: 'Stored index data is corrupted',
|
|
7719
|
+
original: error,
|
|
7720
|
+
};
|
|
7946
7721
|
}
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7722
|
+
}));
|
|
7723
|
+
}
|
|
7724
|
+
getPageMetafield(pageId) {
|
|
7725
|
+
const key = `page_${pageId}`;
|
|
7726
|
+
return this.getMetafield(NAMESPACE, key).pipe(map$1(metafield => {
|
|
7727
|
+
if (!metafield) {
|
|
7728
|
+
return null;
|
|
7950
7729
|
}
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
<button class="btn-secondary" (click)="onRetry()">Reintentar</button>
|
|
7730
|
+
try {
|
|
7731
|
+
return JSON.parse(metafield.value);
|
|
7954
7732
|
}
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7733
|
+
catch (error) {
|
|
7734
|
+
throw {
|
|
7735
|
+
code: 'PAGE_PARSE_ERROR',
|
|
7736
|
+
message: 'Stored page data is corrupted',
|
|
7737
|
+
original: error,
|
|
7738
|
+
};
|
|
7958
7739
|
}
|
|
7959
|
-
|
|
7960
|
-
</div>
|
|
7961
|
-
</div>
|
|
7962
|
-
} @else {
|
|
7963
|
-
<ng-content></ng-content>
|
|
7964
|
-
|
|
7965
|
-
@if (shopAuthService.isLicenseExpiringSoon() && showExpirationWarning()) {
|
|
7966
|
-
<div class="expiration-warning">
|
|
7967
|
-
<span class="warning-icon">!</span>
|
|
7968
|
-
<span>Tu licencia expira en {{ shopAuthService.daysRemaining() }} dias.</span>
|
|
7969
|
-
<button class="btn-small" (click)="onRenew()">Renovar</button>
|
|
7970
|
-
</div>
|
|
7971
|
-
}
|
|
7740
|
+
}));
|
|
7972
7741
|
}
|
|
7973
|
-
|
|
7974
|
-
|
|
7742
|
+
getDefaultIndex() {
|
|
7743
|
+
return {
|
|
7744
|
+
version: 0,
|
|
7745
|
+
lastUpdated: new Date().toISOString(),
|
|
7746
|
+
pages: [],
|
|
7747
|
+
};
|
|
7748
|
+
}
|
|
7749
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontMetafieldRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7750
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontMetafieldRepository, providedIn: 'root' });
|
|
7751
|
+
}
|
|
7752
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: StorefrontMetafieldRepository, decorators: [{
|
|
7753
|
+
type: Injectable,
|
|
7754
|
+
args: [{ providedIn: 'root' }]
|
|
7755
|
+
}] });
|
|
7975
7756
|
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7757
|
+
/**
|
|
7758
|
+
* Read-only page repository using the Shopify Storefront API.
|
|
7759
|
+
* Used by the public-storefront to read published pages without Admin API access.
|
|
7760
|
+
*/
|
|
7761
|
+
class PageStorefrontRepository {
|
|
7762
|
+
metafieldRepo = inject(StorefrontMetafieldRepository);
|
|
7763
|
+
getPages() {
|
|
7764
|
+
return this.metafieldRepo.getPageIndex().pipe(map$1(index => index.pages
|
|
7765
|
+
.map(entry => ({
|
|
7766
|
+
id: entry.id,
|
|
7767
|
+
slug: entry.slug,
|
|
7768
|
+
title: entry.title,
|
|
7769
|
+
status: entry.status,
|
|
7770
|
+
updatedAt: entry.updatedAt,
|
|
7771
|
+
publishedAt: entry.publishedAt,
|
|
7772
|
+
}))
|
|
7773
|
+
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))));
|
|
7989
7774
|
}
|
|
7990
|
-
|
|
7991
|
-
this.
|
|
7775
|
+
getPublishedPages() {
|
|
7776
|
+
return this.getPages().pipe(map$1(pages => pages.filter(p => p.status === 'published')));
|
|
7992
7777
|
}
|
|
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
|
-
}
|
|
7778
|
+
getPage(id) {
|
|
7779
|
+
return this.metafieldRepo.getPageMetafield(id).pipe(map$1(page => {
|
|
7780
|
+
if (!page) {
|
|
7781
|
+
throw {
|
|
7782
|
+
code: 'PAGE_NOT_FOUND',
|
|
7783
|
+
message: `Page with id ${id} not found`,
|
|
7784
|
+
};
|
|
8008
7785
|
}
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
7786
|
+
return page;
|
|
7787
|
+
}));
|
|
7788
|
+
}
|
|
7789
|
+
getPublishedPageBySlug(slug) {
|
|
7790
|
+
return this.metafieldRepo.getPageIndex().pipe(switchMap(index => {
|
|
7791
|
+
const entry = index.pages.find(p => p.slug === slug && p.status === 'published');
|
|
7792
|
+
if (!entry) {
|
|
7793
|
+
return throwError(() => ({
|
|
7794
|
+
code: 'PAGE_NOT_FOUND',
|
|
7795
|
+
message: `Published page with slug '${slug}' not found`,
|
|
7796
|
+
}));
|
|
8015
7797
|
}
|
|
8016
|
-
this.
|
|
8017
|
-
}
|
|
8018
|
-
finally {
|
|
8019
|
-
this.loading.set(false);
|
|
8020
|
-
}
|
|
7798
|
+
return this.getPage(entry.id);
|
|
7799
|
+
}));
|
|
8021
7800
|
}
|
|
8022
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
8023
|
-
static
|
|
8024
|
-
<div class="login-page">
|
|
8025
|
-
<div class="login-bg">
|
|
8026
|
-
<div class="login-gradient"></div>
|
|
8027
|
-
<div class="login-pattern"></div>
|
|
8028
|
-
<div class="login-glow login-glow--1"></div>
|
|
8029
|
-
<div class="login-glow login-glow--2"></div>
|
|
8030
|
-
</div>
|
|
8031
|
-
|
|
8032
|
-
<div class="login-container">
|
|
8033
|
-
<div class="login-card">
|
|
8034
|
-
<header class="login-header">
|
|
8035
|
-
<div class="login-logo-wrap">
|
|
8036
|
-
<img src="/img/kustomizerlogo.png" alt="Kustomizer" class="login-logo" />
|
|
8037
|
-
</div>
|
|
8038
|
-
<h1 class="login-title">{{ isSignUp() ? 'Create account' : 'Sign in' }}</h1>
|
|
8039
|
-
<p class="login-subtitle">{{ isSignUp() ? 'Create your account to get started' : 'Sign in to your Kustomizer account' }}</p>
|
|
8040
|
-
</header>
|
|
8041
|
-
|
|
8042
|
-
@if (error()) {
|
|
8043
|
-
<div class="login-alert">
|
|
8044
|
-
<svg viewBox="0 0 20 20" fill="currentColor" class="login-alert-icon">
|
|
8045
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
|
|
8046
|
-
</svg>
|
|
8047
|
-
<span>{{ error() }}</span>
|
|
8048
|
-
</div>
|
|
8049
|
-
}
|
|
8050
|
-
|
|
8051
|
-
<form (ngSubmit)="onSubmit()" class="login-form">
|
|
8052
|
-
<div class="login-field">
|
|
8053
|
-
<label for="email" class="login-label">Email</label>
|
|
8054
|
-
<input
|
|
8055
|
-
id="email"
|
|
8056
|
-
type="email"
|
|
8057
|
-
[(ngModel)]="email"
|
|
8058
|
-
name="email"
|
|
8059
|
-
placeholder=""
|
|
8060
|
-
class="login-input"
|
|
8061
|
-
required
|
|
8062
|
-
[disabled]="loading()"
|
|
8063
|
-
/>
|
|
8064
|
-
</div>
|
|
8065
|
-
|
|
8066
|
-
<div class="login-field">
|
|
8067
|
-
<label for="password" class="login-label">Password</label>
|
|
8068
|
-
<div class="login-input-wrap">
|
|
8069
|
-
<input
|
|
8070
|
-
id="password"
|
|
8071
|
-
[type]="showPassword() ? 'text' : 'password'"
|
|
8072
|
-
[(ngModel)]="password"
|
|
8073
|
-
name="password"
|
|
8074
|
-
placeholder="••••••••"
|
|
8075
|
-
class="login-input"
|
|
8076
|
-
required
|
|
8077
|
-
[disabled]="loading()"
|
|
8078
|
-
/>
|
|
8079
|
-
<button type="button" class="login-password-toggle" (click)="togglePassword()" tabindex="-1">
|
|
8080
|
-
@if (showPassword()) {
|
|
8081
|
-
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
8082
|
-
<path fill-rule="evenodd" d="M3.28 2.22a.75.75 0 00-1.06 1.06l14.5 14.5a.75.75 0 101.06-1.06l-1.745-1.745a10.029 10.029 0 003.3-4.38 1.651 1.651 0 000-1.185A10.004 10.004 0 009.999 3a9.956 9.956 0 00-4.744 1.194L3.28 2.22zM7.752 6.69l1.092 1.092a2.5 2.5 0 013.374 3.373l1.091 1.092a4 4 0 00-5.557-5.557z" clip-rule="evenodd" />
|
|
8083
|
-
<path d="M10.748 13.93l2.523 2.523a9.987 9.987 0 01-3.27.547c-4.258 0-7.894-2.66-9.337-6.41a1.651 1.651 0 010-1.186A10.007 10.007 0 012.839 6.02L6.07 9.252a4 4 0 004.678 4.678z" />
|
|
8084
|
-
</svg>
|
|
8085
|
-
} @else {
|
|
8086
|
-
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
8087
|
-
<path d="M10 12.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z" />
|
|
8088
|
-
<path fill-rule="evenodd" d="M.664 10.59a1.651 1.651 0 010-1.186A10.004 10.004 0 0110 3c4.257 0 7.893 2.66 9.336 6.41.147.381.146.804 0 1.186A10.004 10.004 0 0110 17c-4.257 0-7.893-2.66-9.336-6.41zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd" />
|
|
8089
|
-
</svg>
|
|
8090
|
-
}
|
|
8091
|
-
</button>
|
|
8092
|
-
</div>
|
|
8093
|
-
</div>
|
|
8094
|
-
|
|
8095
|
-
<button type="submit" class="login-submit" [disabled]="loading()">
|
|
8096
|
-
@if (loading()) {
|
|
8097
|
-
<span class="login-spinner"></span>
|
|
8098
|
-
{{ isSignUp() ? 'Creating...' : 'Signing in...' }}
|
|
8099
|
-
} @else {
|
|
8100
|
-
{{ isSignUp() ? 'Create account' : 'Sign in' }}
|
|
8101
|
-
}
|
|
8102
|
-
</button>
|
|
8103
|
-
</form>
|
|
8104
|
-
|
|
8105
|
-
<div class="login-divider"><span>or</span></div>
|
|
8106
|
-
|
|
8107
|
-
<button type="button" class="login-switch" (click)="toggleMode()">
|
|
8108
|
-
{{ isSignUp() ? 'Already have an account? Sign in' : "Don't have an account? Sign up" }}
|
|
8109
|
-
</button>
|
|
8110
|
-
</div>
|
|
8111
|
-
|
|
8112
|
-
<footer class="login-footer">
|
|
8113
|
-
<span class="login-footer-text">Kustomizer</span>
|
|
8114
|
-
<span class="login-footer-dot">·</span>
|
|
8115
|
-
<a href="https://kustomizer.net" target="_blank" rel="noopener noreferrer" class="login-footer-link">kustomizer.net</a>
|
|
8116
|
-
</footer>
|
|
8117
|
-
</div>
|
|
8118
|
-
</div>
|
|
8119
|
-
`, isInline: true, styles: [":host{display:block}.login-page{--k-primary: #bdf366;--k-primary-hover: #a8e050;--k-primary-text: #1a1f0e;--k-bg: #0c0f08;--k-card: #ffffff;--k-border: #e5e7eb;--k-border-focus: #bdf366;--k-text: #1f2937;--k-text-secondary: #6b7280;--k-text-muted: #9ca3af;--k-error: #dc2626;--k-error-bg: #fef2f2;--k-error-border: #fecaca;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--k-bg);color:var(--k-text);padding:20px;position:relative;overflow:hidden}.login-bg{position:fixed;inset:0;pointer-events:none;z-index:0}.login-gradient{position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(189,243,102,.15),transparent),radial-gradient(ellipse 60% 40% at 100% 100%,rgba(189,243,102,.08),transparent),linear-gradient(180deg,#0c0f08,#111a0a)}.login-pattern{position:absolute;inset:0;background-image:radial-gradient(rgba(189,243,102,.07) 1px,transparent 1px);background-size:32px 32px;mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent);-webkit-mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent)}.login-glow{position:absolute;border-radius:50%;filter:blur(80px);opacity:.6}.login-glow--1{width:400px;height:400px;background:#bdf3661f;top:-150px;left:50%;transform:translate(-50%);animation:glowPulse 8s ease-in-out infinite}.login-glow--2{width:300px;height:300px;background:#bdf3660f;bottom:-100px;right:-50px;animation:glowPulse2 10s ease-in-out infinite}@keyframes glowPulse{0%,to{opacity:.4;transform:translate(-50%) scale(1)}50%{opacity:.7;transform:translate(-50%) scale(1.1)}}@keyframes glowPulse2{0%,to{opacity:.3;transform:scale(1)}50%{opacity:.5;transform:scale(1.15)}}.login-container{position:relative;z-index:1;width:100%;max-width:400px;animation:fadeIn .5s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.login-card{background:var(--k-card);border-radius:20px;padding:40px 32px;box-shadow:0 0 0 1px #ffffff1a,0 25px 50px -12px #00000080,0 0 100px -20px #bdf36633}.login-header{text-align:center;margin-bottom:32px}.login-logo-wrap{margin-bottom:20px}.login-logo{width:56px;height:56px;border-radius:12px}.login-title{font-size:24px;font-weight:600;color:var(--k-text);margin:0 0 8px;letter-spacing:-.3px}.login-subtitle{font-size:14px;color:var(--k-text-secondary);margin:0;font-weight:400}.login-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;font-size:14px;margin-bottom:20px;background:var(--k-error-bg);border:1px solid var(--k-error-border);color:var(--k-error)}.login-alert-icon{width:18px;height:18px;flex-shrink:0;margin-top:1px}.login-form{display:flex;flex-direction:column;gap:20px}.login-field{display:flex;flex-direction:column;gap:6px}.login-label{font-size:14px;font-weight:500;color:var(--k-text)}.login-input-wrap{position:relative;display:flex;align-items:center}.login-input{width:100%;background:var(--k-card);border:1px solid var(--k-border);border-radius:10px;padding:12px 14px;font-size:15px;font-family:inherit;color:var(--k-text);outline:none;transition:border-color .2s ease,box-shadow .2s ease;box-sizing:border-box}.login-input-wrap .login-input{padding-right:44px}.login-input::placeholder{color:var(--k-text-muted)}.login-input:hover{border-color:#d1d5db}.login-input:focus{border-color:var(--k-border-focus);box-shadow:0 0 0 3px #bdf36626}.login-input:disabled{background:#f3f4f6;cursor:not-allowed;opacity:.7}.login-password-toggle{position:absolute;right:10px;background:none;border:none;padding:6px;cursor:pointer;color:var(--k-text-muted);transition:color .2s ease;display:flex;align-items:center;justify-content:center;border-radius:6px}.login-password-toggle:hover{color:var(--k-primary-text);background:#bdf36626}.login-password-toggle svg{width:18px;height:18px}.login-submit{width:100%;background:var(--k-primary);color:var(--k-primary-text);border:none;border-radius:10px;padding:14px 20px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s ease,transform .1s ease;margin-top:4px;display:flex;align-items:center;justify-content:center;gap:8px}.login-submit:hover:not(:disabled){background:var(--k-primary-hover)}.login-submit:active:not(:disabled){transform:scale(.98)}.login-submit:disabled{opacity:.6;cursor:not-allowed}.login-spinner{width:16px;height:16px;border:2px solid transparent;border-top-color:var(--k-primary-text);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.login-divider{display:flex;align-items:center;gap:14px;margin:24px 0}.login-divider:before,.login-divider:after{content:\"\";flex:1;height:1px;background:var(--k-border)}.login-divider span{font-size:13px;color:var(--k-text-muted);text-transform:lowercase}.login-switch{width:100%;background:transparent;border:1px solid var(--k-border);border-radius:10px;padding:12px 16px;font-size:14px;font-weight:500;font-family:inherit;color:var(--k-text-secondary);cursor:pointer;transition:all .2s ease}.login-switch:hover{background:#bdf36614;border-color:#bdf3664d;color:var(--k-primary-text)}.login-footer{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px}.login-footer-text{font-size:13px;color:#fff6;font-weight:500}.login-footer-link{font-size:13px;color:#ffffff80;text-decoration:none;transition:color .2s ease}.login-footer-link:hover{color:var(--k-primary)}.login-footer-dot{color:#ffffff4d;font-size:12px}@media(max-width:440px){.login-page{padding:16px}.login-card{padding:32px 24px}.login-title{font-size:22px}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
|
|
8120
|
-
}
|
|
8121
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: LoginComponent, decorators: [{
|
|
8122
|
-
type: Component,
|
|
8123
|
-
args: [{ selector: 'lib-login', standalone: true, imports: [CommonModule, FormsModule], template: `
|
|
8124
|
-
<div class="login-page">
|
|
8125
|
-
<div class="login-bg">
|
|
8126
|
-
<div class="login-gradient"></div>
|
|
8127
|
-
<div class="login-pattern"></div>
|
|
8128
|
-
<div class="login-glow login-glow--1"></div>
|
|
8129
|
-
<div class="login-glow login-glow--2"></div>
|
|
8130
|
-
</div>
|
|
8131
|
-
|
|
8132
|
-
<div class="login-container">
|
|
8133
|
-
<div class="login-card">
|
|
8134
|
-
<header class="login-header">
|
|
8135
|
-
<div class="login-logo-wrap">
|
|
8136
|
-
<img src="/img/kustomizerlogo.png" alt="Kustomizer" class="login-logo" />
|
|
8137
|
-
</div>
|
|
8138
|
-
<h1 class="login-title">{{ isSignUp() ? 'Create account' : 'Sign in' }}</h1>
|
|
8139
|
-
<p class="login-subtitle">{{ isSignUp() ? 'Create your account to get started' : 'Sign in to your Kustomizer account' }}</p>
|
|
8140
|
-
</header>
|
|
8141
|
-
|
|
8142
|
-
@if (error()) {
|
|
8143
|
-
<div class="login-alert">
|
|
8144
|
-
<svg viewBox="0 0 20 20" fill="currentColor" class="login-alert-icon">
|
|
8145
|
-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z" clip-rule="evenodd" />
|
|
8146
|
-
</svg>
|
|
8147
|
-
<span>{{ error() }}</span>
|
|
8148
|
-
</div>
|
|
8149
|
-
}
|
|
8150
|
-
|
|
8151
|
-
<form (ngSubmit)="onSubmit()" class="login-form">
|
|
8152
|
-
<div class="login-field">
|
|
8153
|
-
<label for="email" class="login-label">Email</label>
|
|
8154
|
-
<input
|
|
8155
|
-
id="email"
|
|
8156
|
-
type="email"
|
|
8157
|
-
[(ngModel)]="email"
|
|
8158
|
-
name="email"
|
|
8159
|
-
placeholder=""
|
|
8160
|
-
class="login-input"
|
|
8161
|
-
required
|
|
8162
|
-
[disabled]="loading()"
|
|
8163
|
-
/>
|
|
8164
|
-
</div>
|
|
8165
|
-
|
|
8166
|
-
<div class="login-field">
|
|
8167
|
-
<label for="password" class="login-label">Password</label>
|
|
8168
|
-
<div class="login-input-wrap">
|
|
8169
|
-
<input
|
|
8170
|
-
id="password"
|
|
8171
|
-
[type]="showPassword() ? 'text' : 'password'"
|
|
8172
|
-
[(ngModel)]="password"
|
|
8173
|
-
name="password"
|
|
8174
|
-
placeholder="••••••••"
|
|
8175
|
-
class="login-input"
|
|
8176
|
-
required
|
|
8177
|
-
[disabled]="loading()"
|
|
8178
|
-
/>
|
|
8179
|
-
<button type="button" class="login-password-toggle" (click)="togglePassword()" tabindex="-1">
|
|
8180
|
-
@if (showPassword()) {
|
|
8181
|
-
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
8182
|
-
<path fill-rule="evenodd" d="M3.28 2.22a.75.75 0 00-1.06 1.06l14.5 14.5a.75.75 0 101.06-1.06l-1.745-1.745a10.029 10.029 0 003.3-4.38 1.651 1.651 0 000-1.185A10.004 10.004 0 009.999 3a9.956 9.956 0 00-4.744 1.194L3.28 2.22zM7.752 6.69l1.092 1.092a2.5 2.5 0 013.374 3.373l1.091 1.092a4 4 0 00-5.557-5.557z" clip-rule="evenodd" />
|
|
8183
|
-
<path d="M10.748 13.93l2.523 2.523a9.987 9.987 0 01-3.27.547c-4.258 0-7.894-2.66-9.337-6.41a1.651 1.651 0 010-1.186A10.007 10.007 0 012.839 6.02L6.07 9.252a4 4 0 004.678 4.678z" />
|
|
8184
|
-
</svg>
|
|
8185
|
-
} @else {
|
|
8186
|
-
<svg viewBox="0 0 20 20" fill="currentColor">
|
|
8187
|
-
<path d="M10 12.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5z" />
|
|
8188
|
-
<path fill-rule="evenodd" d="M.664 10.59a1.651 1.651 0 010-1.186A10.004 10.004 0 0110 3c4.257 0 7.893 2.66 9.336 6.41.147.381.146.804 0 1.186A10.004 10.004 0 0110 17c-4.257 0-7.893-2.66-9.336-6.41zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd" />
|
|
8189
|
-
</svg>
|
|
8190
|
-
}
|
|
8191
|
-
</button>
|
|
8192
|
-
</div>
|
|
8193
|
-
</div>
|
|
8194
|
-
|
|
8195
|
-
<button type="submit" class="login-submit" [disabled]="loading()">
|
|
8196
|
-
@if (loading()) {
|
|
8197
|
-
<span class="login-spinner"></span>
|
|
8198
|
-
{{ isSignUp() ? 'Creating...' : 'Signing in...' }}
|
|
8199
|
-
} @else {
|
|
8200
|
-
{{ isSignUp() ? 'Create account' : 'Sign in' }}
|
|
8201
|
-
}
|
|
8202
|
-
</button>
|
|
8203
|
-
</form>
|
|
8204
|
-
|
|
8205
|
-
<div class="login-divider"><span>or</span></div>
|
|
8206
|
-
|
|
8207
|
-
<button type="button" class="login-switch" (click)="toggleMode()">
|
|
8208
|
-
{{ isSignUp() ? 'Already have an account? Sign in' : "Don't have an account? Sign up" }}
|
|
8209
|
-
</button>
|
|
8210
|
-
</div>
|
|
8211
|
-
|
|
8212
|
-
<footer class="login-footer">
|
|
8213
|
-
<span class="login-footer-text">Kustomizer</span>
|
|
8214
|
-
<span class="login-footer-dot">·</span>
|
|
8215
|
-
<a href="https://kustomizer.net" target="_blank" rel="noopener noreferrer" class="login-footer-link">kustomizer.net</a>
|
|
8216
|
-
</footer>
|
|
8217
|
-
</div>
|
|
8218
|
-
</div>
|
|
8219
|
-
`, styles: [":host{display:block}.login-page{--k-primary: #bdf366;--k-primary-hover: #a8e050;--k-primary-text: #1a1f0e;--k-bg: #0c0f08;--k-card: #ffffff;--k-border: #e5e7eb;--k-border-focus: #bdf366;--k-text: #1f2937;--k-text-secondary: #6b7280;--k-text-muted: #9ca3af;--k-error: #dc2626;--k-error-bg: #fef2f2;--k-error-border: #fecaca;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--k-bg);color:var(--k-text);padding:20px;position:relative;overflow:hidden}.login-bg{position:fixed;inset:0;pointer-events:none;z-index:0}.login-gradient{position:absolute;inset:0;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(189,243,102,.15),transparent),radial-gradient(ellipse 60% 40% at 100% 100%,rgba(189,243,102,.08),transparent),linear-gradient(180deg,#0c0f08,#111a0a)}.login-pattern{position:absolute;inset:0;background-image:radial-gradient(rgba(189,243,102,.07) 1px,transparent 1px);background-size:32px 32px;mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent);-webkit-mask-image:radial-gradient(ellipse 100% 80% at 50% 20%,black,transparent)}.login-glow{position:absolute;border-radius:50%;filter:blur(80px);opacity:.6}.login-glow--1{width:400px;height:400px;background:#bdf3661f;top:-150px;left:50%;transform:translate(-50%);animation:glowPulse 8s ease-in-out infinite}.login-glow--2{width:300px;height:300px;background:#bdf3660f;bottom:-100px;right:-50px;animation:glowPulse2 10s ease-in-out infinite}@keyframes glowPulse{0%,to{opacity:.4;transform:translate(-50%) scale(1)}50%{opacity:.7;transform:translate(-50%) scale(1.1)}}@keyframes glowPulse2{0%,to{opacity:.3;transform:scale(1)}50%{opacity:.5;transform:scale(1.15)}}.login-container{position:relative;z-index:1;width:100%;max-width:400px;animation:fadeIn .5s ease}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.login-card{background:var(--k-card);border-radius:20px;padding:40px 32px;box-shadow:0 0 0 1px #ffffff1a,0 25px 50px -12px #00000080,0 0 100px -20px #bdf36633}.login-header{text-align:center;margin-bottom:32px}.login-logo-wrap{margin-bottom:20px}.login-logo{width:56px;height:56px;border-radius:12px}.login-title{font-size:24px;font-weight:600;color:var(--k-text);margin:0 0 8px;letter-spacing:-.3px}.login-subtitle{font-size:14px;color:var(--k-text-secondary);margin:0;font-weight:400}.login-alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;border-radius:10px;font-size:14px;margin-bottom:20px;background:var(--k-error-bg);border:1px solid var(--k-error-border);color:var(--k-error)}.login-alert-icon{width:18px;height:18px;flex-shrink:0;margin-top:1px}.login-form{display:flex;flex-direction:column;gap:20px}.login-field{display:flex;flex-direction:column;gap:6px}.login-label{font-size:14px;font-weight:500;color:var(--k-text)}.login-input-wrap{position:relative;display:flex;align-items:center}.login-input{width:100%;background:var(--k-card);border:1px solid var(--k-border);border-radius:10px;padding:12px 14px;font-size:15px;font-family:inherit;color:var(--k-text);outline:none;transition:border-color .2s ease,box-shadow .2s ease;box-sizing:border-box}.login-input-wrap .login-input{padding-right:44px}.login-input::placeholder{color:var(--k-text-muted)}.login-input:hover{border-color:#d1d5db}.login-input:focus{border-color:var(--k-border-focus);box-shadow:0 0 0 3px #bdf36626}.login-input:disabled{background:#f3f4f6;cursor:not-allowed;opacity:.7}.login-password-toggle{position:absolute;right:10px;background:none;border:none;padding:6px;cursor:pointer;color:var(--k-text-muted);transition:color .2s ease;display:flex;align-items:center;justify-content:center;border-radius:6px}.login-password-toggle:hover{color:var(--k-primary-text);background:#bdf36626}.login-password-toggle svg{width:18px;height:18px}.login-submit{width:100%;background:var(--k-primary);color:var(--k-primary-text);border:none;border-radius:10px;padding:14px 20px;font-size:15px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .2s ease,transform .1s ease;margin-top:4px;display:flex;align-items:center;justify-content:center;gap:8px}.login-submit:hover:not(:disabled){background:var(--k-primary-hover)}.login-submit:active:not(:disabled){transform:scale(.98)}.login-submit:disabled{opacity:.6;cursor:not-allowed}.login-spinner{width:16px;height:16px;border:2px solid transparent;border-top-color:var(--k-primary-text);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.login-divider{display:flex;align-items:center;gap:14px;margin:24px 0}.login-divider:before,.login-divider:after{content:\"\";flex:1;height:1px;background:var(--k-border)}.login-divider span{font-size:13px;color:var(--k-text-muted);text-transform:lowercase}.login-switch{width:100%;background:transparent;border:1px solid var(--k-border);border-radius:10px;padding:12px 16px;font-size:14px;font-weight:500;font-family:inherit;color:var(--k-text-secondary);cursor:pointer;transition:all .2s ease}.login-switch:hover{background:#bdf36614;border-color:#bdf3664d;color:var(--k-primary-text)}.login-footer{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px}.login-footer-text{font-size:13px;color:#fff6;font-weight:500}.login-footer-link{font-size:13px;color:#ffffff80;text-decoration:none;transition:color .2s ease}.login-footer-link:hover{color:var(--k-primary)}.login-footer-dot{color:#ffffff4d;font-size:12px}@media(max-width:440px){.login-page{padding:16px}.login-card{padding:32px 24px}.login-title{font-size:22px}}\n"] }]
|
|
8220
|
-
}], propDecorators: { loginSuccess: [{ type: i0.Output, args: ["loginSuccess"] }] } });
|
|
8221
|
-
|
|
8222
|
-
class ActivateLicenseComponent {
|
|
8223
|
-
shopAuthService = inject(ShopAuthService);
|
|
8224
|
-
viewState = computed(() => {
|
|
8225
|
-
if (this.shopAuthService.loading()) {
|
|
8226
|
-
return 'loading';
|
|
8227
|
-
}
|
|
8228
|
-
if (!this.shopAuthService.shop()) {
|
|
8229
|
-
return 'no-store';
|
|
8230
|
-
}
|
|
8231
|
-
return 'license-expired';
|
|
8232
|
-
}, ...(ngDevMode ? [{ debugName: "viewState" }] : []));
|
|
8233
|
-
ngOnInit() {
|
|
8234
|
-
this.shopAuthService.authenticate();
|
|
8235
|
-
}
|
|
8236
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ActivateLicenseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8237
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ActivateLicenseComponent, isStandalone: true, selector: "lib-activate-license", ngImport: i0, template: `
|
|
8238
|
-
<div class="activate-page">
|
|
8239
|
-
<div class="activate-bg">
|
|
8240
|
-
<div class="activate-glow"></div>
|
|
8241
|
-
<div class="activate-grid"></div>
|
|
8242
|
-
</div>
|
|
8243
|
-
|
|
8244
|
-
<div class="activate-card">
|
|
8245
|
-
@switch (viewState()) {
|
|
8246
|
-
@case ('loading') {
|
|
8247
|
-
<div class="activate-loading">
|
|
8248
|
-
<span class="pulse-dot"></span>
|
|
8249
|
-
<p>Verificando cuenta...</p>
|
|
8250
|
-
</div>
|
|
8251
|
-
}
|
|
8252
|
-
|
|
8253
|
-
@case ('no-store') {
|
|
8254
|
-
<div class="activate-content">
|
|
8255
|
-
<p class="store-name">Tienda no encontrada</p>
|
|
8256
|
-
<p class="license-status">Licencia expirada</p>
|
|
8257
|
-
<a class="renew-button" href="https://kustomizer.net" target="_blank" rel="noopener noreferrer">
|
|
8258
|
-
Renovar en kustomizer.net
|
|
8259
|
-
</a>
|
|
8260
|
-
</div>
|
|
8261
|
-
}
|
|
8262
|
-
|
|
8263
|
-
@case ('license-expired') {
|
|
8264
|
-
<div class="activate-content">
|
|
8265
|
-
<p class="store-name">{{ shopAuthService.shopName() }}</p>
|
|
8266
|
-
<p class="license-status">Licencia expirada</p>
|
|
8267
|
-
<a class="renew-button" href="https://kustomizer.net" target="_blank" rel="noopener noreferrer">
|
|
8268
|
-
Renovar en kustomizer.net
|
|
8269
|
-
</a>
|
|
8270
|
-
</div>
|
|
8271
|
-
}
|
|
8272
|
-
}
|
|
8273
|
-
</div>
|
|
8274
|
-
</div>
|
|
8275
|
-
`, isInline: true, styles: ["@import\"https://fonts.googleapis.com/css2?family=Newsreader:wght@400;500;600&family=Spline+Sans:wght@400;500;600&display=swap\";:host{display:block}.activate-page{--k-accent: #bdf366;--k-accent-strong: #a8e050;--k-ink: #11140c;--k-ink-muted: rgba(17, 20, 12, .6);--k-ink-faint: rgba(17, 20, 12, .45);--k-ink-ghost: rgba(17, 20, 12, .3);--k-surface: #f7f8f1;--k-surface-strong: #ffffff;--k-outline: rgba(17, 20, 12, .12);--k-shadow: 0 40px 100px -60px rgba(12, 15, 8, .6);min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0c0f08;padding:24px;position:relative;overflow:hidden;font-family:Spline Sans,Segoe UI,sans-serif;color:var(--k-ink)}.activate-bg{position:absolute;inset:0;pointer-events:none}.activate-glow{position:absolute;width:520px;height:520px;left:50%;top:-200px;transform:translate(-50%);background:radial-gradient(circle,rgba(189,243,102,.22),transparent 70%);filter:blur(10px);opacity:.8}.activate-grid{position:absolute;inset:0;background-image:linear-gradient(rgba(189,243,102,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(189,243,102,.08) 1px,transparent 1px);background-size:40px 40px;mask-image:radial-gradient(circle at 50% 10%,black,transparent 70%);-webkit-mask-image:radial-gradient(circle at 50% 10%,black,transparent 70%);opacity:.4}.activate-card{position:relative;z-index:1;width:min(480px,92vw);background:var(--k-surface-strong);border-radius:24px;padding:48px 40px;box-shadow:var(--k-shadow);border:1px solid rgba(255,255,255,.2);animation:rise .5s ease both}@keyframes rise{0%{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}.activate-loading{text-align:center;color:#ffffffb3;font-size:.9rem}.activate-loading p{margin:12px 0 0}.pulse-dot{width:10px;height:10px;border-radius:999px;background:var(--k-accent);display:inline-block;animation:pulse 1.4s ease-in-out infinite}@keyframes pulse{0%,to{transform:scale(1);opacity:.6}50%{transform:scale(1.5);opacity:1}}.activate-content{display:flex;flex-direction:column;gap:16px;align-items:center;text-align:center}.store-name{margin:0;font-family:Newsreader,Times New Roman,serif;font-size:1.6rem;letter-spacing:-.4px;color:var(--k-ink)}.license-status{margin:0;font-size:.95rem;text-transform:uppercase;letter-spacing:.2em;color:var(--k-ink-ghost)}.renew-button{margin-top:16px;padding:12px 22px;border-radius:999px;border:1px solid var(--k-outline);color:var(--k-ink);text-decoration:none;font-size:.9rem;font-weight:600;letter-spacing:.02em;background:var(--k-surface);transition:all .2s ease}.renew-button:hover{border-color:#11140c47;background:#fff;transform:translateY(-1px)}@media(max-width:480px){.activate-card{padding:36px 26px}.store-name{font-size:1.4rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
7801
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageStorefrontRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7802
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageStorefrontRepository, providedIn: 'root' });
|
|
8276
7803
|
}
|
|
8277
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
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"] }]
|
|
7804
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PageStorefrontRepository, decorators: [{
|
|
7805
|
+
type: Injectable,
|
|
7806
|
+
args: [{ providedIn: 'root' }]
|
|
8318
7807
|
}] });
|
|
8319
7808
|
|
|
8320
|
-
|
|
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 {
|
|
7809
|
+
/**
|
|
7810
|
+
* Loads a component manifest from a remote storefront URL and registers
|
|
7811
|
+
* the entries in the ComponentRegistryService as "virtual" definitions
|
|
7812
|
+
* (no Angular component class — only metadata for palette and property panel).
|
|
7813
|
+
*/
|
|
7814
|
+
class ManifestLoaderService {
|
|
8622
7815
|
http = inject(HttpClient);
|
|
8623
|
-
|
|
8624
|
-
authService = inject(SupabaseAuthService);
|
|
8625
|
-
shopAuthService = inject(ShopAuthService);
|
|
8626
|
-
// Estado
|
|
8627
|
-
_team = signal(null, ...(ngDevMode ? [{ debugName: "_team" }] : []));
|
|
8628
|
-
_loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : []));
|
|
8629
|
-
_error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : []));
|
|
8630
|
-
team = this._team.asReadonly();
|
|
8631
|
-
loading = this._loading.asReadonly();
|
|
8632
|
-
error = this._error.asReadonly();
|
|
8633
|
-
// Computed
|
|
8634
|
-
members = computed(() => this._team()?.members ?? [], ...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
8635
|
-
invitations = computed(() => this._team()?.invitations ?? [], ...(ngDevMode ? [{ debugName: "invitations" }] : []));
|
|
8636
|
-
currentUser = computed(() => this._team()?.current_user ?? null, ...(ngDevMode ? [{ debugName: "currentUser" }] : []));
|
|
8637
|
-
activeMembers = computed(() => this.members().filter((m) => m.status === 'active'), ...(ngDevMode ? [{ debugName: "activeMembers" }] : []));
|
|
8638
|
-
pendingInvitations = computed(() => this.invitations().filter((i) => !i.is_expired), ...(ngDevMode ? [{ debugName: "pendingInvitations" }] : []));
|
|
8639
|
-
canInvite = computed(() => this._team()?.current_user?.can_invite ?? false, ...(ngDevMode ? [{ debugName: "canInvite" }] : []));
|
|
8640
|
-
/**
|
|
8641
|
-
* Carga el equipo de la shop actual
|
|
8642
|
-
*/
|
|
8643
|
-
async loadTeam() {
|
|
8644
|
-
const shopId = this.shopAuthService.shopId();
|
|
8645
|
-
if (!shopId) {
|
|
8646
|
-
this._error.set('No shop selected');
|
|
8647
|
-
return null;
|
|
8648
|
-
}
|
|
8649
|
-
const token = this.authService.accessToken();
|
|
8650
|
-
if (!token) {
|
|
8651
|
-
this._error.set('Not authenticated');
|
|
8652
|
-
return null;
|
|
8653
|
-
}
|
|
8654
|
-
this._loading.set(true);
|
|
8655
|
-
this._error.set(null);
|
|
8656
|
-
try {
|
|
8657
|
-
const url = this.config
|
|
8658
|
-
? `${this.config.supabaseUrl}/functions/v1/shop_team`
|
|
8659
|
-
: '/functions/v1/shop_team';
|
|
8660
|
-
const response = await firstValueFrom(this.http.post(url, { shop_id: shopId }, {
|
|
8661
|
-
headers: {
|
|
8662
|
-
'Authorization': `Bearer ${token}`,
|
|
8663
|
-
'Content-Type': 'application/json',
|
|
8664
|
-
},
|
|
8665
|
-
}));
|
|
8666
|
-
this._team.set(response);
|
|
8667
|
-
this._loading.set(false);
|
|
8668
|
-
return response;
|
|
8669
|
-
}
|
|
8670
|
-
catch (err) {
|
|
8671
|
-
console.error('[ShopTeamService] Load team failed:', err);
|
|
8672
|
-
const httpError = err;
|
|
8673
|
-
this._error.set(httpError.error?.error || 'Failed to load team');
|
|
8674
|
-
this._loading.set(false);
|
|
8675
|
-
return null;
|
|
8676
|
-
}
|
|
8677
|
-
}
|
|
7816
|
+
registry = inject(ComponentRegistryService);
|
|
8678
7817
|
/**
|
|
8679
|
-
*
|
|
7818
|
+
* Fetch the manifest from the given URL and register all components.
|
|
8680
7819
|
*/
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
if (!shopId) {
|
|
8684
|
-
this._error.set('No shop selected');
|
|
8685
|
-
return null;
|
|
8686
|
-
}
|
|
8687
|
-
const token = this.authService.accessToken();
|
|
8688
|
-
if (!token) {
|
|
8689
|
-
this._error.set('Not authenticated');
|
|
8690
|
-
return null;
|
|
8691
|
-
}
|
|
8692
|
-
this._loading.set(true);
|
|
8693
|
-
this._error.set(null);
|
|
8694
|
-
try {
|
|
8695
|
-
const url = this.config
|
|
8696
|
-
? `${this.config.supabaseUrl}/functions/v1/invite_user`
|
|
8697
|
-
: '/functions/v1/invite_user';
|
|
8698
|
-
const response = await firstValueFrom(this.http.post(url, {
|
|
8699
|
-
shop_id: shopId,
|
|
8700
|
-
email: email.toLowerCase().trim(),
|
|
8701
|
-
role,
|
|
8702
|
-
message: message?.trim() || undefined,
|
|
8703
|
-
}, {
|
|
8704
|
-
headers: {
|
|
8705
|
-
'Authorization': `Bearer ${token}`,
|
|
8706
|
-
'Content-Type': 'application/json',
|
|
8707
|
-
},
|
|
8708
|
-
}));
|
|
8709
|
-
// Recargar equipo para mostrar la invitación
|
|
8710
|
-
await this.loadTeam();
|
|
8711
|
-
this._loading.set(false);
|
|
8712
|
-
return response;
|
|
8713
|
-
}
|
|
8714
|
-
catch (err) {
|
|
8715
|
-
console.error('[ShopTeamService] Invite user failed:', err);
|
|
8716
|
-
const httpError = err;
|
|
8717
|
-
this._error.set(httpError.error?.error || 'Failed to invite user');
|
|
8718
|
-
this._loading.set(false);
|
|
8719
|
-
return null;
|
|
8720
|
-
}
|
|
7820
|
+
loadManifest(url) {
|
|
7821
|
+
return this.http.get(url).pipe(tap((manifest) => this.registerManifestComponents(manifest)));
|
|
8721
7822
|
}
|
|
8722
7823
|
/**
|
|
8723
|
-
*
|
|
7824
|
+
* Register manifest entries in the ComponentRegistryService.
|
|
7825
|
+
* Each entry becomes a ComponentDefinition without a `component` field.
|
|
8724
7826
|
*/
|
|
8725
|
-
|
|
8726
|
-
const
|
|
8727
|
-
|
|
8728
|
-
this.
|
|
8729
|
-
return null;
|
|
7827
|
+
registerManifestComponents(manifest) {
|
|
7828
|
+
for (const entry of manifest.components) {
|
|
7829
|
+
const definition = this.manifestEntryToDefinition(entry);
|
|
7830
|
+
this.registry.register(definition);
|
|
8730
7831
|
}
|
|
8731
|
-
this._loading.set(true);
|
|
8732
|
-
this._error.set(null);
|
|
8733
|
-
try {
|
|
8734
|
-
const url = this.config
|
|
8735
|
-
? `${this.config.supabaseUrl}/functions/v1/accept_invitation`
|
|
8736
|
-
: '/functions/v1/accept_invitation';
|
|
8737
|
-
const response = await firstValueFrom(this.http.post(url, {
|
|
8738
|
-
token: invitationToken,
|
|
8739
|
-
}, {
|
|
8740
|
-
headers: {
|
|
8741
|
-
'Authorization': `Bearer ${authToken}`,
|
|
8742
|
-
'Content-Type': 'application/json',
|
|
8743
|
-
},
|
|
8744
|
-
}));
|
|
8745
|
-
this._loading.set(false);
|
|
8746
|
-
return response;
|
|
8747
|
-
}
|
|
8748
|
-
catch (err) {
|
|
8749
|
-
console.error('[ShopTeamService] Accept invitation failed:', err);
|
|
8750
|
-
const httpError = err;
|
|
8751
|
-
this._error.set(httpError.error?.error || 'Failed to accept invitation');
|
|
8752
|
-
this._loading.set(false);
|
|
8753
|
-
return null;
|
|
8754
|
-
}
|
|
8755
|
-
}
|
|
8756
|
-
/**
|
|
8757
|
-
* Genera el URL de invitación (para copiar/compartir)
|
|
8758
|
-
*/
|
|
8759
|
-
getInvitationUrl(token, baseUrl) {
|
|
8760
|
-
const base = baseUrl || (typeof window !== 'undefined' ? window.location.origin : '');
|
|
8761
|
-
return `${base}/accept-invitation?token=${token}`;
|
|
8762
7832
|
}
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
|
|
8766
|
-
|
|
8767
|
-
|
|
8768
|
-
|
|
7833
|
+
manifestEntryToDefinition(entry) {
|
|
7834
|
+
return {
|
|
7835
|
+
type: entry.type,
|
|
7836
|
+
name: entry.name,
|
|
7837
|
+
description: entry.description,
|
|
7838
|
+
category: entry.category,
|
|
7839
|
+
icon: entry.icon,
|
|
7840
|
+
props: entry.props,
|
|
7841
|
+
slots: entry.slots,
|
|
7842
|
+
isSection: entry.isSection,
|
|
7843
|
+
isBlock: entry.isBlock,
|
|
7844
|
+
blockScope: entry.blockScope,
|
|
7845
|
+
sectionTypes: entry.sectionTypes,
|
|
7846
|
+
draggable: entry.draggable,
|
|
7847
|
+
deletable: entry.deletable,
|
|
7848
|
+
duplicable: entry.duplicable,
|
|
7849
|
+
tags: entry.tags,
|
|
7850
|
+
order: entry.order,
|
|
7851
|
+
// No `component` — rendering happens in the iframe
|
|
7852
|
+
};
|
|
8769
7853
|
}
|
|
8770
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
8771
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0
|
|
7854
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ManifestLoaderService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
7855
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ManifestLoaderService, providedIn: 'root' });
|
|
8772
7856
|
}
|
|
8773
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
7857
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ManifestLoaderService, decorators: [{
|
|
8774
7858
|
type: Injectable,
|
|
8775
7859
|
args: [{ providedIn: 'root' }]
|
|
8776
7860
|
}] });
|
|
8777
7861
|
|
|
8778
|
-
const SHOPIFY_API_VERSION = '2026-01';
|
|
8779
|
-
const SHOPIFY_CONFIG = new InjectionToken('SHOPIFY_CONFIG');
|
|
8780
|
-
|
|
8781
|
-
const authGuard = async () => {
|
|
8782
|
-
const authService = inject(SupabaseAuthService);
|
|
8783
|
-
const router = inject(Router);
|
|
8784
|
-
await authService.ensureInitialized();
|
|
8785
|
-
if (authService.isAuthenticated()) {
|
|
8786
|
-
return true;
|
|
8787
|
-
}
|
|
8788
|
-
router.navigate(['/login']);
|
|
8789
|
-
return false;
|
|
8790
|
-
};
|
|
8791
|
-
const noAuthGuard = async () => {
|
|
8792
|
-
const authService = inject(SupabaseAuthService);
|
|
8793
|
-
const router = inject(Router);
|
|
8794
|
-
await authService.ensureInitialized();
|
|
8795
|
-
if (!authService.isAuthenticated()) {
|
|
8796
|
-
return true;
|
|
8797
|
-
}
|
|
8798
|
-
router.navigate(['/admin']);
|
|
8799
|
-
return false;
|
|
8800
|
-
};
|
|
8801
|
-
|
|
8802
|
-
const licenseGuard = async () => {
|
|
8803
|
-
const shopAuthService = inject(ShopAuthService);
|
|
8804
|
-
const router = inject(Router);
|
|
8805
|
-
await shopAuthService.authenticate();
|
|
8806
|
-
if (shopAuthService.hasAccess()) {
|
|
8807
|
-
return true;
|
|
8808
|
-
}
|
|
8809
|
-
router.navigate(['/activate']);
|
|
8810
|
-
return false;
|
|
8811
|
-
};
|
|
8812
|
-
const noLicenseGuard = async () => {
|
|
8813
|
-
const shopAuthService = inject(ShopAuthService);
|
|
8814
|
-
const router = inject(Router);
|
|
8815
|
-
await shopAuthService.authenticate();
|
|
8816
|
-
if (!shopAuthService.hasAccess()) {
|
|
8817
|
-
return true;
|
|
8818
|
-
}
|
|
8819
|
-
router.navigate(['/admin']);
|
|
8820
|
-
return false;
|
|
8821
|
-
};
|
|
8822
|
-
|
|
8823
|
-
/**
|
|
8824
|
-
* Guard que verifica:
|
|
8825
|
-
* 1. Usuario autenticado
|
|
8826
|
-
* 2. Usuario tiene acceso a la shop del dominio actual
|
|
8827
|
-
* 3. Licencia activa
|
|
8828
|
-
*
|
|
8829
|
-
* Redirige a:
|
|
8830
|
-
* - /login si no está autenticado
|
|
8831
|
-
* - /no-access si no tiene acceso a la shop
|
|
8832
|
-
* - /license-expired si la licencia expiró
|
|
8833
|
-
*/
|
|
8834
|
-
const shopAuthGuard = async () => {
|
|
8835
|
-
const authService = inject(SupabaseAuthService);
|
|
8836
|
-
const shopAuthService = inject(ShopAuthService);
|
|
8837
|
-
const router = inject(Router);
|
|
8838
|
-
// Esperar inicialización del auth
|
|
8839
|
-
await authService.ensureInitialized();
|
|
8840
|
-
// Verificar autenticación básica
|
|
8841
|
-
if (!authService.isAuthenticated()) {
|
|
8842
|
-
router.navigate(['/login']);
|
|
8843
|
-
return false;
|
|
8844
|
-
}
|
|
8845
|
-
// Autenticar contra la shop
|
|
8846
|
-
await shopAuthService.authenticate();
|
|
8847
|
-
// Verificar acceso
|
|
8848
|
-
if (shopAuthService.hasAccess()) {
|
|
8849
|
-
return true;
|
|
8850
|
-
}
|
|
8851
|
-
// Manejar diferentes tipos de error
|
|
8852
|
-
const error = shopAuthService.error();
|
|
8853
|
-
if (error?.code === 'NOT_AUTHENTICATED') {
|
|
8854
|
-
router.navigate(['/login']);
|
|
8855
|
-
return false;
|
|
8856
|
-
}
|
|
8857
|
-
if (error?.code === 'LICENSE_EXPIRED') {
|
|
8858
|
-
router.navigate(['/license-expired']);
|
|
8859
|
-
return false;
|
|
8860
|
-
}
|
|
8861
|
-
if (error?.code === 'NO_ACCESS' || error?.code === 'SHOP_NOT_FOUND') {
|
|
8862
|
-
router.navigate(['/no-access']);
|
|
8863
|
-
return false;
|
|
8864
|
-
}
|
|
8865
|
-
if (error?.code === 'LOCALHOST_NO_SHOP') {
|
|
8866
|
-
alert('Add ?shop=your-store.myshopify.com to the URL');
|
|
8867
|
-
return false;
|
|
8868
|
-
}
|
|
8869
|
-
if (error?.code === 'USER_INACTIVE') {
|
|
8870
|
-
router.navigate(['/account-inactive']);
|
|
8871
|
-
return false;
|
|
8872
|
-
}
|
|
8873
|
-
// Error desconocido, redirigir a no-access
|
|
8874
|
-
router.navigate(['/no-access']);
|
|
8875
|
-
return false;
|
|
8876
|
-
};
|
|
8877
|
-
/**
|
|
8878
|
-
* Guard que solo permite acceso si el usuario NO tiene acceso a una shop.
|
|
8879
|
-
* Útil para páginas como /login que no deberían ser accesibles si ya tienes acceso.
|
|
8880
|
-
*/
|
|
8881
|
-
const noShopAuthGuard = async () => {
|
|
8882
|
-
const authService = inject(SupabaseAuthService);
|
|
8883
|
-
const shopAuthService = inject(ShopAuthService);
|
|
8884
|
-
const router = inject(Router);
|
|
8885
|
-
await authService.ensureInitialized();
|
|
8886
|
-
// Si no está autenticado, permitir (puede ver login)
|
|
8887
|
-
if (!authService.isAuthenticated()) {
|
|
8888
|
-
return true;
|
|
8889
|
-
}
|
|
8890
|
-
// Verificar acceso a shop
|
|
8891
|
-
await shopAuthService.authenticate();
|
|
8892
|
-
// Si tiene acceso completo, redirigir al admin
|
|
8893
|
-
if (shopAuthService.hasAccess()) {
|
|
8894
|
-
router.navigate(['/admin']);
|
|
8895
|
-
return false;
|
|
8896
|
-
}
|
|
8897
|
-
// No tiene acceso completo, permitir ver la página
|
|
8898
|
-
return true;
|
|
8899
|
-
};
|
|
8900
|
-
/**
|
|
8901
|
-
* Guard que verifica que el usuario tenga rol de admin o owner.
|
|
8902
|
-
* Requiere que shopAuthGuard se haya ejecutado primero.
|
|
8903
|
-
*/
|
|
8904
|
-
const shopAdminGuard = async () => {
|
|
8905
|
-
const shopAuthService = inject(ShopAuthService);
|
|
8906
|
-
const router = inject(Router);
|
|
8907
|
-
// Asegurar que ya se autenticó
|
|
8908
|
-
if (!shopAuthService.initialized()) {
|
|
8909
|
-
await shopAuthService.authenticate();
|
|
8910
|
-
}
|
|
8911
|
-
if (shopAuthService.isAdmin()) {
|
|
8912
|
-
return true;
|
|
8913
|
-
}
|
|
8914
|
-
// No es admin, redirigir a admin (sin permisos de gestión)
|
|
8915
|
-
router.navigate(['/admin']);
|
|
8916
|
-
return false;
|
|
8917
|
-
};
|
|
8918
|
-
/**
|
|
8919
|
-
* Guard que verifica que el usuario pueda editar (owner o admin).
|
|
8920
|
-
* Los readers solo pueden ver, no editar.
|
|
8921
|
-
*/
|
|
8922
|
-
const shopEditorGuard = async () => {
|
|
8923
|
-
const shopAuthService = inject(ShopAuthService);
|
|
8924
|
-
const router = inject(Router);
|
|
8925
|
-
if (!shopAuthService.initialized()) {
|
|
8926
|
-
await shopAuthService.authenticate();
|
|
8927
|
-
}
|
|
8928
|
-
if (shopAuthService.canEdit()) {
|
|
8929
|
-
return true;
|
|
8930
|
-
}
|
|
8931
|
-
// Es reader, mostrar página de solo lectura o redirigir
|
|
8932
|
-
router.navigate(['/admin']);
|
|
8933
|
-
return false;
|
|
8934
|
-
};
|
|
8935
|
-
|
|
8936
7862
|
class VisualEditor {
|
|
8937
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0
|
|
8938
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0
|
|
7863
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7864
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: VisualEditor, isStandalone: true, selector: "lib-visual-editor", ngImport: i0, template: `
|
|
8939
7865
|
<p>
|
|
8940
7866
|
visual-editor works!
|
|
8941
7867
|
</p>
|
|
8942
7868
|
`, isInline: true, styles: [""] });
|
|
8943
7869
|
}
|
|
8944
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0
|
|
7870
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: VisualEditor, decorators: [{
|
|
8945
7871
|
type: Component,
|
|
8946
7872
|
args: [{ selector: 'lib-visual-editor', imports: [], template: `
|
|
8947
7873
|
<p>
|
|
@@ -8959,5 +7885,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
8959
7885
|
* Generated bundle index. Do not edit.
|
|
8960
7886
|
*/
|
|
8961
7887
|
|
|
8962
|
-
export {
|
|
7888
|
+
export { BlockTreeItemComponent, CREATE_METAFIELD_DEFINITION_MUTATION, ComponentRegistryService, DEFAULT_ROUTER_NAVIGATION_CONFIG, DEFAULT_VISUAL_EDITOR_CONFIG, DELETE_METAFIELDS_MUTATION, DELETE_METAFIELD_DEFINITION_MUTATION, DefaultRouterNavigationService, DragDropService, DynamicRendererComponent, EDITOR_COMPONENT_DEFINITIONS, FILES_QUERY, GET_METAFIELD_DEFINITION_QUERY, GET_SHOP_ID_QUERY, GET_SHOP_METAFIELD_QUERY$1 as GET_SHOP_METAFIELD_QUERY, INDEX_KEY$1 as INDEX_KEY, IframeBridgeService, InputPageLoadingStrategy, MAX_METAFIELD_SIZE, ManifestLoaderService, NAMESPACE$1 as NAMESPACE, PageLoadingStrategy, PageManagerComponent, PageService, PageShopifyRepository, PageStorefrontRepository, ROUTER_NAVIGATION_CONFIG, RoutePageLoadingStrategy, SET_METAFIELD_MUTATION, SHOPIFY_CONFIG, STOREFRONT_CONFIG, STOREFRONT_URL, ShopifyFilePickerComponent, ShopifyFilesService, ShopifyGraphQLClient, ShopifyMetafieldRepository, SlotRendererComponent, StorefrontGraphQLClient, StorefrontMetafieldRepository, StorefrontUrlService, USE_IN_MEMORY_PAGES, VISUAL_EDITOR_CONFIG, VISUAL_EDITOR_FEATURE_KEY, VisualEditor, VisualEditorActions, VisualEditorComponent, VisualEditorFacade, VisualEditorNavigation, initialVisualEditorState, provideEditorComponents, provideVisualEditor, provideVisualEditorStore, selectBlocksForSection, selectBlocksForSlot, selectCanRedo, selectCanUndo, selectCurrentPage, selectCurrentPageId, selectCurrentPageSlug, selectCurrentPageStatus, selectCurrentPageTitle, selectCurrentPageVersion, selectDraggedElementId, selectElementById, selectHistory, selectHistoryIndex, selectHistoryLength, selectIsDirty, selectIsDragging, selectIsPageLoaded, selectLastAction, selectSectionById, selectSections, selectSelectedBlock, selectSelectedBlockSlotName, selectSelectedElement, selectSelectedElementId, selectSelectedElementType, selectSelectedSection, selectSelectedSectionId, selectSelectedSectionType, selectVisualEditorState, visualEditorReducer };
|
|
8963
7889
|
//# sourceMappingURL=kustomizer-visual-editor.mjs.map
|