Chatbot React avec Prestashop

Dans ce cours, on va voir comment intégrer un chatbot développé en React et Node.js (Hono) dans une boutique PrestaShop.

L'idée, c'est de ne pas coder le chatbot dans PrestaShop directement, mais de le faire à côté (hébergé ailleurs) et de l'afficher sur le site comme un widget. C'est ce qu'on appelle une architecture "Headless" ou découplée.

Objectifs

  • Comprendre l'architecture globale
  • Créer l'API Node.js avec Hono (le cerveau du bot)
  • Créer le widget React (l'interface visuelle)
  • Créer le module PrestaShop pour faire le lien (le pont)

Architecture

En gros, on va avoir 3 morceaux distincts qui communiquent ensemble :

  1. PrestaShop : Le site e-commerce. Il affiche juste le script du bot.
  2. L'API (Back) : Un serveur Node.js (Hono) qui gère la logique et les réponses.
  3. Le Widget (Front) : Une petite app React injectée par dessus PrestaShop.

1. Le Backend (Node.js + Hono)

On commence par le serveur. On utilise Hono parce que c'est léger, rapide et compatible avec tout (Node, Deno, Bun, Cloudflare Workers).

Création du projet

		# Crée un nouveau projet Hono dans le dossier 'chatbot-api'
npm create hono@latest chatbot-api
	

Le code du serveur

Dans src/index.ts, on va juste faire une API simple qui accepte des messages. Important : on doit configuerer CORS.

src/index.ts
		import { Hono } from 'hono';
import { cors } from 'hono/cors';
 
const app = new Hono();
 
// --- Configuration CORS ---
// On autorise le front (localhost en dev, et le vrai site en prod)
// C'est INDISPENSABLE pour que le widget React puisse parler à l'API.
app.use(
	'/*',
	cors({
		// Liste des domaines autorisés à nous contacter
		origin: ['http://localhost:5173', 'https://votre-boutique.com'],
		allowMethods: ['POST', 'GET', 'OPTIONS']
	})
);
 
// Route de test pour voir si le serveur tourne
app.get('/', (c) => c.text('Chatbot API en ligne !'));
 
// Route principale : réception du message
app.post('/message', async (c) => {
	// "c" est le contexte Hono. Il contient la requête (req) et la réponse (res).
 
	// 1. On récupère le JSON envoyé par React
	const { message, context } = await c.req.json();
 
	// 2. Logique métier (simulée ici)
	// Le "context" contient les infos injectées par PrestaShop (panier, nom...)
	console.log(`Message de ${context?.firstName || 'Invité'}: ${message}`);
 
	// Ici, vous connecteriez une IA ou une logique de réponse custom.
 
	// 3. On renvoie la réponse au format JSON
	return c.json({
		reply: `J'ai reçu : "${message}".`
	});
});
 
export default app;
	

C'est tout pour le back. Il faudra l'héberger quelque part (VPS, Vercel, Render...) pour qu'il soit accessible via une URL publique (ex: https://api.mon-bot.com).


2. Le Frontend (Widget React)

Maintenant, l'interface. Ce n'est pas une page entière, c'est un widget flottant qui viendra se poser par-dessus le site.

Configurer Vite

Par défaut Vite met des noms bizarres (hashés) aux fichiers pour le cache (ex: index-A8z9.js). Ici, comme on veut inclure ce fichier dans PrestaShop via une balise <script>, on veut un nom fixe.

vite.config.js
		import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
 
export default defineConfig({
	plugins: [react()],
	build: {
		rollupOptions: {
			output: {
				// On force le nom du fichier de sortie pour qu'il soit toujours le même
				// C'est plus facile à inclure dans le module PrestaShop après.
				entryFileNames: 'assets/chatbot-widget.js',
				assetFileNames: 'assets/chatbot-style.css'
			}
		}
	}
});
	

Point d'entrée (main.jsx)

Dans une app React classique, on se greffe sur <div id="root">. Mais sur PrestaShop, cette div n'existe pas. On va donc viser un ID spécifique qu'on créera nous-même via le module PHP.

src/main.jsx
		import React from 'react';
import ReactDOM from 'react-dom/client';
import ChatWidget from './ChatWidget';
import './index.css';
 
// On cible un ID unique qu'on a défini dans le footer.tpl de PrestaShop
const TARGET_ID = 'kesval-chatbot-root';
const rootElement = document.getElementById(TARGET_ID);
 
// On vérifie si la div existe avant d'essayer d'y mettre React
// (Pour éviter les erreurs si le module est désactivé)
if (rootElement) {
	ReactDOM.createRoot(rootElement).render(
		<React.StrictMode>
			<ChatWidget />
		</React.StrictMode>
	);
}
	

Le Composant Widget

Voici le cœur du chat. On va utiliser window.PRESTASHOP_CONTEXT (une variable globale qu'on va créer via PHP juste après) pour savoir qui est connecté sur la boutique.

src/ChatWidget.jsx
		import { useState } from 'react';
 
export default function ChatWidget() {
	// État pour ouvrir/fermer le chat
	const [isOpen, setIsOpen] = useState(false);
	// État pour stocker l'historique des messages
	const [messages, setMessages] = useState([]);
 
	// --- RÉCUPÉRATION DU CONTEXTE PRESTASHOP ---
	// On lit la variable globale injectée par le module PHP.
	// Si elle n'existe pas (ex: en dev local), on prend un objet vide {}.
	const psContext = window.PRESTASHOP_CONTEXT || {};
 
	const sendMessage = async (e) => {
		// Empêche le rechargement de la page lors de la soumission du formulaire
		e.preventDefault();
 
		// On utilise FormData pour récupérer les valeurs du formulaire facilement
		// C'est souvent plus performant que de lier chaque input à un state (controlled inputs)
		const formData = new FormData(e.target);
		const text = formData.get('message')?.toString().trim();
 
		if (!text) return;
 
		// On vide l'input visuellement
		e.target.reset();
 
		// 1. Optimistic UI : On affiche le message de l'utilisateur tout de suite
		setMessages((prev) => [...prev, { text, sender: 'user' }]);
 
		try {
			// 2. On envoie le message ET le contexte au back
			const res = await fetch('https://api.mon-bot.com/message', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({
					message: text,
					context: psContext // On envoie les infos du client (Nom, Panier...) au bot
				})
			});
 
			const data = await res.json();
 
			// 3. On affiche la réponse du bot
			setMessages((prev) => [...prev, { text: data.reply, sender: 'bot' }]);
		} catch (err) {
			console.error('Erreur bot', err);
			// En vrai projet, affichez un message d'erreur à l'utilisateur ici
		}
	};
 
	return (
		<>
			{/* BOUTON D'OUVERTURE (visible si fermé) */}
			{!isOpen && (
				<button className="chatbot-launcher" onClick={() => setIsOpen(true)}>
					💬 Aide
				</button>
			)}
 
			{/* FENÊTRE DU CHAT (visible si ouvert) */}
			{isOpen && (
				<div className="chatbot-window">
					<div className="header">
						{/* On utilise le prénom venant de PrestaShop */}
						<span>Bonjour {psContext.firstName || 'Visiteur'} 👋</span>
						<button onClick={() => setIsOpen(false)}>X</button>
					</div>
 
					<div className="messages">
						{messages.map((m, i) => (
							<div key={i} className={`msg ${m.sender}`}>
								{m.text}
							</div>
						))}
					</div>
 
					<form className="input-area" onSubmit={sendMessage}>
						<input name="message" placeholder="Une question ?" />
						<button type="submit">Envoyer</button>
					</form>
				</div>
			)}
		</>
	);
}
	

Une fois le code fini, lancez npm run build. Vous obtiendrez le fichier dist/assets/chatbot-widget.js. Il faudra héberger ce fichier (sur le même serveur que l'API ou un CDN) pour pouvoir l'importer dans PrestaShop.


3. Le Module PrestaShop (Le lien)

C'est la dernière étape. On doit dire à PrestaShop deux choses :

  1. "Affiche cette div vide (pour React) dans le footer".
  2. "Donne-moi les infos du client (Nom, Panier) pour le JS".

Pour faire ça proprement, on crée un module. Créez le dossier modules/kesvalchatbot/.

Le fichier principal (kesvalchatbot.php)

Ce fichier PHP décrit le module et ses actions. On utilise le hook displayFooter pour insérer notre code sur toutes les pages.

modules/kesvalchatbot/kesvalchatbot.php
		<?php
// Sécurité : Interdit l'accès direct à ce fichier si on n'est pas dans PrestaShop
if (!defined('_PS_VERSION_')) exit;
 
class KesvalChatbot extends Module
{
    public function __construct()
    {
        $this->name = 'kesvalchatbot';
        $this->tab = 'front_office_features';
        $this->version = '1.0.0';
        $this->author = 'Kesval';
        $this->bootstrap = true; // Utilise les styles Bootstrap dans la config du module
        parent::__construct();
 
        $this->displayName = $this->l('Chatbot React');
        $this->description = $this->l('Intégration d'un chatbot React externe');
    }
 
    // Fonction appelée lors de l'installation du module
    public function install()
    {
        // On enregistre le module et on s'accroche au hook "displayFooter"
        // C'est ce qui permet d'afficher du contenu en bas de page
        return parent::install() && $this->registerHook('displayFooter');
    }
 
    // Fonction exécutée à chaque fois que le footer est affiché
    public function hookDisplayFooter($params)
    {
        // 1. On récupère les infos via le contexte global de PrestaShop
        $customer = $this->context->customer;
        $cart = $this->context->cart;
 
        // 2. On prépare un tableau propre avec seulement les données utiles pour le JS
        $contextData = [
            'isLogged' => $customer->isLogged(),
            'id' => $customer->isLogged() ? $customer->id : null,
            'firstName' => $customer->isLogged() ? $customer->firstname : 'Invité',
            // Calcul du total du panier (si un panier existe)
            'cartTotal' => $cart ? $cart->getOrderTotal(true, Cart::BOTH) : 0
        ];
 
        // 3. On envoie ces données au template Smarty
        // Elles seront accessibles dans le .tpl via la variable $chatbot_json
        $this->context->smarty->assign([
            'chatbot_json' => json_encode($contextData)
        ]);
 
        // 4. On affiche le template footer.tpl
        return $this->display(__FILE__, 'views/templates/hook/footer.tpl');
    }
}
	

Le Template (footer.tpl)

C'est ici qu'on fait le pont final entre PHP (PrestaShop) et JS (React).

Créez le chemin : modules/kesvalchatbot/views/templates/hook/footer.tpl.

modules/kesvalchatbot/views/templates/hook/footer.tpl
		<!-- 1. On définit la variable globale JS avec les données venant du PHP -->
<script>
	   // On crée une variable globale window.PRESTASHOP_CONTEXT
	   // Le 'nofilter' est CRUCIAL pour avoir du JSON valide
	window.PRESTASHOP_CONTEXT = {$chatbot_json nofilter};
</script>
 
<!-- 2. La div cible où React va s'accrocher (Mount Point) -->
<div id="kesval-chatbot-root"></div>
 
<!-- 3. Le script du bot (hébergé ailleurs) -->
<!-- Important : type="module" pour supporter les imports modernes -->
<script type="module" src="https://mon-serveur.com/chatbot-widget.js"></script>
 
<style>
	/* Un peu de CSS pour positionner le widget en bas à droite */
	#kesval-chatbot-root {
		position: fixed;
		bottom: 20px;
		right: 20px;
		z-index: 10000; /* Très élevé pour passer au-dessus de tout */
	}
</style>
	

Et voilà !

Une fois le module zippé et installé dans le back-office de PrestaShop :

  1. PrestaShop génère la page et appelle hookDisplayFooter.
  2. Le module PHP calcule les infos du client et génère le HTML/JS.
  3. Le navigateur lit window.PRESTASHOP_CONTEXT puis charge votre script React.
  4. React démarre, trouve la div #kesval-chatbot-root, et affiche le chat.
  5. Quand on envoie un message, React contacte votre API Node.js avec le message ET le contexte (panier, nom...).

C'est une architecture robuste : PrestaShop fait ce qu'il fait de mieux (gérer le catalogue/panier), et votre bot React/Node fait ce qu'il fait de mieux (l'interactivité), sans se marcher dessus.