Improve public booking page
This commit is contained in:
parent
3149928484
commit
04885ce3a7
6 changed files with 73 additions and 53 deletions
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
22
apps/main/src/lib/publicRoutes.tsx
Normal file
22
apps/main/src/lib/publicRoutes.tsx
Normal 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 />,
|
||||
},
|
||||
];
|
||||
|
|
@ -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: "/",
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue