@treely/strapi-slices 7.1.4 → 7.3.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.
Files changed (59) hide show
  1. package/dist/__mocks__/swr/index.d.ts +6 -0
  2. package/dist/__mocks__/swr/infinite.d.ts +4 -0
  3. package/dist/components/EventCard/EventCard.d.ts +6 -0
  4. package/dist/components/EventCard/index.d.ts +2 -0
  5. package/dist/components/EventCard/messages.de.d.ts +15 -0
  6. package/dist/components/EventCard/messages.en.d.ts +15 -0
  7. package/dist/components/StrapiLinkButton/StrapiLinkButton.d.ts +1 -0
  8. package/dist/index.d.ts +2 -1
  9. package/dist/models/SWRData.d.ts +5 -0
  10. package/dist/models/hooks/UseInfiniteDataHookProps.d.ts +11 -0
  11. package/dist/models/hooks/useEvents.d.ts +9 -0
  12. package/dist/models/strapi/StrapiEvent.d.ts +48 -0
  13. package/dist/rootMessages.de.d.ts +21 -0
  14. package/dist/rootMessages.en.d.ts +21 -0
  15. package/dist/slices/Events/Events.d.ts +11 -0
  16. package/dist/slices/Events/Events.stories.d.ts +4 -0
  17. package/dist/slices/Events/index.d.ts +2 -0
  18. package/dist/slices/Events/messages.de.d.ts +12 -0
  19. package/dist/slices/Events/messages.en.d.ts +12 -0
  20. package/dist/slices/QAndA/QAndA.d.ts +1 -0
  21. package/dist/strapi-slices.cjs.development.js +785 -44
  22. package/dist/strapi-slices.cjs.development.js.map +1 -1
  23. package/dist/strapi-slices.cjs.production.min.js +1 -1
  24. package/dist/strapi-slices.cjs.production.min.js.map +1 -1
  25. package/dist/strapi-slices.esm.js +786 -46
  26. package/dist/strapi-slices.esm.js.map +1 -1
  27. package/dist/test/strapiMocks/strapiEventMock.d.ts +3 -0
  28. package/dist/utils/getCountryFlag.d.ts +2 -0
  29. package/dist/utils/getMessages.d.ts +42 -0
  30. package/package.json +17 -3
  31. package/src/components/ContextProvider/ContextProvider.tsx +32 -4
  32. package/src/components/CustomerQuoteCard/CustomerQuoteCard.tsx +1 -0
  33. package/src/components/EventCard/EventCard.test.tsx +127 -0
  34. package/src/components/EventCard/EventCard.tsx +309 -0
  35. package/src/components/EventCard/index.ts +3 -0
  36. package/src/components/EventCard/messages.de.ts +16 -0
  37. package/src/components/EventCard/messages.en.ts +16 -0
  38. package/src/components/SliceRenderer/SliceRenderer.tsx +5 -0
  39. package/src/components/StrapiLinkButton/StrapiLinkButton.tsx +1 -0
  40. package/src/index.tsx +2 -0
  41. package/src/models/SWRData.ts +6 -0
  42. package/src/models/hooks/UseInfiniteDataHookProps.tsx +13 -0
  43. package/src/models/hooks/useEvents.ts +46 -0
  44. package/src/models/strapi/StrapiEvent.ts +51 -0
  45. package/src/rootMessages.de.ts +4 -0
  46. package/src/rootMessages.en.ts +4 -0
  47. package/src/slices/Events/Events.stories.tsx +36 -0
  48. package/src/slices/Events/Events.test.tsx +344 -0
  49. package/src/slices/Events/Events.tsx +440 -0
  50. package/src/slices/Events/index.ts +3 -0
  51. package/src/slices/Events/messages.de.ts +14 -0
  52. package/src/slices/Events/messages.en.ts +13 -0
  53. package/src/slices/QAndA/QAndA.stories.tsx +5 -0
  54. package/src/slices/QAndA/QAndA.test.tsx +1 -0
  55. package/src/slices/QAndA/QAndA.tsx +2 -1
  56. package/src/test/strapiMocks/strapiEventMock.ts +57 -0
  57. package/src/utils/getCountryFlag.test.ts +9 -0
  58. package/src/utils/getCountryFlag.ts +6 -0
  59. package/src/utils/strapiMediaUrl.ts +1 -2
