Skip to content
86 changes: 11 additions & 75 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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...");
Expand All @@ -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
});
Expand Down Expand Up @@ -95,82 +92,21 @@ function AppRoutes() {
}

function App() {
const [isLoading, setIsLoading] = useState(true);
const { t } = useTranslation();

useEffect(() => {
const timer = setTimeout(() => {
setIsLoading(false);
}, 750);

return () => {
clearTimeout(timer);
};
}, []);

useEffect(() => {
// É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);
for (const [k, v] of Object.entries(payload)) {
await new Promise<void>((resolve) => {
setTimeout(() => {
overrideStorage({ [k]: v } as Record<
string,
string
>);
resolve();
}, 0);
});
}
setTimeout(() => {
setIsLoading(false);
}, 200);
}
};

console.log("Requesting all data from parent...");
window.parent.postMessage({ type: "REQUEST_ALL_DATA" }, "*");

window.addEventListener("message", handleMessage);

return () => {
window.removeEventListener("message", handleMessage);
};
}, []);

if (isLoading) {
return (
<div className="flex flex-col items-center text-center justify-center h-screen space-y-4">
<p>
{t("common.loading")}
<br />
{t("common.loadingWeb")}
</p>
<Loader className="w-12 h-12 text-muted-foreground animate-spin" />
</div>
);
}

return (
<ThemeProvider defaultTheme="light">
<BackgroundProvider defaultBackground="particles">
<ToastContextProvider>
<ModalContextProvider>
<Toaster richColors position="top-center" />
<BrowserRouter>
<ReactQueryProvider>
<CurrentYearProvider>
<AppRoutes />
</CurrentYearProvider>
</ReactQueryProvider>
</BrowserRouter>
<InitialGetter>
<BrowserRouter>
<ReactQueryProvider>
<CurrentYearProvider>
<AppRoutes />
</CurrentYearProvider>
</ReactQueryProvider>
</BrowserRouter>
</InitialGetter>
</ModalContextProvider>
</ToastContextProvider>
</BackgroundProvider>
Expand Down
11 changes: 8 additions & 3 deletions src/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ export default function Sidebar() {
handleNavigate("/login");
};

const handleModeBeta = () => {
window.parent.postMessage(
{ type: "MODE_BETA", payload: preprodURL },
"*"
);
};

return (
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger asChild>
Expand Down Expand Up @@ -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}
>
<span className="text-xs text-muted-foreground">
<span className="">mode Beta</span>
Expand Down
102 changes: 102 additions & 0 deletions src/contexts/initialGetter.tsx
Original file line number Diff line number Diff line change
@@ -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 %s:", key, payload);
saveFromApp(key, payload);
return;
}

if (type === "ALL_DATA_RESPONSE") {
console.log("All data received:", payload);

if (payload["email"]) {
overrideStorage(payload as Record<string, string>);
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 (
<div className="flex flex-col items-center text-center justify-center h-screen space-y-4">
<p>
{t("common.loading")}
<br />
{t("common.loadingWeb")}
</p>
<Loader className="w-12 h-12 text-muted-foreground animate-spin" />
</div>
);
}

return <>{children}</>;
};
3 changes: 2 additions & 1 deletion src/pages/absences/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export function AbsencesPage() {
isFetching,
} = useQuery<Absence[], Error>({
queryKey: ["absences"],
queryFn: () => fetchAbsences().then((res) => res?.data || []),
queryFn: (): Promise<Absence[]> =>
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
Expand Down
3 changes: 2 additions & 1 deletion src/pages/grades/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export function GradesPage() {
isFetching,
} = useQuery<Grade[], Error>({
queryKey: ["grades"],
queryFn: () => fetchGrades().then((res) => res?.data || []),
queryFn: (): Promise<Grade[]> =>
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
Expand Down
3 changes: 2 additions & 1 deletion src/pages/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export function HomePage() {
isFetching,
} = useQuery<Lesson[], Error>({
queryKey: ["planning"],
queryFn: () => fetchPlanning().then((res) => res?.data || []),
queryFn: (): Promise<Lesson[]> =>
fetchPlanning().then((res) => res?.data || lessons),
staleTime: 1000 * 60 * 5, // 5 min frais
gcTime: 1000 * 60 * 60 * 24, // 24h cache
refetchIntervalInBackground: true,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/home/sections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
>
<Alert className="mb-8 border-none bg-mauria-accent/20 dark:bg-mauria-alert">
<AlertTitle className="font-bold text-mauria-accent dark:text-white">
Expand Down
3 changes: 2 additions & 1 deletion src/pages/planning/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export function PlanningPage() {
dataUpdatedAt,
} = useQuery<Lesson[], Error>({
queryKey: ["planning"],
queryFn: () => fetchPlanning().then((res) => res?.data || []),
queryFn: (): Promise<Lesson[]> =>
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
Expand Down
10 changes: 8 additions & 2 deletions src/pages/secondary/welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Lesson[]> =>
fetchPlanning().then((res) => res?.data || lessons),
staleTime: 1000 * 60 * 5,
gcTime: 1000 * 60 * 60 * 24,
refetchOnWindowFocus: false,
Expand Down