Table des matières

Chapitre 1 : Structure du projet

Vue d'ensemble

Un projet SmartMaker se compose de deux parties :

  1. mobile/ : Code source React (développement)
  2. pwa/ : Application compilée + API PHP (production)

Arborescence complète

monmodule/                          # Module Dolibarr
├── mobile/                         # CODE SOURCE REACT
│   ├── src/
│   │   ├── App.jsx                # Composant racine
│   │   ├── main.jsx               # Point d'entrée
│   │   ├── appConfig.js           # Configuration
│   │   ├── components/
│   │   │   ├── app/
│   │   │   │   └── Router/        # Configuration routes
│   │   │   │       ├── index.jsx
│   │   │   │       └── Guards/    # Protection routes
│   │   │   └── pages/
│   │   │       ├── public/        # Pages sans auth
│   │   │       │   ├── LoginPage/
│   │   │       │   └── WelcomePage/
│   │   │       ├── private/       # Pages avec auth
│   │   │       │   ├── HomePage/
│   │   │       │   └── ItemPage/
│   │   │       └── errors/
│   │   │           └── Error404Page/
│   │   ├── redux/                 # Store Redux (optionnel)
│   │   │   └── reducers/
│   │   ├── i18n/                  # Configuration i18next
│   │   ├── utils/                 # Helpers, constantes
│   │   └── locales/               # Fichiers de traductions
│   ├── public/
│   │   ├── images/                # Icônes PWA
│   │   └── locales/               # Traductions
│   ├── index.html                 # Template HTML
│   ├── package.json               # Dépendances npm
│   ├── vite.config.js             # Configuration Vite
│   └── .env                       # Variables d'environnement
│
├── pwa/                           # APPLICATION COMPILÉE + API
│   ├── api.php                    # Point d'entrée API
│   └── dist/                      # Fichiers React compilés
│
├── smartmaker-api/                # BACKEND PHP
│   ├── Controllers/               # Logique métier
│   └── Mappers/                   # Conversion Dolibarr → DTO
│
└── smartmaker-api-prepend.php     # Bootstrap PHP

Détail des fichiers clés

main.jsx - Point d'entrée

snippet.javascript
// src/main.jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from './App';
import './index.css';
 
createRoot(document.getElementById('root')).render(
    <StrictMode>
        <App />
    </StrictMode>
);

C'est le fichier appelé par index.html. Il monte l'application React dans le DOM.

App.jsx - Composant racine

snippet.javascript
// src/App.jsx
import { Provider } from '@cap-rel/smartcommon';
import { Router } from './components/app/Router';
import { config } from './appConfig';
 
export const App = () => (
    <Provider config={config}>
        <Router />
    </Provider>
);

Le Provider de SmartCommon englobe toute l'application et fournit :

appConfig.js - Configuration

snippet.javascript
// src/appConfig.js
export const config = {
    debug: import.meta.env.DEV,
 
    api: {
        prefixUrl: import.meta.env.VITE_API_URL,
        timeout: 30000,
        paths: {
            login: "login",
            logout: "logout",
            refresh: "refresh"
        }
    },
 
    storage: {
        local: ["session", "settings"]
    },
 
    globalState: {
        reducers: {
            session: null,
            settings: { lng: "fr" },
            items: []
        }
    },
 
    pages: {
        "/": { "/settings": "slideLeft", "*": "fade" },
        "*": "fade"
    }
};

Détaillé dans le chapitre suivant.

index.html - Template

snippet.html
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Mon Application</title>
</head>
<body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
</body>
</html>

Vite injecte automatiquement les scripts compilés en production.

Organisation des composants

Structure recommandée pour une page

components/pages/private/ItemPage/
├── index.jsx           # Composant principal
├── ItemPage.module.css # Styles (optionnel)
└── components/         # Sous-composants locaux (optionnel)
    ├── ItemHeader.jsx
    └── ItemActions.jsx

Exemple de page

snippet.javascript
// components/pages/private/ItemPage/index.jsx
import { useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { Page, Block, Spinner } from '@cap-rel/smartcommon';
import { useApi, useStates } from '@cap-rel/smartcommon';
 
export const ItemPage = () => {
    const { id } = useParams();
    const api = useApi();
 
    const st = useStates({
        initialStates: {
            item: null,
            loading: true,
            error: null
        }
    });
 
    useEffect(() => {
        const fetchItem = async () => {
            try {
                const data = await api.private.get(`items/${id}`).json();
                st.set('item', data);
            } catch (err) {
                st.set('error', err.message);
            } finally {
                st.set('loading', false);
            }
        };
        fetchItem();
    }, [id]);
 
    if (st.get('loading')) {
        return <Page><Spinner /></Page>;
    }
 
    if (st.get('error')) {
        return <Page><Block>Erreur : {st.get('error')}</Block></Page>;
    }
 
    const item = st.get('item');
 
    return (
        <Page title={item.label}>
            <Block>
                <p>{item.description}</p>
            </Block>
        </Page>
    );
};

Le Router

Configuration des routes

snippet.javascript
// components/app/Router/index.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { PublicRoutes, PrivateRoutes } from './Guards';
 
// Pages
import { LoginPage } from '../../pages/public/LoginPage';
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="/login" element={<LoginPage />} />
                </Route>
 
                {/* Routes protégées */}
                <Route element={<PrivateRoutes />}>
                    <Route path="/" element={<HomePage />} />
                    <Route path="/items/:id" element={<ItemPage />} />
                </Route>
 
                {/* Fallback */}
                <Route path="*" element={<Error404Page />} />
            </Routes>
        </BrowserRouter>
    );
};

Guards d'authentification

snippet.javascript
// components/app/Router/Guards/index.jsx
import { Outlet, Navigate } from 'react-router-dom';
import { useGlobalStates } from '@cap-rel/smartcommon';
 
export const PrivateRoutes = () => {
    const [session] = useGlobalStates('session');
    return session ? <Outlet /> : <Navigate to="/login" />;
};
 
export const PublicRoutes = () => {
    const [session] = useGlobalStates('session');
    return session ? <Navigate to="/" /> : <Outlet />;
};

Variables d'environnement

snippet.bash
# .env
VITE_API_URL=https://mondomaine.com/modules/monmodule/pwa/api.php

Accès dans le code :

snippet.javascript
const apiUrl = import.meta.env.VITE_API_URL;
const isDev = import.meta.env.DEV;
const isProd = import.meta.env.PROD;

Important : Les variables doivent commencer par VITE_ pour être exposées au client.

Dossier pwa/ - Production

Après compilation (npm run build), les fichiers sont générés dans pwa/dist/ :

pwa/
├── api.php           # Point d'entrée API (à créer)
└── dist/
    ├── index.html    # HTML avec assets injectés
    ├── assets/
    │   ├── index-abc123.js   # Bundle JS
    │   └── index-def456.css  # Bundle CSS
    └── images/

Points clés à retenir

  1. mobile/ = code source pour développement
  2. pwa/ = code compilé pour production
  3. App.jsx englobe tout avec le Provider SmartCommon
  4. appConfig.js centralise la configuration
  5. Router gère les routes avec guards d'authentification
  6. Variables d'environnement préfixées par VITE_

← Retour au module | Chapitre suivant : Configuration →