@@ -0,0 +1,3 @@
1
+ import IStrapiData from '../../models/strapi/IStrapiData';
2
+ import StrapiEvent from '../../models/strapi/StrapiEvent';
3
+ export declare const strapiEventMock: IStrapiData<StrapiEvent>;
@@ -0,0 +1,2 @@
1
+ declare const getCountryFlag: (countryCode: string) => string;
2
+ export default getCountryFlag;
@@ -24,6 +24,15 @@ declare const getMessages: (locale: string) => {
24
24
  'sections.glossary.copyButtonLabel': string;
25
25
  'sections.glossary.copySuccessMessage': string;
26
26
  'sections.glossary.copyFailureMessage': string;
27
+ 'sections.events.loadMore': string;
28
+ 'sections.events.noUpcomingEvents': string;
29
+ 'sections.events.noPastEvents': string;
30
+ 'sections.events.eventsFilter.searchPlaceholder': string;
31
+ 'sections.events.eventsFilter.eventType': string;
32
+ 'sections.events.eventsFilter.language': string;
33
+ 'sections.events.eventsFilter.sortBy.title': string;
34
+ 'sections.events.eventsFilter.sortBy.newest': string;
35
+ 'sections.events.eventsFilter.sortBy.oldest': string;
27
36
  'sections.customerQuoteCard.more': string;
28
37
  'sections.customerCard.more': string;
29
38
  'sections.cta.backgroundShapesDark': string;
@@ -59,6 +68,18 @@ declare const getMessages: (locale: string) => {
59
68
  'features.projectInfo.properties.year': string;
60
69
  'features.portfolio.documentsDownloadList.projectDocuments': string;
61
70
  'features.portfolio.documentsDownloadList.downloadDocument': string;
71
+ 'sections.eventCard.recommendedEvent': string;
72
+ 'sections.eventCard.buttonShowMore': string;
73
+ 'sections.eventCard.buttonShowLess': string;
74
+ 'sections.eventCard.eventType.conference': string;
75
+ 'sections.eventCard.eventType.webinar': string;
76
+ 'sections.eventCard.eventType.forestwalk': string;
77
+ 'sections.eventCard.eventType.partnerevent': string;
78
+ 'sections.eventCard.eventType.lunch&learn': string;
79
+ 'sections.eventCard.eventType.fair': string;
80
+ 'sections.eventCard.eventType.festival': string;
81
+ 'sections.eventCard.eventType.roadshow': string;
82
+ 'sections.eventCard.eventType.meetup': string;
62
83
  'components.creditsAvailableBadge.text.yes': string;
63
84
  'components.creditsAvailableBadge.text.some': string;
64
85
  'components.creditsAvailableBadge.text.no': string;
@@ -89,6 +110,15 @@ declare const getMessages: (locale: string) => {
89
110
  'sections.glossary.copyButtonLabel': string;
90
111
  'sections.glossary.copySuccessMessage': string;
91
112
  'sections.glossary.copyFailureMessage': string;
113
+ 'sections.events.loadMore': string;
114
+ 'sections.events.noUpcomingEvents': string;
115
+ 'sections.events.noPastEvents': string;
116
+ 'sections.eventsFilter.searchPlaceholder': string;
117
+ 'sections.events.eventsFilter.eventType': string;
118
+ 'sections.events.eventsFilter.language': string;
119
+ 'sections.events.eventsFilter.sortBy.title': string;
120
+ 'sections.events.eventsFilter.sortBy.newest': string;
121
+ 'sections.events.eventsFilter.sortBy.oldest': string;
92
122
  'sections.customerQuoteCard.more': string;
93
123
  'sections.customerCard.more': string;
94
124
  'sections.cta.backgroundShapes': string;
@@ -124,6 +154,18 @@ declare const getMessages: (locale: string) => {
124
154
  'features.projectInfo.properties.year': string;
125
155
  'features.portfolio.documentsDownloadList.projectDocuments': string;
126
156
  'features.portfolio.documentsDownloadList.downloadDocument': string;
157
+ 'sections.eventCard.recommendedEvent': string;
158
+ 'sections.eventCard.buttonShowMore': string;
159
+ 'sections.eventCard.buttonShowLess': string;
160
+ 'sections.eventCard.eventType.conference': string;
161
+ 'sections.eventCard.eventType.webinar': string;
162
+ 'sections.eventCard.eventType.forestwalk': string;
163
+ 'sections.eventCard.eventType.partnerevent': string;
164
+ 'sections.eventCard.eventType.lunch&learn': string;
165
+ 'sections.eventCard.eventType.fair': string;
166
+ 'sections.eventCard.eventType.festival': string;
167
+ 'sections.eventCard.eventType.roadshow': string;
168
+ 'sections.eventCard.eventType.meetup': string;
127
169
  'components.creditsAvailableBadge.text.yes': string;
128
170
  'components.creditsAvailableBadge.text.some': string;
129
171
  'components.creditsAvailableBadge.text.no': string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treely/strapi-slices",
3
- "version": "7.1.4",
3
+ "version": "7.3.0",
4
4
  "license": "MIT",
5
5
  "author": "Tree.ly FlexCo",
6
6
  "description": "@treely/strapi-slices is a open source library maintained by Tree.ly.",
@@ -49,10 +49,17 @@
49
49
  "trailingComma": "es5"
50
50
  },
51
51
  "jest": {
52
+ "transform": {
53
+ "^.+\\.(ts|tsx)$": "ts-jest"
54
+ },
52
55
  "testEnvironment": "jsdom",
53
56
  "setupFilesAfterEnv": [
54
57
  "./src/test/setupTests.ts"
55
58
  ],
59
+ "extensionsToTreatAsEsm": [
60
+ ".ts",
61
+ ".tsx"
62
+ ],
56
63
  "moduleNameMapper": {
57
64
  "^@/(.*)$": "<rootDir>/src/$1",
58
65
  "\\.(css|less|scss|sass)$": "identity-obj-proxy"
@@ -134,12 +141,19 @@
134
141
  "adblock-detect-react": "^1.1.0",
135
142
  "axios": "^1.7.2",
136
143
  "axios-cache-interceptor": "^1.5.3",
137
- "boemly": "^7.2.0",
144
+ "boemly": "^7.5.0",
138
145
  "embla-carousel-auto-scroll": "^8.5.1",
139
146
  "embla-carousel-autoplay": "^8.5.1",
140
147
  "embla-carousel-react": "^8.5.1",
141
148
  "formik": "^2.4.5",
142
149
  "framer-motion": "^10.16.5",
143
- "mapbox-gl": "^2.15.0"
150
+ "mapbox-gl": "^2.15.0",
151
+ "swr": "^2.3.2"
152
+ },
153
+ "msw": {
154
+ "workerDirectory": [
155
+ "public",
156
+ ".storybook/public"
157
+ ]
144
158
  }
145
159
  }
@@ -3,6 +3,8 @@ import { createIntl, createIntlCache } from 'react-intl';
3
3
  import { Global } from '@emotion/react';
4
4
  import { GLOBAL_STYLE } from '../../constants/globalStyle';
5
5
  import getMessages from '../../utils/getMessages';
6
+ import strapiClient from '../../integrations/strapi/strapiClient';
7
+ import { SWRConfig } from 'swr/_internal';
6
8
 
7
9
  const cache = createIntlCache();
8
10
 
@@ -26,12 +28,38 @@ export const ContextProvider: React.FC<ContextProviderProps> = ({
26
28
  children,
27
29
  locale,
28
30
  }: ContextProviderProps): JSX.Element => {
31
+ const fetcher = async (resource: any, init: any) => {
32
+ const response = await strapiClient.get(`${resource}`, {
33
+ ...init,
34
+ headers: {},
35
+ });
36
+ // Check if the response was an error:
37
+ if (response.status < 200 || response.status >= 300) {
38
+ let errorData = { message: '' };
39
+ try {
40
+ errorData = await response.data;
41
+ } catch (error) {
42
+ errorData = {
43
+ message: `An unknown error occurred while fetching data.`,
44
+ };
45
+ }
46
+ throw new Error(errorData.message); // Throwing the error will lead to onError being called
47
+ }
48
+
49
+ return { body: await response.data, headers: response.headers };
50
+ };
29
51
  return (
30
52
  <>
31
- <Global styles={{ GLOBAL_STYLE }} />
32
- <IntlContext.Provider value={intlFactory(locale)}>
33
- {children}
34
- </IntlContext.Provider>
53
+ <SWRConfig
54
+ value={{
55
+ fetcher,
56
+ }}
57
+ >
58
+ <Global styles={{ GLOBAL_STYLE }} />
59
+ <IntlContext.Provider value={intlFactory(locale)}>
60
+ {children}
61
+ </IntlContext.Provider>
62
+ </SWRConfig>
35
63
  </>
36
64
  );
37
65
  };
@@ -8,6 +8,7 @@ import Link from 'next/link';
8
8
  export interface CustomerQuoteCardProps {
9
9
  customerStory: StrapiCustomerStory;
10
10
  }
11
+
11
12
  export const CustomerQuoteCard = ({
12
13
  customerStory,
13
14
  }: CustomerQuoteCardProps): JSX.Element => {
@@ -0,0 +1,127 @@
1
+ import React from 'react';
2
+ import { render, screen, userEvent, waitFor } from '../../test/testUtils';
3
+ import EventCard from '.';
4
+ import { EventCardProps } from './EventCard';
5
+ import { strapiEventMock } from '../../test/strapiMocks/strapiEventMock';
6
+ import { mergeDeep } from '../../utils/mergeDeep';
7
+
8
+ const defaultProps: EventCardProps = {
9
+ event: strapiEventMock.attributes,
10
+ };
11
+
12
+ const setup = (props = {}) => {
13
+ const combinedProps = mergeDeep(defaultProps, props);
14
+ render(<EventCard {...combinedProps} />);
15
+ };
16
+
17
+ describe('The EventCard component', () => {
18
+ it('displays the event cards', () => {
19
+ setup();
20
+
21
+ expect(screen.getByText(defaultProps.event.title)).toBeInTheDocument();
22
+
23
+ expect(screen.getAllByRole('img')[0]).toHaveAttribute(
24
+ 'alt',
25
+ defaultProps.event.image.alt
26
+ );
27
+ expect(screen.getAllByRole('img')[1]).toHaveAttribute(
28
+ 'alt',
29
+ defaultProps.event.logo.alt
30
+ );
31
+
32
+ expect(screen.getByText('Event Description')).toBeInTheDocument();
33
+ expect(screen.getByText('Event Title')).toBeInTheDocument();
34
+ expect(screen.getByText('Meet Up')).toBeInTheDocument();
35
+ expect(screen.getByText('English')).toBeInTheDocument();
36
+
37
+ expect(screen.getByRole('link')).toHaveAttribute(
38
+ 'href',
39
+ defaultProps.event.button.url
40
+ );
41
+
42
+ expect(screen.queryByText('Recommended')).not.toBeInTheDocument();
43
+ expect(screen.queryByText('Online')).not.toBeInTheDocument();
44
+ });
45
+
46
+ it('displays start and end date and time correctly', () => {
47
+ setup();
48
+
49
+ expect(
50
+ screen.getByText(/3\/12\/2024\s*\|\s*08:30\s*-\s*09:30/i)
51
+ ).toBeInTheDocument();
52
+ });
53
+
54
+ it('displays the speakers', async () => {
55
+ setup();
56
+
57
+ const firstSpeakerImage = screen.getAllByRole('img')[2];
58
+ expect(firstSpeakerImage).toHaveAttribute(
59
+ 'alt',
60
+ defaultProps.event.speakers[0].image.alt
61
+ );
62
+
63
+ await waitFor(() => userEvent.hover(firstSpeakerImage));
64
+ await waitFor(() => expect(screen.getByText('John Doe')).toBeVisible());
65
+
66
+ const secondSpeakerImage = screen.getAllByRole('img')[3];
67
+ expect(secondSpeakerImage).toHaveAttribute(
68
+ 'alt',
69
+ defaultProps.event.speakers[1].image.alt
70
+ );
71
+
72
+ await waitFor(() => userEvent.hover(secondSpeakerImage));
73
+ await waitFor(() => expect(screen.getByText('Jane Doe')).toBeVisible());
74
+ });
75
+
76
+ it('displays optional props if they are defined in the slice', () => {
77
+ setup({
78
+ event: {
79
+ ...defaultProps.event,
80
+ recommended: true,
81
+ online: true,
82
+ location: 'Vienna',
83
+ },
84
+ });
85
+
86
+ expect(
87
+ screen.getByText((content) => content.includes('Recommended'))
88
+ ).toBeInTheDocument();
89
+ expect(screen.getByText('Online')).toBeInTheDocument();
90
+ expect(screen.getByText('Vienna')).toBeInTheDocument();
91
+ });
92
+
93
+ it('displays the right country flag', () => {
94
+ setup();
95
+ expect(screen.getByText('🇬🇧')).toBeInTheDocument();
96
+ });
97
+
98
+ it('displays multiple event types if they are defined in the slice', () => {
99
+ setup({
100
+ event: {
101
+ ...defaultProps.event,
102
+ eventTypes: [
103
+ { id: 1, eventType: 'Conference' },
104
+ { id: 2, eventType: 'Meet Up' },
105
+ ],
106
+ },
107
+ });
108
+
109
+ expect(screen.getByText('Conference')).toBeInTheDocument();
110
+ expect(screen.getByText('Meet Up')).toBeInTheDocument();
111
+ });
112
+
113
+ it('displays multiple languages if they are defined in the slice', () => {
114
+ setup({
115
+ event: {
116
+ ...defaultProps.event,
117
+ languages: [
118
+ { id: 1, language: 'German', countryCode: 'de' },
119
+ { id: 2, language: 'English', countryCode: 'en' },
120
+ ],
121
+ },
122
+ });
123
+
124
+ expect(screen.getByText('German')).toBeInTheDocument();
125
+ expect(screen.getByText('English')).toBeInTheDocument();
126
+ });
127
+ });
@@ -0,0 +1,309 @@
1
+ import React, { useContext, useState } from 'react';
2
+ import {
3
+ Text,
4
+ Box,
5
+ Flex,
6
+ Heading,
7
+ Tooltip,
8
+ Tag,
9
+ useMediaQuery,
10
+ Button,
11
+ } from 'boemly';
12
+ import { css } from '@emotion/react';
13
+ import Image from 'next/image';
14
+ import StrapiLinkButton from '../StrapiLinkButton';
15
+ import {
16
+ BowlFood,
17
+ CalendarBlank,
18
+ CaretDown,
19
+ CaretRight,
20
+ CaretUp,
21
+ ChalkboardTeacher,
22
+ Confetti,
23
+ Handshake,
24
+ Headset,
25
+ Info,
26
+ Laptop,
27
+ MapPinLine,
28
+ PersonSimpleWalk,
29
+ ProjectorScreenChart,
30
+ Star,
31
+ UsersThree,
32
+ Webcam,
33
+ } from '@phosphor-icons/react';
34
+ import getCountryFlag from '../../utils/getCountryFlag';
35
+ import { BREAKPOINT_MD_QUERY } from '../../constants/breakpoints';
36
+ import StrapiEvent, { EventType } from '../../models/strapi/StrapiEvent';
37
+ import { IntlContext } from '../ContextProvider';
38
+ import strapiMediaUrl from '../../utils/strapiMediaUrl';
39
+
40
+ export interface EventCardProps {
41
+ event: StrapiEvent;
42
+ }
43
+
44
+ const MAX_LENGTH = 120;
45
+ const LOCATION_MAX_LENGTH = 28;
46
+
47
+ const getEventIcon = (eventType: string): JSX.Element => {
48
+ switch (eventType) {
49
+ case EventType.WEBINAR:
50
+ return <Webcam size={12} />;
51
+ case EventType.CONFERENCE:
52
+ return <Headset size={12} />;
53
+ case EventType.MEET_UP:
54
+ return <UsersThree size={12} />;
55
+ case EventType.FOREST_WALK:
56
+ return <PersonSimpleWalk size={12} />;
57
+ case EventType.PARTNER_EVENT:
58
+ return <Handshake size={12} />;
59
+ case EventType.LUNCH_AND_LEARN:
60
+ return <BowlFood size={12} />;
61
+ case EventType.FAIR:
62
+ return <ChalkboardTeacher size={12} />;
63
+ case EventType.FESTIVAL:
64
+ return <Confetti size={12} />;
65
+ case EventType.ROADSHOW:
66
+ return <ProjectorScreenChart size={12} />;
67
+ default:
68
+ return <Info size={12} weight="fill" />;
69
+ }
70
+ };
71
+
72
+ export const EventCard = ({ event }: EventCardProps): JSX.Element => {
73
+ const { formatDate, formatNumber, formatMessage } = useContext(IntlContext);
74
+ const [isExpanded, setIsExpanded] = useState(false);
75
+ const [mobile] = useMediaQuery(BREAKPOINT_MD_QUERY);
76
+
77
+ const toggleText = () => {
78
+ setIsExpanded(!isExpanded);
79
+ };
80
+
81
+ const isLocationTooLong =
82
+ (event.location?.length ?? 0) >= LOCATION_MAX_LENGTH;
83
+
84
+ return (
85
+ <Box
86
+ borderRadius={['xl', null, null, '2xl']}
87
+ height="full"
88
+ width="full"
89
+ border="1px solid var(--boemly-colors-gray-200)"
90
+ background="white"
91
+ >
92
+ <Box
93
+ position="relative"
94
+ width="full"
95
+ height={['32', null, null, '44']}
96
+ borderTopRadius={['xl', null, null, '2xl']}
97
+ css={css`
98
+ & span,
99
+ div,
100
+ img {
101
+ border-top-left-radius: inherit;
102
+ border-top-right-radius: inherit;
103
+ }
104
+ `}
105
+ >
106
+ <Image
107
+ src={strapiMediaUrl(event.image?.img, 'medium')}
108
+ alt={event.image?.alt}
109
+ fill
110
+ style={{
111
+ objectFit: event.image?.objectFit || 'cover',
112
+ }}
113
+ />
114
+
115
+ <Box
116
+ position="absolute"
117
+ top={['6', null, null, '8']}
118
+ right={['6', null, null, '8']}
119
+ zIndex="1"
120
+ width={['12', null, null, '16']}
121
+ height={['12', null, null, '16']}
122
+ >
123
+ <Image
124
+ src={strapiMediaUrl(event.logo.img, 'medium')}
125
+ alt={event.logo.alt}
126
+ fill
127
+ style={{
128
+ objectFit: event.logo.objectFit || 'contain',
129
+ borderRadius: 'var(--boemly-radii-md)',
130
+ border:
131
+ '1px solid, var(--whiteAlpha-700, rgba(255, 255, 255, 0.64))',
132
+ }}
133
+ />
134
+ </Box>
135
+ </Box>
136
+ <Flex
137
+ flexDir="column"
138
+ p={['6', null, null, '8']}
139
+ h="calc(var(--boemly-sizes-full) - var(--boemly-sizes-44))"
140
+ >
141
+ <Flex flexDir="row" mb="4" gap="2" flexWrap="wrap">
142
+ {event.recommended ? (
143
+ <Flex mb={['2', null, null, '0']}>
144
+ <Tag backgroundColor="green.600">
145
+ <Star size={12} weight="fill" color="white" />
146
+ &nbsp;
147
+ <Text size="xsLowBold" color="white">
148
+ {formatMessage({
149
+ id: 'sections.eventCard.recommendedEvent',
150
+ })}
151
+ </Text>
152
+ </Tag>
153
+ </Flex>
154
+ ) : (
155
+ <></>
156
+ )}
157
+ <Flex flexWrap="wrap" gap="2">
158
+ {event.eventTypes.map((e) => (
159
+ <Tag key={e.id}>
160
+ {getEventIcon(e.eventType)}&nbsp;
161
+ <Text size="xsLowBold" color="gray.800">
162
+ {formatMessage({
163
+ id: `sections.eventCard.eventType.${e.eventType
164
+ .toLowerCase()
165
+ .replace(/\s+/g, '')}`,
166
+ })}
167
+ </Text>
168
+ </Tag>
169
+ ))}
170
+ {event.languages.map(({ id, language, countryCode }) => (
171
+ <Tag key={id}>
172
+ {getCountryFlag(countryCode)}&nbsp;
173
+ <Text size="xsLowBold" color="gray.800">
174
+ {language}
175
+ </Text>
176
+ </Tag>
177
+ ))}
178
+ </Flex>
179
+ </Flex>
180
+ <Heading>{event.title}</Heading>
181
+ <Flex
182
+ gap={isLocationTooLong ? '2' : ['2', null, null, '6']}
183
+ alignItems={mobile || isLocationTooLong ? 'flex-start' : 'center'}
184
+ my="4"
185
+ flexDir={mobile || isLocationTooLong ? 'column' : 'row'}
186
+ >
187
+ {event.online && (
188
+ <Flex gap="2" alignItems="center">
189
+ <Laptop size={20} color={'var(--boemly-colors-primary-700)'} />
190
+ <Text size={['xsLowBold', null, null, 'smLowBold']}>Online</Text>
191
+ </Flex>
192
+ )}
193
+ {event.location && (
194
+ <Flex gap="2" alignItems="center">
195
+ <MapPinLine
196
+ size={20}
197
+ color={'var(--boemly-colors-primary-700)'}
198
+ weight="fill"
199
+ />
200
+ <Text size={['xsLowBold', null, null, 'smLowBold']}>
201
+ {event.location}
202
+ </Text>
203
+ </Flex>
204
+ )}
205
+ <Flex alignItems="center" gap="2">
206
+ <CalendarBlank
207
+ size={20}
208
+ color={'var(--boemly-colors-primary-700)'}
209
+ />
210
+ <Text size={['xsLowBold', null, null, 'smLowBold']}>
211
+ {formatDate(event.start, {
212
+ year: 'numeric',
213
+ month: '2-digit',
214
+ day: '2-digit',
215
+ })}{' '}
216
+ |{' '}
217
+ {formatNumber(new Date(event.start).getUTCHours(), {
218
+ minimumIntegerDigits: 2,
219
+ })}
220
+ :
221
+ {formatNumber(new Date(event.start).getUTCMinutes(), {
222
+ minimumIntegerDigits: 2,
223
+ })}{' '}
224
+ -{' '}
225
+ {formatNumber(new Date(event.end).getUTCHours(), {
226
+ minimumIntegerDigits: 2,
227
+ })}
228
+ :
229
+ {formatNumber(new Date(event.end).getUTCMinutes(), {
230
+ minimumIntegerDigits: 2,
231
+ })}
232
+ </Text>
233
+ </Flex>
234
+ </Flex>
235
+ <Text
236
+ mb={mobile ? '0' : '7'}
237
+ size={['xsRegularNormal', null, null, 'smRegularNormal']}
238
+ >
239
+ {isExpanded || !mobile
240
+ ? event.description
241
+ : `${event.description.substring(0, MAX_LENGTH)}...`}
242
+ </Text>
243
+ {event.description.length > MAX_LENGTH && mobile && (
244
+ <Flex justifyContent="flex-start">
245
+ <Button
246
+ mt="2"
247
+ onClick={toggleText}
248
+ variant="link"
249
+ rightIcon={
250
+ isExpanded ? <CaretUp size="12" /> : <CaretDown size="12" />
251
+ }
252
+ >
253
+ {formatMessage(
254
+ isExpanded
255
+ ? {
256
+ id: 'sections.eventCard.buttonShowLess',
257
+ }
258
+ : { id: 'sections.eventCard.buttonShowMore' }
259
+ )}
260
+ </Button>
261
+ </Flex>
262
+ )}
263
+ <Flex
264
+ mt={mobile ? '7' : 'auto'}
265
+ justifyContent={mobile ? undefined : 'space-between'}
266
+ flexDir={mobile ? 'column-reverse' : 'row'}
267
+ gap={mobile ? '4' : '0'}
268
+ >
269
+ <Flex width={mobile ? 'full' : 'auto'}>
270
+ <StrapiLinkButton
271
+ key={event.button.id}
272
+ size="md"
273
+ variant={event.buttonVariant}
274
+ link={event.button}
275
+ rightIcon={<CaretRight size="10" />}
276
+ width="full"
277
+ />
278
+ </Flex>
279
+ <Flex flexDir="row" gap="2">
280
+ {event.speakers.map((speaker) => (
281
+ <Box key={speaker.id}>
282
+ <Box
283
+ width={['10', null, null, '12']}
284
+ height={['10', null, null, '12']}
285
+ position="relative"
286
+ borderRadius="2xl"
287
+ >
288
+ <Tooltip label={speaker.name}>
289
+ <Image
290
+ src={strapiMediaUrl(speaker.image.img, 'medium')}
291
+ alt={speaker.image.alt}
292
+ fill
293
+ style={{
294
+ objectFit: speaker.image.objectFit || 'cover',
295
+ borderRadius: 'var(--boemly-radii-md)',
296
+ border:
297
+ '1px solid, var(--whiteAlpha-700, rgba(255, 255, 255, 0.64))',
298
+ }}
299
+ />
300
+ </Tooltip>
301
+ </Box>
302
+ </Box>
303
+ ))}
304
+ </Flex>
305
+ </Flex>
306
+ </Flex>
307
+ </Box>
308
+ );
309
+ };
@@ -0,0 +1,3 @@
1
+ import { EventCard } from './EventCard';
2
+
3
+ export default EventCard;
@@ -0,0 +1,16 @@
1
+ const messagesDe = {
2
+ 'sections.eventCard.recommendedEvent': 'Empfohlene Veranstaltung',
3
+ 'sections.eventCard.buttonShowMore': 'Mehr anzeigen',
4
+ 'sections.eventCard.buttonShowLess': 'Weniger anzeigen',
5
+
6
+ 'sections.eventCard.eventType.conference': 'Konferenz',
7
+ 'sections.eventCard.eventType.webinar': 'Webinar',
8
+ 'sections.eventCard.eventType.forestwalk': 'Waldspaziergang',
9
+ 'sections.eventCard.eventType.partnerevent': 'Partnerveranstaltung',
10
+ 'sections.eventCard.eventType.lunch&learn': 'Mittagessen & Lernen',
11
+ 'sections.eventCard.eventType.fair': 'Messe',
12
+ 'sections.eventCard.eventType.festival': 'Festival',
13
+ 'sections.eventCard.eventType.roadshow': 'Roadshow',
14
+ 'sections.eventCard.eventType.meetup': 'Meet Up',
15
+ };
16
+ export default messagesDe;
@@ -0,0 +1,16 @@
1
+ const messagesEn = {
2
+ 'sections.eventCard.recommendedEvent': 'Recommended Event',
3
+ 'sections.eventCard.buttonShowMore': 'Show More',
4
+ 'sections.eventCard.buttonShowLess': 'Show Less',
5
+
6
+ 'sections.eventCard.eventType.conference': 'Conference',
7
+ 'sections.eventCard.eventType.webinar': 'Webinar',
8
+ 'sections.eventCard.eventType.forestwalk': 'Forest Walk',
9
+ 'sections.eventCard.eventType.partnerevent': 'Partner Event',
10
+ 'sections.eventCard.eventType.lunch&learn': 'Lunch & Learn',
11
+ 'sections.eventCard.eventType.fair': 'Fair',
12
+ 'sections.eventCard.eventType.festival': 'Festival',
13
+ 'sections.eventCard.eventType.roadshow': 'Roadshow',
14
+ 'sections.eventCard.eventType.meetup': 'Meet Up',
15
+ };
16
+ export default messagesEn;
@@ -41,6 +41,7 @@ import CarouselMarqueeBanner from '../../slices/CarouselMarqueeBanner';
41
41
  import Locale from '../../models/Locale';
42
42
  import { ContextProvider } from '../ContextProvider';
43
43
  import Timeline from '../../slices/Timeline';
44
+ import Events from '../../slices/Events';
44
45
 
45
46
  export interface CustomSliceProps {
46
47
  slice: any;
@@ -309,6 +310,10 @@ export const SliceRenderer = ({
309
310
  slice={slice}
310
311
  />
311
312
  );
313
+ case 'sections.events':
314
+ return (
315
+ <Events key={`${slice.__component}-${slice.id}`} slice={slice} />
316
+ );
312
317
  default:
313
318
  if (CustomSlice) {
314
319
  return (