Chapter 1: Project Structure

Overview

A SmartMaker project consists of two parts:

  1. mobile/: React source code (development)
  2. pwa/: Compiled application + PHP API (production)

Complete Tree Structure

monmodule/                          # Dolibarr module
├── mobile/                         # REACT SOURCE CODE
│   ├── src/
│   │   ├── App.jsx                # Root component
│   │   ├── main.jsx               # Entry point
│   │   ├── appConfig.js           # Configuration
│   │   ├── components/
│   │   │   ├── app/
│   │   │   │   └── Router/        # Route configuration
│   │   │   │       ├── index.jsx
│   │   │   │       └── Guards/    # Route protection
│   │   │   └── pages/
│   │   │       ├── public/        # Pages without auth
│   │   │       │   ├── LoginPage/
│   │   │       │   └── WelcomePage/
│   │   │       ├── private/       # Pages with auth
│   │   │       │   ├── HomePage/
│   │   │       │   └── ItemPage/
│   │   │       └── errors/
│   │   │           └── Error404Page/
│   │   ├── redux/                 # Redux store (optional)
│   │   │   └── reducers/
│   │   ├── i18n/                  # i18next configuration
│   │   ├── utils/                 # Helpers, constants
│   │   └── locales/               # Translation files
│   ├── public/
│   │   ├── images/                # PWA icons
│   │   └── locales/               # Translations
│   ├── index.html                 # HTML template
│   ├── package.json               # npm dependencies
│   ├── vite.config.js             # Vite configuration
│   └── .env                       # Environment variables
│
├── pwa/                           # COMPILED APPLICATION + API
│   ├── api.php                    # API entry point
│   └── dist/                      # Compiled React files
│
├── smartmaker-api/                # PHP BACKEND
│   ├── Controllers/               # Business logic
│   └── Mappers/                   # Dolibarr -> DTO conversion
│
└── smartmaker-api-prepend.php     # PHP bootstrap

Key Files Detail

main.jsx - Entry Point

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

This is the file called by index.html. It mounts the React application in the DOM.

App.jsx - Root Component

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

The SmartCommon Provider wraps the entire application and provides:

  • Global configuration
  • Redux store
  • API context
  • 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"
    }
};

Detailed in the next chapter.

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>My Application</title>
</head>
<body>
    <div id="root"></div>
    <script type="module" src="/src/main.jsx"></script>
</body>
</html>

Vite automatically injects the compiled scripts in production.

Component Organization

components/pages/private/ItemPage/
├── index.jsx           # Main component
├── ItemPage.module.css # Styles (optional)
└── components/         # Local sub-components (optional)
    ├── ItemHeader.jsx
    └── ItemActions.jsx

Example 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>Error: {st.get('error')}</Block></Page>;
    }

    const item = st.get('item');

    return (
        <Page title={item.label}>
            <Block>
                <p>{item.description}</p>
            </Block>
        </Page>
    );
};

Router

Route Configuration

// 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>
                {/* Public routes */}
                <Route element={<PublicRoutes />}>
                    <Route path="/login" element={<LoginPage />} />
                </Route>

                {/* Protected routes */}
                <Route element={<PrivateRoutes />}>
                    <Route path="/" element={<HomePage />} />
                    <Route path="/items/:id" element={<ItemPage />} />
                </Route>

                {/* Fallback */}
                <Route path="*" element={<Error404Page />} />
            </Routes>
        </BrowserRouter>
    );
};

Authentication Guards

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

Environment Variables

# .env
VITE_API_URL=https://mydomain.com/modules/mymodule/pwa/api.php

Access in code:

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

Important: Variables must start with VITE_ to be exposed to the client.

pwa/ Directory - Production

After compilation (npm run build), files are generated in pwa/dist/:

pwa/
├── api.php           # API entry point (to be created)
└── dist/
    ├── index.html    # HTML with injected assets
    ├── assets/
    │   ├── index-abc123.js   # JS bundle
    │   └── index-def456.css  # CSS bundle
    └── images/

Key Points to Remember

  1. mobile/ = source code for development
  2. pwa/ = compiled code for production
  3. App.jsx wraps everything with the SmartCommon Provider
  4. appConfig.js centralizes configuration
  5. Router manages routes with authentication guards
  6. Environment variables prefixed with VITE_

Back to Module | Next Chapter: Configuration ->