import React, { useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; import { Box, Button } from "@mui/material"; import { api } from "../api"; import { useI18n } from "../i18n/I18nProvider"; import { PageHeader } from "../layout/PageLayout"; import ViewStateNotice from "../components/ViewStateNotice"; const A4_WIDTH_PX = (210 / 25.4) * 96; const A4_HEIGHT_PX = (297 / 25.4) * 96; type PublicCvRead = { slug?: string; html?: string; name?: string; error?: "missing" | "transient"; loading: boolean; height: number; }; // Anonymous read-only public CV at /cv/:slug. Renders the server-produced HTML in a sandboxed // iframe. noindex is enforced server-side (X-Robots-Tag) and reinforced with a meta tag here. export default function PublicCvPage() { const { slug } = useParams(); const { t } = useI18n(); const pdfUrl = `${api.defaults.baseURL}/public-cv/${encodeURIComponent(slug ?? "")}/pdf`; const [read, setRead] = useState({ slug, loading: true, height: A4_HEIGHT_PX }); const [retry, setRetry] = useState(0); const pending = useRef(false); const headingRef = useRef(null); const previousTitle = useRef(""); const ownedTitle = useRef(null); const [frameScale, setFrameScale] = useState(1); // Hide the old document on the first render of a new route, before its effect runs. const current = read.slug === slug ? read : null; const loading = !current || current.loading; const frameHeight = current?.height ?? A4_HEIGHT_PX; const title = t("publicCvTitle"); const unavailable = current?.error === "missing"; const errorTitle = t(unavailable ? "publicCvUnavailableTitle" : "publicCvLoadFailedTitle"); useEffect(() => { const originalTitle = document.title; previousTitle.current = originalTitle; const meta = document.createElement("meta"); meta.name = "robots"; meta.content = "noindex, nofollow"; document.head.appendChild(meta); return () => { meta.remove(); if (document.title === ownedTitle.current) document.title = originalTitle; }; }, []); useEffect(() => { let alive = true; pending.current = true; setRead({ slug, loading: true, height: A4_HEIGHT_PX }); api.get<{ html: string; name: string }>(`/public-cv/${encodeURIComponent(slug ?? "")}`) .then((r) => { if (alive) setRead({ slug, html: r.data.html, name: r.data.name, loading: false, height: A4_HEIGHT_PX }); }) .catch((err) => { if (!alive) return; // 404 = unknown slug or the owner made it private again. Anything else is a transient error. setRead({ slug, error: err?.response?.status === 404 ? "missing" : "transient", loading: false, height: A4_HEIGHT_PX }); }) .finally(() => { if (alive) pending.current = false; }); return () => { alive = false; }; }, [slug, retry]); useEffect(() => { const nextTitle = current?.name ? `${current.name} — CV` : previousTitle.current; document.title = nextTitle; ownedTitle.current = nextTitle; }, [current?.name]); useEffect(() => { const updateScale = () => setFrameScale(Math.min(1, Math.max(0.1, (window.innerWidth - 32) / A4_WIDTH_PX))); updateScale(); window.addEventListener("resize", updateScale); return () => window.removeEventListener("resize", updateScale); }, []); return ( {t("publicCvDownloadPdf")} : undefined} /> { if (pending.current || loading) return; pending.current = true; headingRef.current?.focus(); setRead({ slug, loading: true, height: A4_HEIGHT_PX }); setRetry((value) => value + 1); }} /> {current?.html !== undefined &&