@playpilot/tpi 5.7.0-beta.display.1 → 5.7.0-beta.video-2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playpilot/tpi",
3
- "version": "5.7.0-beta.display.1",
3
+ "version": "5.7.0-beta.video-2",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
package/src/lib/ads.ts CHANGED
@@ -9,45 +9,7 @@ export async function fetchAds() {
9
9
 
10
10
  if (!apiToken) throw new Error('No token was provided')
11
11
 
12
- return [{
13
- campaign_format: 'display',
14
- campaign_type: 'video',
15
- campaign_platforms: ['web', 'android_app', 'ios_app'],
16
- campaign_name: 'some_campaign_NL',
17
- campaign_start: '2025-04-24T00:00:00Z',
18
- campaign_end: '2025-04-30T23:59:00Z',
19
- campaign_region: 'nl',
20
- content: {
21
- header: 'Some top scroll header',
22
- header_logo: 'https://example.com/',
23
- header_logo_uuid: '4fb7d6ea152111f08dd20a58a9feac02',
24
- subheader: null,
25
- image: 'https://example.com/',
26
- image_uuid: '4fb7d6ea152111f08dd20a58a9feac02',
27
- video: null,
28
- format: null,
29
- },
30
- cta: {
31
- header: 'Button Label',
32
- subheader: null,
33
- image: null,
34
- image_uuid: null,
35
- url: 'https://google.com/',
36
- },
37
- content_playlist: null,
38
- provider: null,
39
- impression_trackers: [],
40
- target_title_uuid: null,
41
- target_title_sid: null,
42
- backfill_providers: [],
43
- autogenerated: false,
44
- enabled: true,
45
- hide_imdb_score: false,
46
- hide_sponsored_message: false,
47
- disclaimer: 'Some disclaimer',
48
- }]
49
-
50
- const response = await fetch(apiBaseUrl + `/ads/browse/?api-token=${apiToken}`, { headers })
12
+ const response = await fetch(apiBaseUrl + `/ads/browse/?region=nl&api-token=${apiToken}`, { headers })
51
13
 
52
14
  if (!response.ok) throw response
53
15
 
@@ -63,11 +25,12 @@ export function getFirstAdOfType(format: CampaignFormat): Campaign | null {
63
25
  export function campaignToPlaylink(campaign: Campaign): PlaylinkData {
64
26
  return {
65
27
  sid: '',
66
- name: campaign.cta.header || '',
28
+ name: campaign.content.header || '',
67
29
  url: campaign.cta.url || '',
68
30
  logo_url: campaign.cta.image || '',
69
31
  highlighted: true,
70
- cta_text: campaign.cta.header,
32
+ cta_text: campaign.content.subheader,
33
+ action_text: campaign.cta.header,
71
34
  extra_info: {
72
35
  category: 'SVOD'
73
36
  }
@@ -42,4 +42,4 @@ export type CampaignCTA = {
42
42
 
43
43
  export type CampaignPlatform = 'web' | 'android_app' | 'ios_app'
44
44
 
45
- export type CampaignFormat = 'takeover' | 'horizontal' | 'hero' | 'top_scroll' | 'display'
45
+ export type CampaignFormat = 'takeover' | 'horizontal' | 'hero' | 'top_scroll' | 'card'
@@ -5,6 +5,8 @@ export type PlaylinkData = {
5
5
  logo_url: string
6
6
  highlighted?: boolean
7
7
  cta_text?: string | null
8
+ // This doesn't actually exists on the API objects, it's only present from ads
9
+ action_text?: string | null,
8
10
  extra_info: {
9
11
  category: PlaylinkCategory
10
12
  }
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { SplitTest } from '$lib/enums/SplitTest'
3
3
  import { TrackingEvent } from '$lib/enums/TrackingEvent'
4
- import { trackSplitTestAction } from '$lib/splitTest'
4
+ import { trackSplitTestAction, trackSplitTestView } from '$lib/splitTest'
5
5
  import { track } from '$lib/tracking'
6
6
  import type { Campaign } from '$lib/types/campaign'
7
7
 
@@ -11,31 +11,50 @@
11
11
 
12
12
  const { campaign }: Props = $props()
13
13
 
14
+ const { disclaimer, content, cta } = $derived(campaign)
15
+ const { header, subheader, header_logo: logo, image } = $derived(content)
16
+ const { header: buttonLabel, url: href } = $derived(cta)
17
+
18
+ trackSplitTestView(SplitTest.DisplayAdPosition)
19
+
14
20
  function onclick(): void {
15
21
  track(TrackingEvent.DisplayAdClick, null, { campaign_name: campaign.campaign_name })
16
22
  trackSplitTestAction(SplitTest.DisplayAdPosition, 'click')
17
23
  }
18
24
  </script>
19
25
 
20
- <a href="/" target="_blank" class="display" rel="sponsored" {onclick}>
21
- <div class="disclaimer">
22
- Watch The Morningshow on Apple TV+ (Ad)
23
- </div>
24
-
25
- <div class="cta">
26
- <img class="logo" src="https://picsum.photos/seed/a/40/40" alt="" height="40" width="40" />
27
-
28
- <div>
29
- <span class="name">Apple TV+</span>
30
- <div class="subheader">Get Apple TV+ free for 1 week</div>
26
+ <a {href} target="_blank" class="display" rel="sponsored" {onclick}>
27
+ {#if disclaimer}
28
+ <div class="disclaimer">
29
+ {disclaimer}
31
30
  </div>
32
-
33
- <div class="action">
34
- Get offer
31
+ {/if}
32
+
33
+ {#if logo || header || subheader || buttonLabel}
34
+ <div class="cta">
35
+ {#if logo}
36
+ <img class="logo" src={logo} alt="" height="40" width="40" />
37
+ {/if}
38
+
39
+ <div>
40
+ {#if header}
41
+ <span class="name">{header}</span>
42
+ {/if}
43
+
44
+ {#if subheader}
45
+ <div class="subheader">{subheader}</div>
46
+ {/if}
47
+ </div>
48
+
49
+ {#if buttonLabel}
50
+ <div class="action">{buttonLabel}</div>
51
+ {/if}
35
52
  </div>
36
- </div>
53
+ {/if}
37
54
 
38
- <img class="background" src="https://picsum.photos/seed/movie/800/450" alt="" />
55
+ {#if image}
56
+ <img class="background" src={image} alt="" />
57
+ {/if}
39
58
  </a>
40
59
 
41
60
  <style lang="scss">
@@ -5,9 +5,8 @@
5
5
  import DragHandle from './DragHandle.svelte'
6
6
  import { onMount, setContext, type Snippet } from 'svelte'
7
7
  import { prefersReducedMotion } from 'svelte/motion'
8
- import { isInSplitTestVariant, trackSplitTestView } from '$lib/splitTest'
8
+ import { isInSplitTestVariant } from '$lib/splitTest'
9
9
  import { SplitTest } from '$lib/enums/SplitTest'
10
- import { getFirstAdOfType } from '$lib/ads'
11
10
 
12
11
  interface Props {
13
12
  children: Snippet
@@ -30,8 +29,6 @@
30
29
  $effect(() => { if (windowWidth) dialogOffset = dialogElement?.offsetTop || 0 })
31
30
  $effect(() => { setTimeout(() => dialogOffset = dialogElement?.offsetTop || 0) }) // Set after the dialog has shown to get the proper height
32
31
 
33
- if (getFirstAdOfType('display')) trackSplitTestView(SplitTest.DisplayAdPosition)
34
-
35
32
  setContext('scope', 'modal')
36
33
 
37
34
  onMount(() => {
@@ -216,5 +213,11 @@
216
213
  margin-top: 0;
217
214
  }
218
215
  }
216
+
217
+ .prepend + & {
218
+ &:not(.inline) {
219
+ margin-top: 0;
220
+ }
221
+ }
219
222
  }
220
223
  </style>
@@ -11,7 +11,7 @@
11
11
 
12
12
  const { playlink, hideCategory = false, onclick = () => null }: Props = $props()
13
13
 
14
- const { name, url, logo_url, highlighted, cta_text, extra_info: { category } } = $derived(playlink)
14
+ const { name, url, logo_url, highlighted, cta_text, action_text, extra_info: { category } } = $derived(playlink)
15
15
 
16
16
  const categoryStrings = {
17
17
  SVOD: t('Stream'),
@@ -36,7 +36,7 @@
36
36
  {/if}
37
37
 
38
38
  <div class="action">
39
- {t('Watch')}
39
+ {action_text || t('Watch')}
40
40
  </div>
41
41
  </div>
42
42
  </a>
@@ -130,7 +130,7 @@
130
130
  z-index: 5;
131
131
  display: grid;
132
132
  grid-template-areas: "image name action" "image category action";
133
- grid-template-columns: $image-size auto margin(4);
133
+ grid-template-columns: $image-size auto margin(6);
134
134
  align-items: center;
135
135
  gap: 0 margin(0.75);
136
136
  padding: margin(0.75);
@@ -21,7 +21,7 @@
21
21
  const { playlinks, title }: Props = $props()
22
22
 
23
23
  const isModal = getContext('scope') === 'modal'
24
- const displayAd = getFirstAdOfType('display')
24
+ const displayAd = getFirstAdOfType('card')
25
25
  const showDisplayAd = displayAd && isModal && isInSplitTestVariant(SplitTest.DisplayAdPosition, 0)
26
26
  const showPlaylinkAd = displayAd && isModal && isInSplitTestVariant(SplitTest.DisplayAdPosition, 1)
27
27
 
@@ -19,7 +19,7 @@
19
19
  const { onclose, title }: Props = $props()
20
20
 
21
21
  const topScrollAd = getFirstAdOfType('top_scroll')
22
- const displayAd = getFirstAdOfType('display')
22
+ const displayAd = getFirstAdOfType('card')
23
23
 
24
24
  const isInDisplayAdFormatSplitTest = isInSplitTestVariant(SplitTest.DisplayAdPosition, 1) || isInSplitTestVariant(SplitTest.DisplayAdPosition, 2)
25
25
 
@@ -56,4 +56,54 @@ describe('Display.svelte', () => {
56
56
 
57
57
  expect(track).toHaveBeenCalledWith(TrackingEvent.DisplayAdClick, null, { campaign_name: campaign.campaign_name })
58
58
  })
59
+
60
+ it('Should render link without any content by default', () => {
61
+ /** @ts-ignore */
62
+ const { container } = render(Display, { campaign: { cta: {}, content: {} } })
63
+
64
+ expect(/** @type {HTMLAnchorElement} */ (container.querySelector('a'))).toBeTruthy()
65
+ expect(/** @type {HTMLAnchorElement} */ (container.querySelector('a')).children).toHaveLength(0)
66
+ })
67
+
68
+ it('Should render disclaimer when given', () => {
69
+ /** @ts-ignore */
70
+ const { getByText } = render(Display, { campaign: { disclaimer: 'Some disclaimer', cta: {}, content: {} } })
71
+
72
+ expect(getByText('Some disclaimer')).toBeTruthy()
73
+ })
74
+
75
+ it('Should render logo when given', () => {
76
+ /** @ts-ignore */
77
+ const { getByRole } = render(Display, { campaign: { cta: {}, content: { header_logo: 'test' } } })
78
+
79
+ expect(getByRole('presentation').getAttribute('src')).toBe('test')
80
+ })
81
+
82
+ it('Should render background image when given', () => {
83
+ /** @ts-ignore */
84
+ const { getByRole } = render(Display, { campaign: { cta: {}, content: { image: 'test' } } })
85
+
86
+ expect(getByRole('presentation').getAttribute('src')).toBe('test')
87
+ })
88
+
89
+ it('Should render header when given', () => {
90
+ /** @ts-ignore */
91
+ const { getByText } = render(Display, { campaign: { cta: {}, content: { header: 'Some header' } } })
92
+
93
+ expect(getByText('Some header')).toBeTruthy()
94
+ })
95
+
96
+ it('Should render subheader when given', () => {
97
+ /** @ts-ignore */
98
+ const { getByText } = render(Display, { campaign: { cta: {}, content: { header: 'Some subheader' } } })
99
+
100
+ expect(getByText('Some subheader')).toBeTruthy()
101
+ })
102
+
103
+ it('Should render button when cta header is given', () => {
104
+ /** @ts-ignore */
105
+ const { getByText } = render(Display, { campaign: { cta: { header: 'Some button label' }, content: {} } })
106
+
107
+ expect(getByText('Some button label')).toBeTruthy()
108
+ })
59
109
  })
@@ -85,4 +85,11 @@ describe('Playlink.svelte', () => {
85
85
  expect(getByText('Some playlink').closest('.playlink')?.classList).not.toContain('highlighted')
86
86
  expect(queryByText('Some CTA')).not.toBeTruthy()
87
87
  })
88
+
89
+ it('Should use action_text in button label when given', () => {
90
+ // @ts-ignore
91
+ const { getByText } = render(Playlink, { playlink: { name: 'Some highlighted playlink', action_text: 'Some action text', extra_info: { category: 'BUY' } } })
92
+
93
+ expect(getByText('Some action text')).toBeTruthy()
94
+ })
88
95
  })
@@ -66,7 +66,7 @@ describe('TitleModal.svelte', () => {
66
66
 
67
67
  it('Should render display ad when given', () => {
68
68
  // @ts-ignore
69
- window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'display', content: {}, cta: {} }] }
69
+ window.PlayPilotLinkInjections = { ads: [{ campaign_format: 'card', content: {}, cta: {} }] }
70
70
 
71
71
  const { container } = render(TitleModal, { onclose, title })
72
72