Table des matières

Composants avancés (high-level)

SmartCommon expose plusieurs composants “métier” qui encapsulent un flux complet (login, identification d'appareil, scan de QR/codes-barres, navigation dans un catalogue produit, annotation de photos, modal “À propos”). Plutôt que de réinventer ces écrans dans chaque application, on les injecte directement.

Tous suivent la même convention :

Voir aussi SmartCommon pour la liste complète des composants.

LoginComponent

Formulaire de connexion Dolibarr complet (email + mot de passe + sélection d'entité optionnelle + checkbox “Se souvenir de moi”) avec flux QR pair smartAuth intégré par défaut.

Usage de base

import { LoginComponent } from '@cap-rel/smartcommon';

<LoginComponent
  onSuccess={(user) => navigate("/")}
  onError={(err) => log.error(err)}
/>

Avec libellés et options

<LoginComponent
  onSuccess={handleSuccess}
  onError={handleError}
  showRememberMe                          // false par défaut
  showEntities                            // true par défaut
  enableQrPair                            // true par défaut
  deviceLabel={navigator.userAgent}
  qrPollIntervalMs={2000}
  qrTimeoutMs={120000}
  labels={{
    emailLabel: t("login.email"),
    passwordLabel: t("login.password"),
    submitLabel: t("login.submit"),
    scanQrLabel: t("login.scan-qr"),
    qrSeparator: t("login.or"),
  }}
  // Mapping d'erreurs custom (optionnel)
  getErrorLabel={(err) => err?.statusCode === 401 ? "Identifiants invalides" : null}
/>

Slots de styling

containerProps, formProps, inputProps, passwordInputProps, selectProps, booleanProps, submitButtonProps, scanQrButtonProps, qrSeparatorProps, qrOverlayProps, errorAlertProps, qrErrorAlertProps.

Chaque slot est étalé sur l'élément cible avec twMerge, donc les classes Tailwind se résolvent correctement (gap-4 vs gap-6).

Comportements clés

<note tip>Le QR pair est activé par défaut car SmartCommon présume que le backend est SmartAuth (qui ship /qr-pair). Pour désactiver : enableQrPair={false}.</note>

DeviceIdentificationComponent

Formulaire d'identification d'appareil pour SmartAuth. Lit useApi().user.deviceOptions pour décider du rendu :

Usage

import { DeviceIdentificationComponent } from '@cap-rel/smartcommon';

<DeviceIdentificationComponent
  onSuccess={() => navigate("/")}
  onError={(err) => toast.error(err.message)}
  labels={{
    title: t("device.title"),
    devicesDescription: t("device.choose-existing"),
    noDevicesDescription: t("device.first-device"),
    devicesCheckerLabel: t("device.devices"),
    noDeviceLabel: t("device.new-device"),
    newDeviceInputLabel: t("device.label"),
    newDeviceInputHelp: t("device.help"),
    submitLabel: t("device.submit"),
  }}
/>

Slots de styling

containerProps, formProps, iconWrapperProps, iconProps, titleProps, descriptionProps, devicesCheckerProps, labelInputProps, submitButtonProps, errorAlertProps.

Notes

RouteGuard

Composant de garde pour react-router qui remplace 4 layouts custom historiques (PublicPagesLayout, PrivatePagesLayout, PreDeviceIdentificationLayout, PostDeviceIdentificationLayout).

Lit useApi().user (et user.deviceOptions pour les modes device) et soit rend ses enfants (ou <Outlet /> s'il est utilisé comme route element), soit redirige via <Navigate>.

Quatre modes mutuellement orthogonaux

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

<Routes>
  // Pages publiques sans utilisateur (login, register, forgot-password)
  <Route element={<RouteGuard requireGuest />}>
    <Route path="/login" element={<LoginPage />} />
    <Route path="/welcome" element={<WelcomePage />} />
  </Route>

  // Page d'identification d'appareil : user authentifié + deviceOptions présent
  <Route element={<RouteGuard requireDeviceIdentification />}>
    <Route path="/device-identification" element={<DeviceIdentificationPage />} />
  </Route>

  // Toutes les pages privées : user authentifié + deviceOptions absent
  <Route element={<RouteGuard requireDeviceIdentified redirectTo="/device-identification" />}>
    <Route path="/" element={<HomePage />} />
    <Route path="/settings" element={<SettingsPage />} />
  </Route>

  // Cas plus rare : authentifié, peu importe le device
  <Route element={<RouteGuard requireAuth />}>
    <Route path="/profile" element={<ProfilePage />} />
  </Route>
</Routes>

Redirections par défaut

Mode Redirige vers
requireAuth /login
requireGuest /
requireDeviceIdentification / (déjà identifié)
requireDeviceIdentified /identify-device (à identifier)

Surchargeable via redirectTo. Les modes device impliquent toujours requireAuth : si pas d'utilisateur, redirige vers /login quel que soit le redirectTo.

Conflits

requireAuth + requireGuest ou les deux modes device détectés via console.warn. Le mode listé en premier l'emporte.

AboutModal

Modal “À propos” affichant le nom de l'application, la version et des champs libres. Bouton intégré “Vérifier les mises à jour” qui utilise usePWAUpdate pour relancer le Service Worker.

Usage

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

<AboutModal
  open={isOpen}
  onClose={() => setIsOpen(false)}
  appName="SmartPOS"
  version={APP_VERSION}
  fields={[
    { label: "Backend", value: prefixUrl },
    { label: "Utilisateur", value: user?.email },
  ]}
  labels={{
    title: t("about.title"),
    checkUpdates: t("about.check-updates"),
    upToDate: t("about.up-to-date"),
    installUpdate: t("about.install"),
    close: t("about.close"),
  }}
/>

Notes

BarcodeScanner

Scanner QR / codes-barres plein écran. Lazy-load la dépendance html5-qrcode (~150 kB) à la première ouverture, donc les apps qui ne scannent jamais ne paient pas le coût du bundle. Fallback en saisie manuelle si la permission caméra est refusée.

Usage

import { BarcodeScanner } from '@cap-rel/smartcommon';

const [open, setOpen] = useState(false);

<BarcodeScanner
  open={open}
  onScan={(text) => addToCart(text)}
  onClose={() => setOpen(false)}
  continuous={false}                  // ferme après chaque scan (défaut)
  formats={["QR_CODE", "EAN_13"]}     // 7 formats courants par défaut
  debounceMs={1500}
  labels={{
    title: "Scanner",
    cancelButton: "Annuler",
    manualEntry: "Saisie manuelle",
  }}
/>

Notes