From 1e0596179847247f5e6d56109ecadd0352a55fb3 Mon Sep 17 00:00:00 2001 From: Badmus Damola Khadijah Date: Mon, 1 Jun 2026 20:10:12 +0100 Subject: [PATCH 1/3] Revert "Revert "Fix Mentorship Resource Link" (#279)" This reverts commit 55a2a59905ea6083397dfdfd5f874772e61660ba. --- playwright-tests/tests/home.page.spec.ts | 2 +- src/components/ResourcesCard.tsx | 2 + .../__tests__/ResourcesCard.test.tsx | 11 ++- src/lib/api.ts | 2 + src/lib/responses/mentorshipResources.json | 78 ++++++++++++------- src/pages/mentorship/resources.tsx | 30 ++++--- src/utils/types.ts | 1 + 7 files changed, 85 insertions(+), 41 deletions(-) diff --git a/playwright-tests/tests/home.page.spec.ts b/playwright-tests/tests/home.page.spec.ts index a624bc1a..15010c68 100644 --- a/playwright-tests/tests/home.page.spec.ts +++ b/playwright-tests/tests/home.page.spec.ts @@ -90,7 +90,7 @@ test.describe('Validate Home Page', () => { await basePage.verifyURL('/mentorship/mentor-registration'); await basePage.verifyPageContainsText( - 'Welcome to the MentorRegistrationPage', + 'WCC: Registration Form for Mentors', ); }); diff --git a/src/components/ResourcesCard.tsx b/src/components/ResourcesCard.tsx index 70cbb153..6d1d21f0 100644 --- a/src/components/ResourcesCard.tsx +++ b/src/components/ResourcesCard.tsx @@ -67,6 +67,8 @@ export const ResourcesCard: React.FC = ({ variant="contained" color="primary" href={link} + target="_blank" + rel="noopener noreferrer" sx={{ textTransform: 'none', borderRadius: 2, diff --git a/src/components/__tests__/ResourcesCard.test.tsx b/src/components/__tests__/ResourcesCard.test.tsx index aea508d5..36bd397a 100644 --- a/src/components/__tests__/ResourcesCard.test.tsx +++ b/src/components/__tests__/ResourcesCard.test.tsx @@ -10,15 +10,20 @@ describe('ResourcesCard', () => { title: 'Test Card', description: 'This is a test description', buttonText: 'Click Me', - link: '#', + link: 'https://drive.google.com/file/d/1xPbW8BlQoLXkuAJ7m0RuvOV02Opyr445', }; - it('renders the title, description, button, and image', () => { + it('renders the title, description, button, link, and image', () => { render(); expect(screen.getByText('Test Card')).toBeInTheDocument(); expect(screen.getByText('This is a test description')).toBeInTheDocument(); - expect(screen.getByRole('link', { name: 'Click Me' })).toBeInTheDocument(); + const link = screen.getByRole('link', { name: 'Click Me' }); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute( + 'href', + 'https://drive.google.com/file/d/1xPbW8BlQoLXkuAJ7m0RuvOV02Opyr445', + ); const img = screen.getByRole('img'); expect(img).toHaveAttribute('src', '/test.jpg'); diff --git a/src/lib/api.ts b/src/lib/api.ts index 50317f86..75ce493b 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -11,6 +11,7 @@ import mentors from './responses/mentors.json'; import mentorShipPage from './responses/mentorship.json'; import mentorShipCodeOfConduct from './responses/mentorshipCodeOfConduct.json'; import mentorshipFaqPageData from './responses/mentorshipFaqPage.json'; +import mentorshipResourcesPage from './responses/mentorshipResources.json'; import studyGroupsPage from './responses/mentorshipStudyGroupsPage.json'; const apiBaseUrl = process.env.API_BASE_URL; @@ -67,6 +68,7 @@ const pageData = { 'mentorship/code-of-conduct': mentorShipCodeOfConduct, team: aboutUsTeam, 'mentorship/faq': mentorshipFaqPageData, + 'mentorship/resources': mentorshipResourcesPage, 'mentorship/study-groups': studyGroupsPage, }; diff --git a/src/lib/responses/mentorshipResources.json b/src/lib/responses/mentorshipResources.json index ed6cd747..ae1d60d4 100644 --- a/src/lib/responses/mentorshipResources.json +++ b/src/lib/responses/mentorshipResources.json @@ -1,28 +1,54 @@ { - "heroTitle": "Mentorship Resources", - "heroDescription": "Whether you’re a mentee looking to navigate your journey, a mentor aiming to provide the best guidance, or a seasoned mentor seeking quick tips, we have the tools you need. Explore our guides for insightful mentorship advice and strategies.", - "resources": [ - { - "image": "/mentee_guideMedia.jpg", - "title": "Mentee’s Guide", - "description": "", - "buttonText": "View Guide", - "link": "#" - }, - { - "image": "/mentor_pocketbookMedia.jpg", - "title": "Mentor’s Pocketbook", - "description": "", - "buttonText": "View Pocketbook", - "link": "#" - }, - { - "image": "/mentor_guideeMedia.jpg", - "title": "Mentor’s Guide", - "description": "", - "buttonText": "View Guide", - "link": "#" - } - ], - "footer": "footerData.json content" + "id": "page:MENTORSHIP_RESOURCES", + "heroSection": { + "title": "Mentorship Resources" + }, + "section": { + "description": "Whether you’re a mentee looking to navigate your journey, a mentor aiming to provide the best guidance, or a seasoned mentor seeking quick tips, we have the tools you need. Explore our guides for insightful mentorship advice and strategies." + }, + "resourcesSection": { + "title": "Available Resources", + "description": "Download and explore our curated mentorship materials", + "items": [ + { + "title": "Mentees Guide", + "description": "A practical guide for mentees to get the most out of mentorship.", + "link": { + "label": "Download PDF", + "uri": "https://drive.google.com/file/d/1xPbW8BlQoLXkuAJ7m0RuvOV02Opyr445" + }, + "image": { + "path": "/mentee_guideMedia.jpg", + "alt": "woman working on a laptop", + "type": "desktop" + } + }, + { + "title": "Mentor's Pocketbook", + "description": "Quick mentor reference notes and useful mentorship tips.", + "link": { + "label": "Download PDF", + "uri": "https://drive.google.com/file/d/1rgoOTqqG4Gu6e4tw45efFspDnoYRgYd9" + }, + "image": { + "path": "/mentor_pocketbookMedia.jpg", + "alt": "Image decorative", + "type": "desktop" + } + }, + { + "title": "Mentor's Guide", + "description": "Guidance and best practices for mentors to support their mentees effectively.", + "link": { + "label": "Download PDF", + "uri": "https://drive.google.com/file/d/1wTkCSG95BVg-0XX4r7FnFiV24_SXlbM_" + }, + "image": { + "path": "/mentor_guideeMedia.jpg", + "alt": "two women sitting by a desk and talking together", + "type": "desktop" + } + } + ] + } } diff --git a/src/pages/mentorship/resources.tsx b/src/pages/mentorship/resources.tsx index 08babfb7..e8eeabbb 100644 --- a/src/pages/mentorship/resources.tsx +++ b/src/pages/mentorship/resources.tsx @@ -7,7 +7,6 @@ import { Title, ResourcesCard, Footer, BreadCrumbsDynamic } from '@components'; import { useIsMobile } from '@utils/theme-utils'; import { FooterResponse, MentorshipResourcesResponse } from '@utils/types'; import { fetchData } from 'lib/api'; -import footerData from 'lib/responses/footer.json'; import pageData from 'lib/responses/mentorshipResources.json'; type CombinedResponse = { @@ -15,9 +14,18 @@ type CombinedResponse = { footer: FooterResponse; }; -const MentorshipResourcesPage: React.FC = () => { +type MentorshipResourcesPageProps = { + data?: MentorshipResourcesResponse; + footer: FooterResponse; +}; + +const MentorshipResourcesPage: React.FC = ({ + data, + footer, +}) => { const isMobile = useIsMobile(); - const { heroTitle, heroDescription, resources } = pageData; + const page = (data ?? pageData) as MentorshipResourcesResponse; + const { heroSection, section, resourcesSection } = page; return ( <> @@ -27,7 +35,7 @@ const MentorshipResourcesPage: React.FC = () => { sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }} > - + <Title title={heroSection.title} /> <Box sx={{ @@ -44,7 +52,7 @@ const MentorshipResourcesPage: React.FC = () => { lineHeight: 1.5, }} > - {heroDescription} + {section.description} </Typography> </Box> @@ -57,14 +65,14 @@ const MentorshipResourcesPage: React.FC = () => { }} > <Grid container spacing={4}> - {resources.map((res, index) => ( + {resourcesSection.items.map((res, index) => ( <Grid item xs={12} sm={6} md={6} lg={4} key={index}> <ResourcesCard - image={res.image} + image={res.image.path} title={res.title} - description={res.description} - buttonText={res.buttonText} - link={res.link} + description={res.description ?? ''} + buttonText={res.link.label} + link={res.link.uri} buttonIcon={<OpenInNewIcon />} /> </Grid> @@ -72,7 +80,7 @@ const MentorshipResourcesPage: React.FC = () => { </Grid> </Box> </Box> - <Footer {...footerData} /> + <Footer {...footer} /> </Box> </> ); diff --git a/src/utils/types.ts b/src/utils/types.ts index 3db42df8..d3c37526 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -409,6 +409,7 @@ export type LongTermTimeLineResponse = { export type ResourceItem = { title: string; + description?: string; link: Link; image: Image; }; From 907ef85bf79b93f143941bbdc11723c4d75b48b8 Mon Sep 17 00:00:00 2001 From: Badmus Damola Khadijah <Damolabadmus014@gmail.com> Date: Mon, 1 Jun 2026 21:00:25 +0100 Subject: [PATCH 2/3] fix: normalize mentorship resource image urls --- src/components/ResourcesCard.tsx | 25 ++++++++++++++- .../__tests__/ResourcesCard.test.tsx | 30 +++++++++++++++++ src/pages/mentorship/resources.tsx | 32 +++++++++++-------- src/utils/types.ts | 2 +- 4 files changed, 73 insertions(+), 16 deletions(-) diff --git a/src/components/ResourcesCard.tsx b/src/components/ResourcesCard.tsx index 6d1d21f0..19a0d1d1 100644 --- a/src/components/ResourcesCard.tsx +++ b/src/components/ResourcesCard.tsx @@ -19,6 +19,28 @@ interface ResourcesCardProps { buttonIcon?: React.ReactNode; } +const normalizeGoogleDriveImageUrl = (imageUrl: string) => { + try { + const url = new URL(imageUrl); + + if (url.hostname !== 'drive.google.com') { + return imageUrl; + } + + const fileId = + url.pathname.match(/\/file\/d\/([^/]+)/)?.[1] ?? + url.searchParams.get('id'); + + if (!fileId) { + return imageUrl; + } + + return `https://drive.google.com/thumbnail?id=${fileId}&sz=w1000`; + } catch { + return imageUrl; + } +}; + export const ResourcesCard: React.FC<ResourcesCardProps> = ({ image, title, @@ -28,6 +50,7 @@ export const ResourcesCard: React.FC<ResourcesCardProps> = ({ buttonIcon, }) => { const theme = useTheme(); + const imageSrc = normalizeGoogleDriveImageUrl(image); return ( <Card @@ -48,7 +71,7 @@ export const ResourcesCard: React.FC<ResourcesCardProps> = ({ <CardMedia component="img" height="180" - image={image} + image={imageSrc} alt={title} sx={{ objectFit: 'cover' }} /> diff --git a/src/components/__tests__/ResourcesCard.test.tsx b/src/components/__tests__/ResourcesCard.test.tsx index 36bd397a..5f0e4411 100644 --- a/src/components/__tests__/ResourcesCard.test.tsx +++ b/src/components/__tests__/ResourcesCard.test.tsx @@ -28,4 +28,34 @@ describe('ResourcesCard', () => { const img = screen.getByRole('img'); expect(img).toHaveAttribute('src', '/test.jpg'); }); + + it('normalizes Google Drive file preview URLs for the image source', () => { + render( + <ResourcesCard + {...props} + image="https://drive.google.com/file/d/1AQKAp76gjk1kMX7pB7pnY5G5TMnxmDVk" + />, + ); + + const img = screen.getByRole('img'); + expect(img).toHaveAttribute( + 'src', + 'https://drive.google.com/thumbnail?id=1AQKAp76gjk1kMX7pB7pnY5G5TMnxmDVk&sz=w1000', + ); + }); + + it('normalizes Google Drive download URLs for the image source', () => { + render( + <ResourcesCard + {...props} + image="https://drive.google.com/uc?id=1AQKAp76gjk1kMX7pB7pnY5G5TMnxmDVk&export=download" + />, + ); + + const img = screen.getByRole('img'); + expect(img).toHaveAttribute( + 'src', + 'https://drive.google.com/thumbnail?id=1AQKAp76gjk1kMX7pB7pnY5G5TMnxmDVk&sz=w1000', + ); + }); }); diff --git a/src/pages/mentorship/resources.tsx b/src/pages/mentorship/resources.tsx index e8eeabbb..a3667e27 100644 --- a/src/pages/mentorship/resources.tsx +++ b/src/pages/mentorship/resources.tsx @@ -7,7 +7,6 @@ import { Title, ResourcesCard, Footer, BreadCrumbsDynamic } from '@components'; import { useIsMobile } from '@utils/theme-utils'; import { FooterResponse, MentorshipResourcesResponse } from '@utils/types'; import { fetchData } from 'lib/api'; -import pageData from 'lib/responses/mentorshipResources.json'; type CombinedResponse = { data: MentorshipResourcesResponse; @@ -24,7 +23,7 @@ const MentorshipResourcesPage: React.FC<MentorshipResourcesPageProps> = ({ footer, }) => { const isMobile = useIsMobile(); - const page = (data ?? pageData) as MentorshipResourcesResponse; + const page = data as MentorshipResourcesResponse; const { heroSection, section, resourcesSection } = page; return ( @@ -65,18 +64,23 @@ const MentorshipResourcesPage: React.FC<MentorshipResourcesPageProps> = ({ }} > <Grid container spacing={4}> - {resourcesSection.items.map((res, index) => ( - <Grid item xs={12} sm={6} md={6} lg={4} key={index}> - <ResourcesCard - image={res.image.path} - title={res.title} - description={res.description ?? ''} - buttonText={res.link.label} - link={res.link.uri} - buttonIcon={<OpenInNewIcon />} - /> - </Grid> - ))} + {resourcesSection.items.map((res, index) => { + const image = + typeof res.image === 'string' ? res.image : res.image.path; + + return ( + <Grid item xs={12} sm={6} md={6} lg={4} key={index}> + <ResourcesCard + image={image} + title={res.title} + description={res.description ?? ''} + buttonText={res.link.label} + link={res.link.uri} + buttonIcon={<OpenInNewIcon />} + /> + </Grid> + ); + })} </Grid> </Box> </Box> diff --git a/src/utils/types.ts b/src/utils/types.ts index d3c37526..7dc0ff75 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -411,7 +411,7 @@ export type ResourceItem = { title: string; description?: string; link: Link; - image: Image; + image: string | Image; }; export type ResourcesSection = { From 9702fa72e5c7cdd92fa08cde91a2d4a8bd97054e Mon Sep 17 00:00:00 2001 From: Badmus Damola Khadijah <Damolabadmus014@gmail.com> Date: Mon, 1 Jun 2026 21:36:48 +0100 Subject: [PATCH 3/3] fix: pin pnpm in docker build --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f8e39d96..654869c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,8 @@ FROM mcr.microsoft.com/playwright:v1.57.0-noble AS base WORKDIR /app ENV CI=true -RUN npm install -g pnpm +ENV HUSKY=0 +RUN npm install -g pnpm@9.15.9 COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile @@ -20,4 +21,4 @@ CMD ["pnpm", "next", "start"] # ---- Playwright stage ---- FROM base AS playwright COPY playwright-tests ./playwright-tests -USER pwuser \ No newline at end of file +USER pwuser