SmartBoot: A ready-to-use skeleton
SmartBoot generates the complete structure of a Dolibarr module augmented with SmartMaker (React front + PHP back).
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
The PowerShell script is being finalised.
git clone https://inligit.fr/cap-rel/dolibarr/smartmaker/smartboot.git && powershell ./smartboot/setup.ps1
Then follow the wizard steps:
Is your project name Coucou ?
[y/n] y
ok on continue
please wait during npm install depends ... it could take time :)
Generated structure
After installation, SmartBoot adds to your module:
monmodule/
├── mobile/ # React application
│ ├── index.html # Entry point (dynamic PWA manifest)
│ ├── vite.config.js # Vite + PWA + dev proxy configuration
│ ├── .env.example # .env template (created by setup.sh)
│ ├── src/
│ │ ├── main.jsx # React entry point
│ │ ├── App.jsx # Root component
│ │ ├── appConfig.js # Global configuration
│ │ ├── sw.js # Service Worker (injectManifest mode)
│ │ ├── api/
│ │ │ └── mapping/ # Backend <-> front mapping, one per feature
│ │ ├── db/
│ │ │ ├── index.js # Db instantiation
│ │ │ └── stores/users/ # indexes.js + useDbUsers.jsx
│ │ ├── components/
│ │ │ ├── app/ # Router, Provider, Head, Toaster
│ │ │ ├── layouts/
│ │ │ │ ├── AnimationLayout/ # Page transitions
│ │ │ │ └── PagesLayout/ # Global layout (theme, scale)
│ │ │ └── pages/
│ │ │ ├── public/ # Pages without auth (Login, Welcome)
│ │ │ ├── private/ # Pages with auth (Home, DeviceIdentification)
│ │ │ └── errors/ # Error404Page
│ │ ├── global-state/slices/ # Interface state only
│ │ ├── hooks/
│ │ │ └── useSmartcommonLabels/ # smartcommon labels in the active language
│ │ ├── i18n/
│ │ └── utils/
│ │ ├── constants/
│ │ ├── functions/
│ │ └── maps/ # form.jsx and list.jsx
│ └── public/
│ ├── images/ # PWA icons
│ └── locales/<lang>/<ns>.json # One file per language AND per feature
├── pwa/ # Production build
│ ├── api.php # API router
│ └── .htaccess # Apache redirection
├── smartmaker-api/
│ ├── Controllers/ # Your PHP controllers
│ ├── dmGenericObject.php # Dolibarr mapping example
│ └── HomeController.php # Example controller
└── smartmaker-api-prepend.php # SmartAuth initialization
This tree is not decorative: it applies the organisation rules described in Architecture, in particular splitting the data layer into indexes.js and a useDb<Feature> hook, and splitting the translations per namespace.
Translations per namespace
The skeleton ships five namespaces, in French and in English: common, welcomePage, loginPage, deviceIdentificationPage and homePage.
const { t } = useTranslation('loginPage');
Warning
A Makefile deriving VITE_LOCALES must list the subdirectories of public/locales/, not the *.json files. Older recipes looked for locales/*.json: since the move to multiple namespaces they no longer find anything, the language list is empty and the application stays stuck on the fallback language.
Development proxy
If your Dolibarr is not served by the Vite development server, set the target in mobile/.env:
VITE_DEV_PROXY_TARGET=https://dolibarr.local/custom/monmodule/pwa
This is the directory that contains api.php, with no trailing slash. When the variable is missing or empty, no proxy is registered. The reasoning is explained in PWA, section "Front origin and API origin".
Two-pass linting
npm run lint runs oxlint then eslint .. The first pass is native and fast, the second covers what the first one does not handle. eslint-plugin-oxlint disables on the ESLint side the rules already checked by oxlint.
Dynamic PWA manifest
SmartBoot automatically configures the PWA manifest dynamically through SmartAuth. The index.html file points at api.php/manifest.webmanifest instead of a static file:
<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">
And in vite.config.js, the static manifest is disabled:
VitePWA({
// ...
manifest: false, // Served dynamically by SmartAuth
})
Important
This link is relative: it assumes the front and the API are served from the same origin, which is the case once the PWA is deployed in pwa/. In development, with vite dev on port 5173 and Dolibarr on another host, api.php has to be proxied. See PWA, section "Front origin and API origin".
See PWA for the Dolibarr constants, the two Service Worker modes and offline startup.
Layouts and route guards
SmartBoot only provides two visual layouts:
| Layout | Role |
|---|---|
PagesLayout |
Global layout: applies the theme, the dark mode and the scale |
AnimationLayout |
Handles the animated transitions between pages (Framer Motion) |
Authentication and device identification are handled by the <RouteGuard> component from smartcommon (4 modes: requireGuest, requireAuth, requireDeviceIdentification, requireDeviceIdentified). See Advanced components -> RouteGuard for the detailed documentation.
Router example (SmartBoot skeleton)
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 />}>
{/* Public pages: an authenticated user is sent back to / */}
<Route element={<RouteGuard requireGuest />}>
<Route path="/welcome" element={<WelcomePage />} />
<Route path="/login" element={<LoginPage />} />
</Route>
{/* Authenticated, device identification still to be done */}
<Route element={<RouteGuard requireDeviceIdentification />}>
<Route path="/device-identification" element={<DeviceIdentificationPage />} />
</Route>
{/* Authenticated + device identified: every private page */}
<Route element={<RouteGuard requireDeviceIdentified />}>
{/* AnimationLayout is mounted ONCE here, never page by page */}
<Route element={<AnimationLayout />}>
<Route path="/" element={<HomePage />} />
{/* Add your private routes here */}
</Route>
</Route>
<Route path="*" element={<Error404Page />} />
</Route>
</Routes>
);
Warning
No <BrowserRouter> here. The SmartCommon <Provider> already mounts one. Adding a second one produces the error You cannot render a <Router> inside another <Router>. A PWA served under a sub-path, or using hash-based deep links, passes config.router: "hash" and config.basename to the Provider rather than mounting its own router.
Note
Routes and Route are re-exported by SmartCommon: a page must never import react-router-dom directly. To navigate, use useNavigation().
Tip
Before the migration to smartcommon, SmartBoot provided four custom layouts (PrivatePagesLayout, PublicPagesLayout, PreDeviceIdentificationLayout, PostDeviceIdentificationLayout). They were replaced by <RouteGuard>, which centralises the logic in smartcommon and makes it possible to evolve without touching the skeleton.
Generated pages (LoginPage, DeviceIdentificationPage)
The public login and identification pages use the high-level components <LoginComponent> and <DeviceIdentificationComponent> from smartcommon. The skeleton only dresses them up (visual wrapper, background waves, register/forgot-password links) and wires onSuccess/onError to the local store.
<LoginComponent>includes the smartAuth QR pair flow for free (scan -> claim -> poll). See details.<DeviceIdentificationComponent>readsuseApi().user.deviceOptionsto display either a plain input (first device) or a radio + input (pairing). See details.
AboutModal
SmartBoot integrates the smartcommon AboutModal (automatic PWA update check through 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}
/>
This component displays:
- The application name and the version (
APP_VERSIONcoming fromVITE_APP_VERSION) - Optional free fields through the
fieldsprop - A "Check for updates" button that restarts the Service Worker
See Advanced components -> AboutModal for the labels and slots.
Example controller
SmartBoot generates an example HomeController.php with a dmGenericObject.php mapping:
// smartmaker-api/HomeController.php
class HomeController
{
public function index($arr = null)
{
global $db, $langs;
$ret = [
'statusCode' => 200,
'generic_message' => "",
'lastupdate' => "",
'home' => "",
];
return ([$ret, 200]);
}
}
Components mounted by default
The skeleton already mounts, in App.jsx, three cross-cutting SmartCommon components. You have nothing to add:
| Component | Role |
|---|---|
<UpdatePrompt> |
offers to reload when a new version is ready |
<InstallPrompt> |
offers to install the PWA on the device |
<ViewportProvider> |
exposes the display breakpoint (mobile, tablet, desktop) |
Note
InstallPrompt has no translation bundle: its default labels are in English. The skeleton passes it its own labels, to be translated in your namespaces. See PWA, section "Offering the installation".
Next steps
You can now move on to development:
- PHP development (back) - Routes and controllers
- React development (front) - User interface
- Architecture - file organisation, to read before adding your first feature