Skip to content
Open
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
55 changes: 55 additions & 0 deletions frontend-next-migration/src/entities/Prg/api/mappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { PrgPage, PrgPageData, PrgBoardMemberDirectus, PrgDocument } from '../types/types';
import { envHelper } from '@/shared/const/envHelper';

/**
* Maps the PRG page data to a more structured format.
* @param prgPage The PRG page data.
* @param prgDocuments The PRG documents.
* @param prgBoardMembers The PRG board members.
* @param lng The language.
* @returns The mapped PRG page data.
*/
export const mapPrgPageData = (
prgPage?: PrgPage,
prgDocuments?: PrgDocument[],
prgBoardMembers?: PrgBoardMemberDirectus[],
lng: 'fi' | 'en' = 'en',
): PrgPageData => {
const documentsMap: Record<string, string | null> = {
action_plan: null,
activity_report: null,
bylaws: null,
};

prgDocuments?.forEach((doc) => {
if (doc.key && documentsMap.hasOwnProperty(doc.key)) {
documentsMap[doc.key] = doc.url;
}
});

const boardMembers = prgBoardMembers?.map((member) => ({
name: member.name,
jobTitle: member[`job_title_${lng}`] || member.job_title_en || '',
profession: member[`profession_${lng}`] || member.profession_en || '',
imageUrl: member.image ? normalizedDirectusHost() + '/assets/' + member.image : undefined,
}));

return {
heroImageUrl: prgPage?.hero_image
? normalizedDirectusHost() + '/assets/' + prgPage.hero_image
: undefined,
documents: {
actionPlanUrl: documentsMap.action_plan || undefined,
activityReportUrl: documentsMap.activity_report || undefined,
bylawsUrl: documentsMap.bylaws || undefined,
},
registryInfo: prgPage?.[`registry_info_${lng}`] || prgPage?.registry_info_en || '',
boardMembers: boardMembers && boardMembers.length > 0 ? boardMembers : undefined,
};
};

// remove trailing slash from directusHost if present
const normalizedDirectusHost = () => {
const directusHost = envHelper.directusHost || '';
return directusHost.endsWith('/') ? directusHost.slice(0, -1) : directusHost;
};
81 changes: 81 additions & 0 deletions frontend-next-migration/src/entities/Prg/api/prgPageApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { directusApi } from '@/shared/api';
import { envHelper } from '@/shared/const/envHelper';
import { createDirectus, rest, readItems, readSingleton } from '@directus/sdk';
import { PrgPage, PrgDocument, PrgBoardMemberDirectus, PrgDirectusSchema } from '../types/types';
import { FetchBaseQueryError } from '@reduxjs/toolkit/dist/query/react';

const directusBaseUrl = envHelper.directusHost || '';
const client = createDirectus<PrgDirectusSchema>(directusBaseUrl).with(rest());

const errorHandler = (error: unknown): FetchBaseQueryError => {
const status =
typeof error === 'object' &&
error !== null &&
'status' in error &&
typeof error.status === 'number'
? error.status
: 500;
const message = error instanceof Error ? error.message : 'Data fetch failed';
return {
status,
data: { message: message },
};
};

/**
* Fetches the PRG page data from Directus, including the PRG page hero image and registroy info, document links, and board members.
*
* @returns An object containing the PRG page data, loading state, and any error that occurred during the fetch.
*/
const prgPageApi = directusApi.injectEndpoints({
endpoints: (builder) => ({
getPrgPage: builder.query({
queryFn: async (
_arg: void,
): Promise<{ data: PrgPage } | { error: FetchBaseQueryError }> => {
try {
const prgPage = await client.request(
readSingleton('prg_page', { fields: ['*'] }),
);
return { data: prgPage };
} catch (error: unknown) {
return { error: errorHandler(error) };
}
},
}),
getPrgDocuments: builder.query<PrgDocument[], void>({
queryFn: async (): Promise<
{ data: PrgDocument[] } | { error: FetchBaseQueryError }
> => {
try {
const prgDocuments = await client.request(
readItems('prg_documents', { fields: ['*'] }),
);
return { data: prgDocuments };
} catch (error) {
return { error: errorHandler(error) };
}
},
}),
getPrgBoardMembers: builder.query<PrgBoardMemberDirectus[], void>({
queryFn: async (): Promise<
{ data: PrgBoardMemberDirectus[] } | { error: FetchBaseQueryError }
> => {
try {
const prgBoardMembers = await client.request(
readItems('prg_board_members', {
fields: ['*'],
sort: ['sort', 'id'],
}),
);
return { data: prgBoardMembers };
} catch (error) {
return { error: errorHandler(error) };
}
},
}),
}),
});

export const { useGetPrgPageQuery, useGetPrgDocumentsQuery, useGetPrgBoardMembersQuery } =
prgPageApi;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
useGetPrgPageQuery,
useGetPrgDocumentsQuery,
useGetPrgBoardMembersQuery,
} from './prgPageApi';
import { mapPrgPageData } from './mappers';
import { useMemo } from 'react';

