288 lines
8.0 KiB
JavaScript
288 lines
8.0 KiB
JavaScript
import { useEffect, useState } from "react";
|
|
import { useParams } from "react-router-dom";
|
|
import { Box, Typography, CircularProgress } from "@mui/material";
|
|
import { MoodleApi } from "../../services/moodleApi";
|
|
const PUBLIC_LINKS_STORAGE_KEY = "listeCoursPublicLinks";
|
|
|
|
const CoursLecture = () => {
|
|
const { token } = useParams();
|
|
const [content, setContent] = useState(null);
|
|
const [title, setTitle] = useState("");
|
|
const [loading, setLoading] = useState(true);
|
|
const [tokenValid, setTokenValid] = useState(true);
|
|
|
|
// ✅ Masquer Header/Footer pendant l'affichage
|
|
useEffect(() => {
|
|
if (typeof document === "undefined") {
|
|
return () => {};
|
|
}
|
|
const header = document.querySelector("header");
|
|
const footer = document.querySelector("footer");
|
|
const previousHeaderDisplay = header?.style.display;
|
|
const previousFooterDisplay = footer?.style.display;
|
|
if (header) header.style.display = "none";
|
|
if (footer) footer.style.display = "none";
|
|
|
|
const handleContextMenu = (event) => event.preventDefault();
|
|
const handleCopyPaste = (event) => event.preventDefault();
|
|
document.addEventListener("contextmenu", handleContextMenu);
|
|
document.addEventListener("copy", handleCopyPaste);
|
|
document.addEventListener("cut", handleCopyPaste);
|
|
document.addEventListener("paste", handleCopyPaste);
|
|
|
|
const previousUserSelect = document.body.style.userSelect;
|
|
document.body.style.userSelect = "none";
|
|
|
|
return () => {
|
|
if (header) header.style.display = previousHeaderDisplay || "";
|
|
if (footer) footer.style.display = previousFooterDisplay || "";
|
|
document.removeEventListener("contextmenu", handleContextMenu);
|
|
document.removeEventListener("copy", handleCopyPaste);
|
|
document.removeEventListener("cut", handleCopyPaste);
|
|
document.removeEventListener("paste", handleCopyPaste);
|
|
document.body.style.userSelect = previousUserSelect || "auto";
|
|
};
|
|
}, []);
|
|
|
|
// ✅ Récupération contenu
|
|
useEffect(() => {
|
|
const decodeToken = () => {
|
|
if (!token) {
|
|
return null;
|
|
}
|
|
try {
|
|
const raw = atob(token);
|
|
try {
|
|
const parsed = JSON.parse(raw);
|
|
if (!parsed || typeof parsed !== "object") {
|
|
throw new Error("Invalid JSON payload");
|
|
}
|
|
const {
|
|
courseId,
|
|
itemType = "page",
|
|
itemId,
|
|
expiry,
|
|
linkId = null,
|
|
} = parsed;
|
|
if (!courseId || !itemId || !expiry) {
|
|
return null;
|
|
}
|
|
return {
|
|
courseId: String(courseId),
|
|
itemType: itemType || "page",
|
|
itemId: String(itemId),
|
|
expiry,
|
|
linkId,
|
|
format: "json",
|
|
};
|
|
} catch (parseError) {
|
|
console.warn("Lien public JSON invalide :", parseError);
|
|
const parts = raw.split("_");
|
|
if (parts.length === 3) {
|
|
const [courseId, itemId, expiry] = parts;
|
|
return {
|
|
courseId,
|
|
itemType: "page",
|
|
itemId,
|
|
expiry,
|
|
linkId: null,
|
|
format: "legacy",
|
|
};
|
|
}
|
|
if (parts.length === 4) {
|
|
const [courseId, itemType, itemId, expiry] = parts;
|
|
return {
|
|
courseId,
|
|
itemType,
|
|
itemId,
|
|
expiry,
|
|
linkId: null,
|
|
format: "legacy",
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
} catch (decodeError) {
|
|
console.warn("Impossible de décoder le lien sécurisé :", decodeError);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const decoded = decodeToken();
|
|
if (!decoded) {
|
|
setTokenValid(false);
|
|
setContent("⛔ Lien invalide.");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
const { courseId, itemType, itemId, expiry, format } = decoded;
|
|
const expiryValue = Number(expiry);
|
|
if (
|
|
!courseId ||
|
|
!itemId ||
|
|
!Number.isFinite(expiryValue) ||
|
|
Date.now() > expiryValue
|
|
) {
|
|
setTokenValid(false);
|
|
setContent("⛔ Le cours est terminé !");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
const normalizedItemType = itemType || "page";
|
|
const linkStillActive = () => {
|
|
if (format === "legacy") {
|
|
return true;
|
|
}
|
|
if (typeof window === "undefined") {
|
|
return true;
|
|
}
|
|
try {
|
|
const stored = localStorage.getItem(PUBLIC_LINKS_STORAGE_KEY);
|
|
if (!stored) {
|
|
return false;
|
|
}
|
|
const registry = JSON.parse(stored);
|
|
const key = `${courseId}-${normalizedItemType}-${itemId}`;
|
|
const entry = registry?.[key];
|
|
if (!entry || !entry.active) {
|
|
return false;
|
|
}
|
|
if (entry.token && entry.token !== token) {
|
|
return false;
|
|
}
|
|
if (
|
|
entry.expiry !== undefined &&
|
|
Number.isFinite(Number(entry.expiry)) &&
|
|
Date.now() > Number(entry.expiry)
|
|
) {
|
|
return false;
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Impossible de vérifier l'état du lien public :", error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
if (!linkStillActive()) {
|
|
setTokenValid(false);
|
|
setContent("⛔ Ce lien n'est plus actif.");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
const fetchPage = async () => {
|
|
try {
|
|
const pages = await MoodleApi.getPagesByCourse(Number(courseId));
|
|
const parsedItemId = parseInt(itemId, 10);
|
|
const found = pages.find((p) => p.id === parsedItemId);
|
|
if (found) {
|
|
setTitle(found.name || "");
|
|
setContent(found.content || "");
|
|
} else {
|
|
setTokenValid(false);
|
|
setContent("⛔ Page non trouvée.");
|
|
}
|
|
} catch (error) {
|
|
console.error("Erreur lors du chargement de la page Moodle :", error);
|
|
setTokenValid(false);
|
|
setContent("❌ Erreur lors du chargement.");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const fetchAssignment = async () => {
|
|
try {
|
|
const assignments = await MoodleApi.getAssignmentsByCourse(
|
|
Number(courseId)
|
|
);
|
|
const parsedAssignmentId = parseInt(itemId, 10);
|
|
const assignment = assignments.find(
|
|
(a) => a.id === parsedAssignmentId
|
|
);
|
|
if (assignment) {
|
|
setTitle(assignment.name || "");
|
|
setContent(assignment.intro || "<p>Aucun contenu disponible.</p>");
|
|
} else {
|
|
setTokenValid(false);
|
|
setContent("⛔ Devoir introuvable.");
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
"Erreur lors du chargement du devoir Moodle :",
|
|
error
|
|
);
|
|
setTokenValid(false);
|
|
setContent("❌ Erreur lors du chargement du devoir.");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
if (normalizedItemType === "assignment") {
|
|
fetchAssignment();
|
|
} else {
|
|
fetchPage();
|
|
}
|
|
}, [token]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<Box
|
|
sx={{
|
|
padding: 4,
|
|
textAlign: "center",
|
|
userSelect: "none",
|
|
WebkitUserSelect: "none",
|
|
MozUserSelect: "none",
|
|
msUserSelect: "none",
|
|
}}
|
|
>
|
|
<CircularProgress />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (!tokenValid) {
|
|
return (
|
|
<Box
|
|
sx={{
|
|
height: "100vh",
|
|
display: "flex",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
backgroundColor: "#f8d7da",
|
|
}}
|
|
>
|
|
<Typography
|
|
variant="h3"
|
|
sx={{
|
|
color: "#721c24",
|
|
fontWeight: "bold",
|
|
textAlign: "center",
|
|
padding: 4,
|
|
}}
|
|
>
|
|
{content}
|
|
</Typography>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box sx={{ padding: 4, mt: 5 }}>
|
|
{title ? (
|
|
<Typography variant="h4" sx={{ mb: 3, color: "#315397" }}>
|
|
{title}
|
|
</Typography>
|
|
) : null}
|
|
<div dangerouslySetInnerHTML={{ __html: content }} />
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default CoursLecture;
|