Différences
Ci-dessous, les différences entre deux révisions de la page.
| Les deux révisions précédentesRévision précédente | |||
| howto:devfront [2026/01/14 14:33] – [Lancement] caprel | howto:devfront [Date inconnue] (Version actuelle) – supprimée - modification externe (Date inconnue) 127.0.0.1 | ||
|---|---|---|---|
| Ligne 1: | Ligne 1: | ||
| - | # 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 '' | ||
| - | |||
| - | ``` | ||
| - | VITE_API_URL=https:// | ||
| - | VITE_APP_VERSION=dev | ||
| - | VITE_LOCALES=en, | ||
| - | ``` | ||
| - | |||
| - | ### Configuration de l' | ||
| - | |||
| - | Le fichier '' | ||
| - | |||
| - | ``` | ||
| - | // src/ | ||
| - | |||
| - | 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: " | ||
| - | logout: " | ||
| - | refresh: " | ||
| - | }, | ||
| - | }, | ||
| - | |||
| - | // Stockage | ||
| - | storage: { | ||
| - | local: [" | ||
| - | }, | ||
| - | |||
| - | // État global initial | ||
| - | globalState: | ||
| - | reducers: { | ||
| - | session: null, | ||
| - | settings: { lng: " | ||
| - | items: [], | ||
| - | }, | ||
| - | }, | ||
| - | |||
| - | // Animations de pages | ||
| - | pages: { | ||
| - | "/": | ||
| - | " | ||
| - | }, | ||
| - | }; | ||
| - | ``` | ||
| - | ## Lancement | ||
| - | |||
| - | ``` | ||
| - | cd mobile | ||
| - | npm run dev | ||
| - | ``` | ||
| - | |||
| - | Ouvrez http:// | ||
| - | |||
| - | Attention [[SmartAuth]] est nécessaire ! | ||
| - | |||
| - | ## Structure des pages | ||
| - | |||
| - | L' | ||
| - | |||
| - | ``` | ||
| - | src/ | ||
| - | ├── errors/ | ||
| - | │ | ||
| - | │ | ||
| - | ├── private/ | ||
| - | │ | ||
| - | │ | ||
| - | └── public/ | ||
| - | ├── LoginPage/ | ||
| - | │ | ||
| - | └── WelcomePage/ | ||
| - | └── index.jsx | ||
| - | ``` | ||
| - | |||
| - | ^ Dossier ^ Description ^ | ||
| - | | '' | ||
| - | | '' | ||
| - | | '' | ||
| - | |||
| - | ## Créer une page de login | ||
| - | |||
| - | Avec SmartCommon, | ||
| - | |||
| - | ``` | ||
| - | // src/ | ||
| - | |||
| - | import { useApi, useGlobalStates, | ||
| - | import { Form, Input, Button } from ' | ||
| - | import { z } from ' | ||
| - | |||
| - | const schema = z.object({ | ||
| - | login: z.string().min(1, | ||
| - | password: z.string().min(1, | ||
| - | }); | ||
| - | |||
| - | export const LoginPage = () => { | ||
| - | const api = useApi(); | ||
| - | const navigate = useNavigation(); | ||
| - | const [, setSession] = useGlobalStates(' | ||
| - | |||
| - | const form = useForm({ schema }); | ||
| - | |||
| - | const handleSubmit = async (data) => { | ||
| - | const response = await api.public.post(' | ||
| - | |||
| - | if (response.success) { | ||
| - | setSession(response.data); | ||
| - | navigate('/' | ||
| - | } | ||
| - | }; | ||
| - | |||
| - | return ( | ||
| - | <div className=" | ||
| - | <Form form={form} onSubmit={handleSubmit} className=" | ||
| - | <Input | ||
| - | name=" | ||
| - | label=" | ||
| - | placeholder=" | ||
| - | /> | ||
| - | <Input | ||
| - | name=" | ||
| - | type=" | ||
| - | label=" | ||
| - | placeholder=" | ||
| - | /> | ||
| - | <Button type=" | ||
| - | Connexion | ||
| - | </ | ||
| - | </ | ||
| - | </ | ||
| - | ); | ||
| - | }; | ||
| - | ``` | ||
| - | |||
| - | ## Créer une page privée | ||
| - | |||
| - | ### Page d' | ||
| - | |||
| - | ``` | ||
| - | // src/ | ||
| - | |||
| - | import { useApi, useGlobalStates, | ||
| - | import { useEffect } from ' | ||
| - | |||
| - | export const HomePage = () => { | ||
| - | const api = useApi(); | ||
| - | const navigate = useNavigation(); | ||
| - | const [items, setItems] = useGlobalStates(' | ||
| - | const [session, setSession] = useGlobalStates(' | ||
| - | |||
| - | // Charger les items au montage | ||
| - | useEffect(() => { | ||
| - | const fetchItems = async () => { | ||
| - | const response = await api.private.get(' | ||
| - | if (response.success) { | ||
| - | setItems(response.data); | ||
| - | } | ||
| - | }; | ||
| - | fetchItems(); | ||
| - | }, []); | ||
| - | |||
| - | // Déconnexion | ||
| - | const handleLogout = async () => { | ||
| - | await api.private.post(' | ||
| - | setSession(null); | ||
| - | navigate('/ | ||
| - | }; | ||
| - | |||
| - | return ( | ||
| - | <div className=" | ||
| - | <header className=" | ||
| - | <h1 className=" | ||
| - | <button onClick={handleLogout} className=" | ||
| - | Déconnexion | ||
| - | </ | ||
| - | </ | ||
| - | |||
| - | <div className=" | ||
| - | {items.map((item) => ( | ||
| - | <div | ||
| - | key={item.id} | ||
| - | onClick={() => navigate(`/ | ||
| - | className=" | ||
| - | > | ||
| - | <h2 className=" | ||
| - | <p className=" | ||
| - | </ | ||
| - | ))} | ||
| - | </ | ||
| - | </ | ||
| - | ); | ||
| - | }; | ||
| - | ``` | ||
| - | |||
| - | ### Page de détail | ||
| - | |||
| - | ``` | ||
| - | // src/ | ||
| - | |||
| - | import { useApi, useGlobalStates, | ||
| - | import { useEffect, useState } from ' | ||
| - | import { useParams } from ' | ||
| - | |||
| - | 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/ | ||
| - | if (response.success) { | ||
| - | setItem(response.data); | ||
| - | } | ||
| - | }; | ||
| - | fetchItem(); | ||
| - | }, [id]); | ||
| - | |||
| - | if (!item) { | ||
| - | return <div className=" | ||
| - | } | ||
| - | |||
| - | return ( | ||
| - | <div className=" | ||
| - | <header className=" | ||
| - | <button onClick={() => navigate(-1)} className=" | ||
| - | ← Retour | ||
| - | </ | ||
| - | </ | ||
| - | |||
| - | <div className=" | ||
| - | <h1 className=" | ||
| - | <p className=" | ||
| - | </ | ||
| - | </ | ||
| - | ); | ||
| - | }; | ||
| - | ``` | ||
| - | |||
| - | ## Configurer le routeur | ||
| - | |||
| - | ``` | ||
| - | // src/ | ||
| - | |||
| - | import { BrowserRouter, | ||
| - | import { PublicRoutes, | ||
| - | |||
| - | import { LoginPage } from ' | ||
| - | import { WelcomePage } from ' | ||
| - | import { HomePage } from ' | ||
| - | import { ItemPage } from ' | ||
| - | import { Error404Page } from ' | ||
| - | |||
| - | export const Router = () => { | ||
| - | return ( | ||
| - | < | ||
| - | < | ||
| - | {/* Routes publiques */} | ||
| - | <Route element={< | ||
| - | <Route path="/ | ||
| - | <Route path="/ | ||
| - | </ | ||
| - | |||
| - | {/* Routes privées */} | ||
| - | <Route element={< | ||
| - | <Route path="/" | ||
| - | <Route path="/ | ||
| - | </ | ||
| - | |||
| - | {/* Erreur 404 */} | ||
| - | <Route path=" | ||
| - | </ | ||
| - | </ | ||
| - | ); | ||
| - | }; | ||
| - | ``` | ||
| - | |||
| - | ### Guards avec SmartCommon | ||
| - | |||
| - | ``` | ||
| - | // src/ | ||
| - | |||
| - | import { Outlet, Navigate } from ' | ||
| - | import { useGlobalStates } from ' | ||
| - | |||
| - | export const PublicRoutes = () => { | ||
| - | const [session] = useGlobalStates(' | ||
| - | return session ? < | ||
| - | }; | ||
| - | |||
| - | export const PrivateRoutes = () => { | ||
| - | const [session] = useGlobalStates(' | ||
| - | return session ? <Outlet /> : < | ||
| - | }; | ||
| - | ``` | ||
| - | |||
| - | ## Utiliser les états globaux | ||
| - | |||
| - | ### Stocker des données | ||
| - | |||
| - | ``` | ||
| - | import { useGlobalStates } from ' | ||
| - | |||
| - | // Lecture + écriture | ||
| - | const [items, setItems] = useGlobalStates(' | ||
| - | |||
| - | // Lecture seule | ||
| - | const [items] = useGlobalStates(' | ||
| - | |||
| - | // Accès à une propriété imbriquée | ||
| - | const [settings, setSettings] = useGlobalStates(' | ||
| - | const [lng, setLng] = useGlobalStates(' | ||
| - | ``` | ||
| - | |||
| - | ### Persistance automatique | ||
| - | |||
| - | Les clés listées dans '' | ||
| - | |||
| - | ``` | ||
| - | // appConfig.js | ||
| - | storage: { | ||
| - | local: [" | ||
| - | } | ||
| - | ``` | ||
| - | |||
| - | ## Utiliser les formulaires | ||
| - | |||
| - | ### Formulaire complet avec validation | ||
| - | |||
| - | ``` | ||
| - | import { useForm, Form, Input, Select, Button } from ' | ||
| - | import { z } from ' | ||
| - | |||
| - | const schema = z.object({ | ||
| - | label: z.string().min(3, | ||
| - | type: z.enum([" | ||
| - | description: | ||
| - | }); | ||
| - | |||
| - | const CreateItemForm = ({ onSuccess }) => { | ||
| - | const api = useApi(); | ||
| - | const form = useForm({ schema }); | ||
| - | |||
| - | const handleSubmit = async (data) => { | ||
| - | const response = await api.private.post(' | ||
| - | if (response.success) { | ||
| - | onSuccess(response.data); | ||
| - | form.reset(); | ||
| - | } | ||
| - | }; | ||
| - | |||
| - | return ( | ||
| - | <Form form={form} onSubmit={handleSubmit}> | ||
| - | <Input name=" | ||
| - | <Select | ||
| - | name=" | ||
| - | label=" | ||
| - | options={[ | ||
| - | { value: " | ||
| - | { value: " | ||
| - | { value: " | ||
| - | ]} | ||
| - | /> | ||
| - | <Input name=" | ||
| - | <Button type=" | ||
| - | </ | ||
| - | ); | ||
| - | }; | ||
| - | ``` | ||
| - | |||
| - | ## 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 | ||
| - | |||
| - | * [[../ | ||
| - | * [[../ | ||
| - | * [[../ | ||
| - | * [[../ | ||
| - | * [[devback|Développement PHP]] - API backend | ||