diff --git a/Dockerfile b/Dockerfile index f8e39d9..654869c 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 diff --git a/playwright-tests/tests/home.page.spec.ts b/playwright-tests/tests/home.page.spec.ts index a624bc1..15010c6 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 70cbb15..19a0d1d 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 = ({ image, title, @@ -28,6 +50,7 @@ export const ResourcesCard: React.FC = ({ buttonIcon, }) => { const theme = useTheme(); + const imageSrc = normalizeGoogleDriveImageUrl(image); return ( = ({ @@ -67,6 +90,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 aea508d..5f0e441 100644 --- a/src/components/__tests__/ResourcesCard.test.tsx +++ b/src/components/__tests__/ResourcesCard.test.tsx @@ -10,17 +10,52 @@ 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'); }); + + it('normalizes Google Drive file preview URLs for the image source', () => { + render( + , + ); + + 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( + , + ); + + const img = screen.getByRole('img'); + expect(img).toHaveAttribute( + 'src', + 'https://drive.google.com/thumbnail?id=1AQKAp76gjk1kMX7pB7pnY5G5TMnxmDVk&sz=w1000', + ); + }); }); diff --git a/src/lib/api.ts b/src/lib/api.ts index 50317f8..75ce493 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 ed6cd74..ae1d60d 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 08babfb..a3667e2 100644 --- a/src/pages/mentorship/resources.tsx +++ b/src/pages/mentorship/resources.tsx @@ -7,17 +7,24 @@ 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 = { data: MentorshipResourcesResponse; 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 as MentorshipResourcesResponse; + const { heroSection, section, resourcesSection } = page; return ( <> @@ -27,7 +34,7 @@ const MentorshipResourcesPage: React.FC = () => { sx={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }} > - + <Title title={heroSection.title} /> <Box sx={{ @@ -44,7 +51,7 @@ const MentorshipResourcesPage: React.FC = () => { lineHeight: 1.5, }} > - {heroDescription} + {section.description} </Typography> </Box> @@ -57,22 +64,27 @@ const MentorshipResourcesPage: React.FC = () => { }} > <Grid container spacing={4}> - {resources.map((res, index) => ( - <Grid item xs={12} sm={6} md={6} lg={4} key={index}> - <ResourcesCard - image={res.image} - title={res.title} - description={res.description} - buttonText={res.buttonText} - link={res.link} - 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> - <Footer {...footerData} /> + <Footer {...footer} /> </Box> </> ); diff --git a/src/utils/types.ts b/src/utils/types.ts index 3db42df..7dc0ff7 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -409,8 +409,9 @@ export type LongTermTimeLineResponse = { export type ResourceItem = { title: string; + description?: string; link: Link; - image: Image; + image: string | Image; }; export type ResourcesSection = {