diff --git a/frontends/main/src/app-pages/CertificatePage/CertificatePage.test.tsx b/frontends/main/src/app-pages/CertificatePage/CertificatePage.test.tsx index 4015429088..37b61afb3a 100644 --- a/frontends/main/src/app-pages/CertificatePage/CertificatePage.test.tsx +++ b/frontends/main/src/app-pages/CertificatePage/CertificatePage.test.tsx @@ -1,9 +1,15 @@ import React from "react" import moment from "moment" import { factories, setMockResponse } from "api/test-utils" -import { screen, renderWithProviders } from "@/test-utils" +import { screen, renderWithProviders, user } from "@/test-utils" import CertificatePage, { CertificateType } from "./CertificatePage" +import SharePopover from "./SharePopover" import { urls } from "api/mitxonline-test-utils" +import { + FACEBOOK_SHARE_BASE_URL, + TWITTER_SHARE_BASE_URL, + LINKEDIN_SHARE_BASE_URL, +} from "@/common/urls" describe("CertificatePage", () => { it("renders a course certificate", async () => { @@ -18,6 +24,7 @@ describe("CertificatePage", () => { , ) @@ -80,6 +87,7 @@ describe("CertificatePage", () => { , ) @@ -99,3 +107,70 @@ describe("CertificatePage", () => { await screen.findAllByText(certificate.uuid) }) }) + +describe("CertificatePage - SharePopover", () => { + const mockProps = { + open: true, + title: "Test Certificate", + anchorEl: document.createElement("div"), + onClose: jest.fn(), + pageUrl: "https://example.com/certificate/123", + } + + const mockWriteText = jest.fn() + Object.assign(navigator, { + clipboard: { + writeText: mockWriteText, + }, + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + it("renders the SharePopover with correct content", () => { + renderWithProviders() + + expect(screen.getByText("Share on social")).toBeInTheDocument() + expect(screen.getByText("Share a link")).toBeInTheDocument() + expect(screen.getByDisplayValue(mockProps.pageUrl)).toBeInTheDocument() + expect(screen.getByText("Copy Link")).toBeInTheDocument() + }) + + it("renders social media share links with correct URLs", () => { + renderWithProviders() + + const facebookHref = `${FACEBOOK_SHARE_BASE_URL}?u=${encodeURIComponent(mockProps.pageUrl)}` + const twitterHref = `${TWITTER_SHARE_BASE_URL}?text=${encodeURIComponent(mockProps.title)}&url=${encodeURIComponent(mockProps.pageUrl)}` + const linkedinHref = `${LINKEDIN_SHARE_BASE_URL}?url=${encodeURIComponent(mockProps.pageUrl)}` + + const facebookLink = screen.getByRole("link", { name: "Share on Facebook" }) + const twitterLink = screen.getByRole("link", { name: "Share on Twitter" }) + const linkedinLink = screen.getByRole("link", { name: "Share on LinkedIn" }) + + expect(facebookLink).toHaveAttribute("href", facebookHref) + expect(twitterLink).toHaveAttribute("href", twitterHref) + expect(linkedinLink).toHaveAttribute("href", linkedinHref) + + expect(facebookLink).toHaveAttribute("target", "_blank") + expect(twitterLink).toHaveAttribute("target", "_blank") + expect(linkedinLink).toHaveAttribute("target", "_blank") + }) + + it("copies link to clipboard when copy button is clicked", async () => { + renderWithProviders() + + const copyButton = screen.getByRole("button", { name: "Copy Link" }) + await user.click(copyButton) + + expect(mockWriteText).toHaveBeenCalledWith(mockProps.pageUrl) + screen.getByRole("button", { name: "Copied!" }) + }) + + it("does not render when open is false", () => { + renderWithProviders() + + expect(screen.queryByText("Share on social")).not.toBeInTheDocument() + expect(screen.queryByText("Share a link")).not.toBeInTheDocument() + }) +}) diff --git a/frontends/main/src/app-pages/CertificatePage/CertificatePage.tsx b/frontends/main/src/app-pages/CertificatePage/CertificatePage.tsx index a9a2ae60bf..f8317c5b7b 100644 --- a/frontends/main/src/app-pages/CertificatePage/CertificatePage.tsx +++ b/frontends/main/src/app-pages/CertificatePage/CertificatePage.tsx @@ -1,6 +1,6 @@ "use client" -import React, { useRef, useEffect, useCallback } from "react" +import React, { useRef, useEffect, useCallback, useState } from "react" import { notFound } from "next/navigation" import Image from "next/image" import { Link, Typography, styled } from "ol-components" @@ -12,12 +12,13 @@ import OpenLearningLogo from "@/public/images/mit-open-learning-logo.svg" import CertificateBadgeDesktop from "@/public/images/certificate-badge-desktop.svg" import CertificateBadgeMobile from "@/public/images/certificate-badge-mobile.svg" import { formatDate, NoSSR } from "ol-utilities" -import { RiDownloadLine, RiPrinterLine } from "@remixicon/react" +import { RiDownloadLine, RiPrinterLine, RiShareLine } from "@remixicon/react" import type { V2ProgramCertificate, V2CourseRunCertificate, SignatoryItem, } from "@mitodl/mitxonline-api-axios/v2" +import SharePopover from "./SharePopover" const Page = styled.div(({ theme }) => ({ backgroundImage: `url(${backgroundImage.src})`, @@ -57,12 +58,16 @@ const Title = styled(Typography)(({ theme }) => ({ }, })) -const Buttons = styled.div({ +const Buttons = styled.div(({ theme }) => ({ display: "flex", gap: "12px", justifyContent: "center", - marginBottom: "50px", -}) + width: "fit-content", + margin: "0 auto 50px auto", + [theme.breakpoints.down("md")]: { + margin: "0 auto 32px auto", + }, +})) const Outer = styled.div(({ theme }) => ({ maxWidth: "1306px", @@ -640,7 +645,8 @@ export enum CertificateType { const CertificatePage: React.FC<{ certificateType: CertificateType uuid: string -}> = ({ certificateType, uuid }) => { + pageUrl: string +}> = ({ certificateType, uuid, pageUrl }) => { const { data: courseCertificateData, isLoading: isCourseLoading, @@ -694,6 +700,9 @@ const CertificatePage: React.FC<{ } }, [print]) + const [shareOpen, setShareOpen] = useState(false) + const shareButtonRef = useRef(null) + if (isCourseLoading || isProgramLoading) { return } @@ -709,7 +718,7 @@ const CertificatePage: React.FC<{ const url = window.URL.createObjectURL(blob) const a = document.createElement("a") a.href = url - a.download = `${title} Certificate - MIT Open Learning.pdf` + a.download = `${title} Certificate issued by MIT Open Learning.pdf` document.body.appendChild(a) a.click() document.body.removeChild(a) @@ -728,12 +737,19 @@ const CertificatePage: React.FC<{ return ( + setShareOpen(false)} + pageUrl={pageUrl} + /> <Typography variant="h3"> <strong>{title}</strong> {displayType} </Typography> - + +