Improve flow

This commit is contained in:
Arthur Belleville 2025-11-06 22:47:28 +01:00
parent 2bfe51017f
commit 03a25dace3
No known key found for this signature in database
4 changed files with 63 additions and 29 deletions

View file

@ -58,6 +58,7 @@ export const getBookingRouter = () => {
return c.json({ error: "owner_id is incorrect" }, 400);
}
let hasCreatedAccount = false;
if (!maybeUser) {
// Check if email already exists in the database
const { data: existingUser, error: existingUserError } = await supabase
@ -72,6 +73,7 @@ export const getBookingRouter = () => {
}
if (!existingUser) {
hasCreatedAccount = true;
// Create a temporary user for the booking
const result = await createInvitedUser(
supabase,
@ -181,7 +183,7 @@ export const getBookingRouter = () => {
}
// Grant access to the current user (invited user) as a non-admin member
const { error: tabloAccessError } = await supabase.from("tablo_access").insert(
const { error: tabloAccessError } = await supabase.from("tablo_access").upsert(
{
tablo_id: tabloData.id,
user_id: bookerUserDataTyped.id,
@ -190,10 +192,10 @@ export const getBookingRouter = () => {
// -------------
is_active: true,
granted_by: ownerId,
},
{
onConflict: "tablo_id, user_id",
}
// {
// onConflict: "tablo_id, user_id",
// }
);
if (tabloAccessError) {
@ -275,7 +277,12 @@ export const getBookingRouter = () => {
`,
});
return c.json({ id: tabloData.id });
return c.json({
message: "Booking successful",
tablo_id: tabloData.id,
hasCreatedAccount,
email: bookerUserDataTyped.email,
});
}
);

View file

@ -1,6 +1,7 @@
import { useMutation } from "@tanstack/react-query";
import { toast, useSession } from "@xtablo/shared";
import { invalidatePublicSlots, queryClient, toast, useSession } from "@xtablo/shared";
import { api } from "../lib/api";
import { useNavigate } from "react-router-dom";
type BookSlotPayload = {
event_type_standard_name: string;
@ -17,9 +18,14 @@ type BookSlotPayload = {
};
// Book a slot with an event type owner
export const useBookSlot = (onSuccess?: (data: { id: string }) => void) => {
export const useBookSlot = () => {
const navigate = useNavigate();
const { session } = useSession();
return useMutation<{ id: string }, unknown, BookSlotPayload>({
return useMutation<
{ tablo_id: string; hasCreatedAccount: boolean; email: string },
unknown,
BookSlotPayload
>({
mutationFn: async (payload: BookSlotPayload) => {
const { data } = await api.post("/api/v1/book/slot", payload, {
headers: {
@ -29,7 +35,23 @@ export const useBookSlot = (onSuccess?: (data: { id: string }) => void) => {
});
return data;
},
onSuccess,
onSuccess: ({ tablo_id, hasCreatedAccount, email }) => {
toast.add({
title: "Réservation confirmée avec succès",
description: hasCreatedAccount
? "Vous avez reçu un email de confirmation et votre compte a été créé automatiquement. Un mot de passe vous a été envoyé par email."
: "Vous recevrez un email de confirmation dans quelques instants",
type: "success",
});
queryClient.invalidateQueries({ queryKey: ["tablos"] });
invalidatePublicSlots();
if (hasCreatedAccount) {
navigate(`/login?email=${email}`, { replace: true });
} else {
navigate(`/tablos/${tablo_id}`, { replace: true });
}
// navigate(0);
},
onError: (error) => {
console.error(error);
toast.add(

View file

@ -1,9 +1,8 @@
import { useQueryClient } from "@tanstack/react-query";
import { CustomModal } from "@ui/components/CustomModal";
import { useBookSlot } from "../hooks/book";
import { useSession } from "@xtablo/shared/contexts/SessionContext";
import { useTheme } from "@xtablo/shared/contexts/ThemeContext";
import { invalidatePublicSlots, TimeSlot, usePublicSlots } from "@xtablo/shared/hooks/public";
import { TimeSlot, usePublicSlots } from "@xtablo/shared/hooks/public";
import { Button } from "@xtablo/ui/components/button";
import { FieldError } from "@xtablo/ui/components/field";
import { Input } from "@xtablo/ui/components/input";
@ -27,7 +26,7 @@ import {
UserIcon,
} from "lucide-react";
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import { twMerge } from "tailwind-merge";
import { api } from "../lib/api";
@ -36,8 +35,6 @@ export function PublicBookingPage() {
user_info: string;
event_type_standard_name: string;
}>();
const queryClient = useQueryClient();
const navigate = useNavigate();
// const { mutateAsync: signUpWithoutPassword, isPending: isSigningUpWithoutPassword } =
// useSignUpWithoutPassword(supabase, api);
const { session } = useSession();
@ -51,14 +48,7 @@ export function PublicBookingPage() {
event_type_standard_name || ""
);
const { mutateAsync: bookSlot, isPending: isBookingSlot } = useBookSlot(
(data: { id: string }) => {
queryClient.invalidateQueries({ queryKey: ["tablos"] });
invalidatePublicSlots();
navigate(`/tablos/${data.id}`, { replace: true });
navigate(0);
}
);
const { mutateAsync: bookSlot, isPending: isBookingSlot } = useBookSlot();
const isPending = isBookingSlot;
@ -76,6 +66,7 @@ export function PublicBookingPage() {
date: Date;
slot: TimeSlot;
} | null>(null);
const [formData, setFormData] = useState({
email: "",
name: "",
@ -88,7 +79,7 @@ export function PublicBookingPage() {
// Loading messages rotation
const loadingMessages = [
"Nous créons votre rendez-vous, veuillez patienter",
"Creation de votre compte, ...",
"Creation de votre compte...",
"Préparation de votre réservation...",
"Configuration de votre appel...",
"Finalisation de votre créneau...",
@ -246,6 +237,10 @@ export function PublicBookingPage() {
};
const validateForm = () => {
if (user) {
return true;
}
const errors = { email: "", name: "" };
let isValid = true;
@ -288,6 +283,16 @@ export function PublicBookingPage() {
const duration = eventType?.duration || 60; // duration in minutes
const endTime = calculateEndTime(startTime, duration);
const userDetails = user
? {
name: user.user_metadata.name,
email: user.email || "",
}
: {
name: formData.name,
email: formData.email,
};
bookSlot({
event_type_standard_name: event_type_standard_name as string,
owner_short_id: shortUserId || "",
@ -296,10 +301,7 @@ export function PublicBookingPage() {
start_time: selectedSlot?.slot.time || "",
end_time: endTime || "",
},
user_details: {
name: formData.name,
email: formData.email,
},
user_details: userDetails,
});
handleCloseModal();

View file

@ -8,11 +8,13 @@ import { Label } from "@xtablo/ui/components/label";
import { MonitorIcon, MoonIcon, SunIcon } from "lucide-react";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { Link, useSearchParams } from "react-router-dom";
import { twMerge } from "tailwind-merge";
import { useLoginEmail } from "../hooks/auth";
export function LoginPage() {
const [searchParams] = useSearchParams();
const emailParam = searchParams.get("email");
const { t } = useTranslation(["auth", "common"]);
const redirectUrl = localStorage.getItem("redirectUrl");
const {
@ -22,8 +24,9 @@ export function LoginPage() {
} = useLoginEmail({
redirectUrl: redirectUrl ?? null,
});
const [formData, setFormData] = useState({
email: "",
email: emailParam ?? "",
password: "",
});