From ad1d1e86d1e74c9f06e8621cf2c752a0a17da057 Mon Sep 17 00:00:00 2001 From: Dark-Louis Date: Tue, 7 Oct 2025 09:41:39 +0200 Subject: [PATCH 1/6] OLED small fix - Updated important message background color in oled mode --- src/pages/home/sections.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/sections.tsx b/src/pages/home/sections.tsx index 79fdc58..42cb9f5 100644 --- a/src/pages/home/sections.tsx +++ b/src/pages/home/sections.tsx @@ -164,7 +164,7 @@ export const ImportantMessage = ({ message }: { message?: MessageEntry }) => ( initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3, ease: "easeOut", delay: 0.05 }} - className="bg-white rounded-lg" + className="rounded-lg bg-white dark:bg-mauria-alert oled:bg-black" > From 49ee9739bb8e72c2ed13462a813ff0b30267b9b3 Mon Sep 17 00:00:00 2001 From: MiloM Date: Tue, 7 Oct 2025 19:00:47 +0200 Subject: [PATCH 2/6] feat: enhance data fetching logic with retry mechanism and loading state management --- src/App.tsx | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 0ebfbf6..095c28e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,7 @@ import { LoginPage } from "./pages/secondary/login"; import { AgendaPage } from "./pages/secondary/agenda"; import { WelcomePage } from "./pages/secondary/welcome"; import * as Sentry from "@sentry/react"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { overrideStorage, saveFromApp } from "./lib/utils/storage"; import { Loader } from "lucide-react"; import { useTranslation } from "react-i18next"; @@ -96,6 +96,7 @@ function AppRoutes() { function App() { const [isLoading, setIsLoading] = useState(true); + const nbTryRef = useRef(0); const { t } = useTranslation(); useEffect(() => { @@ -109,6 +110,11 @@ function App() { }, []); useEffect(() => { + const requestData = () => { + console.log("Requesting all data from parent..."); + window.parent.postMessage({ type: "REQUEST_ALL_DATA" }, "*"); + }; + // Écouter la réponse d'Ionic pour récupérer une donnée const handleMessage = async (event: MessageEvent) => { const { type, key, payload } = event.data; @@ -118,25 +124,41 @@ function App() { } if (type === "ALL_DATA_RESPONSE" && payload) { console.log("Toutes les données reçues: ", payload); - for (const [k, v] of Object.entries(payload)) { - await new Promise((resolve) => { - setTimeout(() => { - overrideStorage({ [k]: v } as Record< - string, - string - >); - resolve(); - }, 0); - }); + + if (payload["email"]) { + for (const [k, v] of Object.entries(payload)) { + await new Promise((resolve) => { + setTimeout(() => { + overrideStorage({ [k]: v } as Record< + string, + string + >); + resolve(); + }, 0); + }); + } + setIsLoading(false); + return; } + console.log( + "No email found in payload, not saving data and staying on loading screen." + ); setTimeout(() => { - setIsLoading(false); + requestData(); }, 200); + + nbTryRef.current++; + if (nbTryRef.current > 3) { + console.log( + "Tried to get data 3 times, stopping to avoid infinite loop." + ); + setIsLoading(false); + } + return; } }; - console.log("Requesting all data from parent..."); - window.parent.postMessage({ type: "REQUEST_ALL_DATA" }, "*"); + requestData(); window.addEventListener("message", handleMessage); From 8f7e5743c4ca334b5bd3453e0dca0fea213f97ca Mon Sep 17 00:00:00 2001 From: MiloM Date: Tue, 7 Oct 2025 19:10:12 +0200 Subject: [PATCH 3/6] fix: update query functions to provide default values for empty responses --- src/pages/absences/page.tsx | 3 ++- src/pages/grades/page.tsx | 3 ++- src/pages/home/page.tsx | 3 ++- src/pages/planning/page.tsx | 3 ++- src/pages/secondary/welcome.tsx | 10 ++++++++-- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/pages/absences/page.tsx b/src/pages/absences/page.tsx index 9bfcfb2..4cfb0e7 100644 --- a/src/pages/absences/page.tsx +++ b/src/pages/absences/page.tsx @@ -39,7 +39,8 @@ export function AbsencesPage() { isFetching, } = useQuery({ queryKey: ["absences"], - queryFn: () => fetchAbsences().then((res) => res?.data || []), + queryFn: (): Promise => + fetchAbsences().then((res) => res?.data || absences), staleTime: 1000 * 60 * 5, // 5 min frais gcTime: 1000 * 60 * 60 * 24, // 24h cache refetchOnWindowFocus: true, // refresh background si focus fenêtre diff --git a/src/pages/grades/page.tsx b/src/pages/grades/page.tsx index 9c266ae..334cda8 100644 --- a/src/pages/grades/page.tsx +++ b/src/pages/grades/page.tsx @@ -48,7 +48,8 @@ export function GradesPage() { isFetching, } = useQuery({ queryKey: ["grades"], - queryFn: () => fetchGrades().then((res) => res?.data || []), + queryFn: (): Promise => + fetchGrades().then((res) => res?.data || grades), staleTime: 1000 * 60 * 5, // 5 min frais gcTime: 1000 * 60 * 60 * 24, // 24h cache refetchOnWindowFocus: true, // refresh background si focus fenêtre diff --git a/src/pages/home/page.tsx b/src/pages/home/page.tsx index 7ce3674..735bbcf 100644 --- a/src/pages/home/page.tsx +++ b/src/pages/home/page.tsx @@ -31,7 +31,8 @@ export function HomePage() { isFetching, } = useQuery({ queryKey: ["planning"], - queryFn: () => fetchPlanning().then((res) => res?.data || []), + queryFn: (): Promise => + fetchPlanning().then((res) => res?.data || lessons), staleTime: 1000 * 60 * 5, // 5 min frais gcTime: 1000 * 60 * 60 * 24, // 24h cache refetchIntervalInBackground: true, diff --git a/src/pages/planning/page.tsx b/src/pages/planning/page.tsx index 422c0a4..cbaafda 100644 --- a/src/pages/planning/page.tsx +++ b/src/pages/planning/page.tsx @@ -44,7 +44,8 @@ export function PlanningPage() { dataUpdatedAt, } = useQuery({ queryKey: ["planning"], - queryFn: () => fetchPlanning().then((res) => res?.data || []), + queryFn: (): Promise => + fetchPlanning().then((res) => res?.data || lessons), staleTime: 1000 * 60 * 5, // 5 min frais gcTime: 1000 * 60 * 60 * 24, // 24h cache refetchOnWindowFocus: true, // refresh background si focus fenêtre diff --git a/src/pages/secondary/welcome.tsx b/src/pages/secondary/welcome.tsx index f211364..4cce43d 100644 --- a/src/pages/secondary/welcome.tsx +++ b/src/pages/secondary/welcome.tsx @@ -14,6 +14,7 @@ import { useQuery } from "@tanstack/react-query"; import { fetchPlanning } from "@/lib/api/aurion"; import { saveToStorage } from "@/lib/utils/storage"; import { useTranslation } from "react-i18next"; +import { Lesson } from "@/types/aurion"; type WelcomeSection = { title: string; @@ -61,9 +62,14 @@ const WELCOME_SECTIONS: WelcomeSection[] = [ export function WelcomePage() { const navigate = useNavigate(); const { t } = useTranslation(); - const { isLoading, isFetching } = useQuery({ + const { + isLoading, + isFetching, + data: lessons = [], + } = useQuery({ queryKey: ["planning"], - queryFn: () => fetchPlanning().then((res) => res?.data || []), + queryFn: (): Promise => + fetchPlanning().then((res) => res?.data || lessons), staleTime: 1000 * 60 * 5, gcTime: 1000 * 60 * 60 * 24, refetchOnWindowFocus: false, From 84eed5eacf5b66b3dcbc93f8f1bc9c3441783a81 Mon Sep 17 00:00:00 2001 From: MiloM Date: Tue, 7 Oct 2025 19:39:34 +0200 Subject: [PATCH 4/6] feat: implement InitialGetter component for data synchronization and loading state management --- src/App.tsx | 108 ++++----------------------------- src/contexts/initialGetter.tsx | 102 +++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 97 deletions(-) create mode 100644 src/contexts/initialGetter.tsx diff --git a/src/App.tsx b/src/App.tsx index 095c28e..872bfe5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,10 +25,7 @@ import { LoginPage } from "./pages/secondary/login"; import { AgendaPage } from "./pages/secondary/agenda"; import { WelcomePage } from "./pages/secondary/welcome"; import * as Sentry from "@sentry/react"; -import { useEffect, useRef, useState } from "react"; -import { overrideStorage, saveFromApp } from "./lib/utils/storage"; -import { Loader } from "lucide-react"; -import { useTranslation } from "react-i18next"; +import { InitialGetter } from "./contexts/initialGetter"; if (import.meta.env.PROD) { console.log("Initializing Sentry in production mode..."); @@ -41,7 +38,7 @@ if (import.meta.env.PROD) { Sentry.replayIntegration(), Sentry.browserTracingIntegration(), ], - replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. + replaysSessionSampleRate: 0, // This sets the sample rate at 0%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. tracesSampleRate: 1.0, // Adjust this value in production as needed }); @@ -95,104 +92,21 @@ function AppRoutes() { } function App() { - const [isLoading, setIsLoading] = useState(true); - const nbTryRef = useRef(0); - const { t } = useTranslation(); - - useEffect(() => { - const timer = setTimeout(() => { - setIsLoading(false); - }, 750); - - return () => { - clearTimeout(timer); - }; - }, []); - - useEffect(() => { - const requestData = () => { - console.log("Requesting all data from parent..."); - window.parent.postMessage({ type: "REQUEST_ALL_DATA" }, "*"); - }; - - // Écouter la réponse d'Ionic pour récupérer une donnée - const handleMessage = async (event: MessageEvent) => { - const { type, key, payload } = event.data; - if (type === "DATA_RESPONSE" && key) { - console.log(`Donnée reçue pour ${key}: `, payload); - saveFromApp(key, payload); - } - if (type === "ALL_DATA_RESPONSE" && payload) { - console.log("Toutes les données reçues: ", payload); - - if (payload["email"]) { - for (const [k, v] of Object.entries(payload)) { - await new Promise((resolve) => { - setTimeout(() => { - overrideStorage({ [k]: v } as Record< - string, - string - >); - resolve(); - }, 0); - }); - } - setIsLoading(false); - return; - } - console.log( - "No email found in payload, not saving data and staying on loading screen." - ); - setTimeout(() => { - requestData(); - }, 200); - - nbTryRef.current++; - if (nbTryRef.current > 3) { - console.log( - "Tried to get data 3 times, stopping to avoid infinite loop." - ); - setIsLoading(false); - } - return; - } - }; - - requestData(); - - window.addEventListener("message", handleMessage); - - return () => { - window.removeEventListener("message", handleMessage); - }; - }, []); - - if (isLoading) { - return ( -
-

- {t("common.loading")} -
- {t("common.loadingWeb")} -

- -
- ); - } - return ( - - - - - - - + + + + + + + + + diff --git a/src/contexts/initialGetter.tsx b/src/contexts/initialGetter.tsx new file mode 100644 index 0000000..6432e14 --- /dev/null +++ b/src/contexts/initialGetter.tsx @@ -0,0 +1,102 @@ +import { ReactNode, useEffect, useRef, useState } from "react"; +import { overrideStorage, saveFromApp } from "@/lib/utils/storage"; +import { Loader } from "lucide-react"; +import { useTranslation } from "react-i18next"; + +interface Props { + children: ReactNode; +} + +export const InitialGetter = ({ children }: Props) => { + const [isLoading, setIsLoading] = useState(true); + const nbTryRef = useRef(0); + const hasTimedOut = useRef(false); + const { t } = useTranslation(); + + useEffect(() => { + const MAX_RETRIES = 3; + const TIMEOUT_MS = 1500; // global timeout 1.5s + + const requestData = () => { + console.log("Requesting all data from parent..."); + window.parent.postMessage({ type: "REQUEST_ALL_DATA" }, "*"); + }; + + const handleMessage = async (event: MessageEvent) => { + const { type, key, payload } = event.data ?? {}; + + if (type === "DATA_RESPONSE" && key) { + console.log(`Data received for ${key}:`, payload); + saveFromApp(key, payload); + return; + } + + if (type === "ALL_DATA_RESPONSE") { + console.log("All data received:", payload); + + if (payload["email"]) { + overrideStorage(payload as Record); + setIsLoading(false); + } else { + nbTryRef.current += 1; + console.warn( + `No email found (attempt ${nbTryRef.current}/${MAX_RETRIES})` + ); + + if ( + nbTryRef.current < MAX_RETRIES && + !hasTimedOut.current + ) { + const delay = 200 * nbTryRef.current; // 200ms, 400ms, 600ms + setTimeout(requestData, delay); + } else { + console.warn("Giving up after 3 failed attempts."); + setIsLoading(false); + } + } + } + }; + + // Initial request + requestData(); + + // Global timeout in case parent never answers + const timeout = setTimeout(() => { + if (isLoading) { + hasTimedOut.current = true; + console.warn("Global timeout reached, skipping data sync."); + setIsLoading(false); + } + }, TIMEOUT_MS); + + window.addEventListener("message", handleMessage); + return () => { + window.removeEventListener("message", handleMessage); + clearTimeout(timeout); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Minimum splash duration (smooth UX) + useEffect(() => { + const timer = setTimeout(() => { + if (isLoading) return; + }, 750); + return () => clearTimeout(timer); + }, [isLoading]); + + if (isLoading) { + return ( +
+

+ {t("common.loading")} +
+ {t("common.loadingWeb")} +

+ +
+ ); + } + + return <>{children}; +}; From 80735a8293660074ed2121e05b0cb9189d2048d2 Mon Sep 17 00:00:00 2001 From: MiloM Date: Tue, 7 Oct 2025 20:08:56 +0200 Subject: [PATCH 5/6] feat: add handleModeBeta function to support preprod URL navigation --- src/components/sidebar.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 6246626..5c98d1e 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -197,6 +197,13 @@ export default function Sidebar() { handleNavigate("/login"); }; + const handleModeBeta = () => { + window.parent.postMessage( + { type: "MODE_BETA", payload: preprodURL }, + "*" + ); + }; + return ( @@ -373,9 +380,7 @@ export default function Sidebar() { variant="link" size="sm" className="p-0 ml-2" - onClick={() => - window.parent.location.assign(preprodURL) - } + onClick={handleModeBeta} > mode Beta From 8a5ee778aa9e72746cc9d6f9163c5bd7410cafa0 Mon Sep 17 00:00:00 2001 From: Mylow Date: Tue, 7 Oct 2025 20:30:14 +0200 Subject: [PATCH 6/6] Potential fix for code scanning alert no. 6: Use of externally-controlled format string Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- src/contexts/initialGetter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contexts/initialGetter.tsx b/src/contexts/initialGetter.tsx index 6432e14..d6e9a3f 100644 --- a/src/contexts/initialGetter.tsx +++ b/src/contexts/initialGetter.tsx @@ -26,7 +26,7 @@ export const InitialGetter = ({ children }: Props) => { const { type, key, payload } = event.data ?? {}; if (type === "DATA_RESPONSE" && key) { - console.log(`Data received for ${key}:`, payload); + console.log("Data received for %s:", key, payload); saveFromApp(key, payload); return; }