From a2de135b11517273a89604e55766201240180972 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Wed, 15 Oct 2025 16:30:36 +0200 Subject: [PATCH] Fix invite --- api/src/tablo.ts | 85 ++++++++++++++++++++------------ api/src/transporter.ts | 4 +- ui/src/components/TabloModal.tsx | 24 +++++---- ui/src/hooks/invite.ts | 12 +++-- 4 files changed, 80 insertions(+), 45 deletions(-) diff --git a/api/src/tablo.ts b/api/src/tablo.ts index ade1b99..3377bb0 100644 --- a/api/src/tablo.ts +++ b/api/src/tablo.ts @@ -1,15 +1,27 @@ import { PutObjectCommand, type S3Client } from "@aws-sdk/client-s3"; -import { PostgrestError, type SupabaseClient, type User } from "@supabase/supabase-js"; +import { + PostgrestError, + type SupabaseClient, + type User, +} from "@supabase/supabase-js"; import { Hono } from "hono"; import type { Transporter } from "nodemailer"; import type { StreamChat } from "stream-chat"; import { config } from "./config.js"; import type { Tables } from "./database.types.ts"; import { generateICSFromEvents, writeCalendarFileToR2 } from "./helpers.js"; -import { authMiddleware, r2Middleware, streamChatMiddleware } from "./middleware.js"; +import { + authMiddleware, + r2Middleware, + streamChatMiddleware, +} from "./middleware.js"; import { generateToken } from "./token.js"; import { transporter } from "./transporter.js"; -import type { EventAndTablo, EventInsertInTablo, TabloInsert } from "./types.ts"; +import type { + EventAndTablo, + EventInsertInTablo, + TabloInsert, +} from "./types.ts"; export const tabloRouter = new Hono<{ Variables: { @@ -166,7 +178,9 @@ tabloRouter.post("/create-and-invite", async (c) => { const { data: insertedTablo, error } = await supabase .from("tablos") .insert({ - name: `${invitedUserDataTyped.name || "Invité"} / ${ownerDataTyped.name || "Propriétaire"}`, + name: `${invitedUserDataTyped.name || "Invité"} / ${ + ownerDataTyped.name || "Propriétaire" + }`, color: "bg-blue-500", status: "todo", owner_id: ownerId, @@ -184,20 +198,22 @@ tabloRouter.post("/create-and-invite", async (c) => { } // Grant access to the current user (invited user) as a non-admin member - const { error: tabloAccessError } = await supabase.from("tablo_access").insert( - { - tablo_id: tabloData.id, - user_id: user.id, - // ** IMPORTANT ** - is_admin: false, - // ------------- - is_active: true, - granted_by: ownerId, - } - // { - // onConflict: "tablo_id, user_id", - // } - ); + const { error: tabloAccessError } = await supabase + .from("tablo_access") + .insert( + { + tablo_id: tabloData.id, + user_id: user.id, + // ** IMPORTANT ** + is_admin: false, + // ------------- + is_active: true, + granted_by: ownerId, + } + // { + // onConflict: "tablo_id, user_id", + // } + ); if (tabloAccessError) { console.error("tabloAccessError", tabloAccessError); @@ -290,7 +306,8 @@ tabloRouter.patch("/update", async (c) => { const updatedTablo = update as Tables<"tablos">; - const isUpdatingName = tablo.name !== undefined && tablo.name !== updatedTablo.name; + const isUpdatingName = + tablo.name !== undefined && tablo.name !== updatedTablo.name; if (error) { return c.json({ error: error.message }, 500); @@ -344,7 +361,6 @@ tabloRouter.delete("/delete", async (c) => { tabloRouter.post("/invite", async (c) => { const sender = c.get("user"); const supabase = c.get("supabase"); - const transporter = c.get("transporter"); const { email: recipientmail, tablo_id } = await c.req.json(); const token = generateToken(); @@ -366,7 +382,10 @@ tabloRouter.post("/invite", async (c) => { } if (tablo.owner_id !== sender.id) { - return c.json({ error: "You are not allowed to invite users to this tablo" }, 400); + return c.json( + { error: "You are not allowed to invite users to this tablo" }, + 400 + ); } const { error } = await supabase.from("tablo_invites").insert({ @@ -386,7 +405,9 @@ tabloRouter.post("/invite", async (c) => { subject: "Vous avez été invité à un tablo", html: `

Vous avez été invité à un tablo avec ce lien

`, + }/join/${encodeURIComponent(tablo.name)}?token=${encodeURIComponent( + token + )}">ce lien

`, }); return c.json({ @@ -419,15 +440,17 @@ tabloRouter.post("/join", async (c) => { const { id: invite_id, tablo_id, invited_by } = inviteData; - const { error: tabloAccessError } = await supabase.from("tablo_access").insert({ - tablo_id, - user_id: joiner.id, - // ** IMPORTANT ** - is_admin: false, - // ------------- - is_active: true, - granted_by: invited_by, - }); + const { error: tabloAccessError } = await supabase + .from("tablo_access") + .insert({ + tablo_id, + user_id: joiner.id, + // ** IMPORTANT ** + is_admin: false, + // ------------- + is_active: true, + granted_by: invited_by, + }); if (tabloAccessError) { console.error("tabloAccessError", tabloAccessError); diff --git a/api/src/transporter.ts b/api/src/transporter.ts index 87357cf..42df093 100644 --- a/api/src/transporter.ts +++ b/api/src/transporter.ts @@ -4,7 +4,7 @@ import { config } from "./config.js"; const OAuth2 = google.auth.OAuth2; -export const createTransporter = async () => { +export const createTransporter = () => { const oauth2Client = new OAuth2( config.EMAIL_CLIENT_ID, config.EMAIL_CLIENT_SECRET, @@ -31,4 +31,4 @@ export const createTransporter = async () => { return transporter; }; -export const transporter = await createTransporter(); +export const transporter = createTransporter(); diff --git a/ui/src/components/TabloModal.tsx b/ui/src/components/TabloModal.tsx index 2790bf8..b56551a 100644 --- a/ui/src/components/TabloModal.tsx +++ b/ui/src/components/TabloModal.tsx @@ -40,7 +40,7 @@ export const TabloModal = ({ tablo, onClose, onEdit }: TabloModalProps) => { const [showMembers, setShowMembers] = useState(false); const [inviteEmail, setInviteEmail] = useState(""); - const inviteUser = useInviteUser(); + const { mutate: inviteUser, isPending: isInvitingUser } = useInviteUser(); const { data: fileData, @@ -425,14 +425,20 @@ export const TabloModal = ({ tablo, onClose, onEdit }: TabloModalProps) => { placeholder="Email de l'utilisateur à inviter" className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white" /> - + {isInvitingUser ? ( +
+
+
+ ) : ( + + )} diff --git a/ui/src/hooks/invite.ts b/ui/src/hooks/invite.ts index 2fbf268..b3d0f12 100644 --- a/ui/src/hooks/invite.ts +++ b/ui/src/hooks/invite.ts @@ -6,8 +6,14 @@ import { toast } from "@ui/ui-library/toast/toast-queue"; // Invite user by email export const useInviteUser = () => { const { session } = useSession(); - const { mutate } = useMutation({ - mutationFn: async ({ email, tablo_id }: { email: string; tablo_id: string }) => { + const { mutate, isPending } = useMutation({ + mutationFn: async ({ + email, + tablo_id, + }: { + email: string; + tablo_id: string; + }) => { const { data } = await api.post( "/api/v1/tablos/invite", { @@ -35,7 +41,7 @@ export const useInviteUser = () => { ); }, }); - return mutate; + return { mutate, isPending }; }; export const useJoinTablo = () => {