fix(icons): migrate Marketplace Builder to Lucide
Replace every PrimeIcons pi-* usage across the builder: overview page (back link, next-step arrow, section cards, readiness checklist, quick links), sidebar nav (group/section status dots), main layout (back/home/menu/help icons), brand + homepage overview panels (checklist ok/pending dots, contrast warning), footer/homepage/widgets section editors (drag handles, move up/down, duplicate, remove), and the HTML editor toolbar (list/link/image/table/divider/code/embed). Notable correctness fix: PrimeIcons reused pi-bars for both the hamburger menu toggle AND every drag handle - two different meanings sharing one icon (exactly the kind of icon collision the audit calls out). Added a dedicated 'grip' icon (GripVertical) for drag handles so menu and drag-to-reorder are visually distinct. Added a global .spin utility (icon-registry has no built-in spinner animation) for the one loading-spinner icon in the builder overview checklist. All icon-bearing fields (BuilderGroup.icon, BlockCatalogEntry.icon, WIDGET_ICONS, STATUS_ICON, HtmlEditorToolbarCommand.icon, etc.) are now typed AppIconName instead of string. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { ProjectEditorSectionId } from '../models/project-editor.model';
|
import { ProjectEditorSectionId } from '../models/project-editor.model';
|
||||||
|
import { AppIconName } from '../../../shared/ui/icon/icon-registry';
|
||||||
|
|
||||||
export type BuilderGroupId =
|
export type BuilderGroupId =
|
||||||
| 'marketplace'
|
| 'marketplace'
|
||||||
@@ -12,7 +13,7 @@ export type BuilderGroupId =
|
|||||||
|
|
||||||
export interface BuilderGroup {
|
export interface BuilderGroup {
|
||||||
id: BuilderGroupId;
|
id: BuilderGroupId;
|
||||||
icon: string;
|
icon: AppIconName;
|
||||||
labelKey: string;
|
labelKey: string;
|
||||||
descriptionKey: string;
|
descriptionKey: string;
|
||||||
purposeKey: string;
|
purposeKey: string;
|
||||||
@@ -31,7 +32,7 @@ export interface BuilderGroup {
|
|||||||
export const BUILDER_GROUPS: BuilderGroup[] = [
|
export const BUILDER_GROUPS: BuilderGroup[] = [
|
||||||
{
|
{
|
||||||
id: 'marketplace',
|
id: 'marketplace',
|
||||||
icon: 'pi-shop',
|
icon: 'store',
|
||||||
labelKey: 'builder.groupMarketplaceLabel',
|
labelKey: 'builder.groupMarketplaceLabel',
|
||||||
descriptionKey: 'builder.groupMarketplaceDescription',
|
descriptionKey: 'builder.groupMarketplaceDescription',
|
||||||
purposeKey: 'builder.groupMarketplacePurpose',
|
purposeKey: 'builder.groupMarketplacePurpose',
|
||||||
@@ -41,7 +42,7 @@ export const BUILDER_GROUPS: BuilderGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'branding',
|
id: 'branding',
|
||||||
icon: 'pi-palette',
|
icon: 'palette',
|
||||||
labelKey: 'builder.groupBrandingLabel',
|
labelKey: 'builder.groupBrandingLabel',
|
||||||
descriptionKey: 'builder.groupBrandingDescription',
|
descriptionKey: 'builder.groupBrandingDescription',
|
||||||
purposeKey: 'builder.groupBrandingPurpose',
|
purposeKey: 'builder.groupBrandingPurpose',
|
||||||
@@ -53,7 +54,7 @@ export const BUILDER_GROUPS: BuilderGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'homepage',
|
id: 'homepage',
|
||||||
icon: 'pi-home',
|
icon: 'home',
|
||||||
labelKey: 'builder.groupHomepageLabel',
|
labelKey: 'builder.groupHomepageLabel',
|
||||||
descriptionKey: 'builder.groupHomepageDescription',
|
descriptionKey: 'builder.groupHomepageDescription',
|
||||||
purposeKey: 'builder.groupHomepagePurpose',
|
purposeKey: 'builder.groupHomepagePurpose',
|
||||||
@@ -63,7 +64,7 @@ export const BUILDER_GROUPS: BuilderGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'content',
|
id: 'content',
|
||||||
icon: 'pi-file-edit',
|
icon: 'edit',
|
||||||
labelKey: 'builder.groupContentLabel',
|
labelKey: 'builder.groupContentLabel',
|
||||||
descriptionKey: 'builder.groupContentDescription',
|
descriptionKey: 'builder.groupContentDescription',
|
||||||
purposeKey: 'builder.groupContentPurpose',
|
purposeKey: 'builder.groupContentPurpose',
|
||||||
@@ -73,7 +74,7 @@ export const BUILDER_GROUPS: BuilderGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'languages',
|
id: 'languages',
|
||||||
icon: 'pi-globe',
|
icon: 'globe',
|
||||||
labelKey: 'builder.groupLanguagesLabel',
|
labelKey: 'builder.groupLanguagesLabel',
|
||||||
descriptionKey: 'builder.groupLanguagesDescription',
|
descriptionKey: 'builder.groupLanguagesDescription',
|
||||||
purposeKey: 'builder.groupLanguagesPurpose',
|
purposeKey: 'builder.groupLanguagesPurpose',
|
||||||
@@ -83,7 +84,7 @@ export const BUILDER_GROUPS: BuilderGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'features',
|
id: 'features',
|
||||||
icon: 'pi-sliders-h',
|
icon: 'slidersHorizontal',
|
||||||
labelKey: 'builder.groupFeaturesLabel',
|
labelKey: 'builder.groupFeaturesLabel',
|
||||||
descriptionKey: 'builder.groupFeaturesDescription',
|
descriptionKey: 'builder.groupFeaturesDescription',
|
||||||
purposeKey: 'builder.groupFeaturesPurpose',
|
purposeKey: 'builder.groupFeaturesPurpose',
|
||||||
@@ -93,7 +94,7 @@ export const BUILDER_GROUPS: BuilderGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'navigation',
|
id: 'navigation',
|
||||||
icon: 'pi-compass',
|
icon: 'compass',
|
||||||
labelKey: 'builder.groupNavigationLabel',
|
labelKey: 'builder.groupNavigationLabel',
|
||||||
descriptionKey: 'builder.groupNavigationDescription',
|
descriptionKey: 'builder.groupNavigationDescription',
|
||||||
purposeKey: 'builder.groupNavigationPurpose',
|
purposeKey: 'builder.groupNavigationPurpose',
|
||||||
@@ -103,7 +104,7 @@ export const BUILDER_GROUPS: BuilderGroup[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'preview',
|
id: 'preview',
|
||||||
icon: 'pi-desktop',
|
icon: 'monitor',
|
||||||
labelKey: 'builder.groupPreviewLabel',
|
labelKey: 'builder.groupPreviewLabel',
|
||||||
descriptionKey: 'builder.groupPreviewDescription',
|
descriptionKey: 'builder.groupPreviewDescription',
|
||||||
purposeKey: 'builder.groupPreviewPurpose',
|
purposeKey: 'builder.groupPreviewPurpose',
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
(click)="runCommand(item)"
|
(click)="runCommand(item)"
|
||||||
>
|
>
|
||||||
@if (item.icon) {
|
@if (item.icon) {
|
||||||
<span class="pi {{ item.icon }}" aria-hidden="true"></span>
|
<app-icon [name]="item.icon" [size]="16" />
|
||||||
} @else {
|
} @else {
|
||||||
{{ item.label }}
|
{{ item.label }}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,16 @@ import { TranslateService } from '../../../../i18n/translate.service';
|
|||||||
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||||
import { validateHtml } from '../../schema/validators/primitives';
|
import { validateHtml } from '../../schema/validators/primitives';
|
||||||
import { CodeEditorComponent } from '../../../../shared/ui/code-editor/code-editor.component';
|
import { CodeEditorComponent } from '../../../../shared/ui/code-editor/code-editor.component';
|
||||||
|
import { IconComponent } from '../../../../shared/ui/icon/icon.component';
|
||||||
|
import { AppIconName } from '../../../../shared/ui/icon/icon-registry';
|
||||||
|
|
||||||
export interface HtmlEditorToolbarCommand {
|
export interface HtmlEditorToolbarCommand {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
command: string;
|
command: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
/** PrimeIcons class (e.g. 'pi-list'); when set the icon renders instead of the label. */
|
/** Lucide icon name (see icon-registry.ts); when set the icon renders instead of the label. */
|
||||||
icon?: string;
|
icon?: AppIconName;
|
||||||
/** i18n key for the tooltip/aria-label. */
|
/** i18n key for the tooltip/aria-label. */
|
||||||
titleKey?: string;
|
titleKey?: string;
|
||||||
}
|
}
|
||||||
@@ -21,14 +23,14 @@ export const HTML_EDITOR_TOOLBAR: HtmlEditorToolbarCommand[] = [
|
|||||||
{ id: 'underline', label: 'U', command: 'underline', titleKey: 'builder.htmlToolUnderline' },
|
{ id: 'underline', label: 'U', command: 'underline', titleKey: 'builder.htmlToolUnderline' },
|
||||||
{ id: 'h2', label: 'H2', command: 'formatBlock', value: 'H2', titleKey: 'builder.htmlToolHeading' },
|
{ id: 'h2', label: 'H2', command: 'formatBlock', value: 'H2', titleKey: 'builder.htmlToolHeading' },
|
||||||
{ id: 'h3', label: 'H3', command: 'formatBlock', value: 'H3', titleKey: 'builder.htmlToolSubheading' },
|
{ id: 'h3', label: 'H3', command: 'formatBlock', value: 'H3', titleKey: 'builder.htmlToolSubheading' },
|
||||||
{ id: 'ul', label: '', icon: 'pi-list', command: 'insertUnorderedList', titleKey: 'builder.htmlToolBulletList' },
|
{ id: 'ul', label: '', icon: 'list', command: 'insertUnorderedList', titleKey: 'builder.htmlToolBulletList' },
|
||||||
{ id: 'ol', label: '1.', command: 'insertOrderedList', titleKey: 'builder.htmlToolNumberedList' },
|
{ id: 'ol', label: '1.', command: 'insertOrderedList', titleKey: 'builder.htmlToolNumberedList' },
|
||||||
{ id: 'link', label: '', icon: 'pi-link', command: 'createLink', titleKey: 'builder.htmlToolLink' },
|
{ id: 'link', label: '', icon: 'link', command: 'createLink', titleKey: 'builder.htmlToolLink' },
|
||||||
{ id: 'image', label: '', icon: 'pi-image', command: 'insertImage', titleKey: 'builder.htmlToolImage' },
|
{ id: 'image', label: '', icon: 'image', command: 'insertImage', titleKey: 'builder.htmlToolImage' },
|
||||||
{ id: 'table', label: '', icon: 'pi-table', command: 'insertHTML', value: '<table><tr><td> </td><td> </td></tr></table>', titleKey: 'builder.htmlToolTable' },
|
{ id: 'table', label: '', icon: 'table', command: 'insertHTML', value: '<table><tr><td> </td><td> </td></tr></table>', titleKey: 'builder.htmlToolTable' },
|
||||||
{ id: 'hr', label: '', icon: 'pi-minus', command: 'insertHorizontalRule', titleKey: 'builder.htmlToolDivider' },
|
{ id: 'hr', label: '', icon: 'minus', command: 'insertHorizontalRule', titleKey: 'builder.htmlToolDivider' },
|
||||||
{ id: 'codeblock', label: '', icon: 'pi-code', command: 'formatBlock', value: 'PRE', titleKey: 'builder.htmlToolCodeBlock' },
|
{ id: 'codeblock', label: '', icon: 'code', command: 'formatBlock', value: 'PRE', titleKey: 'builder.htmlToolCodeBlock' },
|
||||||
{ id: 'embed', label: '', icon: 'pi-video', command: 'insertEmbed', titleKey: 'builder.htmlToolEmbed' },
|
{ id: 'embed', label: '', icon: 'video', command: 'insertEmbed', titleKey: 'builder.htmlToolEmbed' },
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Visual grouping for the toolbar: text style | headings | lists | insert | advanced. */
|
/** Visual grouping for the toolbar: text style | headings | lists | insert | advanced. */
|
||||||
@@ -43,7 +45,7 @@ export const HTML_EDITOR_TOOLBAR_GROUPS: string[][] = [
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-marketplace-html-editor',
|
selector: 'app-marketplace-html-editor',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TranslatePipe, CodeEditorComponent],
|
imports: [TranslatePipe, CodeEditorComponent, IconComponent],
|
||||||
templateUrl: './marketplace-html-editor.component.html',
|
templateUrl: './marketplace-html-editor.component.html',
|
||||||
styleUrls: ['./marketplace-html-editor.component.scss'],
|
styleUrls: ['./marketplace-html-editor.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
@for (item of groups(); track item.group.id) {
|
@for (item of groups(); track item.group.id) {
|
||||||
<div class="editor-nav-group">
|
<div class="editor-nav-group">
|
||||||
<div class="editor-nav-group__header">
|
<div class="editor-nav-group__header">
|
||||||
<span class="pi {{ item.group.icon }} editor-nav-group__icon" aria-hidden="true"></span>
|
<app-icon [name]="item.group.icon" [size]="18" class="editor-nav-group__icon" />
|
||||||
<span class="editor-nav-group__label">{{ item.group.labelKey | translate }}</span>
|
<span class="editor-nav-group__label">{{ item.group.labelKey | translate }}</span>
|
||||||
<span class="pi {{ item.statusIcon }} editor-nav-group__status editor-nav-group__status--{{ item.status }}" [attr.aria-label]="item.statusLabelKey | translate"></span>
|
<app-icon [name]="item.statusIcon" [size]="14" [ariaLabel]="item.statusLabelKey | translate" class="editor-nav-group__status editor-nav-group__status--{{ item.status }}" />
|
||||||
</div>
|
</div>
|
||||||
<ul class="editor-nav-group__list">
|
<ul class="editor-nav-group__list">
|
||||||
@for (section of item.sections; track section.id) {
|
@for (section of item.sections; track section.id) {
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
class="editor-nav-link"
|
class="editor-nav-link"
|
||||||
(click)="onLinkClick()"
|
(click)="onLinkClick()"
|
||||||
>
|
>
|
||||||
<span class="pi {{ section.statusIcon }} editor-nav-link__status editor-nav-link__status--{{ section.status }}" [attr.aria-label]="section.statusLabelKey | translate"></span>
|
<app-icon [name]="section.statusIcon" [size]="14" [ariaLabel]="section.statusLabelKey | translate" class="editor-nav-link__status editor-nav-link__status--{{ section.status }}" />
|
||||||
<span class="editor-nav-link__label">{{ section.labelKey | translate }}</span>
|
<span class="editor-nav-link__label">{{ section.labelKey | translate }}</span>
|
||||||
@if (section.issueCount > 0) {
|
@if (section.issueCount > 0) {
|
||||||
<span class="editor-nav-badge" [attr.aria-label]="section.issueCount + ' ' + ('builder.title' | translate)">{{ section.issueCount }}</span>
|
<span class="editor-nav-badge" [attr.aria-label]="section.issueCount + ' ' + ('builder.title' | translate)">{{ section.issueCount }}</span>
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import { LangRoutePipe } from '../../../pipes/lang-route.pipe';
|
|||||||
import { ProjectEditorSectionId, BuilderSectionStatus } from '../models/project-editor.model';
|
import { ProjectEditorSectionId, BuilderSectionStatus } from '../models/project-editor.model';
|
||||||
import { ProjectEditorFacade } from '../facade/project-editor.facade';
|
import { ProjectEditorFacade } from '../facade/project-editor.facade';
|
||||||
import { BUILDER_GROUPS, BuilderGroup } from '../builder/builder-groups.model';
|
import { BUILDER_GROUPS, BuilderGroup } from '../builder/builder-groups.model';
|
||||||
|
import { IconComponent } from '../../../shared/ui/icon/icon.component';
|
||||||
|
import { AppIconName } from '../../../shared/ui/icon/icon-registry';
|
||||||
|
|
||||||
const STATUS_ICON: Record<BuilderSectionStatus, string> = {
|
const STATUS_ICON: Record<BuilderSectionStatus, AppIconName> = {
|
||||||
complete: 'pi-check-circle',
|
complete: 'checkCircle',
|
||||||
'in-progress': 'pi-circle-fill',
|
'in-progress': 'circle',
|
||||||
'not-started': 'pi-circle',
|
'not-started': 'circle',
|
||||||
unknown: 'pi-question-circle',
|
unknown: 'help',
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATUS_LABEL_KEY: Record<BuilderSectionStatus, string> = {
|
const STATUS_LABEL_KEY: Record<BuilderSectionStatus, string> = {
|
||||||
@@ -24,7 +26,7 @@ interface NavSectionItem {
|
|||||||
id: ProjectEditorSectionId;
|
id: ProjectEditorSectionId;
|
||||||
labelKey: string;
|
labelKey: string;
|
||||||
status: BuilderSectionStatus;
|
status: BuilderSectionStatus;
|
||||||
statusIcon: string;
|
statusIcon: AppIconName;
|
||||||
statusLabelKey: string;
|
statusLabelKey: string;
|
||||||
modified: boolean;
|
modified: boolean;
|
||||||
issueCount: number;
|
issueCount: number;
|
||||||
@@ -33,7 +35,7 @@ interface NavSectionItem {
|
|||||||
interface NavGroupItem {
|
interface NavGroupItem {
|
||||||
group: BuilderGroup;
|
group: BuilderGroup;
|
||||||
status: BuilderSectionStatus;
|
status: BuilderSectionStatus;
|
||||||
statusIcon: string;
|
statusIcon: AppIconName;
|
||||||
statusLabelKey: string;
|
statusLabelKey: string;
|
||||||
sections: NavSectionItem[];
|
sections: NavSectionItem[];
|
||||||
}
|
}
|
||||||
@@ -56,7 +58,7 @@ const SECTION_LABEL_KEYS: Record<ProjectEditorSectionId, string> = {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-project-editor-nav',
|
selector: 'app-project-editor-nav',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TranslatePipe, RouterLink, RouterLinkActive, LangRoutePipe],
|
imports: [TranslatePipe, RouterLink, RouterLinkActive, LangRoutePipe, IconComponent],
|
||||||
templateUrl: './project-editor-nav.component.html',
|
templateUrl: './project-editor-nav.component.html',
|
||||||
styleUrls: ['./project-editor-nav.component.scss'],
|
styleUrls: ['./project-editor-nav.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<main class="builder-overview">
|
<main class="builder-overview">
|
||||||
<a class="builder-overview__back" [routerLink]="['/', 'backoffice', 'dashboard'] | langRoute">
|
<a class="builder-overview__back" [routerLink]="['/', 'backoffice', 'dashboard'] | langRoute">
|
||||||
<span class="pi pi-arrow-left" aria-hidden="true"></span>{{ 'adminShell.pages.dashboard.title' | translate }}
|
<app-icon name="arrowLeft" [size]="16" />{{ 'adminShell.pages.dashboard.title' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<header class="builder-overview__hero">
|
<header class="builder-overview__hero">
|
||||||
<p class="builder-overview__eyebrow">{{ 'builder.appName' | translate }}</p>
|
<p class="builder-overview__eyebrow">{{ 'builder.appName' | translate }}</p>
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="builder-overview__next-step">
|
<div class="builder-overview__next-step">
|
||||||
<span class="pi pi-arrow-right-circle" aria-hidden="true"></span>
|
<app-icon name="arrowRight" [size]="20" />
|
||||||
<div>
|
<div>
|
||||||
<p class="builder-overview__next-step-label">{{ 'builder.overviewNextStepLabel' | translate }}</p>
|
<p class="builder-overview__next-step-label">{{ 'builder.overviewNextStepLabel' | translate }}</p>
|
||||||
@if (nextStepLabelKey(); as labelKey) {
|
@if (nextStepLabelKey(); as labelKey) {
|
||||||
@@ -46,11 +46,13 @@
|
|||||||
@for (group of groups; track group.id) {
|
@for (group of groups; track group.id) {
|
||||||
<a class="builder-overview__card" [routerLink]="groupSectionRoute(group.sections[0])">
|
<a class="builder-overview__card" [routerLink]="groupSectionRoute(group.sections[0])">
|
||||||
<div class="builder-overview__card-header">
|
<div class="builder-overview__card-header">
|
||||||
<span class="pi {{ group.icon }} builder-overview__card-icon" aria-hidden="true"></span>
|
<app-icon [name]="group.icon" [size]="20" class="builder-overview__card-icon" />
|
||||||
<span
|
<app-icon
|
||||||
class="pi {{ statusIcon[groupStatus().get(group.id) ?? 'unknown'] }} builder-overview__card-status builder-overview__card-status--{{ groupStatus().get(group.id) ?? 'unknown' }}"
|
[name]="statusIcon[groupStatus().get(group.id) ?? 'unknown']"
|
||||||
[attr.aria-label]="statusLabelKey[groupStatus().get(group.id) ?? 'unknown'] | translate"
|
[size]="16"
|
||||||
></span>
|
[ariaLabel]="statusLabelKey[groupStatus().get(group.id) ?? 'unknown'] | translate"
|
||||||
|
class="builder-overview__card-status builder-overview__card-status--{{ groupStatus().get(group.id) ?? 'unknown' }}"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="builder-overview__card-title">{{ group.labelKey | translate }}</h3>
|
<h3 class="builder-overview__card-title">{{ group.labelKey | translate }}</h3>
|
||||||
<p class="builder-overview__card-description">{{ group.descriptionKey | translate }}</p>
|
<p class="builder-overview__card-description">{{ group.descriptionKey | translate }}</p>
|
||||||
@@ -66,10 +68,10 @@
|
|||||||
@for (check of readinessChecklist(); track check.labelKey) {
|
@for (check of readinessChecklist(); track check.labelKey) {
|
||||||
<li class="builder-overview__check builder-overview__check--{{ check.state }}">
|
<li class="builder-overview__check builder-overview__check--{{ check.state }}">
|
||||||
@switch (check.state) {
|
@switch (check.state) {
|
||||||
@case ('true') { <span class="pi pi-check-circle" aria-hidden="true"></span> }
|
@case ('true') { <app-icon name="checkCircle" [size]="16" /> }
|
||||||
@case ('false') { <span class="pi pi-circle" aria-hidden="true"></span> }
|
@case ('false') { <app-icon name="circle" [size]="16" /> }
|
||||||
@case ('loading') { <span class="pi pi-spin pi-spinner" aria-hidden="true"></span> }
|
@case ('loading') { <app-icon name="spinner" [size]="16" class="spin" /> }
|
||||||
@default { <span class="pi pi-question-circle" aria-hidden="true"></span> }
|
@default { <app-icon name="help" [size]="16" /> }
|
||||||
}
|
}
|
||||||
<span>{{ check.labelKey | translate }}</span>
|
<span>{{ check.labelKey | translate }}</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -81,10 +83,10 @@
|
|||||||
<h2 id="builder-overview-links-title">{{ 'builder.overviewQuickLinksTitle' | translate }}</h2>
|
<h2 id="builder-overview-links-title">{{ 'builder.overviewQuickLinksTitle' | translate }}</h2>
|
||||||
<div class="builder-overview__quick-links">
|
<div class="builder-overview__quick-links">
|
||||||
<a class="builder-overview__quick-link" [routerLink]="groupSectionRoute('preview')">
|
<a class="builder-overview__quick-link" [routerLink]="groupSectionRoute('preview')">
|
||||||
<span class="pi pi-desktop" aria-hidden="true"></span>{{ 'builder.preview' | translate }}
|
<app-icon name="monitor" [size]="18" />{{ 'builder.preview' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<a class="builder-overview__quick-link" [routerLink]="groupSectionRoute('general')">
|
<a class="builder-overview__quick-link" [routerLink]="groupSectionRoute('general')">
|
||||||
<span class="pi pi-shop" aria-hidden="true"></span>{{ 'builder.general' | translate }}
|
<app-icon name="store" [size]="18" />{{ 'builder.general' | translate }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ import { ProjectEditorFacade } from '../facade/project-editor.facade';
|
|||||||
import { BackofficeDataService } from '../../../core/backoffice/backoffice-data.service';
|
import { BackofficeDataService } from '../../../core/backoffice/backoffice-data.service';
|
||||||
import { BUILDER_GROUPS } from '../builder/builder-groups.model';
|
import { BUILDER_GROUPS } from '../builder/builder-groups.model';
|
||||||
import { BuilderSectionStatus } from '../models/project-editor.model';
|
import { BuilderSectionStatus } from '../models/project-editor.model';
|
||||||
|
import { IconComponent } from '../../../shared/ui/icon/icon.component';
|
||||||
|
import { AppIconName } from '../../../shared/ui/icon/icon-registry';
|
||||||
|
|
||||||
const STATUS_ICON: Record<BuilderSectionStatus, string> = {
|
const STATUS_ICON: Record<BuilderSectionStatus, AppIconName> = {
|
||||||
complete: 'pi-check-circle',
|
complete: 'checkCircle',
|
||||||
'in-progress': 'pi-circle-fill',
|
'in-progress': 'circle',
|
||||||
'not-started': 'pi-circle',
|
'not-started': 'circle',
|
||||||
unknown: 'pi-question-circle',
|
unknown: 'help',
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATUS_LABEL_KEY: Record<BuilderSectionStatus, string> = {
|
const STATUS_LABEL_KEY: Record<BuilderSectionStatus, string> = {
|
||||||
@@ -49,7 +51,7 @@ const SECTION_LABEL_KEYS: Record<string, string> = {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-builder-overview-page',
|
selector: 'app-builder-overview-page',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [RouterLink, TranslatePipe, LangRoutePipe],
|
imports: [RouterLink, TranslatePipe, LangRoutePipe, IconComponent],
|
||||||
templateUrl: './builder-overview-page.component.html',
|
templateUrl: './builder-overview-page.component.html',
|
||||||
styleUrl: './builder-overview-page.component.scss',
|
styleUrl: './builder-overview-page.component.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
<aside class="project-editor-layout__sidebar">
|
<aside class="project-editor-layout__sidebar">
|
||||||
<a class="project-editor-layout__brand" [routerLink]="overviewRoute()">
|
<a class="project-editor-layout__brand" [routerLink]="overviewRoute()">
|
||||||
<span class="pi pi-arrow-left" aria-hidden="true"></span>
|
<app-icon name="arrowLeft" [size]="16" />
|
||||||
{{ 'builder.appName' | translate }}
|
{{ 'builder.appName' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<a class="project-editor-layout__dashboard-link" [routerLink]="dashboardRoute()">
|
<a class="project-editor-layout__dashboard-link" [routerLink]="dashboardRoute()">
|
||||||
<span class="pi pi-home" aria-hidden="true"></span>
|
<app-icon name="home" [size]="16" />
|
||||||
{{ 'builder.backToDashboard' | translate }}
|
{{ 'builder.backToDashboard' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<app-project-editor-nav (linkClicked)="closeMobileDrawer()" />
|
<app-project-editor-nav (linkClicked)="closeMobileDrawer()" />
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
[attr.aria-label]="(mobileDrawerOpen() ? 'builder.closeSidebar' : 'builder.openSidebar') | translate"
|
[attr.aria-label]="(mobileDrawerOpen() ? 'builder.closeSidebar' : 'builder.openSidebar') | translate"
|
||||||
(click)="toggleMobileDrawer()"
|
(click)="toggleMobileDrawer()"
|
||||||
>
|
>
|
||||||
<span class="pi pi-bars" aria-hidden="true"></span>
|
<app-icon name="menu" [size]="20" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="project-editor-layout__heading">
|
<div class="project-editor-layout__heading">
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="button" class="project-editor-layout__help-toggle" [attr.aria-expanded]="helpOpen()" (click)="toggleHelp()">
|
<button type="button" class="project-editor-layout__help-toggle" [attr.aria-expanded]="helpOpen()" (click)="toggleHelp()">
|
||||||
<span class="pi pi-question-circle" aria-hidden="true"></span>
|
<app-icon name="help" [size]="16" />
|
||||||
{{ (helpOpen() ? 'builder.helpToggleHide' : 'builder.helpToggleShow') | translate }}
|
{{ (helpOpen() ? 'builder.helpToggleHide' : 'builder.helpToggleShow') | translate }}
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { ProjectEditorPreviewSectionComponent } from '../sections/preview-sectio
|
|||||||
import { TranslatePipe } from '../../../i18n/translate.pipe';
|
import { TranslatePipe } from '../../../i18n/translate.pipe';
|
||||||
import { TranslateService } from '../../../i18n/translate.service';
|
import { TranslateService } from '../../../i18n/translate.service';
|
||||||
import { LanguageService } from '../../../services/language.service';
|
import { LanguageService } from '../../../services/language.service';
|
||||||
|
import { IconComponent } from '../../../shared/ui/icon/icon.component';
|
||||||
import { StaticPagesEditorComponent } from '../../content-management/components/static-pages-editor.component';
|
import { StaticPagesEditorComponent } from '../../content-management/components/static-pages-editor.component';
|
||||||
import { ProjectEditorSaveBarComponent } from '../components/save-bar/project-editor-save-bar.component';
|
import { ProjectEditorSaveBarComponent } from '../components/save-bar/project-editor-save-bar.component';
|
||||||
import { EDITOR_SECTION_BOOTSTRAP_KEYS, ProjectEditorSectionId } from '../models/project-editor.model';
|
import { EDITOR_SECTION_BOOTSTRAP_KEYS, ProjectEditorSectionId } from '../models/project-editor.model';
|
||||||
@@ -62,6 +63,7 @@ const SECTION_LABEL_KEYS: Record<ProjectEditorSectionId, string> = {
|
|||||||
ProjectEditorNavigationSectionComponent,
|
ProjectEditorNavigationSectionComponent,
|
||||||
ProjectEditorPreviewSectionComponent,
|
ProjectEditorPreviewSectionComponent,
|
||||||
ProjectEditorSaveBarComponent,
|
ProjectEditorSaveBarComponent,
|
||||||
|
IconComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './project-editor-page.component.html',
|
templateUrl: './project-editor-page.component.html',
|
||||||
styleUrls: ['./project-editor-page.component.scss'],
|
styleUrls: ['./project-editor-page.component.scss'],
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
<p>{{ 'builder.brandOverviewSubtitle' | translate }}</p>
|
<p>{{ 'builder.brandOverviewSubtitle' | translate }}</p>
|
||||||
@if (nextStepKey(); as key) {
|
@if (nextStepKey(); as key) {
|
||||||
<p class="brand-overview__next-step">
|
<p class="brand-overview__next-step">
|
||||||
<span class="pi pi-arrow-right-circle" aria-hidden="true"></span>
|
<app-icon name="arrowRight" [size]="16" />
|
||||||
{{ 'builder.overviewNextStepLabel' | translate }}: {{ key | translate }}
|
{{ 'builder.overviewNextStepLabel' | translate }}: {{ key | translate }}
|
||||||
</p>
|
</p>
|
||||||
} @else {
|
} @else {
|
||||||
<p class="brand-overview__next-step brand-overview__next-step--done">
|
<p class="brand-overview__next-step brand-overview__next-step--done">
|
||||||
<span class="pi pi-check-circle" aria-hidden="true"></span>
|
<app-icon name="checkCircle" [size]="16" />
|
||||||
{{ 'builder.brandOverviewComplete' | translate }}
|
{{ 'builder.brandOverviewComplete' | translate }}
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<ul class="brand-overview__checklist">
|
<ul class="brand-overview__checklist">
|
||||||
@for (item of healthItems(); track item.labelKey) {
|
@for (item of healthItems(); track item.labelKey) {
|
||||||
<li [class.brand-overview__check--ok]="item.ok">
|
<li [class.brand-overview__check--ok]="item.ok">
|
||||||
<span class="pi" [class.pi-check-circle]="item.ok" [class.pi-circle]="!item.ok" aria-hidden="true"></span>
|
<app-icon [name]="item.ok ? 'checkCircle' : 'circle'" [size]="16" />
|
||||||
{{ item.labelKey | translate }}
|
{{ item.labelKey | translate }}
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@if (!textContrastPasses()) {
|
@if (!textContrastPasses()) {
|
||||||
<p class="brand-overview__contrast-warning" role="status">
|
<p class="brand-overview__contrast-warning" role="status">
|
||||||
<span class="pi pi-exclamation-triangle" aria-hidden="true"></span>
|
<app-icon name="warning" [size]="16" />
|
||||||
{{ 'builder.brandContrastWarning' | translate }}
|
{{ 'builder.brandContrastWarning' | translate }}
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
|||||||
import { LanguageService } from '../../../../services/language.service';
|
import { LanguageService } from '../../../../services/language.service';
|
||||||
import { ProjectEditorFacade } from '../../facade/project-editor.facade';
|
import { ProjectEditorFacade } from '../../facade/project-editor.facade';
|
||||||
import { contrastRatio } from './contrast.util';
|
import { contrastRatio } from './contrast.util';
|
||||||
|
import { IconComponent } from '../../../../shared/ui/icon/icon.component';
|
||||||
|
|
||||||
interface BrandHealthItem {
|
interface BrandHealthItem {
|
||||||
labelKey: string;
|
labelKey: string;
|
||||||
@@ -12,7 +13,7 @@ interface BrandHealthItem {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-brand-overview',
|
selector: 'app-brand-overview',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TranslatePipe],
|
imports: [TranslatePipe, IconComponent],
|
||||||
templateUrl: './brand-overview.component.html',
|
templateUrl: './brand-overview.component.html',
|
||||||
styleUrl: './brand-overview.component.scss',
|
styleUrl: './brand-overview.component.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
@for (column of columns(); track column.id) {
|
@for (column of columns(); track column.id) {
|
||||||
<div class="footer-column" cdkDrag>
|
<div class="footer-column" cdkDrag>
|
||||||
<div class="footer-column__head" cdkDragHandle>
|
<div class="footer-column__head" cdkDragHandle>
|
||||||
<span class="pi pi-bars footer-column__grip" aria-hidden="true"></span>
|
<app-icon name="grip" [size]="16" class="footer-column__grip" />
|
||||||
<app-input [ngModel]="column.title" (ngModelChange)="updateColumnTitle(column.id, $event)" [placeholder]="'builder.footerColumnTitlePlaceholder' | translate" />
|
<app-input [ngModel]="column.title" (ngModelChange)="updateColumnTitle(column.id, $event)" [placeholder]="'builder.footerColumnTitlePlaceholder' | translate" />
|
||||||
<button type="button" class="footer-column__remove" (click)="removeColumn(column.id)" [attr.aria-label]="'builder.removeColumn' | translate">×</button>
|
<button type="button" class="footer-column__remove" (click)="removeColumn(column.id)" [attr.aria-label]="'builder.removeColumn' | translate">×</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
<div class="footer-column__links" cdkDropList [cdkDropListData]="column.links" (cdkDropListDropped)="dropLink(column.id, $event)">
|
<div class="footer-column__links" cdkDropList [cdkDropListData]="column.links" (cdkDropListDropped)="dropLink(column.id, $event)">
|
||||||
@for (link of column.links; track link.id) {
|
@for (link of column.links; track link.id) {
|
||||||
<div class="footer-link" cdkDrag>
|
<div class="footer-link" cdkDrag>
|
||||||
<span class="pi pi-bars footer-link__grip" cdkDragHandle aria-hidden="true"></span>
|
<app-icon name="grip" [size]="14" cdkDragHandle class="footer-link__grip" />
|
||||||
<app-input [ngModel]="link.label" (ngModelChange)="updateLink(column.id, link.id, { label: $event })" [placeholder]="'builder.footerLinkLabelPlaceholder' | translate" />
|
<app-input [ngModel]="link.label" (ngModelChange)="updateLink(column.id, link.id, { label: $event })" [placeholder]="'builder.footerLinkLabelPlaceholder' | translate" />
|
||||||
|
|
||||||
<select class="footer-link__source" [ngModel]="linkSourceMode(link)" (ngModelChange)="setLinkSourceMode(column.id, link.id, $event)">
|
<select class="footer-link__source" [ngModel]="linkSourceMode(link)" (ngModelChange)="setLinkSourceMode(column.id, link.id, $event)">
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { FormFieldComponent } from '../../../shared/ui/form-field/form-field.com
|
|||||||
import { SectionCardComponent } from '../../../shared/ui/section-card/section-card.component';
|
import { SectionCardComponent } from '../../../shared/ui/section-card/section-card.component';
|
||||||
import { KeyValueEditorComponent } from '../../../shared/ui/key-value-editor/key-value-editor.component';
|
import { KeyValueEditorComponent } from '../../../shared/ui/key-value-editor/key-value-editor.component';
|
||||||
import { ImageFieldComponent } from '../../../shared/ui/image-field/image-field.component';
|
import { ImageFieldComponent } from '../../../shared/ui/image-field/image-field.component';
|
||||||
|
import { IconComponent } from '../../../shared/ui/icon/icon.component';
|
||||||
import { FooterColumnConfig, FooterLinkConfig, FooterPaymentIconConfig, FooterSocialLinkConfig } from '../../../shared/models/config';
|
import { FooterColumnConfig, FooterLinkConfig, FooterPaymentIconConfig, FooterSocialLinkConfig } from '../../../shared/models/config';
|
||||||
import { isValidHttpUrl } from '../schema/validators/primitives';
|
import { isValidHttpUrl } from '../schema/validators/primitives';
|
||||||
import { ContentPageService } from '../../content-management/services/content-page.service';
|
import { ContentPageService } from '../../content-management/services/content-page.service';
|
||||||
@@ -26,7 +27,8 @@ import { ContentPageService } from '../../content-management/services/content-pa
|
|||||||
FormFieldComponent,
|
FormFieldComponent,
|
||||||
SectionCardComponent,
|
SectionCardComponent,
|
||||||
KeyValueEditorComponent,
|
KeyValueEditorComponent,
|
||||||
ImageFieldComponent
|
ImageFieldComponent,
|
||||||
|
IconComponent
|
||||||
],
|
],
|
||||||
templateUrl: './footer-section.component.html',
|
templateUrl: './footer-section.component.html',
|
||||||
styleUrls: ['./section.shared.scss', './footer-section.component.scss'],
|
styleUrls: ['./section.shared.scss', './footer-section.component.scss'],
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
@for (section of sections(); track section.id) {
|
@for (section of sections(); track section.id) {
|
||||||
<div class="block-card" cdkDrag [class.block-card--hidden]="section.visible === false">
|
<div class="block-card" cdkDrag [class.block-card--hidden]="section.visible === false">
|
||||||
<div class="block-card__handle" cdkDragHandle [attr.aria-label]="'builder.dragToReorder' | translate">
|
<div class="block-card__handle" cdkDragHandle [attr.aria-label]="'builder.dragToReorder' | translate">
|
||||||
<span class="pi pi-bars" aria-hidden="true"></span>
|
<app-icon name="grip" [size]="16" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block-card__preview">
|
<div class="block-card__preview">
|
||||||
<span class="pi {{ blockIcon(section.type) }}" aria-hidden="true"></span>
|
<app-icon [name]="blockIcon(section.type)" [size]="20" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block-card__body">
|
<div class="block-card__body">
|
||||||
@@ -50,10 +50,10 @@
|
|||||||
<span>{{ 'builder.visible' | translate }}</span>
|
<span>{{ 'builder.visible' | translate }}</span>
|
||||||
</label>
|
</label>
|
||||||
<button type="button" [attr.aria-label]="'builder.widgetDuplicate' | translate" (click)="duplicateBlock(section.id)">
|
<button type="button" [attr.aria-label]="'builder.widgetDuplicate' | translate" (click)="duplicateBlock(section.id)">
|
||||||
<span class="pi pi-copy" aria-hidden="true"></span>
|
<app-icon name="copy" [size]="16" />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="block-card__remove" [attr.aria-label]="'builder.widgetRemove' | translate" (click)="removeBlock(section.id)">
|
<button type="button" class="block-card__remove" [attr.aria-label]="'builder.widgetRemove' | translate" (click)="removeBlock(section.id)">
|
||||||
<span class="pi pi-trash" aria-hidden="true"></span>
|
<app-icon name="trash" [size]="16" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
<div class="block-catalog__grid">
|
<div class="block-catalog__grid">
|
||||||
@for (entry of blockCatalog; track entry.type) {
|
@for (entry of blockCatalog; track entry.type) {
|
||||||
<button type="button" class="block-catalog__item" (click)="addBlock(entry.type)">
|
<button type="button" class="block-catalog__item" (click)="addBlock(entry.type)">
|
||||||
<span class="pi {{ entry.icon }}" aria-hidden="true"></span>
|
<app-icon [name]="entry.icon" [size]="18" />
|
||||||
<span>{{ entry.labelKey | translate }}</span>
|
<span>{{ entry.labelKey | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { SectionCardComponent } from '../../../shared/ui/section-card/section-ca
|
|||||||
import { ToggleComponent } from '../../../shared/ui/toggle/toggle.component';
|
import { ToggleComponent } from '../../../shared/ui/toggle/toggle.component';
|
||||||
import { HomepageOverviewComponent } from './homepage/homepage-overview.component';
|
import { HomepageOverviewComponent } from './homepage/homepage-overview.component';
|
||||||
import { SectionConfig } from '../../../shared/models/config';
|
import { SectionConfig } from '../../../shared/models/config';
|
||||||
|
import { IconComponent } from '../../../shared/ui/icon/icon.component';
|
||||||
|
import { AppIconName } from '../../../shared/ui/icon/icon-registry';
|
||||||
|
|
||||||
export interface LayoutStrategyOption {
|
export interface LayoutStrategyOption {
|
||||||
value: 'stack' | 'grid' | 'hero' | 'carousel' | 'split';
|
value: 'stack' | 'grid' | 'hero' | 'carousel' | 'split';
|
||||||
@@ -19,21 +21,21 @@ export interface LayoutStrategyOption {
|
|||||||
/** Merchant-facing catalog of the block types an admin can add to the homepage. Not the widget manifest (that carries technical settingsSchema) - this is presentation-only, matching the pattern already used in widgets-section.component.ts. */
|
/** Merchant-facing catalog of the block types an admin can add to the homepage. Not the widget manifest (that carries technical settingsSchema) - this is presentation-only, matching the pattern already used in widgets-section.component.ts. */
|
||||||
interface BlockCatalogEntry {
|
interface BlockCatalogEntry {
|
||||||
type: string;
|
type: string;
|
||||||
icon: string;
|
icon: AppIconName;
|
||||||
labelKey: string;
|
labelKey: string;
|
||||||
descKey: string;
|
descKey: string;
|
||||||
defaultLayout: LayoutStrategyOption['value'];
|
defaultLayout: LayoutStrategyOption['value'];
|
||||||
}
|
}
|
||||||
|
|
||||||
const BLOCK_CATALOG: BlockCatalogEntry[] = [
|
const BLOCK_CATALOG: BlockCatalogEntry[] = [
|
||||||
{ type: 'hero', icon: 'pi-image', labelKey: 'builder.blockHero', descKey: 'builder.blockHeroDesc', defaultLayout: 'hero' },
|
{ type: 'hero', icon: 'image', labelKey: 'builder.blockHero', descKey: 'builder.blockHeroDesc', defaultLayout: 'hero' },
|
||||||
{ type: 'categories', icon: 'pi-th-large', labelKey: 'builder.blockCategories', descKey: 'builder.blockCategoriesDesc', defaultLayout: 'grid' },
|
{ type: 'categories', icon: 'layoutGrid', labelKey: 'builder.blockCategories', descKey: 'builder.blockCategoriesDesc', defaultLayout: 'grid' },
|
||||||
{ type: 'product-collection', icon: 'pi-box', labelKey: 'builder.blockFeaturedProducts', descKey: 'builder.blockFeaturedProductsDesc', defaultLayout: 'grid' },
|
{ type: 'product-collection', icon: 'package', labelKey: 'builder.blockFeaturedProducts', descKey: 'builder.blockFeaturedProductsDesc', defaultLayout: 'grid' },
|
||||||
{ type: 'product-carousel', icon: 'pi-images', labelKey: 'builder.blockProductCarousel', descKey: 'builder.blockProductCarouselDesc', defaultLayout: 'carousel' },
|
{ type: 'product-carousel', icon: 'images', labelKey: 'builder.blockProductCarousel', descKey: 'builder.blockProductCarouselDesc', defaultLayout: 'carousel' },
|
||||||
{ type: 'recently-viewed', icon: 'pi-history', labelKey: 'builder.blockRecentlyViewed', descKey: 'builder.blockRecentlyViewedDesc', defaultLayout: 'grid' },
|
{ type: 'recently-viewed', icon: 'history', labelKey: 'builder.blockRecentlyViewed', descKey: 'builder.blockRecentlyViewedDesc', defaultLayout: 'grid' },
|
||||||
{ type: 'banner', icon: 'pi-megaphone', labelKey: 'builder.blockBanner', descKey: 'builder.blockBannerDesc', defaultLayout: 'split' },
|
{ type: 'banner', icon: 'megaphone', labelKey: 'builder.blockBanner', descKey: 'builder.blockBannerDesc', defaultLayout: 'split' },
|
||||||
{ type: 'partners', icon: 'pi-verified', labelKey: 'builder.blockPartners', descKey: 'builder.blockPartnersDesc', defaultLayout: 'stack' },
|
{ type: 'partners', icon: 'verified', labelKey: 'builder.blockPartners', descKey: 'builder.blockPartnersDesc', defaultLayout: 'stack' },
|
||||||
{ type: 'html', icon: 'pi-code', labelKey: 'builder.blockCustomHtml', descKey: 'builder.blockCustomHtmlDesc', defaultLayout: 'stack' },
|
{ type: 'html', icon: 'code', labelKey: 'builder.blockCustomHtml', descKey: 'builder.blockCustomHtmlDesc', defaultLayout: 'stack' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const BLOCK_BY_TYPE = new Map(BLOCK_CATALOG.map(entry => [entry.type, entry]));
|
const BLOCK_BY_TYPE = new Map(BLOCK_CATALOG.map(entry => [entry.type, entry]));
|
||||||
@@ -41,7 +43,7 @@ const BLOCK_BY_TYPE = new Map(BLOCK_CATALOG.map(entry => [entry.type, entry]));
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-project-editor-homepage-section',
|
selector: 'app-project-editor-homepage-section',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [DragDropModule, FormsModule, TranslatePipe, InputComponent, SectionCardComponent, ToggleComponent, HomepageOverviewComponent],
|
imports: [DragDropModule, FormsModule, TranslatePipe, InputComponent, SectionCardComponent, ToggleComponent, HomepageOverviewComponent, IconComponent],
|
||||||
templateUrl: './homepage-section.component.html',
|
templateUrl: './homepage-section.component.html',
|
||||||
styleUrls: ['./section.shared.scss', './homepage-section.component.scss'],
|
styleUrls: ['./section.shared.scss', './homepage-section.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
@@ -80,8 +82,8 @@ export class ProjectEditorHomepageSectionComponent {
|
|||||||
return entry ? this.translate.t(entry.descKey) : '';
|
return entry ? this.translate.t(entry.descKey) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
blockIcon(type: string): string {
|
blockIcon(type: string): AppIconName {
|
||||||
return BLOCK_BY_TYPE.get(type)?.icon ?? 'pi-stop';
|
return BLOCK_BY_TYPE.get(type)?.icon ?? 'stop';
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(event: CdkDragDrop<unknown[]>): void {
|
drop(event: CdkDragDrop<unknown[]>): void {
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
</p>
|
</p>
|
||||||
@if (nextStepKey(); as key) {
|
@if (nextStepKey(); as key) {
|
||||||
<p class="homepage-overview__next-step">
|
<p class="homepage-overview__next-step">
|
||||||
<span class="pi pi-arrow-right-circle" aria-hidden="true"></span>
|
<app-icon name="arrowRight" [size]="16" />
|
||||||
{{ 'builder.overviewNextStepLabel' | translate }}: {{ key | translate }}
|
{{ 'builder.overviewNextStepLabel' | translate }}: {{ key | translate }}
|
||||||
</p>
|
</p>
|
||||||
} @else {
|
} @else {
|
||||||
<p class="homepage-overview__next-step homepage-overview__next-step--done">
|
<p class="homepage-overview__next-step homepage-overview__next-step--done">
|
||||||
<span class="pi pi-check-circle" aria-hidden="true"></span>
|
<app-icon name="checkCircle" [size]="16" />
|
||||||
{{ 'builder.homepageOverviewComplete' | translate }}
|
{{ 'builder.homepageOverviewComplete' | translate }}
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<ul class="homepage-overview__checklist">
|
<ul class="homepage-overview__checklist">
|
||||||
@for (item of healthItems(); track item.labelKey) {
|
@for (item of healthItems(); track item.labelKey) {
|
||||||
<li [class.homepage-overview__check--ok]="item.ok">
|
<li [class.homepage-overview__check--ok]="item.ok">
|
||||||
<span class="pi" [class.pi-check-circle]="item.ok" [class.pi-circle]="!item.ok" aria-hidden="true"></span>
|
<app-icon [name]="item.ok ? 'checkCircle' : 'circle'" [size]="16" />
|
||||||
{{ item.labelKey | translate }}
|
{{ item.labelKey | translate }}
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/c
|
|||||||
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
import { TranslatePipe } from '../../../../i18n/translate.pipe';
|
||||||
import { LanguageService } from '../../../../services/language.service';
|
import { LanguageService } from '../../../../services/language.service';
|
||||||
import { ProjectEditorFacade } from '../../facade/project-editor.facade';
|
import { ProjectEditorFacade } from '../../facade/project-editor.facade';
|
||||||
|
import { IconComponent } from '../../../../shared/ui/icon/icon.component';
|
||||||
|
|
||||||
interface HomepageHealthItem {
|
interface HomepageHealthItem {
|
||||||
labelKey: string;
|
labelKey: string;
|
||||||
@@ -11,7 +12,7 @@ interface HomepageHealthItem {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-homepage-overview',
|
selector: 'app-homepage-overview',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [TranslatePipe],
|
imports: [TranslatePipe, IconComponent],
|
||||||
templateUrl: './homepage-overview.component.html',
|
templateUrl: './homepage-overview.component.html',
|
||||||
styleUrl: './homepage-overview.component.scss',
|
styleUrl: './homepage-overview.component.scss',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
|||||||
@@ -7,27 +7,27 @@
|
|||||||
@for (entry of widgets(); track entry.widget.id; let first = $first; let last = $last) {
|
@for (entry of widgets(); track entry.widget.id; let first = $first; let last = $last) {
|
||||||
<article class="sub-card widget-card" [class.widget-card--hidden]="!isVisible(entry.widget)">
|
<article class="sub-card widget-card" [class.widget-card--hidden]="!isVisible(entry.widget)">
|
||||||
<header class="widget-card__header">
|
<header class="widget-card__header">
|
||||||
<span class="pi {{ widgetIcon(entry.widget.type) }} widget-card__icon" aria-hidden="true"></span>
|
<app-icon [name]="widgetIcon(entry.widget.type)" [size]="18" class="widget-card__icon" />
|
||||||
<h3 class="widget-card__title">{{ widgetLabel(entry.widget) }}</h3>
|
<h3 class="widget-card__title">{{ widgetLabel(entry.widget) }}</h3>
|
||||||
@if (!isVisible(entry.widget)) {
|
@if (!isVisible(entry.widget)) {
|
||||||
<span class="widget-card__hidden-badge">{{ 'builder.widgetHidden' | translate }}</span>
|
<span class="widget-card__hidden-badge">{{ 'builder.widgetHidden' | translate }}</span>
|
||||||
}
|
}
|
||||||
<div class="widget-card__actions">
|
<div class="widget-card__actions">
|
||||||
<button type="button" [attr.aria-label]="'builder.widgetMoveUp' | translate" [disabled]="first" (click)="moveWidget(entry.widget.id, -1)">
|
<button type="button" [attr.aria-label]="'builder.widgetMoveUp' | translate" [disabled]="first" (click)="moveWidget(entry.widget.id, -1)">
|
||||||
<span class="pi pi-arrow-up" aria-hidden="true"></span>
|
<app-icon name="arrowUp" [size]="16" />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" [attr.aria-label]="'builder.widgetMoveDown' | translate" [disabled]="last" (click)="moveWidget(entry.widget.id, 1)">
|
<button type="button" [attr.aria-label]="'builder.widgetMoveDown' | translate" [disabled]="last" (click)="moveWidget(entry.widget.id, 1)">
|
||||||
<span class="pi pi-arrow-down" aria-hidden="true"></span>
|
<app-icon name="arrowDown" [size]="16" />
|
||||||
</button>
|
</button>
|
||||||
<label class="widget-card__visible-toggle">
|
<label class="widget-card__visible-toggle">
|
||||||
<app-toggle [ngModel]="isVisible(entry.widget)" (ngModelChange)="toggleVisible(entry.widget.id)" [ariaLabel]="'builder.widgetVisible' | translate" />
|
<app-toggle [ngModel]="isVisible(entry.widget)" (ngModelChange)="toggleVisible(entry.widget.id)" [ariaLabel]="'builder.widgetVisible' | translate" />
|
||||||
<span>{{ 'builder.widgetVisible' | translate }}</span>
|
<span>{{ 'builder.widgetVisible' | translate }}</span>
|
||||||
</label>
|
</label>
|
||||||
<button type="button" [attr.aria-label]="'builder.widgetDuplicate' | translate" (click)="duplicateWidget(entry.widget.id)">
|
<button type="button" [attr.aria-label]="'builder.widgetDuplicate' | translate" (click)="duplicateWidget(entry.widget.id)">
|
||||||
<span class="pi pi-copy" aria-hidden="true"></span>
|
<app-icon name="copy" [size]="16" />
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="widget-card__remove" [attr.aria-label]="'builder.widgetRemove' | translate" (click)="removeWidget(entry.widget.id)">
|
<button type="button" class="widget-card__remove" [attr.aria-label]="'builder.widgetRemove' | translate" (click)="removeWidget(entry.widget.id)">
|
||||||
<span class="pi pi-trash" aria-hidden="true"></span>
|
<app-icon name="trash" [size]="16" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -8,16 +8,18 @@ import { ButtonComponent } from '../../../shared/ui/button/button.component';
|
|||||||
import { SectionCardComponent } from '../../../shared/ui/section-card/section-card.component';
|
import { SectionCardComponent } from '../../../shared/ui/section-card/section-card.component';
|
||||||
import { ToggleComponent } from '../../../shared/ui/toggle/toggle.component';
|
import { ToggleComponent } from '../../../shared/ui/toggle/toggle.component';
|
||||||
import { WidgetConfig } from '../../../shared/models/config';
|
import { WidgetConfig } from '../../../shared/models/config';
|
||||||
|
import { IconComponent } from '../../../shared/ui/icon/icon.component';
|
||||||
|
import { AppIconName } from '../../../shared/ui/icon/icon-registry';
|
||||||
|
|
||||||
interface HeroSlideDraft {
|
interface HeroSlideDraft {
|
||||||
title: string;
|
title: string;
|
||||||
subtitle: string;
|
subtitle: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const WIDGET_ICONS: Record<string, string> = {
|
const WIDGET_ICONS: Record<string, AppIconName> = {
|
||||||
hero: 'pi-image',
|
hero: 'image',
|
||||||
categories: 'pi-th-large',
|
categories: 'layoutGrid',
|
||||||
'product-collection': 'pi-box',
|
'product-collection': 'package',
|
||||||
};
|
};
|
||||||
|
|
||||||
const WIDGET_LABEL_KEYS: Record<string, string> = {
|
const WIDGET_LABEL_KEYS: Record<string, string> = {
|
||||||
@@ -29,7 +31,7 @@ const WIDGET_LABEL_KEYS: Record<string, string> = {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'app-project-editor-widgets-section',
|
selector: 'app-project-editor-widgets-section',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [FormsModule, TranslatePipe, InputComponent, ButtonComponent, SectionCardComponent, ToggleComponent],
|
imports: [FormsModule, TranslatePipe, InputComponent, ButtonComponent, SectionCardComponent, ToggleComponent, IconComponent],
|
||||||
templateUrl: './widgets-section.component.html',
|
templateUrl: './widgets-section.component.html',
|
||||||
styleUrls: ['./section.shared.scss', './widgets-section.component.scss'],
|
styleUrls: ['./section.shared.scss', './widgets-section.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
@@ -43,8 +45,8 @@ export class ProjectEditorWidgetsSectionComponent {
|
|||||||
return messageKey ? this.translate.t(messageKey) : null;
|
return messageKey ? this.translate.t(messageKey) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
widgetIcon(type: string): string {
|
widgetIcon(type: string): AppIconName {
|
||||||
return WIDGET_ICONS[type] ?? 'pi-stop';
|
return WIDGET_ICONS[type] ?? 'stop';
|
||||||
}
|
}
|
||||||
|
|
||||||
widgetLabel(widget: WidgetConfig): string {
|
widgetLabel(widget: WidgetConfig): string {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
LucideFlag as Flag,
|
LucideFlag as Flag,
|
||||||
LucideFolder as Folder,
|
LucideFolder as Folder,
|
||||||
LucideGlobe as Globe,
|
LucideGlobe as Globe,
|
||||||
|
LucideGripVertical as GripVertical,
|
||||||
LucideHeart as Heart,
|
LucideHeart as Heart,
|
||||||
LucideHelpCircle as HelpCircle,
|
LucideHelpCircle as HelpCircle,
|
||||||
LucideHistory as History,
|
LucideHistory as History,
|
||||||
@@ -144,6 +145,7 @@ export const APP_ICONS = {
|
|||||||
creditCard: CreditCard,
|
creditCard: CreditCard,
|
||||||
flag: Flag,
|
flag: Flag,
|
||||||
globe: Globe,
|
globe: Globe,
|
||||||
|
grip: GripVertical,
|
||||||
history: History,
|
history: History,
|
||||||
link: Link,
|
link: Link,
|
||||||
list: List,
|
list: List,
|
||||||
|
|||||||
@@ -140,6 +140,15 @@ a, button, input, textarea, select {
|
|||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Spinner rotation for app-icon name="spinner" used as a loading indicator */
|
||||||
|
@keyframes app-icon-spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.spin {
|
||||||
|
animation: app-icon-spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
/* Respect OS-level reduced-motion preference globally: neutralizes hover
|
/* Respect OS-level reduced-motion preference globally: neutralizes hover
|
||||||
transforms, card-entrance/skeleton-shimmer animations, and smooth-scroll
|
transforms, card-entrance/skeleton-shimmer animations, and smooth-scroll
|
||||||
everywhere in one place rather than requiring every component to opt in
|
everywhere in one place rather than requiring every component to opt in
|
||||||
|
|||||||
Reference in New Issue
Block a user