102 lines
2.9 KiB
TypeScript
102 lines
2.9 KiB
TypeScript
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react';
|
||
|
||
import {
|
||
abmelden as abmeldenApi,
|
||
aktuellesKonto,
|
||
brandSicherstellen,
|
||
eigeneBrand,
|
||
type Brand,
|
||
type Konto,
|
||
} from './auth';
|
||
import { ordnerListe, type Ordner } from './folders';
|
||
|
||
type Session = {
|
||
/** true, solange noch nicht feststeht, ob jemand angemeldet ist. */
|
||
laedt: boolean;
|
||
konto: Konto | null;
|
||
brand: Brand | null;
|
||
/**
|
||
* Der aktive Ordner bestimmt, welches Wissen in den Prompt wandert. Er ist
|
||
* deshalb App-Zustand und kein Bildschirm-lokaler Wert (app-aufbau.md §2.2) –
|
||
* sonst generiert der Nutzer im falschen Scope und versteht das Ergebnis nicht.
|
||
*/
|
||
aktiverOrdner: Ordner | null;
|
||
ordner: Ordner[];
|
||
setzeAktivenOrdner: (o: Ordner | null) => void;
|
||
ordnerNeuLaden: () => Promise<void>;
|
||
neuLaden: () => Promise<void>;
|
||
ausloggen: () => Promise<void>;
|
||
};
|
||
|
||
const Ctx = createContext<Session | null>(null);
|
||
|
||
export function SessionProvider({ children }: { children: ReactNode }) {
|
||
const [laedt, setLaedt] = useState(true);
|
||
const [konto, setKonto] = useState<Konto | null>(null);
|
||
const [brand, setBrand] = useState<Brand | null>(null);
|
||
const [ordner, setOrdner] = useState<Ordner[]>([]);
|
||
const [aktiverOrdner, setzeAktivenOrdner] = useState<Ordner | null>(null);
|
||
|
||
const ordnerFuer = useCallback(async (b: Brand | null) => {
|
||
if (!b) {
|
||
setOrdner([]);
|
||
setzeAktivenOrdner(null);
|
||
return;
|
||
}
|
||
const liste = await ordnerListe(b.$id);
|
||
setOrdner(liste);
|
||
setzeAktivenOrdner((bisher) => {
|
||
if (bisher) return liste.find((o) => o.$id === bisher.$id) ?? liste[0] ?? null;
|
||
return liste.find((o) => o.ist_default) ?? liste[0] ?? null;
|
||
});
|
||
}, []);
|
||
|
||
const neuLaden = useCallback(async () => {
|
||
const k = await aktuellesKonto();
|
||
setKonto(k);
|
||
if (!k) {
|
||
setBrand(null);
|
||
await ordnerFuer(null);
|
||
setLaedt(false);
|
||
return;
|
||
}
|
||
// Konto ohne Brand kann entstehen, wenn die Registrierung mittendrin
|
||
// abgebrochen ist – hier wird das stillschweigend nachgeholt.
|
||
let b = await eigeneBrand();
|
||
if (!b) b = await brandSicherstellen(k.name || k.email);
|
||
setBrand(b);
|
||
await ordnerFuer(b);
|
||
setLaedt(false);
|
||
}, [ordnerFuer]);
|
||
|
||
const ordnerNeuLaden = useCallback(() => ordnerFuer(brand), [brand, ordnerFuer]);
|
||
|
||
const ausloggen = useCallback(async () => {
|
||
await abmeldenApi();
|
||
setKonto(null);
|
||
setBrand(null);
|
||
setOrdner([]);
|
||
setzeAktivenOrdner(null);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
void neuLaden();
|
||
}, [neuLaden]);
|
||
|
||
return (
|
||
<Ctx.Provider
|
||
value={{
|
||
laedt, konto, brand, ordner, aktiverOrdner,
|
||
setzeAktivenOrdner, ordnerNeuLaden, neuLaden, ausloggen,
|
||
}}>
|
||
{children}
|
||
</Ctx.Provider>
|
||
);
|
||
}
|
||
|
||
export function useSession(): Session {
|
||
const v = useContext(Ctx);
|
||
if (!v) throw new Error('useSession muss innerhalb von <SessionProvider> stehen.');
|
||
return v;
|
||
}
|