SmartBoot : Un squelette prêt à l'emploi

SmartBoot génère la structure complète d'un module Dolibarr augmenté avec SmartMaker (front React + back PHP).

Sources: https://inligit.fr/cap-rel/dolibarr/smartmaker/smartboot.git

Installation

Linux

git clone https://inligit.fr/cap-rel/dolibarr/smartmaker/smartboot.git && ./smartboot/setup.sh

Windows

Note

Le script PowerShell est en cours de finalisation.

git clone https://inligit.fr/cap-rel/dolibarr/smartmaker/smartboot.git && powershell ./smartboot/setup.ps1

Puis suivez les étapes de l'assistant :

Is your project name Coucou ?
[y/n] y
ok on continue

please wait during npm install depends ... it could take time :)

Structure générée

Après installation, SmartBoot ajoute à votre module :

monmodule/
├── mobile/                          # Application React
│   ├── index.html                   # Point d'entrée (manifest PWA dynamique)
│   ├── vite.config.js               # Configuration Vite + PWA + proxy de dev
│   ├── .env.example                 # Modèle du .env (créé par setup.sh)
│   ├── src/
│   │   ├── main.jsx                 # Point d'entrée React
│   │   ├── App.jsx                  # Composant racine
│   │   ├── appConfig.js             # Configuration globale
│   │   ├── sw.js                    # Service Worker (mode injectManifest)
│   │   ├── api/
│   │   │   └── mapping/             # Mapping backend <-> front, un par feature
│   │   ├── db/
│   │   │   ├── index.js             # Instanciation de Db
│   │   │   └── stores/users/        # indexes.js + useDbUsers.jsx
│   │   ├── components/
│   │   │   ├── app/                 # Router, Provider, Head, Toaster
│   │   │   ├── layouts/
│   │   │   │   ├── AnimationLayout/ # Transitions de pages
│   │   │   │   └── PagesLayout/     # Layout global (thème, scale)
│   │   │   └── pages/
│   │   │       ├── public/          # Pages sans auth (Login, Welcome)
│   │   │       ├── private/         # Pages avec auth (Home, DeviceIdentification)
│   │   │       └── errors/          # Error404Page
│   │   ├── global-state/slices/     # État d'interface uniquement
│   │   ├── hooks/
│   │   │   └── useSmartcommonLabels/  # Libellés smartcommon dans la langue active
│   │   ├── i18n/
│   │   └── utils/
│   │       ├── constants/
│   │       ├── functions/
│   │       └── maps/                # form.jsx et list.jsx
│   └── public/
│       ├── images/                  # Icônes PWA
│       └── locales/<lang>/<ns>.json # Un fichier par langue ET par feature
├── pwa/                             # Build de production
│   ├── api.php                      # Routeur API
│   └── .htaccess                    # Redirection Apache
├── smartmaker-api/
│   ├── Controllers/                 # Vos controllers PHP
│   ├── dmGenericObject.php          # Exemple de mapping Dolibarr
│   └── HomeController.php           # Controller d'exemple
└── smartmaker-api-prepend.php       # Initialisation SmartAuth

Cette arborescence n'est pas décorative : elle applique les règles d'organisation décrites dans Architecture, notamment le découpage de la couche données en indexes.js et hook useDb<Feature>, et le découpage des traductions par namespace.

Traductions par namespace

Le squelette livre cinq namespaces, en français et en anglais : common, welcomePage, loginPage, deviceIdentificationPage et homePage.

const { t } = useTranslation('loginPage');

Attention

Un Makefile qui dérive VITE_LOCALES doit lister les sous-dossiers de public/locales/, et non les fichiers *.json. Les recettes anciennes cherchaient locales/*.json : depuis le passage au multi-namespace, elles ne trouvent plus rien, la liste des langues est vide et l'application reste figée sur la langue de repli.

Proxy de développement

Si votre Dolibarr n'est pas servi par le serveur de développement Vite, renseignez la cible dans mobile/.env :

VITE_DEV_PROXY_TARGET=https://dolibarr.local/custom/monmodule/pwa

C'est le répertoire qui contient api.php, sans slash final. Variable absente ou vide : aucun proxy n'est enregistré. Le pourquoi est expliqué dans PWA, section "Origine du front et origine de l'API".

Lint en deux passes

npm run lint exécute oxlint puis eslint .. La première passe est native et rapide, la seconde couvre ce que la première ne traite pas. eslint-plugin-oxlint désactive côté ESLint les règles déjà vérifiées par oxlint.

Manifest PWA dynamique

SmartBoot configure automatiquement le manifest PWA de manière dynamique via SmartAuth. Le fichier index.html pointe vers api.php/manifest.webmanifest au lieu d'un fichier statique :

<link rel="manifest" href="api.php/manifest.webmanifest">
<link rel="icon" type="image/png" href="api.php/icon/64">
<link rel="apple-touch-icon" href="api.php/icon/192">

Et dans vite.config.js, le manifest statique est désactivé :

VitePWA({
  // ...
  manifest: false, // Servi dynamiquement par SmartAuth
})

Important

Ce lien est relatif : il suppose que le front et l'API sont servis depuis la même origine, ce qui est le cas une fois la PWA déployée dans pwa/. En développement, avec vite dev sur le port 5173 et Dolibarr sur un autre hôte, il faut proxifier api.php. Voir PWA, section "Origine du front et origine de l'API".

Voir PWA pour les constantes Dolibarr, les deux modes de Service Worker et le démarrage hors ligne.

Layouts et gardes de routes

SmartBoot ne fournit que deux layouts visuels :

Layout Rôle
PagesLayout Layout global : applique le thème, le dark mode et le scale
AnimationLayout Gère les transitions animées entre pages (Framer Motion)

L'authentification et l'identification d'appareil sont gérées par le composant <RouteGuard> de smartcommon (4 modes : requireGuest, requireAuth, requireDeviceIdentification, requireDeviceIdentified). Voir Composants avancés -> RouteGuard pour la documentation détaillée.

Exemple de Router (skel SmartBoot)

import { Routes, Route, RouteGuard } from '@cap-rel/smartcommon';

import {
  LoginPage, HomePage, Error404Page, PagesLayout,
  WelcomePage, DeviceIdentificationPage, AnimationLayout,
} from 'src/components';

export const Router = () => (
  <Routes>
    <Route element={<PagesLayout />}>
      {/* Pages publiques : un utilisateur authentifie est renvoye vers / */}
      <Route element={<RouteGuard requireGuest />}>
        <Route path="/welcome" element={<WelcomePage />} />
        <Route path="/login" element={<LoginPage />} />
      </Route>

      {/* Authentifie, identification d'appareil encore a faire */}
      <Route element={<RouteGuard requireDeviceIdentification />}>
        <Route path="/device-identification" element={<DeviceIdentificationPage />} />
      </Route>

      {/* Authentifie + appareil identifie : toutes les pages privees */}
      <Route element={<RouteGuard requireDeviceIdentified />}>
        {/* AnimationLayout est monte UNE fois ici, jamais page par page */}
        <Route element={<AnimationLayout />}>
          <Route path="/" element={<HomePage />} />
          {/* Ajouter vos routes privees ici */}
        </Route>
      </Route>

      <Route path="*" element={<Error404Page />} />
    </Route>
  </Routes>
);

Attention

Pas de <BrowserRouter> ici. Le <Provider> de SmartCommon en monte déjà un. En ajouter un second produit l'erreur You cannot render a <Router> inside another <Router>. Une PWA servie sous un sous-chemin, ou qui utilise des liens profonds par hash, passe config.router: "hash" et config.basename au Provider plutôt que de monter son propre routeur.

Note

Routes et Route sont réexportés par SmartCommon : une page ne doit jamais importer react-router-dom directement. Pour naviguer, utilisez useNavigation().

Astuce

Avant la migration vers smartcommon, SmartBoot fournissait quatre layouts custom (PrivatePagesLayout, PublicPagesLayout, PreDeviceIdentificationLayout, PostDeviceIdentificationLayout). Ils ont été remplacés par <RouteGuard> qui centralise la logique dans smartcommon et permet d'évoluer sans toucher au skel.

Pages générées (LoginPage, DeviceIdentificationPage)

Les pages publiques de connexion et d'identification utilisent les composants high-level <LoginComponent> et <DeviceIdentificationComponent> de smartcommon. Le skel ne fait que les habiller (wrapper visuel, vagues de fond, liens register/forgot-password) et brancher onSuccess/onError sur le store local.

  • <LoginComponent> inclut gratuitement le flux QR pair smartAuth (scan -> claim -> poll). Voir détails.
  • <DeviceIdentificationComponent> lit useApi().user.deviceOptions pour afficher soit un input simple (premier device), soit un radio + input (pairing). Voir détails.

AboutModal

SmartBoot intègre l'AboutModal de smartcommon (vérification automatique des mises à jour PWA via usePWAUpdate) :

import { AboutModal } from '@cap-rel/smartcommon';
import { APP_VERSION } from 'src/utils';

<AboutModal
  open={showAbout}
  onClose={() => setShowAbout(false)}
  appName="Mon Application"
  version={APP_VERSION}
/>

Ce composant affiche :

  • Le nom de l'application et la version (APP_VERSION issu de VITE_APP_VERSION)
  • Des champs libres optionnels via la prop fields
  • Un bouton "Vérifier les mises à jour" qui relance le Service Worker

Voir Composants avancés -> AboutModal pour les libellés et slots.

Controller d'exemple

SmartBoot génère un HomeController.php d'exemple avec un mapping dmGenericObject.php :

// smartmaker-api/HomeController.php
class HomeController
{
    public function index($arr = null)
    {
        global $db, $langs;

        $ret = [
            'statusCode' => 200,
            'generic_message' => "",
            'lastupdate'  => "",
            'home'  => "",
        ];

        return ([$ret, 200]);
    }
}

Composants montés par défaut

Le squelette monte déjà, dans App.jsx, trois composants transverses de SmartCommon. Vous n'avez rien à ajouter :

Composant Rôle
<UpdatePrompt> propose le rechargement quand une nouvelle version est prête
<InstallPrompt> propose l'installation de la PWA sur l'appareil
<ViewportProvider> expose le palier d'affichage (mobile, tablette, bureau)

Note

InstallPrompt n'a pas de bundle de traductions : ses libellés par défaut sont en anglais. Le squelette lui passe ses propres labels, à traduire dans vos namespaces. Voir PWA, section "Proposer l'installation".

Étapes suivantes

Vous pouvez maintenant passer au développement :