@times-components/ts-components 1.145.1-b32ea924749bafdf12e3e8515023538fa20e7808.3 → 1.145.1-cfea81c4084e6f91221ea00fec9fc730d5b933cb.4
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/dist/components/carousel-component/CarouselComponent.stories.js +146 -0
- package/dist/components/carousel-component/CarouselItem.d.ts +3 -0
- package/dist/components/carousel-component/CarouselItem.js +11 -0
- package/dist/components/carousel-component/DefaultNavigationArrow.d.ts +8 -0
- package/dist/components/carousel-component/DefaultNavigationArrow.js +6 -0
- package/dist/components/carousel-component/DefaultPageDot.d.ts +8 -0
- package/dist/components/carousel-component/DefaultPageDot.js +4 -0
- package/dist/components/carousel-component/__tests__/CarouselComponent.test.d.ts +1 -0
- package/dist/components/carousel-component/__tests__/CarouselComponent.test.js +163 -0
- package/dist/components/carousel-component/__tests__/CarouselItem.test.d.ts +1 -0
- package/dist/components/carousel-component/__tests__/CarouselItem.test.js +80 -0
- package/dist/components/carousel-component/__tests__/DefaultNavigationArrow.test.d.ts +1 -0
- package/dist/components/carousel-component/__tests__/DefaultNavigationArrow.test.js +62 -0
- package/dist/components/carousel-component/__tests__/DefaultPageDot.test.d.ts +1 -0
- package/dist/components/carousel-component/__tests__/DefaultPageDot.test.js +68 -0
- package/dist/components/carousel-component/hooks/__tests__/useCarousel.test.d.ts +1 -0
- package/dist/components/carousel-component/hooks/__tests__/useCarousel.test.js +314 -0
- package/dist/components/carousel-component/hooks/useCarousel.d.ts +2 -0
- package/dist/components/carousel-component/hooks/useCarousel.js +140 -0
- package/dist/components/carousel-component/index.d.ts +4 -0
- package/dist/components/carousel-component/index.js +20 -0
- package/dist/components/carousel-component/styles.d.ts +30 -0
- package/dist/components/carousel-component/styles.js +120 -0
- package/dist/components/carousel-component/types.d.ts +46 -0
- package/dist/components/carousel-component/types.js +2 -0
- package/dist/components/opta/cricket/scorecard/OptaCricketScorecard.js +2 -10
- package/dist/components/opta/cricket/scorecard/__tests__/OptaCricketScorecard.test.js +17 -94
- package/dist/components/opta/football/summary/OptaFootballSummary.js +2 -10
- package/dist/components/opta/football/summary/__tests__/OptaFootballSummary.test.js +18 -95
- package/dist/components/opta/rugby/summary/OptaRugbySummary.js +2 -10
- package/dist/components/opta/rugby/summary/__tests__/OptaRugbySummary.test.js +17 -94
- package/dist/components/trip-cards/SkeletonCard.d.ts +2 -0
- package/dist/components/trip-cards/SkeletonCard.js +21 -0
- package/dist/components/trip-cards/TripCard.d.ts +3 -0
- package/dist/components/trip-cards/TripCard.js +47 -0
- package/dist/components/trip-cards/TripCards.stories.d.ts +1 -0
- package/dist/components/trip-cards/TripCards.stories.js +40 -0
- package/dist/components/trip-cards/TripCardsLayout.d.ts +3 -0
- package/dist/components/trip-cards/TripCardsLayout.js +26 -0
- package/dist/components/trip-cards/__tests__/SkeletonCard.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/SkeletonCard.test.js +139 -0
- package/dist/components/trip-cards/__tests__/TripCard.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/TripCard.test.js +95 -0
- package/dist/components/trip-cards/__tests__/TripCardsLayout.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/TripCardsLayout.test.js +277 -0
- package/dist/components/trip-cards/__tests__/assets.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/assets.test.js +165 -0
- package/dist/components/trip-cards/__tests__/helpers.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/helpers.test.js +135 -0
- package/dist/components/trip-cards/__tests__/index.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/index.test.js +437 -0
- package/dist/components/trip-cards/__tests__/mockData.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/mockData.test.js +57 -0
- package/dist/components/trip-cards/__tests__/skeletonStyles.test.d.ts +1 -0
- package/dist/components/trip-cards/__tests__/skeletonStyles.test.js +194 -0
- package/dist/components/trip-cards/assets/BoatIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/BoatIcon.js +4 -0
- package/dist/components/trip-cards/assets/CalendarIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/CalendarIcon.js +4 -0
- package/dist/components/trip-cards/assets/ChevronRightIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/ChevronRightIcon.js +4 -0
- package/dist/components/trip-cards/assets/LocationIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/LocationIcon.js +4 -0
- package/dist/components/trip-cards/assets/MoonIcon.d.ts +1 -0
- package/dist/components/trip-cards/assets/MoonIcon.js +4 -0
- package/dist/components/trip-cards/assets/index.d.ts +6 -0
- package/dist/components/trip-cards/assets/index.js +7 -0
- package/dist/components/trip-cards/helpers.d.ts +4 -0
- package/dist/components/trip-cards/helpers.js +74 -0
- package/dist/components/trip-cards/index.d.ts +4 -0
- package/dist/components/trip-cards/index.js +70 -0
- package/dist/components/trip-cards/mockData.d.ts +3 -0
- package/dist/components/trip-cards/mockData.js +323 -0
- package/dist/components/trip-cards/skeletonStyles.d.ts +9 -0
- package/dist/components/trip-cards/skeletonStyles.js +37 -0
- package/dist/components/trip-cards/styles.d.ts +39 -0
- package/dist/components/trip-cards/styles.js +387 -0
- package/dist/components/trip-cards/types.d.ts +87 -0
- package/dist/components/trip-cards/types.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/package.json +3 -3
- package/rnw.js +1 -1
- package/src/components/carousel-component/CarouselComponent.stories.tsx +220 -0
- package/src/components/carousel-component/CarouselItem.tsx +20 -0
- package/src/components/carousel-component/DefaultNavigationArrow.tsx +37 -0
- package/src/components/carousel-component/DefaultPageDot.tsx +20 -0
- package/src/components/carousel-component/__tests__/CarouselComponent.test.tsx +259 -0
- package/src/components/carousel-component/__tests__/CarouselItem.test.tsx +140 -0
- package/src/components/carousel-component/__tests__/DefaultNavigationArrow.test.tsx +153 -0
- package/src/components/carousel-component/__tests__/DefaultPageDot.test.tsx +105 -0
- package/src/components/carousel-component/hooks/__tests__/useCarousel.test.ts +438 -0
- package/src/components/carousel-component/hooks/useCarousel.ts +187 -0
- package/src/components/carousel-component/index.tsx +88 -0
- package/src/components/carousel-component/styles.ts +140 -0
- package/src/components/carousel-component/types.ts +51 -0
- package/src/components/opta/cricket/scorecard/OptaCricketScorecard.tsx +0 -13
- package/src/components/opta/cricket/scorecard/__tests__/OptaCricketScorecard.test.tsx +16 -126
- package/src/components/opta/cricket/scorecard/__tests__/__snapshots__/OptaCricketScorecard.test.tsx.snap +6 -5
- package/src/components/opta/football/summary/OptaFootballSummary.tsx +0 -13
- package/src/components/opta/football/summary/__tests__/OptaFootballSummary.test.tsx +18 -127
- package/src/components/opta/football/summary/__tests__/__snapshots__/OptaFootballSummary.test.tsx.snap +6 -5
- package/src/components/opta/rugby/summary/OptaRugbySummary.tsx +0 -13
- package/src/components/opta/rugby/summary/__tests__/OptaRugbySummary.test.tsx +17 -127
- package/src/components/opta/rugby/summary/__tests__/__snapshots__/OptaRugbySummary.test.tsx.snap +6 -5
- package/src/components/trip-cards/SkeletonCard.tsx +54 -0
- package/src/components/trip-cards/TripCard.tsx +135 -0
- package/src/components/trip-cards/TripCards.stories.tsx +67 -0
- package/src/components/trip-cards/TripCardsLayout.tsx +75 -0
- package/src/components/trip-cards/__tests__/SkeletonCard.test.tsx +169 -0
- package/src/components/trip-cards/__tests__/TripCard.test.tsx +120 -0
- package/src/components/trip-cards/__tests__/TripCardsLayout.test.tsx +532 -0
- package/src/components/trip-cards/__tests__/assets.test.tsx +206 -0
- package/src/components/trip-cards/__tests__/helpers.test.ts +165 -0
- package/src/components/trip-cards/__tests__/index.test.tsx +499 -0
- package/src/components/trip-cards/__tests__/mockData.test.ts +67 -0
- package/src/components/trip-cards/__tests__/skeletonStyles.test.tsx +256 -0
- package/src/components/trip-cards/assets/BoatIcon.tsx +17 -0
- package/src/components/trip-cards/assets/CalendarIcon.tsx +17 -0
- package/src/components/trip-cards/assets/ChevronRightIcon.tsx +20 -0
- package/src/components/trip-cards/assets/LocationIcon.tsx +17 -0
- package/src/components/trip-cards/assets/MoonIcon.tsx +17 -0
- package/src/components/trip-cards/assets/index.ts +7 -0
- package/src/components/trip-cards/helpers.ts +99 -0
- package/src/components/trip-cards/index.tsx +104 -0
- package/src/components/trip-cards/mockData.ts +351 -0
- package/src/components/trip-cards/skeletonStyles.ts +46 -0
- package/src/components/trip-cards/styles.ts +426 -0
- package/src/components/trip-cards/types.ts +91 -0
- package/src/index.ts +2 -0
- package/dist/components/opta/utils/__tests__/emitEvent.test.js +0 -264
- package/dist/components/opta/utils/emitEvent.d.ts +0 -1
- package/dist/components/opta/utils/emitEvent.js +0 -9
- package/src/components/opta/utils/__tests__/emitEvent.test.tsx +0 -415
- package/src/components/opta/utils/emitEvent.ts +0 -11
- /package/dist/components/{opta/utils/__tests__/emitEvent.test.d.ts → carousel-component/CarouselComponent.stories.d.ts} +0 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import {
|
|
5
|
+
SkeletonLine,
|
|
6
|
+
SkeletonHeadline,
|
|
7
|
+
SkeletonPrice,
|
|
8
|
+
SkeletonButton
|
|
9
|
+
} from '../skeletonStyles';
|
|
10
|
+
|
|
11
|
+
describe('Skeleton Styles', () => {
|
|
12
|
+
describe('SkeletonLine', () => {
|
|
13
|
+
it('renders with default width', () => {
|
|
14
|
+
const { container } = render(<SkeletonLine />);
|
|
15
|
+
const element = container.firstChild;
|
|
16
|
+
expect(element).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('accepts custom width prop', () => {
|
|
20
|
+
const { container } = render(<SkeletonLine width="80px" />);
|
|
21
|
+
const element = container.firstChild;
|
|
22
|
+
expect(element).toBeInTheDocument();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('accepts custom marginBottom prop', () => {
|
|
26
|
+
const { container } = render(<SkeletonLine marginBottom="4px" />);
|
|
27
|
+
const element = container.firstChild;
|
|
28
|
+
expect(element).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('accepts both width and marginBottom props', () => {
|
|
32
|
+
const { container } = render(
|
|
33
|
+
<SkeletonLine width="60px" marginBottom="8px" />
|
|
34
|
+
);
|
|
35
|
+
const element = container.firstChild;
|
|
36
|
+
expect(element).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders as a div element', () => {
|
|
40
|
+
const { container } = render(<SkeletonLine />);
|
|
41
|
+
const element = container.firstChild;
|
|
42
|
+
if (element) {
|
|
43
|
+
expect(element.nodeName).toBe('DIV');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('applies skeleton styling', () => {
|
|
48
|
+
const { container } = render(<SkeletonLine />);
|
|
49
|
+
const element = container.firstChild as HTMLElement;
|
|
50
|
+
expect(element.className).toMatch(/sc-/);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('SkeletonHeadline', () => {
|
|
55
|
+
it('renders correctly', () => {
|
|
56
|
+
const { container } = render(<SkeletonHeadline />);
|
|
57
|
+
const element = container.firstChild;
|
|
58
|
+
expect(element).toBeInTheDocument();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('renders as a div element', () => {
|
|
62
|
+
const { container } = render(<SkeletonHeadline />);
|
|
63
|
+
const element = container.firstChild;
|
|
64
|
+
if (element) {
|
|
65
|
+
expect(element.nodeName).toBe('DIV');
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('has styled component class', () => {
|
|
70
|
+
const { container } = render(<SkeletonHeadline />);
|
|
71
|
+
const element = container.firstChild as HTMLElement;
|
|
72
|
+
expect(element.className).toMatch(/sc-/);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('accepts className prop', () => {
|
|
76
|
+
const { container } = render(<SkeletonHeadline className="custom" />);
|
|
77
|
+
const element = container.firstChild as HTMLElement;
|
|
78
|
+
expect(element).toHaveClass('custom');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('SkeletonPrice', () => {
|
|
83
|
+
it('renders correctly', () => {
|
|
84
|
+
const { container } = render(<SkeletonPrice />);
|
|
85
|
+
const element = container.firstChild;
|
|
86
|
+
expect(element).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('renders as a div element', () => {
|
|
90
|
+
const { container } = render(<SkeletonPrice />);
|
|
91
|
+
const element = container.firstChild;
|
|
92
|
+
if (element) {
|
|
93
|
+
expect(element.nodeName).toBe('DIV');
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('has styled component class', () => {
|
|
98
|
+
const { container } = render(<SkeletonPrice />);
|
|
99
|
+
const element = container.firstChild as HTMLElement;
|
|
100
|
+
expect(element.className).toMatch(/sc-/);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('can be used multiple times', () => {
|
|
104
|
+
const { container } = render(
|
|
105
|
+
<>
|
|
106
|
+
<SkeletonPrice />
|
|
107
|
+
<SkeletonPrice />
|
|
108
|
+
<SkeletonPrice />
|
|
109
|
+
</>
|
|
110
|
+
);
|
|
111
|
+
const elements = container.querySelectorAll('div');
|
|
112
|
+
expect(elements.length).toBe(3);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('SkeletonButton', () => {
|
|
117
|
+
it('renders correctly', () => {
|
|
118
|
+
const { container } = render(<SkeletonButton as="div" />);
|
|
119
|
+
const element = container.firstChild;
|
|
120
|
+
expect(element).toBeInTheDocument();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('renders as div when specified', () => {
|
|
124
|
+
const { container } = render(<SkeletonButton as="div" />);
|
|
125
|
+
const element = container.firstChild;
|
|
126
|
+
if (element) {
|
|
127
|
+
expect(element.nodeName).toBe('DIV');
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('has styled component class', () => {
|
|
132
|
+
const { container } = render(<SkeletonButton as="div" />);
|
|
133
|
+
const element = container.firstChild as HTMLElement;
|
|
134
|
+
expect(element.className).toMatch(/sc-/);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('accepts additional props', () => {
|
|
138
|
+
const { container } = render(
|
|
139
|
+
<SkeletonButton as="div" data-testid="skeleton-button" />
|
|
140
|
+
);
|
|
141
|
+
const element = container.firstChild;
|
|
142
|
+
expect(element).toHaveAttribute('data-testid', 'skeleton-button');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('is not interactive', () => {
|
|
146
|
+
const { container } = render(<SkeletonButton as="div" />);
|
|
147
|
+
const element = container.firstChild;
|
|
148
|
+
if (element) {
|
|
149
|
+
expect(element.nodeName).toBe('DIV');
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('Skeleton Components Integration', () => {
|
|
155
|
+
it('all skeleton components render together', () => {
|
|
156
|
+
const { container } = render(
|
|
157
|
+
<div>
|
|
158
|
+
<SkeletonHeadline />
|
|
159
|
+
<SkeletonLine width="80px" />
|
|
160
|
+
<SkeletonPrice />
|
|
161
|
+
<SkeletonButton as="div" />
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
// Verify all components are present (4 skeleton components + 1 wrapper div = 5 divs minimum)
|
|
166
|
+
const elements = container.querySelectorAll('div');
|
|
167
|
+
expect(elements.length).toBeGreaterThanOrEqual(5);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('skeleton components maintain consistent styling', () => {
|
|
171
|
+
const { container } = render(
|
|
172
|
+
<div>
|
|
173
|
+
<SkeletonLine />
|
|
174
|
+
<SkeletonHeadline />
|
|
175
|
+
<SkeletonPrice />
|
|
176
|
+
</div>
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const styledElements = container.querySelectorAll('[class*="sc-"]');
|
|
180
|
+
expect(styledElements.length).toBeGreaterThanOrEqual(3);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('skeleton components are div elements by default', () => {
|
|
184
|
+
const { container } = render(
|
|
185
|
+
<div>
|
|
186
|
+
<SkeletonLine />
|
|
187
|
+
<SkeletonHeadline />
|
|
188
|
+
<SkeletonPrice />
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const divs = Array.from(container.querySelectorAll('div'));
|
|
193
|
+
expect(divs.length).toBeGreaterThanOrEqual(4);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('props are correctly applied to skeleton components', () => {
|
|
197
|
+
const { container } = render(
|
|
198
|
+
<div>
|
|
199
|
+
<SkeletonLine width="50px" marginBottom="10px" />
|
|
200
|
+
<SkeletonButton as="div" className="btn-skeleton" />
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const elements = container.querySelectorAll('div');
|
|
205
|
+
expect(elements.length).toBeGreaterThanOrEqual(2);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('Styled Components Structure', () => {
|
|
210
|
+
it('SkeletonLine has correct structure', () => {
|
|
211
|
+
const { container } = render(<SkeletonLine />);
|
|
212
|
+
expect(container.firstChild).toBeTruthy();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('SkeletonHeadline has correct structure', () => {
|
|
216
|
+
const { container } = render(<SkeletonHeadline />);
|
|
217
|
+
expect(container.firstChild).toBeTruthy();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('SkeletonPrice has correct structure', () => {
|
|
221
|
+
const { container } = render(<SkeletonPrice />);
|
|
222
|
+
expect(container.firstChild).toBeTruthy();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('SkeletonButton has correct structure', () => {
|
|
226
|
+
const { container } = render(<SkeletonButton as="div" />);
|
|
227
|
+
expect(container.firstChild).toBeTruthy();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('Props and Variants', () => {
|
|
232
|
+
it('SkeletonLine with different widths', () => {
|
|
233
|
+
const { rerender, container } = render(<SkeletonLine width="20px" />);
|
|
234
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
235
|
+
|
|
236
|
+
rerender(<SkeletonLine width="100px" />);
|
|
237
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
238
|
+
|
|
239
|
+
rerender(<SkeletonLine width="50%" />);
|
|
240
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('SkeletonLine with different margins', () => {
|
|
244
|
+
const { rerender, container } = render(
|
|
245
|
+
<SkeletonLine marginBottom="2px" />
|
|
246
|
+
);
|
|
247
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
248
|
+
|
|
249
|
+
rerender(<SkeletonLine marginBottom="8px" />);
|
|
250
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
251
|
+
|
|
252
|
+
rerender(<SkeletonLine marginBottom="16px" />);
|
|
253
|
+
expect(container.firstChild).toBeInTheDocument();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export const BoatIcon = (props: any) => (
|
|
4
|
+
<svg
|
|
5
|
+
width="15"
|
|
6
|
+
height="16"
|
|
7
|
+
viewBox="0 0 15 16"
|
|
8
|
+
fill="none"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
d="M2.78887 3.51093V6.29999L6.98887 4.92187L11.1889 6.29999V3.51093H2.78887ZM1.34512 12.6L0.0326173 7.94062C0.0107423 7.89687 -0.000195216 7.82031 -0.000195216 7.71093C-0.000195216 7.40468 0.163867 7.18593 0.491992 7.05468L1.37793 6.75937V3.51093C1.37793 3.13906 1.52012 2.81093 1.80449 2.52656C2.08887 2.24218 2.41699 2.09999 2.78887 2.09999H4.88887V-7.20099e-06H9.08887V2.09999H11.1889C11.5607 2.09999 11.8889 2.24218 12.1732 2.52656C12.4576 2.81093 12.5998 3.13906 12.5998 3.51093V6.75937L13.4857 7.05468C13.9451 7.20781 14.0982 7.50312 13.9451 7.94062L12.6326 12.6H12.5998C11.5279 12.6 10.5873 12.1406 9.77793 11.2219C8.96856 12.1406 8.03887 12.6 6.98887 12.6C5.93887 12.6 5.00918 12.1406 4.19981 11.2219C3.39043 12.1406 2.4498 12.6 1.37793 12.6H1.34512ZM12.5998 14.0109H13.9779V15.4219H12.5998C11.6154 15.4219 10.6748 15.1922 9.77793 14.7328C8.90293 15.1703 7.97324 15.3891 6.98887 15.3891C6.00449 15.3891 5.07481 15.1703 4.19981 14.7328C3.30293 15.1922 2.3623 15.4219 1.37793 15.4219H-0.000195216V14.0109H1.37793C2.38418 14.0109 3.32481 13.7047 4.19981 13.0922C5.05293 13.6828 5.98262 13.9781 6.98887 13.9781C7.99512 13.9781 8.92481 13.6828 9.77793 13.0922C10.6529 13.7047 11.5936 14.0109 12.5998 14.0109Z"
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
/>
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export const CalendarIcon = (props: any) => (
|
|
4
|
+
<svg
|
|
5
|
+
width="13"
|
|
6
|
+
height="14"
|
|
7
|
+
viewBox="0 0 13 14"
|
|
8
|
+
fill="none"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
d="M11.189 12.6V4.88907H1.41084V12.6H11.189ZM11.189 1.37813C11.5608 1.37813 11.889 1.52032 12.1733 1.80469C12.4577 2.08907 12.5999 2.41719 12.5999 2.78907V12.6C12.5999 12.9719 12.4577 13.3 12.1733 13.5844C11.889 13.8469 11.5608 13.9781 11.189 13.9781H1.41084C1.01709 13.9781 0.678028 13.8469 0.393653 13.5844C0.131152 13.3219 -9.75132e-05 12.9938 -9.75132e-05 12.6V2.78907C-9.75132e-05 2.41719 0.131152 2.08907 0.393653 1.80469C0.678028 1.52032 1.01709 1.37813 1.41084 1.37813H2.0999V5.05522e-06H3.51084V1.37813H9.08897V5.05522e-06H10.4999V1.37813H11.189ZM9.81084 6.30001V7.67813H8.3999V6.30001H9.81084ZM6.98897 6.30001V7.67813H5.61084V6.30001H6.98897ZM4.1999 6.30001V7.67813H2.78897V6.30001H4.1999Z"
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
/>
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export const ChevronRightIcon = (props: any) => (
|
|
4
|
+
<svg
|
|
5
|
+
width="16"
|
|
6
|
+
height="16"
|
|
7
|
+
viewBox="0 0 16 16"
|
|
8
|
+
fill="none"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
d="M6 12L10 8L6 4"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
strokeWidth="1.5"
|
|
16
|
+
strokeLinecap="round"
|
|
17
|
+
strokeLinejoin="round"
|
|
18
|
+
/>
|
|
19
|
+
</svg>
|
|
20
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export const LocationIcon = (props: any) => (
|
|
4
|
+
<svg
|
|
5
|
+
width="10"
|
|
6
|
+
height="14"
|
|
7
|
+
viewBox="0 0 10 14"
|
|
8
|
+
fill="none"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
d="M3.64238 6.13594C3.99238 6.46407 4.40801 6.62813 4.88926 6.62813C5.37051 6.62813 5.7752 6.46407 6.10332 6.13594C6.45332 5.78594 6.62832 5.37032 6.62832 4.88907C6.62832 4.40782 6.45332 4.00313 6.10332 3.67501C5.7752 3.32501 5.37051 3.15001 4.88926 3.15001C4.40801 3.15001 3.99238 3.32501 3.64238 3.67501C3.31426 4.00313 3.1502 4.40782 3.1502 4.88907C3.1502 5.37032 3.31426 5.78594 3.64238 6.13594ZM1.41113 1.44376C2.37363 0.481255 3.53301 5.05522e-06 4.88926 5.05522e-06C6.24551 5.05522e-06 7.39395 0.481255 8.33457 1.44376C9.29707 2.38438 9.77832 3.53282 9.77832 4.88907C9.77832 5.56719 9.60332 6.34376 9.25332 7.21876C8.9252 8.09376 8.52051 8.91407 8.03926 9.67969C7.55801 10.4453 7.07676 11.1672 6.59551 11.8453C6.13613 12.5016 5.74238 13.0266 5.41426 13.4203L4.88926 13.9781C4.75801 13.825 4.58301 13.6281 4.36426 13.3875C4.14551 13.125 3.75176 12.6219 3.18301 11.8781C2.61426 11.1125 2.11113 10.3797 1.67363 9.67969C1.25801 8.95782 0.875196 8.14844 0.525196 7.25157C0.175196 6.35469 0.000195552 5.56719 0.000195552 4.88907C0.000195552 3.53282 0.470508 2.38438 1.41113 1.44376Z"
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
/>
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
export const MoonIcon = (props: any) => (
|
|
4
|
+
<svg
|
|
5
|
+
width="14"
|
|
6
|
+
height="15"
|
|
7
|
+
viewBox="0 0 14 15"
|
|
8
|
+
fill="none"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
{...props}
|
|
11
|
+
>
|
|
12
|
+
<path
|
|
13
|
+
d="M7.21856 0.0656178C6.69356 0.984368 6.38731 1.92499 6.29981 2.88749C6.21231 3.84999 6.31074 4.77968 6.59512 5.67656C6.90137 6.57343 7.36074 7.38281 7.97324 8.10468C8.58574 8.82656 9.31856 9.40624 10.1717 9.84374C11.0467 10.2812 12.0092 10.5109 13.0592 10.5328C12.6436 11.2328 12.1295 11.8453 11.517 12.3703C10.9045 12.8953 10.2154 13.3109 9.44981 13.6172C8.68418 13.9016 7.86387 14.0437 6.98887 14.0437C6.02637 14.0437 5.11856 13.8687 4.26543 13.5187C3.43418 13.1469 2.69043 12.6437 2.03418 12.0094C1.3998 11.3531 0.89668 10.6094 0.524805 9.77812C0.174805 8.92499 -0.000195216 8.01718 -0.000195216 7.05468C-0.000195216 6.07031 0.185742 5.15156 0.557617 4.29843C0.929492 3.44531 1.44355 2.70156 2.0998 2.06718C2.75605 1.41093 3.52168 0.907805 4.39668 0.557805C5.27168 0.18593 6.21231 0.0218678 7.21856 0.0656178Z"
|
|
14
|
+
fill="currentColor"
|
|
15
|
+
/>
|
|
16
|
+
</svg>
|
|
17
|
+
);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BoatIcon } from './BoatIcon';
|
|
2
|
+
import { CalendarIcon } from './CalendarIcon';
|
|
3
|
+
import { ChevronRightIcon } from './ChevronRightIcon';
|
|
4
|
+
import { LocationIcon } from './LocationIcon';
|
|
5
|
+
import { MoonIcon } from './MoonIcon';
|
|
6
|
+
|
|
7
|
+
export { BoatIcon, LocationIcon, CalendarIcon, MoonIcon, ChevronRightIcon };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TripCardApiData, ApiCruiseResult } from './types';
|
|
2
|
+
|
|
3
|
+
const decodeHtmlEntities = (text: string): string => {
|
|
4
|
+
const textarea = document.createElement('textarea');
|
|
5
|
+
textarea.innerHTML = text;
|
|
6
|
+
return textarea.value;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const transformApiResult = (
|
|
10
|
+
result: ApiCruiseResult
|
|
11
|
+
): TripCardApiData => {
|
|
12
|
+
const formatPrice = (price: number | string): string => {
|
|
13
|
+
if (price === 0 || price === 'Enquire now') {
|
|
14
|
+
return 'Enquire now';
|
|
15
|
+
}
|
|
16
|
+
return typeof price === 'string' ? price : `£${price}`;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const formatRoute = (itinerary: string[]): string => {
|
|
20
|
+
if (itinerary.length <= 2) {
|
|
21
|
+
return itinerary.join(' → ');
|
|
22
|
+
}
|
|
23
|
+
return `${itinerary[0]} → ${itinerary[itinerary.length - 1]}`;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const formatOriginalPrice = (
|
|
27
|
+
wasPrice: number | string
|
|
28
|
+
): string | undefined => {
|
|
29
|
+
if (!wasPrice || wasPrice === 0 || wasPrice === 'Enquire now') {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return typeof wasPrice === 'string' ? wasPrice : `£${wasPrice}`;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
cruise_id: String(result.cruise_id),
|
|
37
|
+
image: result.ship.image,
|
|
38
|
+
offer_label: result.campaigns.length > 0 ? result.campaigns[0] : undefined,
|
|
39
|
+
headline: decodeHtmlEntities(result.cruise_title),
|
|
40
|
+
date: result.extras.date,
|
|
41
|
+
duration: result.extras.duration,
|
|
42
|
+
route: formatRoute(result.itinerary),
|
|
43
|
+
ship: result.ship.name,
|
|
44
|
+
original_price: formatOriginalPrice(result.was_prices.cheapest_price),
|
|
45
|
+
price: formatPrice(result.prices.cheapest_price),
|
|
46
|
+
logo: result.cruise_line.logo,
|
|
47
|
+
logo_url: result.cruise_line.link,
|
|
48
|
+
cta_url: result.link,
|
|
49
|
+
cta_text: 'View Itinerary'
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const decodeIds = (encoded: string): number[] => {
|
|
54
|
+
try {
|
|
55
|
+
const decoded = atob(encoded);
|
|
56
|
+
const data = JSON.parse(decoded);
|
|
57
|
+
if (Array.isArray(data)) {
|
|
58
|
+
return data;
|
|
59
|
+
}
|
|
60
|
+
return [];
|
|
61
|
+
} catch (e) {
|
|
62
|
+
// Failed to decode trip card IDs
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const fetchCruiseCards = async (
|
|
68
|
+
cruiseIds: number[]
|
|
69
|
+
): Promise<TripCardApiData[]> => {
|
|
70
|
+
const formData = new FormData();
|
|
71
|
+
formData.append('action', 'results');
|
|
72
|
+
formData.append('cruise_ids', cruiseIds.join(','));
|
|
73
|
+
|
|
74
|
+
const response = await fetch(
|
|
75
|
+
'https://www.staging-thetimes.com/holidays/wp-admin/admin-ajax.php',
|
|
76
|
+
{
|
|
77
|
+
method: 'POST',
|
|
78
|
+
body: formData
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const data = await response.json();
|
|
87
|
+
|
|
88
|
+
if (data.results && Array.isArray(data.results)) {
|
|
89
|
+
const transformedCards: TripCardApiData[] = data.results.map(
|
|
90
|
+
(result: ApiCruiseResult) => transformApiResult(result)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
return cruiseIds.map(id =>
|
|
94
|
+
transformedCards.find(card => card.cruise_id === String(id))
|
|
95
|
+
) as TripCardApiData[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new Error('Invalid API response format');
|
|
99
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React, { FC, useState, useEffect } from 'react';
|
|
2
|
+
import { TripCardsProps, TripCardApiData } from './types';
|
|
3
|
+
import { TripCard } from './TripCard';
|
|
4
|
+
import { SkeletonCard } from './SkeletonCard';
|
|
5
|
+
import { TripCardsLayout } from './TripCardsLayout';
|
|
6
|
+
import { getMockTripCards } from './mockData';
|
|
7
|
+
import { decodeIds, fetchCruiseCards } from './helpers';
|
|
8
|
+
|
|
9
|
+
export const TripCards: FC<TripCardsProps> = ({
|
|
10
|
+
element,
|
|
11
|
+
useMockData = false
|
|
12
|
+
}) => {
|
|
13
|
+
const [cards, setCards] = useState<TripCardApiData[]>([]);
|
|
14
|
+
const [loading, setLoading] = useState(true);
|
|
15
|
+
const [error, setError] = useState<string | null>(null);
|
|
16
|
+
const [itemsPerPage, setItemsPerPage] = useState(2);
|
|
17
|
+
const allIds = [...decodeIds(element.tripcards || '')].filter(
|
|
18
|
+
(id, index, self) => self.indexOf(id) === index
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
useEffect(
|
|
22
|
+
() => {
|
|
23
|
+
const loadCards = async () => {
|
|
24
|
+
if (allIds.length === 0) {
|
|
25
|
+
setLoading(false);
|
|
26
|
+
setError('No valid cruise IDs provided');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (useMockData) {
|
|
31
|
+
setCards(getMockTripCards(allIds.length));
|
|
32
|
+
setLoading(false);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const transformedCards = await fetchCruiseCards(allIds);
|
|
38
|
+
setCards(transformedCards);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
setError(e instanceof Error ? e.message : 'Failed to load cruises');
|
|
41
|
+
} finally {
|
|
42
|
+
setLoading(false);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
loadCards();
|
|
47
|
+
},
|
|
48
|
+
[element.tripcards, useMockData]
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const handleResize = () => {
|
|
53
|
+
const width = window.innerWidth;
|
|
54
|
+
// sm/xs: < 768px → 1 card per page
|
|
55
|
+
// md/lg/xl: >= 768px → 2 cards per page
|
|
56
|
+
if (width < 768) {
|
|
57
|
+
setItemsPerPage(1);
|
|
58
|
+
} else {
|
|
59
|
+
setItemsPerPage(2);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
handleResize();
|
|
63
|
+
window.addEventListener('resize', handleResize);
|
|
64
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
if (loading) {
|
|
68
|
+
const skeletonItems = Array.from(
|
|
69
|
+
{ length: allIds.length || 2 },
|
|
70
|
+
(_, i) => ({
|
|
71
|
+
id: `skeleton-${i}`
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<TripCardsLayout
|
|
77
|
+
element={element}
|
|
78
|
+
items={skeletonItems}
|
|
79
|
+
CardComponent={SkeletonCard}
|
|
80
|
+
itemsPerPage={itemsPerPage}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (error || cards.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const cardItems = cards.map((card, index) => ({
|
|
90
|
+
id: `${card.cruise_id}-${index}`,
|
|
91
|
+
data: card
|
|
92
|
+
}));
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<TripCardsLayout
|
|
96
|
+
element={element}
|
|
97
|
+
items={cardItems}
|
|
98
|
+
CardComponent={TripCard}
|
|
99
|
+
itemsPerPage={itemsPerPage}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export default TripCards;
|