Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/components/ProjectGridCard/ProjectGridCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { render, screen } from '../../test/testUtils';
import { ProjectGridCardProps } from './ProjectGridCard';
import ProjectGridCard from '.';
import fpmProjectMock from '../../test/integrationMocks/fpmProjectMock';
import getProjectTranslation from '../../utils/getProjectTranslation';
import { strapiMediaMock } from '../../test/strapiMocks/strapiMedia';
import React from 'react';
import messagesEn from '../../components/CreditsAvailableBadge/messages.en';
Expand All @@ -23,7 +24,15 @@ describe('The ProjectGridCard component', () => {
it('displays the project card', () => {
setup();

expect(screen.getByText(fpmProjectMock.title)).toBeInTheDocument();
expect(
screen.getByText(
getProjectTranslation(
fpmProjectMock.nameTranslations,
'en',
fpmProjectMock.title
)
)
).toBeInTheDocument();
});

it('displays the project thumbnail', () => {
Expand Down
5 changes: 3 additions & 2 deletions src/components/ProjectGridCard/ProjectGridCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FORMAT_AS_HECTARE_CONFIG } from '../../constants/formatter';
import CreditsAvailableBadge from '../../components/CreditsAvailableBadge';
import CertificationBadge from '../../components/CertificationBadge';
import { IntlContext } from '../ContextProvider';
import getProjectTranslation from '../../utils/getProjectTranslation';

export interface ProjectGridCardProps {
project: PortfolioProject;
Expand All @@ -15,7 +16,7 @@ export interface ProjectGridCardProps {
export const ProjectGridCard = ({
project,
}: ProjectGridCardProps): React.JSX.Element => {
const { formatNumber } = useContext(IntlContext);
const { formatNumber, locale } = useContext(IntlContext);

return (
<Container height="full">
Expand All @@ -34,7 +35,7 @@ export const ProjectGridCard = ({
</Box>
)}
<Heading my="4" size="lg">
{project.friendlyName || project.title}
{getProjectTranslation(project.nameTranslations, locale, project.title)}
</Heading>
<Flex flexDir="row" gap="2" flexWrap="wrap">
<BoemlyTag backgroundColor="gray.100">
Expand Down
11 changes: 10 additions & 1 deletion src/components/ProjectGridCardV2/ProjectGridCardV2.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { render, screen } from '../../test/testUtils';
import { ProjectGridCardV2Props } from './ProjectGridCardV2';
import ProjectGridCardV2 from '.';
import fpmProjectMock from '../../test/integrationMocks/fpmProjectMock';
import getProjectTranslation from '../../utils/getProjectTranslation';
import { strapiMediaMock } from '../../test/strapiMocks/strapiMedia';
import messagesEnCertificationBadge from '../CertificationBadge/messages.en';
import messagesEnCreditsAvailableBadge from '../CreditsAvailableBadge/messages.en';
Expand All @@ -23,7 +24,15 @@ describe('The ProjectGridCardV2 component', () => {
it('displays the project title', () => {
setup();

expect(screen.getByText(fpmProjectMock.title)).toBeInTheDocument();
expect(
screen.getByText(
getProjectTranslation(
fpmProjectMock.nameTranslations,
'en',
fpmProjectMock.title
)
)
).toBeInTheDocument();
});

it('displays the project thumbnail', () => {
Expand Down
5 changes: 3 additions & 2 deletions src/components/ProjectGridCardV2/ProjectGridCardV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IntlContext } from '../ContextProvider';
import CreditsAvailableBadge from '../CreditsAvailableBadge';
import CertificationBadge from '../CertificationBadge';
import getCountryFlag from '../../utils/getCountryFlag';
import getProjectTranslation from '../../utils/getProjectTranslation';

export interface ProjectGridCardV2Props {
project: PortfolioProject;
Expand All @@ -32,7 +33,7 @@ const getProjectTypeLabel = (
export const ProjectGridCardV2 = ({
project,
}: ProjectGridCardV2Props): React.JSX.Element => {
const { formatNumber, formatMessage } = useContext(IntlContext);
const { formatNumber, formatMessage, locale } = useContext(IntlContext);

return (
<Box height="full" borderRadius="lg" boxShadow="sm" overflow="hidden">
Expand Down Expand Up @@ -84,7 +85,7 @@ export const ProjectGridCardV2 = ({
{/* Content Section */}
<Box padding="6" backgroundColor="white">
<Heading size="xl" color="primary.700" mb="2">
{project.friendlyName || project.title}
{getProjectTranslation(project.nameTranslations, locale, project.title)}
</Heading>

<Flex flexDir="column" gap="2">
Expand Down
8 changes: 7 additions & 1 deletion src/models/fpm/FPMProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ export enum CreditAvailability {
SOON_CREDITS_AVAILABLE = 'soon_credits_available',
}

export interface FPMProjectNameTranslation {
id: string;
language: string;
value: string;
}

interface FPMProject {
id: string;
title: string;
description?: string;
friendlyName?: string;
nameTranslations?: FPMProjectNameTranslation[];

isPublic?: boolean;
geom?: {
Expand Down
11 changes: 10 additions & 1 deletion src/slices/ProjectsGrid/ProjectsGrid.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { render, screen } from '../../test/testUtils';
import ProjectsGrid from '.';
import { ProjectsGridProps } from './ProjectsGrid';
import fpmProjectMock from '../../test/integrationMocks/fpmProjectMock';
import getProjectTranslation from '../../utils/getProjectTranslation';
import { strapiMediaMock } from '../../test/strapiMocks/strapiMedia';
import { strapiProjectMock } from '../../test/strapiMocks/strapiProject';

Expand All @@ -29,7 +30,15 @@ describe('The ProjectsGrid component', () => {
it('displays the project cards', () => {
setup();

expect(screen.getByText(fpmProjectMock.title)).toBeInTheDocument();
expect(
screen.getByText(
getProjectTranslation(
fpmProjectMock.nameTranslations,
'en',
fpmProjectMock.title
)
)
).toBeInTheDocument();
});

it('links to the portfolio', () => {
Expand Down
11 changes: 10 additions & 1 deletion src/slices/ProjectsGridV2/ProjectsGridV2.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { render, screen } from '../../test/testUtils';
import { ProjectsGridV2Props } from './ProjectsGridV2';
import fpmProjectMock from '../../test/integrationMocks/fpmProjectMock';
import getProjectTranslation from '../../utils/getProjectTranslation';
import { strapiMediaMock } from '../../test/strapiMocks/strapiMedia';
import { strapiProjectMock } from '../../test/strapiMocks/strapiProject';
import ProjectsGridV2 from '.';
Expand Down Expand Up @@ -28,7 +29,15 @@ describe('The ProjectsGridV2 component', () => {
it('displays the project cards', () => {
setup();

expect(screen.getByText(fpmProjectMock.title)).toBeInTheDocument();
expect(
screen.getByText(
getProjectTranslation(
fpmProjectMock.nameTranslations,
'en',
fpmProjectMock.title
)
)
).toBeInTheDocument();
});

it('links to the portfolio', () => {
Expand Down
88 changes: 56 additions & 32 deletions src/slices/ProjectsMap/ProjectsMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ import debounce from 'lodash/debounce';
import getFpmProjectsByBbox from '../../integrations/strapi/getFpmProjectsByBbox';
import getStrapiProjects from '../../integrations/strapi/getStrapiProjects';
import mergeProjectData from '../../utils/mergeProjectData';
import { CreditAvailability } from '../../models/fpm/FPMProject';
import {
CreditAvailability,
FPMProjectNameTranslation,
} from '../../models/fpm/FPMProject';
import IStrapiData from '../../models/strapi/IStrapiData';
import StrapiProject from '../../models/strapi/StrapiProject';
import getProjectTranslation from '../../utils/getProjectTranslation';

const projectPinImage =
'https://cdn.jsdelivr.net/npm/@phosphor-icons/core@2.0.2/assets/fill/map-pin-fill.svg';
Expand Down Expand Up @@ -288,12 +292,28 @@ export const ProjectsMap: React.FC<ProjectsMapProps> = ({
const coordinates = (e.features[0].geometry as any).coordinates.slice();
const {
title,
nameTranslations: nameTranslationsRaw,
projectDeveloper,
slug,
portfolioHost,
creditAvailability,
} = e.features[0].properties;

let parsedNameTranslations: FPMProjectNameTranslation[] | undefined;
try {
parsedNameTranslations = nameTranslationsRaw
? JSON.parse(nameTranslationsRaw)
: undefined;
} catch {
parsedNameTranslations = undefined;
}

const displayName = getProjectTranslation(
parsedNameTranslations,
locale,
title
);

// Calculate if popup would go off screen at the top
const point = map.current!.project(coordinates);
const popupHeight = 150; // Approximate height of popup
Expand Down Expand Up @@ -366,7 +386,7 @@ export const ProjectsMap: React.FC<ProjectsMapProps> = ({
const description = `
<div style="padding: 2px; padding-right: 16px; min-width: 180px; max-width: 260px;">
${badge}
<h3 style="font-size: 15px; font-weight: bold;">${title}</h3>
<h3 style="font-size: 15px; font-weight: bold;">${displayName}</h3>
<p style="font-size: 15px; color: #64748b;">${developer}</p>
${button}
</div>
Expand Down Expand Up @@ -437,9 +457,9 @@ export const ProjectsMap: React.FC<ProjectsMapProps> = ({
const [west, south, east, north] = bbox.split(',').map(Number);
const bounds = new mapboxgl.LngLatBounds([west, south], [east, north]);
const center = bounds.getCenter();

initialCenter = [center.lng, center.lat];

// Use the slice.defaultZoomLevel if provided (e.g., 8), otherwise default to 6
initialZoom = slice.defaultZoomLevel ?? 6;
}
Expand All @@ -457,8 +477,12 @@ export const ProjectsMap: React.FC<ProjectsMapProps> = ({
map.current.addControl(new mapboxgl.NavigationControl(), 'top-right');

map.current.on('load', () => {
if(map.current?.getLayer('road-number-shield')) {
map.current?.setLayoutProperty('road-number-shield', 'visibility', 'none');
if (map.current?.getLayer('road-number-shield')) {
map.current?.setLayoutProperty(
'road-number-shield',
'visibility',
'none'
);
}
setIsMapReady(true);
});
Expand All @@ -476,7 +500,6 @@ export const ProjectsMap: React.FC<ProjectsMapProps> = ({
slice.minZoomLevel,
]);


useEffect(() => {
if (!map.current || !isMapReady) return;
const currentMap = map.current;
Expand Down Expand Up @@ -531,19 +554,20 @@ export const ProjectsMap: React.FC<ProjectsMapProps> = ({
duration: 1500,
});

// Update bbox for this location
const buffer = 1;
const bbox = `${userLoc.lon - buffer},${userLoc.lat - buffer},${
userLoc.lon + buffer
},${userLoc.lat + buffer}`;
initialBboxRef.current = bbox;
fetchProjectsData(bbox);
// Update bbox for this location
const buffer = 1;
const bbox = `${userLoc.lon - buffer},${userLoc.lat - buffer},${
userLoc.lon + buffer
},${userLoc.lat + buffer}`;
initialBboxRef.current = bbox;
fetchProjectsData(bbox);
}
},
() => {
// Permission denied or error - already have fallback data loaded
// No need to re-fetch since we already loaded FALLBACK_BBOX data
}
},
() => {
// Permission denied or error - already have fallback data loaded
// No need to re-fetch since we already loaded FALLBACK_BBOX data
});
);
} else {
// Geolocation not supported or disabled - use fallback
initialBboxRef.current = FALLBACK_BBOX;
Expand All @@ -566,12 +590,12 @@ export const ProjectsMap: React.FC<ProjectsMapProps> = ({
return (
<>
<Global styles={mapboxStyle} />
<Box
height={embeddedHeight}
ref={mapContainer}
borderRadius="xl"
overflow="hidden"
/>
<Box
height={embeddedHeight}
ref={mapContainer}
borderRadius="xl"
overflow="hidden"
/>
</>
);
}
Expand Down Expand Up @@ -601,13 +625,13 @@ export const ProjectsMap: React.FC<ProjectsMapProps> = ({
<Box height="16" />
</>
)}
<Box
height="xl"
ref={mapContainer}
borderRadius="xl"
overflow="hidden"
boxShadow={['md', null, null, 'none']}
/>
<Box
height="xl"
ref={mapContainer}
borderRadius="xl"
overflow="hidden"
boxShadow={['md', null, null, 'none']}
/>
</Wrapper>
</DefaultSectionContainer>
);
Expand Down
11 changes: 10 additions & 1 deletion src/slices/TextWithCard/TextWithCard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import TextWithCard from '.';
import { TextWithCardProps } from './TextWithCard';
import { strapiProjectMock } from '../../test/strapiMocks/strapiProject';
import portfolioProjectMock from '../../test/mocks/portfolioProjectMock';
import getProjectTranslation from '../../utils/getProjectTranslation';

const defaultProps: TextWithCardProps = {
projects: [],
Expand Down Expand Up @@ -83,7 +84,15 @@ describe('The TextWithCard component', () => {
slice: { ...defaultProps.slice, project: { data: strapiProjectMock } },
});

expect(screen.getByText(portfolioProjectMock.title)).toBeInTheDocument();
expect(
screen.getByText(
getProjectTranslation(
portfolioProjectMock.nameTranslations,
'en',
portfolioProjectMock.title
)
)
).toBeInTheDocument();
});

const cardPositions = ['left', 'right'];
Expand Down
12 changes: 12 additions & 0 deletions src/test/integrationMocks/fpmProjectMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ import FPMProject, { CreditAvailability } from '../../models/fpm/FPMProject';
const fpmProjectMock: FPMProject = {
id: '1',
title: 'Project 1',
nameTranslations: [
{
id: 'name-translation-1',
language: 'en',
value: 'Project 1 Display Name English',
},
{
id: 'name-translation-2',
language: 'de',
value: 'Project 1 Display Name German',
},
],
geom: {
type: 'Point',
coordinates: [10.036542145100883, 47.42636837845707],
Expand Down
13 changes: 13 additions & 0 deletions src/utils/getProjectTranslation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { FPMProjectNameTranslation } from '../models/fpm/FPMProject';

const getProjectTranslation = (
nameTranslations: FPMProjectNameTranslation[] | undefined,
locale: string,
fallback: string
): string => {
if (!nameTranslations || nameTranslations.length === 0) return fallback;
const match = nameTranslations.find((t) => t.language === locale);
return match?.value || fallback;
};

export default getProjectTranslation;
Loading