@treely/strapi-slices 7.11.0 → 7.13.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 (36) hide show
  1. package/dist/__mocks__/mapbox-gl.d.ts +11 -2
  2. package/dist/integrations/strapi/getPortfolioProjectsByBbox.d.ts +3 -0
  3. package/dist/models/PageMetadata.d.ts +1 -0
  4. package/dist/models/fpm/FPMProject.d.ts +3 -0
  5. package/dist/models/strapi/StrapiMetadata.d.ts +1 -0
  6. package/dist/slices/ProjectsMap/ProjectsMap.d.ts +1 -2
  7. package/dist/strapi-slices.cjs.development.js +509 -190
  8. package/dist/strapi-slices.cjs.development.js.map +1 -1
  9. package/dist/strapi-slices.cjs.production.min.js +1 -1
  10. package/dist/strapi-slices.cjs.production.min.js.map +1 -1
  11. package/dist/strapi-slices.esm.js +571 -253
  12. package/dist/strapi-slices.esm.js.map +1 -1
  13. package/package.json +2 -1
  14. package/src/components/SliceRenderer/SliceRenderer.tsx +0 -1
  15. package/src/integrations/strapi/getPortfolioProjectsByBbox.test.ts +82 -0
  16. package/src/integrations/strapi/getPortfolioProjectsByBbox.ts +86 -0
  17. package/src/models/PageMetadata.ts +1 -0
  18. package/src/models/fpm/FPMProject.ts +3 -0
  19. package/src/models/strapi/StrapiMetadata.ts +1 -0
  20. package/src/slices/ProjectsMap/ProjectsMap.stories.tsx +0 -48
  21. package/src/slices/ProjectsMap/ProjectsMap.test.tsx +111 -39
  22. package/src/slices/ProjectsMap/ProjectsMap.tsx +477 -91
  23. package/src/slices/TextWithCard/TextWithCard.stories.tsx +1 -0
  24. package/src/test/integrationMocks/fpmProjectMock.ts +1 -0
  25. package/src/test/strapiMocks/strapiMetadata.ts +1 -0
  26. package/src/utils/mergeGlobalAndStrapiBlogPostData.test.ts +81 -0
  27. package/src/utils/mergeGlobalAndStrapiBlogPostData.ts +6 -0
  28. package/src/utils/mergeGlobalAndStrapiCustomerStoryData.test.ts +78 -0
  29. package/src/utils/mergeGlobalAndStrapiCustomerStoryData.ts +6 -0
  30. package/src/utils/mergeGlobalAndStrapiPageData.test.ts +84 -0
  31. package/src/utils/mergeGlobalAndStrapiPageData.ts +6 -0
  32. package/src/utils/mergeGlobalAndStrapiProjectData.test.ts +81 -0
  33. package/src/utils/mergeGlobalAndStrapiProjectData.ts +6 -0
  34. package/dist/slices/ProjectsMap/MapMarker.d.ts +0 -12
  35. package/src/slices/ProjectsMap/MapMarker.test.tsx +0 -101
  36. package/src/slices/ProjectsMap/MapMarker.tsx +0 -102
@@ -219,4 +219,85 @@ describe('The mergeGlobalAndStrapiBlogPostData util', () => {
219
219
 
220
220
  expect(result.isFallbackLocale).toBeTruthy();
221
221
  });
