Refactor session handling: implement useEnforcedSession for better session validation across components

This commit is contained in:
Marco Pedone 2025-08-20 14:04:23 +02:00
parent 1d70ce9a44
commit 485bcda0d3
11 changed files with 85 additions and 46 deletions

View file

@ -24,8 +24,13 @@ import { Sidebar } from "~/components/area-riservata/sidebar";
import type { UsersId } from "~/schemas/public/Users";
import toast from "react-hot-toast";
import { useRouter } from "next/router";
import { useSession } from "~/providers/SessionProvider";
import {
useSession,
EnforcedSessionContext,
} from "~/providers/SessionProvider";
import { WhatsAppIcon } from "~/components/svgs";
import { LoadingPage } from "~/components/loading";
import type { ValidSession } from "~/server/api/trpc";
interface Props {
children: ReactNode;
@ -66,6 +71,35 @@ export const AreaRiservataLayout = ({
});
const session = useSession();
if (session.status === "pending")
return (
<div className="flex h-full min-h-screen flex-col text-clip">
<SiteHeader />
<main className="flex h-full flex-1 overflow-auto">
<div className="flex h-auto w-full flex-col md:flex-row">
<LoadingPage />;
</div>
</main>
{!noFooter && <MiniFooter />}
</div>
);
if (session.status !== "success" || !session.user) {
window.location.reload();
return (
<div className="flex h-full min-h-screen flex-col text-clip">
<SiteHeader />
<main className="flex h-full flex-1 overflow-auto">
<div className="flex h-auto w-full flex-col md:flex-row">
<LoadingPage />;
</div>
</main>
{!noFooter && <MiniFooter />}
</div>
);
}
return (
<div className="flex h-full min-h-screen flex-col text-clip">
<SiteHeader />
@ -77,9 +111,11 @@ export const AreaRiservataLayout = ({
<main className="flex h-full flex-1 overflow-auto">
<div className="flex h-auto w-full flex-col md:flex-row">
{noSidebar ? null : (
<Sidebar isAdmin={session.user?.isAdmin || false} />
<Sidebar isAdmin={session.user.isAdmin || false} />
)}
{children}
<EnforcedSessionContext.Provider value={session as ValidSession}>
{children}
</EnforcedSessionContext.Provider>
</div>
</main>
{!noFooter && <MiniFooter />}

View file

@ -25,8 +25,12 @@ const AdminChats: NextPageWithLayout = () => {
isLoadingInactiveUsers
)
return <LoadingPage />;
if (!session || !session.user || !activeChats || !inactiveUsers)
return <Status500 />;
if (session.status !== "success" || !session.user) {
window.location.reload();
return <LoadingPage />;
}
if (!activeChats || !inactiveUsers) return <Status500 />;
return (
<>

View file

@ -1,17 +1,12 @@
import { useTranslation } from "~/providers/I18nProvider";
import type { NextPageWithLayout } from "~/pages/_app";
import { useSession } from "~/providers/SessionProvider";
import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page";
import { useEnforcedSession } from "~/providers/SessionProvider";
import { AreaRiservataLayout } from "~/components/Layout";
import Head from "next/head";
import { AllegatiComp } from "~/components/area-riservata/allegati";
const Allegati: NextPageWithLayout = () => {
const { t } = useTranslation();
const session = useSession();
if (session.status === "pending") return <LoadingPage />;
if (!session || !session.user) return <Status500 />;
const session = useEnforcedSession();
return (
<>

View file

@ -4,10 +4,9 @@ import Head from "next/head";
import { useState, type FormEvent } from "react";
import { AreaRiservataLayout } from "~/components/Layout";
import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page";
import { Button } from "~/components/ui/button";
import type { NextPageWithLayout } from "~/pages/_app";
import { useSession } from "~/providers/SessionProvider";
import { useEnforcedSession } from "~/providers/SessionProvider";
import type { StorageindexId } from "~/schemas/public/Storageindex";
import { api } from "~/utils/api";
import toast from "react-hot-toast";
@ -37,7 +36,7 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
allegatoId,
}: AllegatoViewProps) => {
const { t } = useTranslation();
const session = useSession();
const session = useEnforcedSession();
const { mutateAsync: getToken } = api.storage.getStorageToken.useMutation();
const { data: fileInfos, isLoading } = api.storage.getStorageFromId.useQuery({
storageId: allegatoId,
@ -63,12 +62,8 @@ const AllegatoView: NextPageWithLayout<AllegatoViewProps> = ({
},
});
if (session.status === "pending" || isLoading) return <LoadingPage />;
if (!session || !session.user) {
console.error("Session not found or user not authenticated", session);
if (isLoading) return <LoadingPage />;
return <Status500 />;
}
if (!fileInfos) return <DocNotFoundPage />;
return (

View file

@ -76,7 +76,7 @@ export const getServerSideProps = (async (context) => {
}
return {
redirect: {
destination: "/login",
destination: "/login?",
permanent: false,
},
};

View file

@ -1,18 +1,13 @@
import Head from "next/head";
import { useTranslation } from "~/providers/I18nProvider";
import { useSession } from "~/providers/SessionProvider";
import { useEnforcedSession } from "~/providers/SessionProvider";
import { AreaRiservataLayout } from "~/components/Layout";
import type { NextPageWithLayout } from "~/pages/_app";
import { Status500 } from "~/components/status-page";
import { LoadingPage } from "~/components/loading";
import { EmailAccordion } from "~/components/area-riservata/comunicazioni";
const Comunicazioni: NextPageWithLayout = () => {
const { t } = useTranslation();
const session = useSession();
if (session.status === "pending") return <LoadingPage />;
if (!session || !session.user) return <Status500 />;
const session = useEnforcedSession();
return (
<>
@ -23,7 +18,7 @@ const Comunicazioni: NextPageWithLayout = () => {
<div className="mx-auto max-w-7xl grow p-4">
<div className="p-1">
<h1 className="text-2xl font-bold text-primary">Comunicazioni</h1>
<h1 className="text-primary text-2xl font-bold">Comunicazioni</h1>
</div>
<EmailAccordion userId={session.user.id} />

View file

@ -1,10 +1,8 @@
import Head from "next/head";
import { useTranslation } from "~/providers/I18nProvider";
import { useSession } from "~/providers/SessionProvider";
import { useEnforcedSession } from "~/providers/SessionProvider";
import { AreaRiservataLayout } from "~/components/Layout";
import type { NextPageWithLayout } from "~/pages/_app";
import { Status500 } from "~/components/status-page";
import { LoadingPage } from "~/components/loading";
import {
AdminDashboard,
UserDashboard,
@ -12,10 +10,7 @@ import {
const Dashboard: NextPageWithLayout = () => {
const { t } = useTranslation();
const session = useSession();
if (session.status === "pending") return <LoadingPage />;
if (!session || !session.user) return <Status500 />;
const session = useEnforcedSession();
return (
<>

View file

@ -5,16 +5,13 @@ import Head from "next/head";
import { api } from "~/utils/api";
import type { NextPageWithLayout } from "~/pages/_app";
import { Status500 } from "~/components/status-page";
import { useSession } from "~/providers/SessionProvider";
import { useEnforcedSession } from "~/providers/SessionProvider";
import type { UsersId } from "~/schemas/public/Users";
import { OrdiniTable } from "~/components/tables/orders-table";
const Ordini: NextPageWithLayout = () => {
const { t } = useTranslation();
const session = useSession();
if (session.status == "pending") return <LoadingPage />;
if (!session.user) return <Status500 />;
const session = useEnforcedSession();
return (
<>
<Head>

View file

@ -1,5 +1,4 @@
import Head from "next/head";
import { LoadingPage } from "~/components/loading";
import { useTranslation } from "~/providers/I18nProvider";
import { AreaRiservataLayout } from "~/components/Layout";
import type { NextPageWithLayout } from "~/pages/_app";
@ -9,7 +8,7 @@ import { Status500 } from "~/components/status-page";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import { ProfileFormAccount } from "~/forms/FormProfilo_Account";
import { ProfileFormAnagrafica } from "~/forms/FormProfilo_Anagrafica";
import { useSession } from "~/providers/SessionProvider";
import { useEnforcedSession } from "~/providers/SessionProvider";
import {
Card,
CardContent,
@ -18,15 +17,16 @@ import {
CardTitle,
} from "~/components/ui/card";
import { UserAvatar } from "~/components/user_avatar";
import { LoadingPage } from "~/components/loading";
const Dashboard: NextPageWithLayout = () => {
const { t } = useTranslation();
const { data: userData, isLoading } = api.users.getClientProfilo.useQuery({
id: undefined,
});
const session = useSession();
const session = useEnforcedSession();
if (isLoading) return <LoadingPage />;
if (!userData || !session.user) return <Status500 />;
if (!userData) return <Status500 />;
return (
<>

View file

@ -6,7 +6,7 @@ import {
type FC,
type ReactNode,
} from "react";
import type { Session } from "~/server/api/trpc";
import type { Session, ValidSession } from "~/server/api/trpc";
import { api } from "~/utils/api";
type SessionStatus = "pending" | "success" | "error";
export type SessionContextType = {
@ -66,3 +66,20 @@ export const SessionProvider: FC<{ children: ReactNode }> = ({ children }) => {
};
export const useSession = () => useContext(SessionContext);
export const EnforcedSessionContext = createContext<ValidSession | undefined>(
undefined,
);
/**
* AreaRiservata specific session context. It ensures that the session is valid.
*/
export const useEnforcedSession = () => {
const session = useContext(EnforcedSessionContext);
if (!session) {
throw new Error(
"useValidSession must be used within a ValidSessionProvider",
);
}
return session;
};

View file

@ -36,6 +36,11 @@ export const sessionSchema = z.object({
export type Session = z.infer<typeof sessionSchema>;
export type ValidSession = {
user: Session;
status: "success";
};
interface CreateInnerContextOptions extends Partial<CreateNextContextOptions> {
session: Session | null;
res?: NextApiResponse;