# 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 (
); }; ``` ## 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 (

Mes items

{items.map((item) => (
navigate(`/items/${item.id}`)} className="bg-white p-4 rounded-lg shadow" >

{item.label}

{item.description}

))}
); }; ``` ### 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
Chargement...
; } return (

{item.label}

{item.description}

); }; ``` ## 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 ( {/* Routes publiques */} }> } /> } /> {/* Routes privées */} }> } /> } /> {/* Erreur 404 */} } /> ); }; ``` ### 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 ? : ; }; export const PrivateRoutes = () => { const [session] = useGlobalStates('session'); return session ? : ; }; ``` ## 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 (
); }; ``` ## 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 * [[../front/hooks|Hooks]] - Documentation complète des hooks * [[../front/smartcommon|SmartCommon]] - Tous les composants disponibles * [[../front/configuration|Configuration]] - Options du Provider * [[../front/requetes_api|Requêtes API]] - Utilisation de useApi * [[devback|Développement PHP]] - API backend