222
+
223
+ it('handles schema markup types correctly', () => {
224
+ const globalDataWithSchemaMarkup = {
225
+ ...minimalGlobalData,
226
+ attributes: {
227
+ ...minimalGlobalData.attributes,
228
+ metadata: {
229
+ ...minimalGlobalData.attributes.metadata,
230
+ schemaMarkupTypes: ['Organization'],
231
+ },
232
+ },
233
+ };
234
+
235
+ const blogPostDataWithSchemaMarkup = {
236
+ ...strapiBlogPostMock,
237
+ attributes: {
238
+ ...strapiBlogPostMock.attributes,
239
+ metadata: {
240
+ ...strapiMetadataMock,
241
+ schemaMarkupTypes: ['Article', 'BlogPosting'],
242
+ },
243
+ },
244
+ };
245
+
246
+ // Test blog post-level schema markup
247
+ const resultWithBlogPostSchema = mergeGlobalAndStrapiBlogPostData(
248
+ getStaticPropsContextMock,
249
+ globalDataWithSchemaMarkup,
250
+ blogPostDataWithSchemaMarkup,
251
+ [],
252
+ []
253
+ );
254
+ expect(resultWithBlogPostSchema.metadata.schemaMarkupTypes).toEqual([
255
+ 'Article',
256
+ 'BlogPosting',
257
+ ]);
258
+
259
+ // Test global fallback
260
+ const blogPostDataWithoutSchema = {
261
+ ...strapiBlogPostMock,
262
+ attributes: {
263
+ ...strapiBlogPostMock.attributes,
264
+ metadata: {
265
+ ...strapiMetadataMock,
266
+ schemaMarkupTypes: undefined,
267
+ },
268
+ },
269
+ };
270
+
271
+ const resultWithGlobalSchema = mergeGlobalAndStrapiBlogPostData(
272
+ getStaticPropsContextMock,
273
+ globalDataWithSchemaMarkup,
274
+ blogPostDataWithoutSchema,
275
+ [],
276
+ []
277
+ );
278
+ expect(resultWithGlobalSchema.metadata.schemaMarkupTypes).toEqual([
279
+ 'Organization',
280
+ ]);
281
+
282
+ // Test empty array fallback
283
+ const globalDataWithoutSchema = {
284
+ ...minimalGlobalData,
285
+ attributes: {
286
+ ...minimalGlobalData.attributes,
287
+ metadata: {
288
+ ...minimalGlobalData.attributes.metadata,
289
+ schemaMarkupTypes: undefined,
290
+ },
291
+ },
292
+ };
293
+
294
+ const resultWithNoSchema = mergeGlobalAndStrapiBlogPostData(
295
+ getStaticPropsContextMock,
296
+ globalDataWithoutSchema,
297
+ blogPostDataWithoutSchema,
298
+ [],
299
+ []
300
+ );
301
+ expect(resultWithNoSchema.metadata.schemaMarkupTypes).toEqual([]);
302
+ });
222
303
  });
@@ -28,6 +28,11 @@ const mergeGlobalAndStrapiBlogPostData = (
28
28
  )
29
29
  : DEFAULT_SHARE_IMAGE;
30
30
 
31
+ const schemaMarkupTypes =
32
+ post.attributes.metadata?.schemaMarkupTypes ??
33
+ global.attributes.metadata?.schemaMarkupTypes ??
34
+ [];
35
+
31
36
  const returnBlog = post.attributes.slices.some((slice) =>
32
37
  SLICES_WITH_BLOG_POSTS.includes(slice.__component)
33
38
  );
@@ -67,6 +72,7 @@ const mergeGlobalAndStrapiBlogPostData = (
67
72
  },
68
73
  metaTitleSuffix: global.attributes.metaTitleSuffix,
69
74
  favicon: strapiMediaUrl(global.attributes.favicon, 'thumbnail'),
75
+ schemaMarkupTypes,
70
76
  },
71
77
  slices: post?.attributes.slices,
72
78
  blogPosts: returnBlog ? blog : [],
@@ -187,4 +187,82 @@ describe('The mergeGlobalAndStrapiCustomerStoryData util', () => {
187
187
 
188
188
  expect(result.isFallbackLocale).toBeTruthy();
189
189
  });
