diff --git a/src/admin/public/brands/allegro.svg b/src/admin/public/brands/allegro.svg new file mode 100644 index 0000000..afb7da5 --- /dev/null +++ b/src/admin/public/brands/allegro.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/admin/public/brands/amazon.svg b/src/admin/public/brands/amazon.svg new file mode 100644 index 0000000..8b76b54 --- /dev/null +++ b/src/admin/public/brands/amazon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/admin/public/brands/dhl.svg b/src/admin/public/brands/dhl.svg new file mode 100644 index 0000000..156e0ac --- /dev/null +++ b/src/admin/public/brands/dhl.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/admin/public/brands/dpd.svg b/src/admin/public/brands/dpd.svg new file mode 100644 index 0000000..f547c26 --- /dev/null +++ b/src/admin/public/brands/dpd.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/admin/public/brands/ifirma.svg b/src/admin/public/brands/ifirma.svg new file mode 100644 index 0000000..12ed113 --- /dev/null +++ b/src/admin/public/brands/ifirma.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/admin/public/brands/inpost.svg b/src/admin/public/brands/inpost.svg new file mode 100644 index 0000000..74c0c9b --- /dev/null +++ b/src/admin/public/brands/inpost.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/admin/public/brands/payu.svg b/src/admin/public/brands/payu.svg new file mode 100644 index 0000000..03612f5 --- /dev/null +++ b/src/admin/public/brands/payu.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/admin/src/app/core/product-image.util.ts b/src/admin/src/app/core/product-image.util.ts index 22e5aa3..551f2fd 100644 --- a/src/admin/src/app/core/product-image.util.ts +++ b/src/admin/src/app/core/product-image.util.ts @@ -8,7 +8,12 @@ export function resolveProductImageUrl( return ''; } - if (url.startsWith('http://') || url.startsWith('https://')) { + if ( + url.startsWith('http://') || + url.startsWith('https://') || + url.startsWith('blob:') || + url.startsWith('data:') + ) { return url; } diff --git a/src/admin/src/app/features/analytics/analytics-admin.component.html b/src/admin/src/app/features/analytics/analytics-admin.component.html index b13cb2e..afa79e9 100644 --- a/src/admin/src/app/features/analytics/analytics-admin.component.html +++ b/src/admin/src/app/features/analytics/analytics-admin.component.html @@ -119,9 +119,12 @@ @for (c of d.byChannel; track c.source) { - - {{ 'orders.source.' + c.source | translate }} - + {{ c.orders }} {{ c.revenue | i18nCurrency }} diff --git a/src/admin/src/app/features/analytics/analytics-admin.component.ts b/src/admin/src/app/features/analytics/analytics-admin.component.ts index 3613886..b62625c 100644 --- a/src/admin/src/app/features/analytics/analytics-admin.component.ts +++ b/src/admin/src/app/features/analytics/analytics-admin.component.ts @@ -27,6 +27,7 @@ import { I18nCurrencyPipe } from '../../core/i18n/i18n-currency.pipe'; import { TranslatePipe } from '../../core/i18n/translate.pipe'; import { AdminSkeletonComponent } from '../../shared/admin-skeleton.component'; import { ToastService } from '../../shared/toast.service'; +import { OrderChannelIconsComponent } from '../orders/order-channel-icons.component'; Chart.register( BarController, @@ -86,6 +87,7 @@ interface AnalyticsDto { TranslatePipe, I18nCurrencyPipe, AdminSkeletonComponent, + OrderChannelIconsComponent, ], templateUrl: './analytics-admin.component.html', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/admin/src/app/features/orders/order-channel-icons.component.ts b/src/admin/src/app/features/orders/order-channel-icons.component.ts index e0e854a..debf0af 100644 --- a/src/admin/src/app/features/orders/order-channel-icons.component.ts +++ b/src/admin/src/app/features/orders/order-channel-icons.component.ts @@ -57,29 +57,31 @@ import { } - - @if (service() === 'courier') { - - } @else { - - } - @if (showLabels()) { - - {{ 'orders.carrier.' + carrier() | translate }} - · - {{ 'orders.service.' + service() | translate }} - - } - + @if (showDelivery()) { + + @if (service() === 'courier') { + + } @else { + + } + @if (showLabels()) { + + {{ 'orders.carrier.' + carrier() | translate }} + · + {{ 'orders.service.' + service() | translate }} + + } + + } `, changeDetection: ChangeDetectionStrategy.OnPush, @@ -93,6 +95,7 @@ export class OrderChannelIconsComponent { ShippingServiceType | string | null | undefined >('parcelLocker'); readonly showLabels = input(false); + readonly showDelivery = input(true); readonly iconSize = input(14); protected channel(): OrderSource { diff --git a/src/admin/src/app/features/products/product-form-modal.component.html b/src/admin/src/app/features/products/product-form-modal.component.html index 550d490..680527e 100644 --- a/src/admin/src/app/features/products/product-form-modal.component.html +++ b/src/admin/src/app/features/products/product-form-modal.component.html @@ -259,6 +259,11 @@
@@ -286,7 +298,14 @@
@if (img.url) { } + @if (uploadingImageIndex() === $index) { +
+ {{ 'products.uploading' | translate }} +
+ }
diff --git a/src/admin/src/app/features/products/product-form-modal.component.ts b/src/admin/src/app/features/products/product-form-modal.component.ts index 49b8fd3..4623ed4 100644 --- a/src/admin/src/app/features/products/product-form-modal.component.ts +++ b/src/admin/src/app/features/products/product-form-modal.component.ts @@ -169,7 +169,7 @@ export class ProductFormModalComponent { const input = event.target as HTMLInputElement; const file = input.files?.[0]; input.value = ''; - if (!file) { + if (!file || this.uploadingImageIndex() !== null) { return; } @@ -180,7 +180,7 @@ export class ProductFormModalComponent { const input = event.target as HTMLInputElement; const file = input.files?.[0]; input.value = ''; - if (!file) { + if (!file || this.uploadingImageIndex() !== null) { return; } @@ -190,10 +190,23 @@ export class ProductFormModalComponent { } #uploadToIndex(index: number, file: File): void { + this.error.set(''); + this.uploadingImageIndex.set(index); + + const localUrl = URL.createObjectURL(file); + const previousUrl = this.images[index]?.url ?? ''; + this.images[index] = { + ...this.images[index], + url: localUrl, + altText: this.images[index].altText || this.names.pl, + }; + this.images = [...this.images]; + this.#api .upload<{ url: string }>('/admin/products/images/upload', file) .subscribe({ next: (res) => { + URL.revokeObjectURL(localUrl); this.images[index] = { ...this.images[index], url: res.url, @@ -203,6 +216,12 @@ export class ProductFormModalComponent { this.uploadingImageIndex.set(null); }, error: () => { + URL.revokeObjectURL(localUrl); + this.images[index] = { + ...this.images[index], + url: previousUrl.startsWith('blob:') ? '' : previousUrl, + }; + this.images = [...this.images]; this.uploadingImageIndex.set(null); this.error.set('products.errorUpload'); }, diff --git a/src/admin/src/app/features/settings/settings-admin.component.html b/src/admin/src/app/features/settings/settings-admin.component.html index c80ea81..822afe5 100644 --- a/src/admin/src/app/features/settings/settings-admin.component.html +++ b/src/admin/src/app/features/settings/settings-admin.component.html @@ -255,7 +255,9 @@ - InPost + + + - DHL + + + - DPD + + +
-
- PayU - +
+ +
+ +
@@ -457,8 +465,8 @@
-
- InPost +
+
@@ -554,8 +562,8 @@
-
- DHL +
+
@@ -616,8 +624,8 @@
-
- DPD +
+
@@ -679,17 +687,19 @@
-
- Allegro - +
+ +
+ +
@@ -733,17 +743,19 @@
-
- Amazon - +
+ +
+ +
@@ -806,17 +818,19 @@
-
- iFirma - +
+ +
+ +
diff --git a/src/admin/src/app/features/settings/settings-admin.component.ts b/src/admin/src/app/features/settings/settings-admin.component.ts index 3224995..acade4c 100644 --- a/src/admin/src/app/features/settings/settings-admin.component.ts +++ b/src/admin/src/app/features/settings/settings-admin.component.ts @@ -10,6 +10,7 @@ import { ApiService } from '../../core/api.service'; import { TranslatePipe } from '../../core/i18n/translate.pipe'; import { ToastService } from '../../shared/toast.service'; import { AdminSkeletonComponent } from '../../shared/admin-skeleton.component'; +import { IntegrationBrandComponent } from '../../shared/integration-brand.component'; import { AdminSettings, DEFAULT_ADMIN_SETTINGS, @@ -100,7 +101,12 @@ interface StoreSettingsApi { @Component({ selector: 'app-settings-admin', standalone: true, - imports: [FormsModule, TranslatePipe, AdminSkeletonComponent], + imports: [ + FormsModule, + TranslatePipe, + AdminSkeletonComponent, + IntegrationBrandComponent, + ], templateUrl: './settings-admin.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/src/admin/src/app/shared/integration-brand.component.ts b/src/admin/src/app/shared/integration-brand.component.ts new file mode 100644 index 0000000..c6d0486 --- /dev/null +++ b/src/admin/src/app/shared/integration-brand.component.ts @@ -0,0 +1,56 @@ +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; + +/** Supported integration brand keys. */ +export type IntegrationBrandId = + | 'payu' + | 'inPost' + | 'dhl' + | 'dpd' + | 'allegro' + | 'amazon' + | 'ifirma'; + +const BRAND_META: Record< + IntegrationBrandId, + { label: string; src: string } +> = { + payu: { label: 'PayU', src: 'brands/payu.svg' }, + inPost: { label: 'InPost', src: 'brands/inpost.svg' }, + dhl: { label: 'DHL', src: 'brands/dhl.svg' }, + dpd: { label: 'DPD', src: 'brands/dpd.svg' }, + allegro: { label: 'Allegro', src: 'brands/allegro.svg' }, + amazon: { label: 'Amazon', src: 'brands/amazon.svg' }, + ifirma: { label: 'iFirma', src: 'brands/ifirma.svg' }, +}; + +/** + * Brand mark + name for settings integration sections. + */ +@Component({ + selector: 'app-integration-brand', + standalone: true, + template: ` +
+ + @if (showName()) { + {{ meta().label }} + } +
+ `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class IntegrationBrandComponent { + readonly brand = input.required(); + readonly showName = input(true); + readonly compact = input(false); + + protected meta(): { label: string; src: string } { + return BRAND_META[this.brand()]; + } +} diff --git a/src/admin/src/styles/admin-k.css b/src/admin/src/styles/admin-k.css index 72a524e..9f04738 100644 --- a/src/admin/src/styles/admin-k.css +++ b/src/admin/src/styles/admin-k.css @@ -1855,6 +1855,108 @@ table.data tbody tr.is-selected { color: #1e40af; } +.integration-brand { + display: inline-flex; + align-items: center; + gap: 12px; + min-width: 0; +} + +.integration-brand__mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + flex-shrink: 0; + border-radius: 10px; + overflow: hidden; + border: 1px solid var(--border); + background: var(--surface); + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); +} + +.integration-brand__mark img { + display: block; + width: 40px; + height: 40px; + object-fit: cover; +} + +.integration-brand__name { + font-size: 15px; + font-weight: 650; + letter-spacing: -0.01em; + color: var(--text); + line-height: 1.2; +} + +.integration-brand--compact { + gap: 8px; +} + +.integration-brand--compact .integration-brand__mark, +.integration-brand--compact .integration-brand__mark img { + width: 28px; + height: 28px; + border-radius: 8px; +} + +.integration-brand--compact .integration-brand__name { + font-size: 13px; + font-weight: 600; +} + +.integration-head { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px 16px; + min-height: 40px; +} + +.integration-head__actions { + display: inline-flex; + align-items: center; + gap: 10px; + margin-left: auto; +} + +.integration-brand[data-brand='payu'] .integration-brand__mark { + border-color: #c5db6a; + background: #f4f9d9; +} + +.integration-brand[data-brand='inPost'] .integration-brand__mark { + border-color: #f5d76e; + background: #fff8db; +} + +.integration-brand[data-brand='dhl'] .integration-brand__mark { + border-color: #f0c84a; + background: #fff6d6; +} + +.integration-brand[data-brand='dpd'] .integration-brand__mark { + border-color: #f3a4b4; + background: #fde8ed; +} + +.integration-brand[data-brand='allegro'] .integration-brand__mark { + border-color: #ffb081; + background: #fff1e8; +} + +.integration-brand[data-brand='amazon'] .integration-brand__mark { + border-color: #f0c26b; + background: #fff8e8; +} + +.integration-brand[data-brand='ifirma'] .integration-brand__mark { + border-color: #8fc4b0; + background: #e8f5ef; +} + @media (prefers-reduced-motion: reduce) { .reveal, .soft-in,