Improve public booking page

This commit is contained in:
Arthur Belleville 2025-10-26 13:14:44 +01:00
parent 3149928484
commit 04885ce3a7
No known key found for this signature in database
6 changed files with 73 additions and 53 deletions

View file

@ -3,6 +3,7 @@ import { ThemeProvider } from "@xtablo/shared/contexts/ThemeContext";
import { Toaster } from "@xtablo/ui/components/sonner";
import { BrowserRouter as Router, useRoutes } from "react-router-dom";
import { routes } from "./lib/routes";
import { publicRoutes } from "./lib/publicRoutes";
import { supabase } from "./lib/supabase";
import { DatadogRumProvider } from "./providers/DatadogRumProvider";
import { UserStoreProvider } from "./providers/UserStoreProvider";
@ -12,18 +13,25 @@ const AppRoutes = () => {
return element;
};
const PublicRoutes = () => {
const element = useRoutes(publicRoutes);
return element;
};
export const App = () => {
return (
<ThemeProvider>
<SessionProvider supabase={supabase}>
<UserStoreProvider>
<Toaster />
<Router>
<DatadogRumProvider>
<div className="min-h-screen bg-background">
<Toaster />
<Router>
<DatadogRumProvider>
<div className="min-h-screen bg-background">
<PublicRoutes />
<UserStoreProvider>
<AppRoutes />
<style>
{`
</UserStoreProvider>
<style>
{`
@keyframes slide {
0% { transform: translateX(-100vw); }
100% { transform: translateX(100vw); }
@ -32,11 +40,10 @@ export const App = () => {
animation: slide 24s linear infinite;
}
`}
</style>
</div>
</DatadogRumProvider>
</Router>
</UserStoreProvider>
</style>
</div>
</DatadogRumProvider>
</Router>
</SessionProvider>
</ThemeProvider>
);

View file

@ -0,0 +1,22 @@
import { RouteObject } from "react-router-dom";
import { LandingPage } from "../pages/landing";
import { PublicBookingPage } from "../pages/PublicBookingPage";
import { PublicNotePage } from "../pages/PublicNotePage";
export const publicRoutes: RouteObject[] = [
// Landing page
{
path: "/landing",
element: <LandingPage />,
},
// Public booking routes
{
path: "/book/:user_info/:event_type_standard_name",
element: <PublicBookingPage />,
},
// Public notes route (unauthenticated access)
{
path: "/notes/public/:noteId",
element: <PublicNotePage />,
},
];

View file

@ -9,13 +9,10 @@ import { ChatPage } from "../pages/chat";
import { EventsPage } from "../pages/events";
import { FeedbackPage } from "../pages/feedback";
import { JoinPage } from "../pages/join";
import { LandingPage } from "../pages/landing";
import { LoginPage } from "../pages/login";
import { NotFoundPage } from "../pages/NotFoundPage";
import NotesPage from "../pages/notes";
import { OAuthSigninPage } from "../pages/oauth-signin";
import { PublicBookingPage } from "../pages/PublicBookingPage";
import { PublicNotePage } from "../pages/PublicNotePage";
import { PlanningPage } from "../pages/planning";
import { ResetPasswordPage } from "../pages/reset-password";
import SettingsPage from "../pages/settings";
@ -126,21 +123,6 @@ export const routes: RouteObject[] = [
path: "/login-with-oauth",
element: <OAuthSigninPage />,
},
// Landing page
{
path: "/landing",
element: <LandingPage />,
},
// Public booking routes
{
path: "/book/:user_info/:event_type_standard_name",
element: <PublicBookingPage />,
},
// Public notes route (unauthenticated access)
{
path: "/notes/public/:noteId",
element: <PublicNotePage />,
},
// Authentication pages (redirected to "/" if user is authenticated)
{
path: "/",

View file

@ -1,6 +1,5 @@
import { useQueryClient } from "@tanstack/react-query";
import { CustomModal } from "@ui/components/CustomModal";
import { LoadingSpinner } from "@ui/components/LoadingSpinner";
import { useCreateTabloWithOwner } from "@xtablo/shared";
import { useSession } from "@xtablo/shared/contexts/SessionContext";
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
@ -34,7 +33,6 @@ import { useNavigate, useParams } from "react-router-dom";
import { twMerge } from "tailwind-merge";
import { api } from "../lib/api";
import { supabase } from "../lib/supabase";
import { useMaybeUser } from "../providers/UserStoreProvider";
export function PublicBookingPage() {
const { user_info, event_type_standard_name } = useParams<{
@ -43,9 +41,11 @@ export function PublicBookingPage() {
}>();
const queryClient = useQueryClient();
const navigate = useNavigate();
const { mutateAsync: signUpWithoutPassword } = useSignUpWithoutPassword(supabase, api);
const { mutateAsync: signUpWithoutPassword, isPending: isSigningUpWithoutPassword } =
useSignUpWithoutPassword(supabase, api);
const { session } = useSession();
const user = useMaybeUser();
const user = session ? session.user : null;
const shortUserId = user_info?.substring(user_info.lastIndexOf("-") + 1);
const { data: publicSlots, isLoading: isLoadingSlots } = usePublicSlots(
@ -58,10 +58,12 @@ export function PublicBookingPage() {
useCreateTabloWithOwner(api, (data) => {
queryClient.invalidateQueries({ queryKey: ["tablos"] });
invalidatePublicSlots();
navigate(`/chat/${data.id}`, { replace: true });
navigate(`/tablos/${data.id}`, { replace: true });
navigate(0);
});
const isPending = isSigningUpWithoutPassword || isCreatingTabloWithOwner;
const userProfile = publicSlots?.user;
const eventType = publicSlots?.eventType;
const slotsData = publicSlots?.slots || {};
@ -88,6 +90,7 @@ export function PublicBookingPage() {
// Loading messages rotation
const loadingMessages = [
"Nous créons votre rendez-vous, veuillez patienter",
"Creation de votre compte, ...",
"Préparation de votre réservation...",
"Configuration de votre appel...",
"Finalisation de votre créneau...",
@ -96,14 +99,14 @@ export function PublicBookingPage() {
useEffect(() => {
setCurrentMessageIndex(0);
if (isCreatingTabloWithOwner) {
if (isPending) {
const interval = setInterval(() => {
setCurrentMessageIndex((prev) => (prev + 1) % loadingMessages.length);
}, 1000);
return () => clearInterval(interval);
}
}, [isCreatingTabloWithOwner]);
}, [isPending]);
// Theme
const { theme, setTheme } = useTheme();
@ -217,17 +220,6 @@ export function PublicBookingPage() {
return date.toLocaleDateString("fr-FR", { month: "long", year: "numeric" });
};
if (isLoadingSlots) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex items-center justify-center">
<div className="text-center">
<LoadingSpinner />
<p className="mt-4 text-gray-600 dark:text-gray-400">Chargement des disponibilités...</p>
</div>
</div>
);
}
const formatDuration = (minutes: number) => {
if (minutes < 60) {
return `${minutes} min`;
@ -459,7 +451,20 @@ export function PublicBookingPage() {
</div>
{/* Right Panel - Calendar & Time Slots */}
<div className="flex-1 flex flex-col p-6 lg:p-8">
<div className="flex-1 flex flex-col p-6 lg:p-8 relative">
{/* Loading Overlay for Calendar/Slots */}
{isLoadingSlots && (
<div className="absolute inset-0 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm z-10 flex items-center justify-center rounded-lg">
<div className="flex flex-col items-center gap-4">
<div className="text-center">
<Text className="text-gray-900 dark:text-white font-semibold">
Chargement des disponibilités...
</Text>
</div>
</div>
</div>
)}
<div className="flex-1 flex flex-col lg:flex-row gap-6">
{/* Calendar */}
<div className="flex-1">
@ -589,7 +594,7 @@ export function PublicBookingPage() {
</div>
{/* Loading Overlay */}
{isCreatingTabloWithOwner && (
{isPending && (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[100] flex items-center justify-center">
<div className="bg-white dark:bg-gray-800 rounded-2xl shadow-2xl p-8 w-md mx-4 border border-gray-200 dark:border-gray-700">
<div className="flex flex-col items-center gap-6">
@ -666,7 +671,11 @@ export function PublicBookingPage() {
id="name"
type="text"
placeholder="Votre nom complet"
value={user?.name || formData.name}
value={
user
? `${user.user_metadata.first_name} ${user.user_metadata.last_name}`
: formData.name
}
onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
disabled={!!user}
/>

File diff suppressed because one or more lines are too long

View file

@ -8,7 +8,7 @@ export const buildApi = (baseURL: string) =>
headers: {
"Content-Type": "application/json",
},
timeout: 4000,
timeout: 5000,
});
// Create React Query client with default options