190
+
191
+ it('handles schema markup types correctly', () => {
192
+ const globalDataWithSchemaMarkup = {
193
+ ...minimalGlobalData,
194
+ attributes: {
195
+ ...minimalGlobalData.attributes,
196
+ metadata: {
197
+ ...minimalGlobalData.attributes.metadata,
198
+ schemaMarkupTypes: ['Organization'],
199
+ },
200
+ },
201
+ };
202
+
203
+ const customerStoryDataWithSchemaMarkup = {
204
+ ...strapiCustomerStoryMock,
205
+ attributes: {
206
+ ...strapiCustomerStoryMock.attributes,
207
+ metadata: {
208
+ ...strapiMetadataMock,
209
+ schemaMarkupTypes: ['Article', 'BlogPosting'],
210
+ },
211
+ },
212
+ };
213
+
214
+ // Test customer story-level schema markup
215
+ const resultWithCustomerStorySchema = mergeGlobalAndStrapiCustomerStoryData(
216
+ getStaticPropsContextMock,
217
+ globalDataWithSchemaMarkup,
218
+ customerStoryDataWithSchemaMarkup,
219
+ []
220
+ );
221
+ expect(resultWithCustomerStorySchema.metadata.schemaMarkupTypes).toEqual([
222
+ 'Article',
223
+ 'BlogPosting',
224
+ ]);
225
+
226
+ // Test global fallback
227
+ const customerStoryDataWithoutSchema = {
228
+ ...strapiCustomerStoryMock,
229
+ attributes: {
230
+ ...strapiCustomerStoryMock.attributes,
231
+ metadata: {
232
+ ...strapiMetadataMock,
233
+ schemaMarkupTypes: undefined,
234
+ },
235
+ },
236
+ };
237
+
238
+ const resultWithGlobalSchema = mergeGlobalAndStrapiCustomerStoryData(
239
+ getStaticPropsContextMock,
240
+ globalDataWithSchemaMarkup,
241
+ customerStoryDataWithoutSchema,
242
+ []
243
+ );
244
+ expect(resultWithGlobalSchema.metadata.schemaMarkupTypes).toEqual([
245
+ 'Organization',
246
+ ]);
247
+
248
+ // Test empty array fallback
249
+ const globalDataWithoutSchema = {
250
+ ...minimalGlobalData,
251
+ attributes: {
252
+ ...minimalGlobalData.attributes,
253
+ metadata: {
254
+ ...minimalGlobalData.attributes.metadata,
255
+ schemaMarkupTypes: undefined,
256
+ },
257
+ },
258
+ };
259
+
260
+ const resultWithNoSchema = mergeGlobalAndStrapiCustomerStoryData(
261
+ getStaticPropsContextMock,
262
+ globalDataWithoutSchema,
263
+ customerStoryDataWithoutSchema,
264
+ []
265
+ );
266
+ expect(resultWithNoSchema.metadata.schemaMarkupTypes).toEqual([]);
267
+ });
190
268
  });
@@ -23,6 +23,11 @@ const mergeGlobalAndStrapiCustomerStoryData = (
23
23
  )
24
24
  : DEFAULT_SHARE_IMAGE;
25
25
 
26
+ const schemaMarkupTypes =
27
+ customerStory.attributes.metadata?.schemaMarkupTypes ??
28
+ global.attributes.metadata?.schemaMarkupTypes ??
29
+ [];
30
+
26
31
  const returnCustomerStories = customerStory.attributes.slices.some((slice) =>
27
32
  SLICES_WITH_CUSTOMER_STORIES.includes(slice.__component)
28
33
  );
@@ -60,6 +65,7 @@ const mergeGlobalAndStrapiCustomerStoryData = (
60
65
  },
61
66
  metaTitleSuffix: global.attributes.metaTitleSuffix,
62
67
  favicon: strapiMediaUrl(global.attributes.favicon, 'thumbnail'),
68
+ schemaMarkupTypes,
63
69
  },
64
70
  slices: customerStory?.attributes.slices,
65
71
  customerStories: returnCustomerStories ? customerStories : [],
@@ -339,4 +339,88 @@ describe('The mergeGlobalAndStrapiPageData util', () => {
339
339
 
340
340
  expect(result.isFallbackLocale).toBeTruthy();
341
341
  });
