@treely/strapi-slices 7.3.0 → 7.4.1
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/README.md +2 -1
- package/dist/models/strapi/StrapiEvent.d.ts +2 -2
- package/dist/strapi-slices.cjs.development.js +96 -46
- package/dist/strapi-slices.cjs.development.js.map +1 -1
- package/dist/strapi-slices.cjs.production.min.js +1 -1
- package/dist/strapi-slices.cjs.production.min.js.map +1 -1
- package/dist/strapi-slices.esm.js +96 -46
- package/dist/strapi-slices.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/EventCard/EventCard.test.tsx +3 -3
- package/src/components/EventCard/EventCard.tsx +39 -35
- package/src/models/strapi/StrapiEvent.ts +2 -2
- package/src/slices/Events/Events.stories.tsx +2 -2
- package/src/slices/Events/Events.test.tsx +8 -2
- package/src/slices/Events/Events.tsx +78 -49
package/package.json
CHANGED
|
@@ -36,7 +36,7 @@ describe('The EventCard component', () => {
|
|
|
36
36
|
|
|
37
37
|
expect(screen.getByRole('link')).toHaveAttribute(
|
|
38
38
|
'href',
|
|
39
|
-
defaultProps.event.button
|
|
39
|
+
defaultProps.event.button?.url
|
|
40
40
|
);
|
|
41
41
|
|
|
42
42
|
expect(screen.queryByText('Recommended')).not.toBeInTheDocument();
|
|
@@ -57,7 +57,7 @@ describe('The EventCard component', () => {
|
|
|
57
57
|
const firstSpeakerImage = screen.getAllByRole('img')[2];
|
|
58
58
|
expect(firstSpeakerImage).toHaveAttribute(
|
|
59
59
|
'alt',
|
|
60
|
-
defaultProps.event.speakers[0]
|
|
60
|
+
defaultProps.event.speakers?.[0]?.image.alt
|
|
61
61
|
);
|
|
62
62
|
|
|
63
63
|
await waitFor(() => userEvent.hover(firstSpeakerImage));
|
|
@@ -66,7 +66,7 @@ describe('The EventCard component', () => {
|
|
|
66
66
|
const secondSpeakerImage = screen.getAllByRole('img')[3];
|
|
67
67
|
expect(secondSpeakerImage).toHaveAttribute(
|
|
68
68
|
'alt',
|
|
69
|
-
defaultProps.event.speakers[1]
|
|
69
|
+
defaultProps.event.speakers?.[1]?.image.alt
|
|
70
70
|
);
|
|
71
71
|
|
|
72
72
|
await waitFor(() => userEvent.hover(secondSpeakerImage));
|
|
@@ -266,42 +266,46 @@ export const EventCard = ({ event }: EventCardProps): JSX.Element => {
|
|
|
266
266
|
flexDir={mobile ? 'column-reverse' : 'row'}
|
|
267
267
|
gap={mobile ? '4' : '0'}
|
|
268
268
|
>
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
269
|
+
{event.button && (
|
|
270
|
+
<Flex width={mobile ? 'full' : 'auto'}>
|
|
271
|
+
<StrapiLinkButton
|
|
272
|
+
key={event.button.id}
|
|
273
|
+
size="md"
|
|
274
|
+
variant={event.buttonVariant}
|
|
275
|
+
link={event.button}
|
|
276
|
+
rightIcon={<CaretRight size="10" />}
|
|
277
|
+
width="full"
|
|
278
|
+
/>
|
|
279
|
+
</Flex>
|
|
280
|
+
)}
|
|
281
|
+
{event.speakers && event.speakers.length > 0 && (
|
|
282
|
+
<Flex flexDir="row" gap="2">
|
|
283
|
+
{event.speakers.map((speaker) => (
|
|
284
|
+
<Box key={speaker.id}>
|
|
285
|
+
<Box
|
|
286
|
+
width={['10', null, null, '12']}
|
|
287
|
+
height={['10', null, null, '12']}
|
|
288
|
+
position="relative"
|
|
289
|
+
borderRadius="2xl"
|
|
290
|
+
>
|
|
291
|
+
<Tooltip label={speaker.name}>
|
|
292
|
+
<Image
|
|
293
|
+
src={strapiMediaUrl(speaker.image.img, 'medium')}
|
|
294
|
+
alt={speaker.image.alt}
|
|
295
|
+
fill
|
|
296
|
+
style={{
|
|
297
|
+
objectFit: speaker.image.objectFit || 'cover',
|
|
298
|
+
borderRadius: 'var(--boemly-radii-md)',
|
|
299
|
+
border:
|
|
300
|
+
'1px solid, var(--whiteAlpha-700, rgba(255, 255, 255, 0.64))',
|
|
301
|
+
}}
|
|
302
|
+
/>
|
|
303
|
+
</Tooltip>
|
|
304
|
+
</Box>
|
|
301
305
|
</Box>
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
))}
|
|
307
|
+
</Flex>
|
|
308
|
+
)}
|
|
305
309
|
</Flex>
|
|
306
310
|
</Flex>
|
|
307
311
|
</Box>
|
|
@@ -19,10 +19,10 @@ export enum EventType {
|
|
|
19
19
|
interface StrapiEvent {
|
|
20
20
|
title: string;
|
|
21
21
|
description: string;
|
|
22
|
-
button
|
|
22
|
+
button?: StrapiLink;
|
|
23
23
|
buttonVariant?: 'outline' | 'ghost' | 'link' | 'solid' | 'outlineWhite';
|
|
24
24
|
recommended?: boolean;
|
|
25
|
-
speakers
|
|
25
|
+
speakers?: {
|
|
26
26
|
id: number;
|
|
27
27
|
name: string;
|
|
28
28
|
image: StrapiImage;
|
|
@@ -16,7 +16,7 @@ const Template: StoryFn<typeof Events> = (args) => <Events {...args} />;
|
|
|
16
16
|
export const Minimal = Template.bind({});
|
|
17
17
|
Minimal.args = {
|
|
18
18
|
slice: {
|
|
19
|
-
upcomingTitle: '
|
|
19
|
+
upcomingTitle: 'Ready to Learn more about Forest Climate Protection?',
|
|
20
20
|
upcomingDescription: 'Join us for these amazing events',
|
|
21
21
|
pastTitle: 'Past Events',
|
|
22
22
|
pastDescription: 'Check out our past events',
|
|
@@ -27,7 +27,7 @@ Minimal.args = {
|
|
|
27
27
|
export const WithFilterSearch = Template.bind({});
|
|
28
28
|
WithFilterSearch.args = {
|
|
29
29
|
slice: {
|
|
30
|
-
upcomingTitle: '
|
|
30
|
+
upcomingTitle: 'Ready to Learn more about Forest Climate Protection?',
|
|
31
31
|
upcomingDescription: 'Join us for these amazing events',
|
|
32
32
|
pastTitle: 'Past Events',
|
|
33
33
|
pastDescription: 'Check out our past events',
|
|
@@ -58,6 +58,12 @@ describe('The Events slice', () => {
|
|
|
58
58
|
jest.useFakeTimers().setSystemTime(NOW);
|
|
59
59
|
getKeyCalls = [];
|
|
60
60
|
|
|
61
|
+
global.fetch = jest.fn().mockResolvedValue({
|
|
62
|
+
json: jest.fn().mockResolvedValue({
|
|
63
|
+
data: [upcomingEventMock, pastEventMock],
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
61
67
|
(useEvents as jest.Mock).mockImplementation(({ getKey }) => {
|
|
62
68
|
const key = getKey(0, null);
|
|
63
69
|
getKeyCalls.push(key.toString());
|
|
@@ -239,7 +245,7 @@ describe('The Events slice', () => {
|
|
|
239
245
|
expect.arrayContaining([
|
|
240
246
|
expect.stringContaining('filters[start][$gte]'),
|
|
241
247
|
expect.stringContaining(
|
|
242
|
-
'filters[$
|
|
248
|
+
'filters[$or][0][eventTypes][eventType]=Conference'
|
|
243
249
|
),
|
|
244
250
|
])
|
|
245
251
|
);
|
|
@@ -274,7 +280,7 @@ describe('The Events slice', () => {
|
|
|
274
280
|
expect.arrayContaining([
|
|
275
281
|
expect.stringContaining('filters[start][$gte]'),
|
|
276
282
|
expect.stringContaining(
|
|
277
|
-
'filters[$
|
|
283
|
+
'filters[$or][0][languages][language]=German'
|
|
278
284
|
),
|
|
279
285
|
])
|
|
280
286
|
);
|
|
@@ -57,12 +57,24 @@ const enum Sort {
|
|
|
57
57
|
OLDEST_FIRST = 'oldest',
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
interface EventOption {
|
|
61
|
+
value: string;
|
|
62
|
+
label: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
61
66
|
const { formatMessage, locale } = useContext(IntlContext);
|
|
62
67
|
const [eventTypeFilter, setEventTypeFilter] = useState([] as string[]);
|
|
63
68
|
const [languageFilter, setLanguageFilter] = useState([] as string[]);
|
|
64
69
|
const [sort, setSort] = useState([Sort.NEWEST_FIRST] as string[]);
|
|
65
70
|
|
|
71
|
+
const [allEventTypeOptions, setAllEventTypeOptions] = useState<EventOption[]>(
|
|
72
|
+
[]
|
|
73
|
+
);
|
|
74
|
+
const [allLanguageOptions, setAllLanguageOptions] = useState<EventOption[]>(
|
|
75
|
+
[]
|
|
76
|
+
);
|
|
77
|
+
|
|
66
78
|
const now = new Date().toISOString();
|
|
67
79
|
|
|
68
80
|
const buildEventsUrl = (
|
|
@@ -86,12 +98,19 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
86
98
|
url.searchParams.append('sort', 'start:desc');
|
|
87
99
|
}
|
|
88
100
|
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
i
|
|
101
|
+
if (eventTypeFilter.length) {
|
|
102
|
+
eventTypeFilter.forEach((filter, i) => {
|
|
103
|
+
url.searchParams.append(
|
|
104
|
+
`filters[$or][${i}][eventTypes][eventType]`,
|
|
105
|
+
filter
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (languageFilter.length) {
|
|
111
|
+
languageFilter.forEach((filter, i) => {
|
|
93
112
|
url.searchParams.append(
|
|
94
|
-
`filters[$
|
|
113
|
+
`filters[$or][${eventTypeFilter.length + i}][languages][language]`,
|
|
95
114
|
filter
|
|
96
115
|
);
|
|
97
116
|
});
|
|
@@ -131,6 +150,7 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
131
150
|
data?.flatMap((d: any) => d?.body?.data)?.filter((t: any) => !!t) || []
|
|
132
151
|
);
|
|
133
152
|
};
|
|
153
|
+
|
|
134
154
|
// Process upcoming events
|
|
135
155
|
const upcomingEvents = useMemo(() => {
|
|
136
156
|
return processEvents(upcomingData);
|
|
@@ -141,40 +161,53 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
141
161
|
return processEvents(pastData);
|
|
142
162
|
}, [pastData]);
|
|
143
163
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const uniqueValues = new Set<string>();
|
|
150
|
-
|
|
151
|
-
const options = items
|
|
152
|
-
.flatMap((card) =>
|
|
153
|
-
card.attributes[key].map((item: any) => ({
|
|
154
|
-
value: item[key.slice(0, -1)],
|
|
155
|
-
label: item[key.slice(0, -1)],
|
|
156
|
-
}))
|
|
157
|
-
)
|
|
158
|
-
.filter((option) => {
|
|
159
|
-
if (uniqueValues.has(option.value)) return false;
|
|
160
|
-
uniqueValues.add(option.value);
|
|
161
|
-
return true;
|
|
162
|
-
});
|
|
164
|
+
// Function to fetch all possible options
|
|
165
|
+
const fetchAllOptions = useCallback(async () => {
|
|
166
|
+
const url = new URL(`/treely-events`, STRAPI_URI);
|
|
167
|
+
url.searchParams.append('locale', locale);
|
|
168
|
+
url.searchParams.append('populate', 'deep,6');
|
|
163
169
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
const response = await fetch(
|
|
171
|
+
`${STRAPI_URI}/api/treely-events${url.search}`
|
|
172
|
+
);
|
|
173
|
+
const data = await response.json();
|
|
174
|
+
|
|
175
|
+
const events = data?.data || [];
|
|
176
|
+
|
|
177
|
+
// Extract all event types
|
|
178
|
+
const allEventTypes = new Set<string>();
|
|
179
|
+
events.forEach((event: any) => {
|
|
180
|
+
if (event?.attributes?.eventTypes) {
|
|
181
|
+
event.attributes.eventTypes.forEach((item: any) => {
|
|
182
|
+
allEventTypes.add(item.eventType);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Extract all languages
|
|
188
|
+
const allLanguages = new Set<string>();
|
|
189
|
+
events.forEach((event: any) => {
|
|
190
|
+
if (event?.attributes?.languages) {
|
|
191
|
+
event.attributes.languages.forEach((item: any) => {
|
|
192
|
+
allLanguages.add(item.language);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Update state with all options
|
|
198
|
+
setAllEventTypeOptions(
|
|
199
|
+
Array.from(allEventTypes).map((value) => ({ value, label: value }))
|
|
168
200
|
);
|
|
169
|
-
};
|
|
170
201
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
202
|
+
setAllLanguageOptions(
|
|
203
|
+
Array.from(allLanguages).map((value) => ({ value, label: value }))
|
|
204
|
+
);
|
|
205
|
+
}, [locale]);
|
|
174
206
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
207
|
+
// Fetch all options when component mounts
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
fetchAllOptions();
|
|
210
|
+
}, [fetchAllOptions]);
|
|
178
211
|
|
|
179
212
|
const removeFilter = (
|
|
180
213
|
filterType: keyof FiltersProps,
|
|
@@ -203,6 +236,8 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
203
236
|
<DefaultSectionHeader
|
|
204
237
|
title={slice.upcomingTitle}
|
|
205
238
|
text={slice.upcomingDescription}
|
|
239
|
+
titleProps={{ maxW: '3xl' }}
|
|
240
|
+
textProps={{ maxW: '3xl' }}
|
|
206
241
|
/>
|
|
207
242
|
<Spacer h="10" />
|
|
208
243
|
</>
|
|
@@ -241,7 +276,7 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
241
276
|
searchPlaceholder={formatMessage({
|
|
242
277
|
id: 'sections.events.eventsFilter.searchPlaceholder',
|
|
243
278
|
})}
|
|
244
|
-
options={
|
|
279
|
+
options={allEventTypeOptions}
|
|
245
280
|
value={eventTypeFilter ?? []}
|
|
246
281
|
onChange={(selected: string[]) => {
|
|
247
282
|
setEventTypeFilter(selected);
|
|
@@ -260,7 +295,7 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
260
295
|
searchPlaceholder={formatMessage({
|
|
261
296
|
id: 'sections.events.eventsFilter.searchPlaceholder',
|
|
262
297
|
})}
|
|
263
|
-
options={
|
|
298
|
+
options={allLanguageOptions}
|
|
264
299
|
value={languageFilter ?? []}
|
|
265
300
|
onChange={(selected: string[]) => {
|
|
266
301
|
setLanguageFilter(selected);
|
|
@@ -272,7 +307,7 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
272
307
|
{/* Filter Tags */}
|
|
273
308
|
<Box display="flex" flexWrap="wrap" minHeight="6" gap="2">
|
|
274
309
|
{eventTypeFilter.map((eventType) => {
|
|
275
|
-
const event =
|
|
310
|
+
const event = allEventTypeOptions.find(
|
|
276
311
|
(option) => option.value === eventType
|
|
277
312
|
);
|
|
278
313
|
return (
|
|
@@ -287,7 +322,7 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
287
322
|
})}
|
|
288
323
|
|
|
289
324
|
{languageFilter.map((singleLanguage) => {
|
|
290
|
-
const language =
|
|
325
|
+
const language = allLanguageOptions.find(
|
|
291
326
|
(option) => option.value === singleLanguage
|
|
292
327
|
);
|
|
293
328
|
return (
|
|
@@ -355,11 +390,7 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
355
390
|
placeItems="center"
|
|
356
391
|
>
|
|
357
392
|
{upcomingEvents.map((event: IStrapiData<StrapiEvent>) => (
|
|
358
|
-
<Box
|
|
359
|
-
key={event.id}
|
|
360
|
-
width="full"
|
|
361
|
-
height={['full', null, null, 'xl']}
|
|
362
|
-
>
|
|
393
|
+
<Box key={event.id} width="full" height="full">
|
|
363
394
|
<EventCard event={event.attributes} />
|
|
364
395
|
</Box>
|
|
365
396
|
))}
|
|
@@ -387,6 +418,8 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
387
418
|
<DefaultSectionHeader
|
|
388
419
|
title={slice.pastTitle}
|
|
389
420
|
text={slice.pastDescription}
|
|
421
|
+
titleProps={{ maxW: '3xl' }}
|
|
422
|
+
textProps={{ maxW: '3xl' }}
|
|
390
423
|
/>
|
|
391
424
|
|
|
392
425
|
<Spacer h="10" />
|
|
@@ -409,11 +442,7 @@ export const Events: React.FC<EventsProps> = ({ slice }: EventsProps) => {
|
|
|
409
442
|
mb={['10', null, null, '20']}
|
|
410
443
|
>
|
|
411
444
|
{pastEvents.map((event: IStrapiData<StrapiEvent>) => (
|
|
412
|
-
<Box
|
|
413
|
-
key={event.id}
|
|
414
|
-
height={['full', null, null, 'xl']}
|
|
415
|
-
width="full"
|
|
416
|
-
>
|
|
445
|
+
<Box key={event.id} height="full" width="full">
|
|
417
446
|
<EventCard event={event.attributes} />
|
|
418
447
|
</Box>
|
|
419
448
|
))}
|