From 97c50a964c1aed24bebfb55f21ed688fe05ccca0 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 10 Jul 2025 22:04:11 +0200 Subject: [PATCH] Add tutorial --- ui/src/components/TabloTutorial.tsx | 244 ++++++++++++++++++++++++++++ ui/src/pages/tablo.tsx | 85 +++++++++- 2 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 ui/src/components/TabloTutorial.tsx diff --git a/ui/src/components/TabloTutorial.tsx b/ui/src/components/TabloTutorial.tsx new file mode 100644 index 0000000..7455184 --- /dev/null +++ b/ui/src/components/TabloTutorial.tsx @@ -0,0 +1,244 @@ +import React, { useState } from "react"; +import { Button } from "@ui/ui-library/button"; +import { twMerge } from "tailwind-merge"; +import { X, ArrowRight, ArrowLeft, HelpCircle } from "lucide-react"; + +interface TutorialStep { + id: string; + title: string; + description: string; + target?: string; + position?: "top" | "bottom" | "left" | "right"; + action?: () => void; +} + +interface TabloTutorialProps { + isOpen: boolean; + onClose: () => void; + onCreateTablo: () => void; +} + +const tutorialSteps: TutorialStep[] = [ + { + id: "welcome", + title: "Bienvenue sur XTablo ! 🎉", + description: + "Découvrez comment XTablo peut révolutionner votre façon de gérer vos projets, équipes et collaborations.", + }, + { + id: "what-is-tablo", + title: "Qu'est-ce qu'un Tablo ?", + description: + "Un Tablo est un espace de travail collaboratif qui regroupe toutes les fonctionnalités dont vous avez besoin : conversations, planning, documents et plus encore.", + }, + + { + id: "tablo-features", + title: "Fonctionnalités des Tablos", + description: + "Chaque Tablo vous donne accès à :\n• 💬 Conversations en temps réel\n• 📅 Planning partagé\n• 📝 Notes et documents\n• 👥 Gestion d'équipe", + }, + { + id: "tablo-status", + title: "Gérer le statut des Tablos", + description: + "Organisez vos projets avec les statuts :\n• 📋 À faire : Projets en attente\n• 🔄 En cours : Projets actifs\n• ✅ Terminé : Projets complétés", + }, + { + id: "collaboration", + title: "Collaboration d'équipe", + description: + "Invitez vos collègues dans vos Tablos. Vous serez Admin du Tablo que vous créez, et pourrez inviter d'autres membres en tant qu'invités.", + }, + { + id: "navigation", + title: "Navigation rapide", + description: + "Utilisez le clic droit sur un Tablo pour accéder rapidement aux conversations et au planning. Filtrez vos Tablos par statut pour une meilleure organisation.", + }, + { + id: "create-first-tablo", + title: "Créer votre premier Tablo", + description: + "Vous êtes maintenant prêt à utiliser XTablo ! Créez votre premier Tablo pour démarrer votre projet.", + target: "create-tablo-button", + position: "bottom", + action: () => {}, // Will be set by the parent component + }, +]; + +export const TabloTutorial: React.FC = ({ + isOpen, + onClose, + onCreateTablo, +}) => { + const [currentStep, setCurrentStep] = useState(0); + + const currentStepData = tutorialSteps[currentStep]; + + const handleNext = () => { + if (currentStep < tutorialSteps.length - 1) { + setCurrentStep(currentStep + 1); + } else { + handleClose(); + } + }; + + const handlePrevious = () => { + if (currentStep > 0) { + setCurrentStep(currentStep - 1); + } + }; + + const handleClose = () => { + setCurrentStep(0); + localStorage.setItem("xtablo-tutorial-completed", "true"); + onClose(); + }; + + const handleCreateTabloAction = () => { + if (currentStepData.id === "create-first-tablo") { + onCreateTablo(); + handleNext(); + } + }; + + const handleSkip = () => { + setCurrentStep(tutorialSteps.length - 1); + }; + + if (!isOpen) return null; + + return ( +
+
+ {/* Header */} +
+
+
+ +
+
+

+ Guide de démarrage +

+

+ Étape {currentStep + 1} sur {tutorialSteps.length} +

+
+
+ +
+ + {/* Progress Bar */} +
+
+
+
+
+ + {/* Content */} +
+

+ {currentStepData.title} +

+
+ {currentStepData.description} +
+
+ + {/* Actions */} +
+
+ {currentStep > 0 && ( + + )} +
+ +
+ {currentStep < tutorialSteps.length - 1 && ( + + )} + + {currentStepData.id === "create-first-tablo" ? ( + + ) : ( + + )} +
+
+ + {/* Completion Message */} + {currentStep === tutorialSteps.length - 1 && ( +
+
+
+ + + +
+ + Félicitations ! Vous êtes prêt à utiliser XTablo. + +
+
+ )} +
+
+ ); +}; diff --git a/ui/src/pages/tablo.tsx b/ui/src/pages/tablo.tsx index 89627c6..e632346 100644 --- a/ui/src/pages/tablo.tsx +++ b/ui/src/pages/tablo.tsx @@ -2,6 +2,7 @@ import { SignOutButton } from "@ui/components/SignOutButton"; import { CreateTabloModal } from "@ui/components/CreateTabloModal"; import { TabloModal } from "@ui/components/TabloModal"; import { DeleteTabloModal } from "@ui/components/DeleteTabloModal"; +import { TabloTutorial } from "@ui/components/TabloTutorial"; import { Select, SelectButton, @@ -9,7 +10,7 @@ import { SelectListBox, SelectListItem, } from "@ui/ui-library/select"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { useTablosList, useCreateTablo, @@ -19,6 +20,7 @@ import { import { LoadingSpinner } from "@ui/components/LoadingSpinner"; import { TabloInsert, TabloUpdate, UserTablo } from "@ui/types/tablos.types"; import { useNavigate } from "react-router-dom"; +import { HelpCircle } from "lucide-react"; type FilterOption = { id: "all" | "todo" | "in_progress" | "done"; @@ -45,6 +47,9 @@ export const TabloPage = () => { const [filterType, setFilterType] = useState< "all" | "todo" | "in_progress" | "done" >("all"); + const [isTutorialOpen, setIsTutorialOpen] = useState(false); + const [hasInteractedWithTutorial, setHasInteractedWithTutorial] = + useState(false); const navigate = useNavigate(); const { data: tablos, isLoading, error } = useTablosList(); @@ -52,6 +57,22 @@ export const TabloPage = () => { const { mutateAsync: updateTablo } = useUpdateTablo(); const { mutateAsync: deleteTablo } = useDeleteTablo(); + // Check if tutorial should be shown + useEffect(() => { + const tutorialCompleted = localStorage.getItem("xtablo-tutorial-completed"); + const tutorialInteracted = localStorage.getItem( + "xtablo-tutorial-interacted" + ); + + // Show tutorial if user hasn't completed it and has no tablos + if (!tutorialCompleted && !isLoading && tablos && tablos.length === 0) { + setIsTutorialOpen(true); + } + + // Check if user has interacted with tutorial before + setHasInteractedWithTutorial(!!tutorialInteracted); + }, [tablos, isLoading]); + // Filter tablos based on status const filteredTablos = tablos?.filter((tablo) => { if (filterType === "todo") { @@ -183,6 +204,20 @@ export const TabloPage = () => { setIsDeleting(false); }; + const handleCloseTutorial = () => { + setIsTutorialOpen(false); + }; + + const handleOpenTutorial = () => { + setIsTutorialOpen(true); + setHasInteractedWithTutorial(true); + localStorage.setItem("xtablo-tutorial-interacted", "true"); + }; + + const handleTutorialCreateTablo = () => { + setIsCreateModalOpen(true); + }; + const getUserRole = (tablo: UserTablo) => { return tablo.is_admin ? "Admin" : "Invité"; }; @@ -544,6 +579,46 @@ export const TabloPage = () => { Tablos
+
+ {!hasInteractedWithTutorial && ( +
+ + Avant de commencer + + + + +
+ )} + +
+ {/* Filter Controls */}