342
+
343
+ it('handles schema markup types correctly', () => {
344
+ const globalDataWithSchemaMarkup = {
345
+ ...minimalGlobalData,
346
+ attributes: {
347
+ ...minimalGlobalData.attributes,
348
+ metadata: {
349
+ ...minimalGlobalData.attributes.metadata,
350
+ schemaMarkupTypes: ['Organization'],
351
+ },
352
+ },
353
+ };
354
+
355
+ const pageDataWithSchemaMarkup = {
356
+ ...strapiPageMock,
357
+ attributes: {
358
+ ...strapiPageMock.attributes,
359
+ metadata: {
360
+ ...strapiMetadataMock,
361
+ schemaMarkupTypes: ['Article', 'BlogPosting'],
362
+ },
363
+ },
364
+ };
365
+
366
+ // Test page-level schema markup
367
+ const resultWithPageSchema = mergeGlobalAndStrapiPageData(
368
+ getStaticPropsContextMock,
369
+ globalDataWithSchemaMarkup,
370
+ pageDataWithSchemaMarkup,
371
+ [],
372
+ [],
373
+ []
374
+ );
375
+ expect(resultWithPageSchema.metadata.schemaMarkupTypes).toEqual([
376
+ 'Article',
377
+ 'BlogPosting',
378
+ ]);
379
+
380
+ // Test global fallback
381
+ const pageDataWithoutSchema = {
382
+ ...strapiPageMock,
383
+ attributes: {
384
+ ...strapiPageMock.attributes,
385
+ metadata: {
386
+ ...strapiMetadataMock,
387
+ schemaMarkupTypes: undefined,
388
+ },
389
+ },
390
+ };
391
+
392
+ const resultWithGlobalSchema = mergeGlobalAndStrapiPageData(
393
+ getStaticPropsContextMock,
394
+ globalDataWithSchemaMarkup,
395
+ pageDataWithoutSchema,
396
+ [],
397
+ [],
398
+ []
399
+ );
400
+ expect(resultWithGlobalSchema.metadata.schemaMarkupTypes).toEqual([
401
+ 'Organization',
402
+ ]);
403
+
404
+ // Test empty array fallback
405
+ const globalDataWithoutSchema = {
406
+ ...minimalGlobalData,
407
+ attributes: {
408
+ ...minimalGlobalData.attributes,
409
+ metadata: {
410
+ ...minimalGlobalData.attributes.metadata,
411
+ schemaMarkupTypes: undefined,
412
+ },
413
+ },
414
+ };
415
+
416
+ const resultWithNoSchema = mergeGlobalAndStrapiPageData(
417
+ getStaticPropsContextMock,
418
+ globalDataWithoutSchema,
419
+ pageDataWithoutSchema,
420
+ [],
421
+ [],
422
+ []
423
+ );
424
+ expect(resultWithNoSchema.metadata.schemaMarkupTypes).toEqual([]);
425
+ });
342
426
  });
@@ -34,6 +34,11 @@ const mergeGlobalAndStrapiPageData = (
34
34
  )
35
35
  : DEFAULT_SHARE_IMAGE;
36
36
 
37
+ const schemaMarkupTypes =
38
+ page.attributes.metadata?.schemaMarkupTypes ??
39
+ global.attributes.metadata?.schemaMarkupTypes ??
40
+ [];
41
+
37
42
  const returnBlogPosts = page.attributes.slices.some((slice) =>
38
43
  SLICES_WITH_BLOG_POSTS.includes(slice.__component)
39
44
  );
@@ -82,6 +87,7 @@ const mergeGlobalAndStrapiPageData = (
82
87
  },
83
88
  metaTitleSuffix: global.attributes.metaTitleSuffix,
84
89
  favicon: strapiMediaUrl(global.attributes.favicon, 'thumbnail'),
90
+ schemaMarkupTypes,
85
91
  },
