Skip to content

État global ​

Certaines informations servent dans toute l'app et doivent survivre aux changements d'écran : qui est connecté, la liste des équipes... Elles vivent dans src/state/, sous forme de contextes React (voir Les bases).

Chaque fichier suit le même modèle :

tsx
const XContext = createContext<XValue | null>(null);

export function XProvider({ children }) {
  // l'état et les fonctions
  return <XContext.Provider value={value}>{children}</XContext.Provider>;
}

export function useX() {
  const value = useContext(XContext);
  if (!value) throw new Error('useX must be used inside XProvider');
  return value;
}

Le throw protège contre un oubli : si un composant utilise useX() en dehors du provider, l'erreur est claire au lieu d'un plantage mystérieux plus loin.

La session : src/state/session.tsx ​

ts
type SessionContextValue = {
  ready: boolean;                 // a-t-on fini de vérifier la session au démarrage ?
  user: SignedInUser | null;      // l'utilisateur connecté, ou null
  signIn: (email, password) => Promise<void>;
  signOut: () => Promise<void>;   // POST /auth/logout puis forget()
  forget: () => Promise<void>;    // oublie la session localement, sans appeler l'API
  refresh: () => Promise<void>;   // recharge l'utilisateur (après un changement de nom, de langue...)
};
HookRenvoieQuand l'utiliser
useSession()Tout l'objet ci-dessusQuand l'utilisateur peut être null (écrans publics, layout)
useCurrentUser()L'utilisateur, jamais nullDans les onglets, où l'on est forcément connecté

useCurrentUser() lève une erreur s'il n'y a personne de connecté. Dans (tabs)/, c'est impossible grâce au garde de _layout.tsx, donc on peut écrire user.id sans vérifier.

La langue suit le compte ​

Chaque fois que l'utilisateur change (connexion, déconnexion, refresh), la session applique sa langue :

ts
function applyLocale(user: SignedInUser | null) {
  i18n.changeLanguage(user?.preferences.locale ?? deviceLocale());
}

Si le compte a choisi le français sur le web, l'app passe en français. Sans choix, on prend la langue du téléphone.

L'annuaire : src/state/directory.tsx ​

Les managers et le GM ont besoin de la liste des personnes et des équipes dans beaucoup d'écrans (Équipe, fiche d'un membre, demandes, utilisateurs...). Plutôt que de la recharger partout, elle est chargée une fois ici.

ts
type DirectoryContextValue = {
  users: User[];
  teams: Team[];
  findUser: (id: number) => User | undefined;
  reload: () => Promise<void>;
};
ts
const reload = useCallback(async () => {
  if (!user || user.role === 'employee') {
    setUsers(user ? [user] : []);
    setTeams([]);
    return;
  }
  const [allUsers, allTeams] = await Promise.all([fetchUsers(), fetchTeams()]);
  setUsers(allUsers);
  setTeams(allTeams);
}, [user]);
Rôleusersteams
EmployéLui-même seulementVide (l'API refuserait de toute façon)
ManagerCe que l'API lui renvoieSes équipes
GMTout le mondeToutes les équipes

Promise.all lance les deux requêtes en même temps au lieu de l'une après l'autre.

findUser(id) cherche d'abord dans users, puis parmi les managers et membres des équipes. Un manager peut ainsi retrouver le nom d'un membre même si /users ne le renvoie pas.

Quand l'annuaire est rechargé ​

  • À chaque changement d'utilisateur (connexion).
  • Après chaque action réussie, par useAction (créer une équipe, changer un rôle...).
  • Quand certains écrans s'affichent, avec useRefreshDirectory() (Équipe, Utilisateurs, Plus, détail d'équipe).

La présentation : src/state/onboarding.tsx ​

ts
type OnboardingContextValue = {
  ready: boolean;   // a-t-on fini de lire SecureStore ?
  seen: boolean;    // la présentation a-t-elle été vue ?
  finish: () => void;
};

Au démarrage, il lit la clé timemanager.onboardingSeen dans SecureStore. finish() la passe à yes. Le layout racine utilise seen pour choisir entre la présentation et la connexion.

La présentation peut être revue depuis Compte, « Revoir la présentation ». Dans ce cas, finish() ramène simplement à l'écran précédent.

Les messages : src/components/ui/toast.tsx ​

Ce n'est pas dans state/, mais c'est aussi un contexte global. ToastProvider affiche un bandeau en haut de l'écran pendant 4 secondes.

ts
const showToast = useToast();
showToast(t('settings.saved'));

Le bandeau est annoncé par VoiceOver et TalkBack (accessibilityRole="alert"). Le plus souvent, on ne l'appelle pas directement : useAction le fait (voir Lire et modifier des données).

Ce qui n'est pas global ​

Tout le reste est local à l'écran qui en a besoin : les heures, les corrections, les notifications... Ces données sont rechargées à chaque affichage de l'écran par useQuery. C'est plus simple et toujours à jour.