# Le back-office React + TypeScript du pop-up store Make My Lemonade > Comment on a construit le back-office d'administration du catalogue patrons/tissus avec Create React App, TypeScript, Material-UI et Firebase — et ce qu'on a appris sur la sécurisation de Firestore avec les custom claims. Date : 25/08/2018 Auteur : Aurélien N. Tags : React, TypeScript, Firebase, Material-UI --- L'[app tablette en boutique](/blog/configurateur-tissus-react-native-firebase-svg) fonctionne. Les clientes choisissent patron + tissu + taille et impriment leur ticket. Mais le catalogue est géré directement dans la console Firebase, ce qui veut dire que le client doit nous appeler à chaque ajout de tissu. On construit un back-office web pour qu'il puisse le faire lui-même. Même base Firebase, stack différent : Create React App avec TypeScript, Material-UI pour l'interface, et un système d'authentification admin basé sur les custom claims Firebase. ## L'architecture L'app tablette et le back-office web partagent le même projet Firebase. L'app tablette lit le catalogue en temps réel (Firestore `onSnapshot`). Le back-office lit et écrit, mais uniquement pour les utilisateurs authentifiés comme admin. ## TypeScript + Create React App On utilise `react-scripts-ts`, le fork TypeScript de Create React App. C'est pré-CRA 2.0 (qui intégrera TypeScript nativement), mais ça fonctionne. TypeScript 2.9 en strict mode, avec `noImplicitAny` et `strictNullChecks`. Le typage des données Firestore évite les erreurs bêtes : ```typescript interface Template interface Size interface Fabric ``` Le modèle est le même que côté React Native, mais typé. Quand quelqu'un change la structure Firestore, le compilateur TypeScript signale les incohérences avant le runtime. Sur un projet avec deux apps qui partagent la même base, c'est un filet de sécurité. ## Material-UI et le pattern Provider L'interface est du Material-UI v1 (MUI) standard : une sidebar de navigation, des tableaux pour les listes, des modales full-screen pour l'édition. Rien de spectaculaire, mais fonctionnel en deux semaines. Les données arrivent via le même pattern render-props que l'app React Native : ```typescript class TemplatesProvider extends React.PureComponent<, TemplatesState> { private unsubscribe?: () => void componentDidMount() { this.unsubscribe = firebase.firestore() .collection("templates") .onSnapshot(snapshot => { const templates = snapshot.docs.map(doc => ()) as Template[] this.setState() }) } componentWillUnmount() render() } ``` Le code est quasi identique à la version React Native. La seule différence : le typage TypeScript et le `as Template[]` pour le cast des données Firestore. Le pattern Provider + HOC est le même des deux côtés : ```typescript const withTemplates = (Component: React.ComponentType) => (props: P) => ( ) ``` ## L'édition : modales Material-UI L'édition d'un patron ou d'un tissu ouvre une modale full-screen. Le formulaire pré-remplit les champs avec les données existantes, et la validation envoie un `update` Firestore : ```typescript async function updateTemplate(id: string, data: Partial) ``` Le `onSnapshot` Firestore détecte le changement et met à jour le tableau automatiquement. Le back-office n'a pas besoin de gérer l'optimistic update : Firestore le fait. L'ajout de tailles disponibles pour un patron (36, 38, 40...) se fait via un sous-formulaire dans la modale, avec un array de `` qui se comporte comme un `accepts_nested_attributes_for` de Rails, mais côté client. ## La sécurité Firebase : custom claims et Cloud Functions C'est le sujet qui mérite le plus d'attention. Firebase est un backend sans serveur. Il n'y a pas de middleware, pas de controller, pas de "before_action" qui vérifie les permissions. La sécurité repose entièrement sur les **Firestore Security Rules** et les **Firebase Auth custom claims**. Le mécanisme : 1. Un utilisateur s'inscrit avec email/password 2. Une **Cloud Function** (`onCreate` trigger) vérifie le domaine email. Si c'est `@imagine-app.fr`, elle écrit un custom claim `` sur le token Firebase Auth 3. Le back-office web lit ce claim via `getIdTokenResult()` et décide d'afficher le dashboard ou un écran "non autorisé" 4. Les Firestore Rules vérifient le claim à chaque écriture ```typescript // Cloud Function : auto-promotion admin .onCreate(async (user) => { if (user.email?.endsWith("@imagine-app.fr")) { await admin.auth().setCustomUserClaims(user.uid, ) } }) ``` ```typescript // Côté client : vérification du claim firebase.auth().onAuthStateChanged(async user => { if (user) { const idTokenResult = await user.getIdTokenResult() const isAdmin = idTokenResult.claims.admin === true this.setState() } }) ``` ## React web vs React Native : ce qui change, ce qui ne change pas En travaillant sur les deux apps en parallèle, on a pu comparer les deux écosystèmes sur le même projet. Quelques observations. Le **pattern Provider** est identique. Le même code Firestore (`onSnapshot`, render-props, HOC) fonctionne tel quel des deux côtés. Firebase est une dépendance JavaScript pure, pas native, donc la couche données est portable. Le **styling** est le changement le plus visible. Côté React Native, c'est `StyleSheet.create()` avec du Flexbox. Côté web, c'est `withStyles()` de Material-UI avec du CSS-in-JS. Les propriétés sont proches (`flexDirection`, `justifyContent`, `alignItems` existent des deux côtés) mais les noms diffèrent parfois (`backgroundColor` vs `background-color` en CSS classique, sauf que Material-UI utilise aussi du camelCase). La **navigation** est très différente. React Navigation v2 côté React Native (Stack Navigator, navigation params), React Router v4 côté web (URL-based, ``, ``). La logique de routage n'est pas partageable. Et TypeScript côté web vs JavaScript pur côté React Native. On aurait pu faire du TypeScript des deux côtés, mais Expo SDK 28 ne le supporte pas nativement (il faudra attendre le SDK 31 pour un support TypeScript correct). En attendant, les `prop-types` côté React Native font un job de validation runtime, mais ça n'a rien à voir avec la rigueur du typage statique. ## Ce qu'on livrera au client Le back-office est déployé sur Firebase Hosting (`firebase deploy`). Le client s'y connecte avec son email, voit le catalogue, ajoute/modifie/supprime des patrons et des tissus. Les changements sont visibles en temps réel sur l'app tablette en boutique. Pas de déploiement, pas de "mettez à jour l'app", c'est instantané via Firestore. Le coût Firebase est dérisoire pour ce volume. Quelques milliers de lectures Firestore par jour, quelques Go de Storage pour les images. On est largement dans le free tier.