@sarimarcus/content-sites-ui 0.21.0 → 0.22.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/CHANGELOG.md +4 -0
- package/package.json +2 -2
- package/src/affiliate/cardItem.ts +42 -0
- package/src/affiliate/index.ts +3 -1
- package/src/affiliate/resources.ts +13 -0
- package/src/affiliate/widgetSrc.ts +7 -0
- package/src/layout/analytics.ts +2 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
All packages in the platform release in lockstep; entries are per release version.
|
|
4
4
|
|
|
5
|
+
## 0.22.0 (2026-10-04)
|
|
6
|
+
|
|
7
|
+
- `./affiliate`: `widgetLoaders` in `resources.ts` declares each Travelpayouts widget kind's loader URL, promo id and campaign id (the CSP coverage gate reads the promo ids from it); `widgetSrc(account, options)` builds a widget loader URL from it; `gygCardItem` and `gygCardLocale` turn a cached GYG activity into an `AffiliateCardItem`. `pageTypeOf` is re-exported from core, unchanged (SLA-2919).
|
|
8
|
+
|
|
5
9
|
## 0.21.0 (2026-10-02)
|
|
6
10
|
|
|
7
11
|
- Responsive images pass core's `IMAGE_LADDER` instead of per-component width arrays, and serve WebP only: `InlineImage` and `EntityCard` default `formats` to `['webp']` (SLA-2912).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sarimarcus/content-sites-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Universal Astro UI components, layout shell and design tokens shared by the content sites.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
"@lucide/astro": "^1.23.0",
|
|
50
50
|
"astro": "^7.0.6",
|
|
51
51
|
"tailwindcss": "^4.1.18",
|
|
52
|
-
"@sarimarcus/content-sites-core": "0.
|
|
52
|
+
"@sarimarcus/content-sites-core": "0.22.0"
|
|
53
53
|
}
|
|
54
54
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { gygProductId } from '@sarimarcus/content-sites-core/affiliate';
|
|
2
|
+
import type { AffiliateCardItem } from './types';
|
|
3
|
+
|
|
4
|
+
/** One GetYourGuide activity as cached by a site (`src/data/affiliates/gyg.json`). */
|
|
5
|
+
export interface GygCardActivity {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
price: number;
|
|
9
|
+
currency: string;
|
|
10
|
+
rating?: number;
|
|
11
|
+
reviewCount?: number;
|
|
12
|
+
imageUrl: string;
|
|
13
|
+
affiliateUrl: string;
|
|
14
|
+
duration: string;
|
|
15
|
+
language: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const LOCALES: Record<string, string> = { en: 'en-GB', fr: 'fr-FR' };
|
|
19
|
+
|
|
20
|
+
/** The card's number/currency locale for an activity's language code; en-GB when unknown. */
|
|
21
|
+
export const gygCardLocale = (language: string) => LOCALES[language] || 'en-GB';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A cached GYG activity as an AffiliateCard item. The site passes what it owns: the campaign-tagged `href` and the
|
|
25
|
+
* resolved `image`.
|
|
26
|
+
*/
|
|
27
|
+
export function gygCardItem(a: GygCardActivity, { href, image }: { href: string; image: AffiliateCardItem['image'] }): AffiliateCardItem {
|
|
28
|
+
return {
|
|
29
|
+
href,
|
|
30
|
+
provider: 'gyg',
|
|
31
|
+
productId: gygProductId(a.affiliateUrl),
|
|
32
|
+
image,
|
|
33
|
+
title: a.title,
|
|
34
|
+
description: a.description,
|
|
35
|
+
price: a.price,
|
|
36
|
+
currency: a.currency,
|
|
37
|
+
rating: a.rating,
|
|
38
|
+
reviewCount: a.reviewCount,
|
|
39
|
+
duration: a.duration,
|
|
40
|
+
locale: gygCardLocale(a.language),
|
|
41
|
+
};
|
|
42
|
+
}
|
package/src/affiliate/index.ts
CHANGED
|
@@ -11,7 +11,9 @@ export { default as AffiliateCardSection } from './AffiliateCardSection.astro';
|
|
|
11
11
|
export { default as AffiliateCarousel } from './AffiliateCarousel.astro';
|
|
12
12
|
export { default as AffiliateComparisonTable } from './AffiliateComparisonTable.astro';
|
|
13
13
|
export { default as HotelAffiliateCTA } from './HotelAffiliateCTA.astro';
|
|
14
|
-
export { externalResources, emeraldResources, EMERALD_ORIGIN } from './resources';
|
|
14
|
+
export { externalResources, emeraldResources, EMERALD_ORIGIN, widgetLoaders } from './resources';
|
|
15
|
+
export { widgetSrc } from './widgetSrc';
|
|
16
|
+
export { gygCardItem, gygCardLocale, type GygCardActivity } from './cardItem';
|
|
15
17
|
export type { WidgetKind, CspDirective, WidgetResources } from './resources';
|
|
16
18
|
export type { AffiliateCardLabels, AffiliateCardItem, HotelCtaLabels, ComparisonRow, ComparisonTableLabels } from './types';
|
|
17
19
|
export { selectActivities, type SelectableActivity, type SelectOptions } from './select';
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// Every other component in this folder loads nothing: its outbound links are caller data, not resources.
|
|
7
7
|
// Plain data and erasable types only: the gate imports this file under Node.
|
|
8
8
|
|
|
9
|
+
import type { WidgetLoader } from '@sarimarcus/content-sites-core/affiliate';
|
|
10
|
+
|
|
9
11
|
export type WidgetKind = 'hotel' | 'activity' | 'train' | 'car';
|
|
10
12
|
export type CspDirective = 'script-src' | 'frame-src' | 'style-src';
|
|
11
13
|
export type WidgetResources = Partial<Record<CspDirective, readonly string[]>>;
|
|
@@ -29,6 +31,17 @@ export const externalResources: Record<WidgetKind, WidgetResources> = {
|
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Each kind's loader script and the ids its URL reports under (SLA-2919). `promoId` is also how
|
|
36
|
+
* scripts/validate-csp-coverage.mjs recognises a widget in a built page that carries no `data-affiliate-widget` marker.
|
|
37
|
+
*/
|
|
38
|
+
export const widgetLoaders: Record<WidgetKind, WidgetLoader> = {
|
|
39
|
+
hotel: { url: 'https://tpwdg.com/content', promoId: '2693', campaignId: '84' },
|
|
40
|
+
activity: { url: 'https://tpwdg.com/content', promoId: '4039', campaignId: '108' },
|
|
41
|
+
train: { url: 'https://c91.travelpayouts.com/content', promoId: '4770' },
|
|
42
|
+
car: { url: 'https://tpwdg.com/content', promoId: '3873', campaignId: '117' },
|
|
43
|
+
};
|
|
44
|
+
|
|
32
45
|
/** The Travelpayouts Emerald loader every page carries (EmeraldLoader): https://emrldtp.com/<base64 trs>.js?t=<trs>. */
|
|
33
46
|
export const EMERALD_ORIGIN = 'https://emrldtp.com';
|
|
34
47
|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { travelpayoutsWidgetSrc, type WidgetAccount, type WidgetOptions } from '@sarimarcus/content-sites-core/affiliate';
|
|
2
|
+
import { widgetLoaders } from './resources';
|
|
3
|
+
|
|
4
|
+
/** A Travelpayouts widget loader URL for `options.kind`, with the loader and ids declared in resources.ts. */
|
|
5
|
+
export function widgetSrc(account: WidgetAccount, options: WidgetOptions): string {
|
|
6
|
+
return travelpayoutsWidgetSrc(widgetLoaders[options.kind], account, options);
|
|
7
|
+
}
|
package/src/layout/analytics.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Pushes custom events to the GTM dataLayer.
|
|
2
2
|
// GTM consent mode (Silktide analytics_storage) governs whether GA4 receives each event.
|
|
3
|
+
import { pageTypeOf } from '@sarimarcus/content-sites-core/url';
|
|
3
4
|
|
|
4
5
|
declare global {
|
|
5
6
|
interface Window {
|
|
@@ -21,19 +22,7 @@ export function track(eventName: string, params: Record<string, unknown> = {}):
|
|
|
21
22
|
window.dataLayer.push({ event: eventName, page_path: window.location.pathname, ...params });
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
* Page type: the first path segment, 'home' at the root.
|
|
26
|
-
*
|
|
27
|
-
* The single definition behind both the GA4 `page_type` param (runtime pathname, '/plan/x') and a site's GYG `cmp`
|
|
28
|
-
* segment (BUILD-time `Astro.url.pathname`, '/index.html' under build.format 'file'). The `.html`/`index`
|
|
29
|
-
* normalisation is what makes the two agree.
|
|
30
|
-
*/
|
|
31
|
-
export function pageTypeOf(pathname: string): string {
|
|
32
|
-
const first = pathname.split('/').filter(Boolean)[0];
|
|
33
|
-
if (!first) return 'home';
|
|
34
|
-
const bare = first.replace(/\.html$/, '');
|
|
35
|
-
return bare === 'index' || bare === '' ? 'home' : bare;
|
|
36
|
-
}
|
|
25
|
+
export { pageTypeOf };
|
|
37
26
|
|
|
38
27
|
// Param key derived from the data-track-* attribute name where the
|
|
39
28
|
// event-specific prefix (e.g. 'cta_', 'affiliate_', 'link_') has been stripped.
|