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

// 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

// 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 :

  • Configuration globale
  • État Redux
  • Contexte API
  • i18next

appConfig.js - Configuration

// 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

<!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

// 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

// 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

// components/app/Router/Guards/index.jsx
import { Outlet, Navigate } from 'react-router-dom';
import { useGlobalStates } from '@cap-rel/smartcommon';

export const PrivateRoutes = () => {
    const gst = useGlobalStates();
    return gst.get('session') ? <Outlet /> : <Navigate to="/login" />;
};

export const PublicRoutes = () => {
    const gst = useGlobalStates();
    return gst.get('session') ? <Navigate to="/" /> : <Outlet />;
};

Variables d'environnement

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

Accès dans le code :

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 ->