Chapter 1: Project Structure
Overview
A SmartMaker project consists of two parts:
- mobile/: React source code (development)
- 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
Recommended Structure for a Page
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
- mobile/ = source code for development
- pwa/ = compiled code for production
- App.jsx wraps everything with the SmartCommon Provider
- appConfig.js centralizes configuration
- Router manages routes with authentication guards
- Environment variables prefixed with VITE_