86
92
  slices: page?.attributes.slices,
87
93
  blogPosts: returnBlogPosts ? blogPosts : [],
@@ -301,4 +301,85 @@ describe('The mergeGlobalAndStrapiProjectData util', () => {
301
301
 
302
302
  expect(result.isFallbackLocale).toBeTruthy();
303
303
  });
304
+
305
+ it('handles schema markup types correctly', () => {
306
+ const globalDataWithSchemaMarkup = {
307
+ ...minimalGlobalData,
308
+ attributes: {
309
+ ...minimalGlobalData.attributes,
310
+ metadata: {
311
+ ...minimalGlobalData.attributes.metadata,
312
+ schemaMarkupTypes: ['Organization'],
313
+ },
314
+ },
315
+ };
316
+
317
+ const projectDataWithSchemaMarkup = {
318
+ ...strapiProjectMock,
319
+ attributes: {
320
+ ...strapiProjectMock.attributes,
321
+ metadata: {
322
+ ...strapiMetadataMock,
323
+ schemaMarkupTypes: ['Article', 'BlogPosting'],
324
+ },
325
+ },
326
+ };
327
+
328
+ // Test project-level schema markup
329
+ const resultWithProjectSchema = mergeGlobalAndStrapiProjectData(
330
+ getStaticPropsContextMock,
331
+ globalDataWithSchemaMarkup,
332
+ projectDataWithSchemaMarkup,
333
+ [],
334
+ []
335
+ );
336
+ expect(resultWithProjectSchema.metadata.schemaMarkupTypes).toEqual([
337
+ 'Article',
338
+ 'BlogPosting',
339
+ ]);
340
+
341
+ // Test global fallback
342
+ const projectDataWithoutSchema = {
343
+ ...strapiProjectMock,
344
+ attributes: {
345
+ ...strapiProjectMock.attributes,
346
+ metadata: {
347
+ ...strapiMetadataMock,
348
+ schemaMarkupTypes: undefined,
349
+ },
350
+ },
351
+ };
352
+
353
+ const resultWithGlobalSchema = mergeGlobalAndStrapiProjectData(
354
+ getStaticPropsContextMock,
355
+ globalDataWithSchemaMarkup,
356
+ projectDataWithoutSchema,
357
+ [],
358
+ []
359
+ );
360
+ expect(resultWithGlobalSchema.metadata.schemaMarkupTypes).toEqual([
361
+ 'Organization',
362
+ ]);
363
+
364
+ // Test empty array fallback
365
+ const globalDataWithoutSchema = {
366
+ ...minimalGlobalData,
367
+ attributes: {
368
+ ...minimalGlobalData.attributes,
369
+ metadata: {
370
+ ...minimalGlobalData.attributes.metadata,
371
+ schemaMarkupTypes: undefined,
372
+ },
373
+ },
374
+ };
375
+
376
+ const resultWithNoSchema = mergeGlobalAndStrapiProjectData(
377
+ getStaticPropsContextMock,
378
+ globalDataWithoutSchema,
379
+ projectDataWithoutSchema,
380
+ [],
381
+ []
382
+ );
383
+ expect(resultWithNoSchema.metadata.schemaMarkupTypes).toEqual([]);
384
+ });
304
385
  });
@@ -31,6 +31,11 @@ const mergeGlobalAndStrapiProject = (
31
31
  )
32
32
  : DEFAULT_SHARE_IMAGE;
33
33
 
34
+ const schemaMarkupTypes =
35
+ project.attributes.metadata?.schemaMarkupTypes ??
36
+ global.attributes.metadata?.schemaMarkupTypes ??
37
+ [];
38
+
34
39
  const returnBlogPosts = project.attributes.slices.some((slice) =>
35
40
  SLICES_WITH_BLOG_POSTS.includes(slice.__component)
36
41
  );
@@ -76,6 +81,7 @@ const mergeGlobalAndStrapiProject = (
76
81
  },
