/** * Composable de gestion du site courant (ticket 3 module Sites). * * Pattern aligne sur `useSidebar` : state singleton au niveau module, * hydrate depuis `useAuthStore().user`, mute de maniere optimistic avec * rollback si la requete PATCH `/api/me/current-site` echoue. * * Garantie d'unicite : le flag `switching` bloque les double-clicks * concurrents. Le reset explicite est appele au logout * (voir `modules/core/pages/logout.vue`). * * Auto-select : aucun. Le backend (`UserRbacProcessor::ensureCurrentSiteConsistency`) * garantit deja l'invariant "user avec sites non vide => currentSite non null" * apres tout PATCH /rbac. Le front consomme l'etat renvoye tel quel. * * Contrainte d'appel : `useCurrentSite()` doit etre invoque au top du * `setup()` d'un composant (ou d'un autre composable appele au setup). * Les dependances `useI18n`, `useApi` et `useAuthStore` sont resolues * a l'initialisation et reutilisees par `switchSite` — ceci evite le * "Must be called at the top of a setup function" qui se produirait * si on les appelait paresseusement depuis une fonction async declenchee * par un handler d'event (hors contexte setup). */ import { ref } from 'vue' import type { Site } from '~/shared/types/sites' import { onAuthSessionCleared } from '~/shared/stores/auth' const currentSite = ref(null) const availableSites = ref([]) const switching = ref(false) // Enregistrement unique au niveau module (singleton) : quand clearSession() // est appelee par l'intercepteur 401 de useApi, le state local est purgé // de la meme facon qu'au logout explicite (logout.vue). onAuthSessionCleared(() => { currentSite.value = null availableSites.value = [] switching.value = false }) export function useCurrentSite() { // Resolution au setup : les 3 services doivent etre invoques dans un // contexte composant. Leur capture ici permet a switchSite() de // s'executer plus tard (handler de click, async) sans crash. const auth = useAuthStore() const api = useApi() const { t } = useI18n() const { loadSidebar } = useSidebar() /** * Synchronise le state singleton depuis le store auth. A appeler au * mount du SiteSelector (ou via un watcher sur `auth.user`). */ function syncFromAuth(): void { availableSites.value = auth.user?.sites ?? [] currentSite.value = auth.user?.currentSite ?? null } /** * Bascule le site courant. Optimistic UI : la mutation locale precede * la requete HTTP. En cas d'echec (`api.patch` throw), l'etat local est * restaure — le store auth n'a PAS ete muté a ce stade (la propagation * `auth.setCurrentSite` se fait uniquement apres un succes HTTP), donc * aucun rollback cote auth n'est necessaire. * * Garde anti-double-submit : si un switch est deja en vol, le second * appel est un no-op silencieux. */ async function switchSite(site: Site): Promise { if (switching.value) { return } const previousLocal = currentSite.value currentSite.value = site switching.value = true try { await api.patch( '/me/current-site', { site: `/api/sites/${site.id}` }, { toastSuccessMessage: t('sites.selector.switchSuccess') }, ) // Propage au store auth via l'action dediee — plus tracable que // la mutation directe et garantit la notification des watchers. // N'est appele qu'apres un succes HTTP donc pas de rollback a // prevoir sur cette ligne. auth.setCurrentSite(site) // Apres un switch reussi : recharger la sidebar (les filtres de // modules peuvent dependre du site courant via SiteScopedQueryExtension) // et invalider toutes les donnees de page pour eviter que l'utilisateur // voie les donnees de l'ancien site sous un toast "Site change". try { await loadSidebar() } catch { // No-op : la sidebar non rafraichie n'est pas bloquante. } try { await refreshNuxtData() } catch { // No-op : certaines pages n'ont pas de useAsyncData a invalider. } } catch (error) { currentSite.value = previousLocal throw error } finally { switching.value = false } } /** * Vide l'etat singleton. Appele au logout pour eviter qu'un user * suivant (connecte sur le meme onglet) voie les sites de l'ancien. */ function resetCurrentSite(): void { currentSite.value = null availableSites.value = [] switching.value = false } return { currentSite, availableSites, switching, switchSite, syncFromAuth, resetCurrentSite, } }