Développement React (front)
Vous venez de déployer SmartBoot dans votre module et vous vous demandez par où commencer ? Vous êtes au bon endroit !
Installation des dépendances
cd mobile npm i
Configuration initiale
Fichier .env
Copiez ou renommez mobile/.env.example en mobile/.env :
VITE_API_URL=https://votre-dolibarr.com/custom/monmodule/pwa/api.php VITE_APP_VERSION=dev VITE_LOCALES=en,fr
Configuration de l'application
Le fichier appConfig.js centralise toute la configuration :
// src/appConfig.js
export const config = {
// Mode debug (logs colorés dans la console)
debug: import.meta.env.DEV,
// Configuration API
api: {
prefixUrl: import.meta.env.VITE_API_URL,
timeout: 30000,
paths: {
login: "login",
logout: "logout",
refresh: "refresh",
},
},
// Stockage
storage: {
local: ["session", "settings"], // Persisté en localStorage
},
// État global initial
globalState: {
reducers: {
session: null,
settings: { lng: "fr" },
items: [],
},
},
// Animations de pages
pages: {
"/": { "/settings": "slideLeft", "*": "fade" },
"*": "fade",
},
};
Lancement
cd mobile npm run dev
Ouvrez http://localhost:5173/ (mode mobile recommandé dans les DevTools).
Attention SmartAuth est nécessaire !
Structure des pages
L'arborescence préinstallée par SmartBoot :
src/components/pages/
├── errors/
│ └── Error404Page/
│ └── index.jsx
├── private/
│ └── HomePage/
│ └── index.jsx
└── public/
├── LoginPage/
│ └── index.jsx
└── WelcomePage/
└── index.jsx
| Dossier | Description |
|---|---|
public/ | Pages accessibles sans authentification |
private/ | Pages nécessitant une authentification |
errors/ | Pages d'erreur (404, etc.) |
Créer une page de login
Avec SmartCommon, la page de login devient très simple :
// src/components/pages/public/LoginPage/index.jsx
import { useApi, useGlobalStates, useForm, useNavigation } from '@cap-rel/smartcommon';
import { Form, Input, Button } from '@cap-rel/smartcommon';
import { z } from 'zod';
const schema = z.object({
login: z.string().min(1, "Login requis"),
password: z.string().min(1, "Mot de passe requis"),
});
export const LoginPage = () => {
const api = useApi();
const navigate = useNavigation();
const [, setSession] = useGlobalStates('session');
const form = useForm({ schema });
const handleSubmit = async (data) => {
const response = await api.public.post('login', { json: data });
if (response.success) {
setSession(response.data);
navigate('/');
}
};
return (
<div className="fixed inset-0 bg-white flex justify-center items-center p-10">
<Form form={form} onSubmit={handleSubmit} className="flex flex-col gap-6 w-full max-w-sm">
<Input
name="login"
label="Identifiant"
placeholder="Votre login..."
/>
<Input
name="password"
type="password"
label="Mot de passe"
placeholder="●●●●●●●●"
/>
<Button type="submit" loading={form.formState.isSubmitting}>
Connexion
</Button>
</Form>
</div>
);
};
Créer une page privée
Page d'accueil avec liste
// src/components/pages/private/HomePage/index.jsx
import { useApi, useGlobalStates, useNavigation } from '@cap-rel/smartcommon';
import { useEffect } from 'react';
export const HomePage = () => {
const api = useApi();
const navigate = useNavigation();
const [items, setItems] = useGlobalStates('items');
const [session, setSession] = useGlobalStates('session');
// Charger les items au montage
useEffect(() => {
const fetchItems = async () => {
const response = await api.private.get('items');
if (response.success) {
setItems(response.data);
}
};
fetchItems();
}, []);
// Déconnexion
const handleLogout = async () => {
await api.private.post('logout');
setSession(null);
navigate('/login');
};
return (
<div className="min-h-screen bg-gray-100 p-4">
<header className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">Mes items</h1>
<button onClick={handleLogout} className="text-red-500">
Déconnexion
</button>
</header>
<div className="space-y-4">
{items.map((item) => (
<div
key={item.id}
onClick={() => navigate(`/items/${item.id}`)}
className="bg-white p-4 rounded-lg shadow"
>
<h2 className="font-semibold">{item.label}</h2>
<p className="text-gray-600">{item.description}</p>
</div>
))}
</div>
</div>
);
};
Page de détail
// src/components/pages/private/ItemPage/index.jsx
import { useApi, useGlobalStates, useNavigation } from '@cap-rel/smartcommon';
import { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
export const ItemPage = () => {
const { id } = useParams();
const api = useApi();
const navigate = useNavigation();
const [item, setItem] = useState(null);
useEffect(() => {
const fetchItem = async () => {
const response = await api.private.get(`items/${id}`);
if (response.success) {
setItem(response.data);
}
};
fetchItem();
}, [id]);
if (!item) {
return <div className="p-4">Chargement...</div>;
}
return (
<div className="min-h-screen bg-gray-100">
<header className="bg-white p-4 shadow">
<button onClick={() => navigate(-1)} className="text-blue-500">
← Retour
</button>
</header>
<div className="p-4">
<h1 className="text-2xl font-bold mb-4">{item.label}</h1>
<p className="text-gray-600">{item.description}</p>
</div>
</div>
);
};
Configurer le routeur
// src/components/app/Router/index.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { PublicRoutes, PrivateRoutes } from './Guards';
import { LoginPage } from '../../pages/public/LoginPage';
import { WelcomePage } from '../../pages/public/WelcomePage';
import { HomePage } from '../../pages/private/HomePage';
import { ItemPage } from '../../pages/private/ItemPage';
import { Error404Page } from '../../pages/errors/Error404Page';
export const Router = () => {
return (
<BrowserRouter>
<Routes>
{/* Routes publiques */}
<Route element={<PublicRoutes />}>
<Route path="/welcome" element={<WelcomePage />} />
<Route path="/login" element={<LoginPage />} />
</Route>
{/* Routes privées */}
<Route element={<PrivateRoutes />}>
<Route path="/" element={<HomePage />} />
<Route path="/items/:id" element={<ItemPage />} />
</Route>
{/* Erreur 404 */}
<Route path="*" element={<Error404Page />} />
</Routes>
</BrowserRouter>
);
};
Guards avec SmartCommon
// src/components/app/Router/Guards/index.jsx
import { Outlet, Navigate } from 'react-router-dom';
import { useGlobalStates } from '@cap-rel/smartcommon';
export const PublicRoutes = () => {
const [session] = useGlobalStates('session');
return session ? <Navigate to="/" /> : <Outlet />;
};
export const PrivateRoutes = () => {
const [session] = useGlobalStates('session');
return session ? <Outlet /> : <Navigate to="/login" />;
};
Utiliser les états globaux
Stocker des données
import { useGlobalStates } from '@cap-rel/smartcommon';
// Lecture + écriture
const [items, setItems] = useGlobalStates('items');
// Lecture seule
const [items] = useGlobalStates('items');
// Accès à une propriété imbriquée
const [settings, setSettings] = useGlobalStates('settings');
const [lng, setLng] = useGlobalStates('settings.lng');
Persistance automatique
Les clés listées dans config.storage.local sont automatiquement persistées en localStorage :
// appConfig.js
storage: {
local: ["session", "settings"], // Persisté automatiquement
}
Utiliser les formulaires
Formulaire complet avec validation
import { useForm, Form, Input, Select, Button } from '@cap-rel/smartcommon';
import { z } from 'zod';
const schema = z.object({
label: z.string().min(3, "Minimum 3 caractères"),
type: z.enum(["A", "B", "C"]),
description: z.string().optional(),
});
const CreateItemForm = ({ onSuccess }) => {
const api = useApi();
const form = useForm({ schema });
const handleSubmit = async (data) => {
const response = await api.private.post('items', { json: data });
if (response.success) {
onSuccess(response.data);
form.reset();
}
};
return (
<Form form={form} onSubmit={handleSubmit}>
<Input name="label" label="Libellé" />
<Select
name="type"
label="Type"
options={[
{ value: "A", label: "Type A" },
{ value: "B", label: "Type B" },
{ value: "C", label: "Type C" },
]}
/>
<Input name="description" label="Description" multiline rows={4} />
<Button type="submit">Créer</Button>
</Form>
);
};
Build et déploiement
# Build de production npm run build # Le build génère le dossier dist/ # Copiez-le dans le dossier pwa/ de votre module cp -r dist/* ../pwa/
Ou utilisez le Makefile :
make pwa
Voir aussi
- Hooks - Documentation complète des hooks
- SmartCommon - Tous les composants disponibles
- Configuration - Options du Provider
- Requêtes API - Utilisation de useApi
- Développement PHP - API backend