/**
* Custom hook to fetch and combine PRG page data, documents, and board members.
*
* @param lng - The language code ('fi' or 'en') for localization. Defaults to 'en'.
* @returns An object containing the combined PRG page data, loading state, and any error that occurred during the fetch.
*/
export const useGetPrgPageData = (lng: 'fi' | 'en' = 'en') => {
const {
data: prgPageData,
error: prgPageError,
isLoading: prgPageIsLoading,
} = useGetPrgPageQuery();
const {
data: prgDocumentsData,
error: prgDocumentsError,
isLoading: prgDocumentsIsLoading,
} = useGetPrgDocumentsQuery();
const {
data: prgBoardMembersData,
error: prgBoardMembersError,
isLoading: prgBoardMembersIsLoading,
} = useGetPrgBoardMembersQuery();

const fullPrgPageData = useMemo(() => {
return mapPrgPageData(prgPageData, prgDocumentsData, prgBoardMembersData, lng);
}, [prgPageData, prgDocumentsData, prgBoardMembersData, lng]);

return {
fullPrgPageData,
loading: { prgPageIsLoading, prgDocumentsIsLoading, prgBoardMembersIsLoading },
error: { prgPageError, prgDocumentsError, prgBoardMembersError },
};
};
2 changes: 2 additions & 0 deletions frontend-next-migration/src/entities/Prg/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { useGetPrgPageData } from './api/useGetPrgPageData';
export type { PrgPageData, PrgBoardMember } from './types/types';
57 changes: 57 additions & 0 deletions frontend-next-migration/src/entities/Prg/types/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Directus data
export interface PrgPage {
id: number;
date_created: string;
date_updated: string | null;
status?: 'draft' | 'published' | 'archived' | null;
hero_image: string | null;
registry_info_fi: string | null;
registry_info_en: string | null;
}

export interface PrgDocument {
key: string | null; // e.g. action_plan, activity_report, bylaws
date_created: string;
date_updated: string | null;
status?: 'draft' | 'published' | 'archived' | null;
url: string | null;
}

export interface PrgBoardMemberDirectus {
id: number;
date_created: string;
date_updated: string | null;
status?: 'draft' | 'published' | 'archived' | null;
name: string;
job_title_fi: string | null;
job_title_en: string | null;
profession_fi: string | null;
profession_en: string | null;
image: string | null;
sort: number | null;
}

export interface PrgDirectusSchema {
prg_page: PrgPage;
prg_documents: PrgDocument[];
prg_board_members: PrgBoardMemberDirectus[];
}

// Mapped data, localized
export interface PrgBoardMember {
name: string;
jobTitle: string;
profession: string;
imageUrl?: string;
}

export interface PrgPageData {
heroImageUrl?: string;
documents: {
actionPlanUrl?: string;
activityReportUrl?: string;
bylawsUrl?: string;
};
registryInfo?: string;
boardMembers?: PrgBoardMember[];
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { render, screen } from '@testing-library/react';
import PRGPage from './PRGPage';
import { useParams } from 'next/navigation';
import { useGetPrgPageData } from '@/entities/Prg';

jest.mock('@/shared/i18n', () => ({
useClientTranslation: jest.fn(),
Expand All @@ -21,6 +23,14 @@ jest.mock('@/shared/ui/CustomSwitch', () => ({
CustomSwitchItems: { ToggleItem: 'ToggleItem' },
}));

jest.mock('next/navigation', () => ({
useParams: jest.fn(),
}));

jest.mock('@/entities/Prg', () => ({
useGetPrgPageData: jest.fn(),
}));

jest.mock('@/shared/ui/AppLink/AppLink', () => ({
AppLink: ({
children,
Expand Down Expand Up @@ -55,6 +65,43 @@ import useSizes from '@/shared/lib/hooks/useSizes';

describe('PRGPage', () => {
beforeEach(() => {
(useParams as jest.Mock).mockReturnValue({ lng: 'en' });

(useGetPrgPageData as jest.Mock).mockReturnValue({
fullPrgPageData: {
heroImageUrl: undefined,
documents: {
actionPlanUrl: 'https://example.com/action-plan.pdf',
activityReportUrl: 'https://example.com/activity-report.pdf',
bylawsUrl: 'https://example.com/bylaws.pdf',
},
registryInfo: 'Test registry information',
boardMembers: [
{
name: 'Helena Pavloff-Pelkonen',
jobTitle: 'Chairperson',
profession: 'Profession',
},
{
name: 'Esa Pavloff-Pelkonen',
jobTitle: 'Member',
profession: 'Profession',
},
{
name: 'Emmi-Irina Pavloff',
jobTitle: 'Member',
profession: 'Profession',
},
],
},
loading: {
prgPageIsLoading: false,
prgDocumentsIsLoading: false,
prgBoardMembersIsLoading: false,
},
error: { prgPageError: null, prgDocumentsError: null, prgBoardMembersError: null },
});

(useClientTranslation as jest.Mock).mockReturnValue({
t: (key: string) => key,
});
Expand Down
Loading
Loading