77
82
  metaTitleSuffix: global.attributes.metaTitleSuffix,
78
83
  favicon: strapiMediaUrl(global.attributes.favicon, 'thumbnail'),
84
+ schemaMarkupTypes,
79
85
  },
80
86
  slices: project.attributes.slices,
81
87
  blogPosts: returnBlogPosts ? blogPosts : [],
@@ -1,12 +0,0 @@
1
- import React from 'react';
2
- import { CreditAvailability } from '../../models/fpm/FPMProject';
3
- export interface MapMarkerProps {
4
- title: string;
5
- isPublic?: boolean;
6
- projectDeveloper?: string;
7
- slug?: string;
8
- portfolioHost?: string;
9
- creditAvailability: CreditAvailability;
10
- }
11
- declare const MapMarker: ({ title, projectDeveloper, slug, creditAvailability, portfolioHost, isPublic, }: MapMarkerProps) => React.JSX.Element;
12
- export default MapMarker;
@@ -1,101 +0,0 @@
1
- import React from 'react';
2
- import { fireEvent, render, screen, waitFor } from '../../test/testUtils';
3
- import MapMarker, { MapMarkerProps } from './MapMarker';
4
- import messagesEn from './messages.en';
5
- import { CreditAvailability } from '../../models/fpm/FPMProject';
6
-
7
- const defaultProps: MapMarkerProps = {
8
- title: 'Project title',
9
- portfolioHost: '',
10
- isPublic: true,
11
- creditAvailability: CreditAvailability.CREDITS_AVAILABLE,
12
- };
13
-
14
- const setup = (props: Partial<MapMarkerProps> = {}) => {
15
- const combinedProps = { ...defaultProps, ...props };
16
- render(<MapMarker {...combinedProps} />);
17
- };
18
-
19
- describe('The MapMarker component', () => {
20
- it('renders successfully with minimal props', () => {
21
- setup({});
22
-
23
- expect(screen.queryByText('Project title')).not.toBeInTheDocument();
24
- });
25
-
26
- it('renders successfully with minimal props', () => {
27
- setup({});
28
-
29
- fireEvent.mouseEnter(screen.getByTestId('mapmarker-pin'));
30
-
31
- waitFor(() => {
32
- expect(screen.findByText('Project title')).toBeInTheDocument();
33
- expect(screen.findByRole('button')).not.toBeInTheDocument();
34
- });
35
- });
36
-
37
- it('renders a button if slug is defined', () => {
38
- setup({ slug: 'slug' });
39
-
40
- expect(screen.getByTestId('mapmarker-pin').parentElement).toHaveProperty(
41
- 'href',
42
- 'http://localhost/portfolio/slug'
43
- );
44
-
45
- fireEvent.mouseEnter(screen.getByTestId('mapmarker-pin'));
46
-
47
- waitFor(() => {
48
- const button = screen.getByText(
49
- messagesEn['sections.projectsMap.link.text']
50
- );
51
- expect(button).toBeInTheDocument();
52
- expect(button).toHaveProperty('href', '/portfolio/slug');
53
- });
54
- });
55
-
56
- it('prefixes the url with the portfolio host', () => {
57
- setup({ slug: 'slug', portfolioHost: 'https://example.com' });
58
-
59
- expect(screen.getByTestId('mapmarker-pin').parentElement).toHaveProperty(
60
- 'href',
61
- 'https://example.com/portfolio/slug'
62
- );
63
-
64
- fireEvent.mouseEnter(screen.getByTestId('mapmarker-pin'));
65
-
66
- waitFor(() => {
67
- const button = screen.getByText(
68
- messagesEn['sections.projectsMap.link.text']
69
- );
70
- expect(button).toBeInTheDocument();
71
- expect(button).toHaveProperty(
72
- 'href',
73
- 'https://example.com/portfolio/slug'
74
- );
75
- });
76
- });
77
-
78
- it('renders the project developer if it is defined', () => {
79
- setup({
80
- projectDeveloper: 'Project developer',
81
- });
82
-
83
- fireEvent.mouseEnter(screen.getByTestId('mapmarker-pin'));
84
-
85
- waitFor(() => {
86
- expect(screen.getByText('Project developer')).toBeInTheDocument();
87
- });
88
- });
89
-
90
- it('renders the credit availability if it is defined', () => {
91
- setup({
92
- creditAvailability: CreditAvailability.CREDITS_AVAILABLE,
93
- });
94
-
95
- fireEvent.mouseEnter(screen.getByTestId('mapmarker-pin'));
96
-
97
- waitFor(() => {
98
- expect(screen.getByText('CREDITS AVAILABLE')).toBeInTheDocument();
99
- });
100
- });
101
- });
@@ -1,102 +0,0 @@
1
- import React, { useContext } from 'react';
2
- import {
3
- Box,
4
- Button,
5
- Container,
6
- Flex,
7
- Heading,
8
- Text,
9
- useDisclosure,
10
- useToken,
11
- } from 'boemly';
12
- import { MapPin } from '@phosphor-icons/react';
13
- import NextLink from 'next/link';
14
- import CreditsAvailableBadge from '../../components/CreditsAvailableBadge';
15
- import { IntlContext } from '../../components/ContextProvider';
16
- import { CreditAvailability } from '../../models/fpm/FPMProject';
17
-
18
- export interface MapMarkerProps {
19
- title: string;
20
- isPublic?: boolean;
21
- projectDeveloper?: string;
22
- slug?: string;
23
- portfolioHost?: string;
24
- creditAvailability: CreditAvailability;
25
- }
26
-
27
- const MapMarker = ({
28
- title,
29
- projectDeveloper,
30
- slug,
31
- creditAvailability,
32
- portfolioHost = '',
33
- isPublic = false,
34
- }: MapMarkerProps) => {
35
- const { formatMessage } = useContext(IntlContext);
36
- const { isOpen, onOpen, onClose } = useDisclosure();
37
- const blue600 = useToken('colors', 'blue.600');
38
-
39
- return (
40
- <Flex
41
- position="absolute"
42
- gap="4"
43
- onMouseEnter={onOpen}
44
- onMouseLeave={onClose}
45
- cursor="grab"
46
- >
47
- <Box
48
- as={slug ? NextLink : undefined}
49
- href={slug && `${portfolioHost}/portfolio/${slug}`}
50
- >
51
- <MapPin
52
- size="40px"
53
- color={blue600}
54
- weight="fill"
55
- data-testid="mapmarker-pin"
56
- filter="drop-shadow(0px 0px 2px #FFF)"
57
- />
58
- </Box>
59
-
60
- {isPublic && isOpen && (
61
- <Container
62
- shadow="md"
63
- width="max-content"
64
- minWidth="3xs"
65
- maxWidth={['3xs', null, null, 'sm']}
66
- >
67
- <Flex direction="column">
68
- <CreditsAvailableBadge
69
- status={creditAvailability}
70
- href={slug && `${portfolioHost}/portfolio/${slug}`}
71
- />
72
- <Heading mt="3" size="md">
73
- {title}
74
- </Heading>
75
-
76
- {projectDeveloper && (
77
- <Text size="smLowNormal" mt="1">
78
- {projectDeveloper}
79
- </Text>
80
- )}
81
-
82
- {slug && (
83
- <Button
84
- width="fit-content"
85
- variant="outline"
86
- size="sm"
87
- as={NextLink}
88
- href={`${portfolioHost}/portfolio/${slug}`}
89
- mt="4"
90
- whiteSpace="nowrap"
91
- >
92
- {formatMessage({ id: 'sections.projectsMap.link.text' })}
93
- </Button>
94
- )}
95
- </Flex>
96
- </Container>
97
- )}
98
- </Flex>
99
- );
100
- };
101
-
102
- export default MapMarker;