diff --git a/apps/main/src/pages/tablo-details.layout.test.tsx b/apps/main/src/pages/tablo-details.layout.test.tsx
index 76bfe34..e591c75 100644
--- a/apps/main/src/pages/tablo-details.layout.test.tsx
+++ b/apps/main/src/pages/tablo-details.layout.test.tsx
@@ -1,4 +1,5 @@
import { screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { renderWithProviders } from "../utils/testHelpers";
import { TabloDetailsPage } from "./tablo-details";
@@ -195,4 +196,20 @@ describe("TabloDetailsPage overview layout", () => {
expect(screen.getByRole("button", { name: "Modifier la mise en page" })).toBeInTheDocument();
});
+
+ it("uses the standard email invite UI in the share dialog", async () => {
+ const user = userEvent.setup();
+
+ renderWithProviders(, {
+ route: "/tablos/tablo-1",
+ path: "/tablos/:tabloId",
+ });
+
+ await user.click(screen.getByRole("button", { name: "Inviter" }));
+
+ expect(screen.getByText("Inviter un utilisateur")).toBeInTheDocument();
+ expect(screen.getByPlaceholderText("Email de l'utilisateur à inviter")).toBeInTheDocument();
+ expect(screen.queryByText("Accès client")).not.toBeInTheDocument();
+ expect(screen.queryByRole("button", { name: "Envoyer le lien" })).not.toBeInTheDocument();
+ });
});
diff --git a/apps/main/src/pages/tablo-details.tsx b/apps/main/src/pages/tablo-details.tsx
index df06def..a9cd3e5 100644
--- a/apps/main/src/pages/tablo-details.tsx
+++ b/apps/main/src/pages/tablo-details.tsx
@@ -186,6 +186,9 @@ const TABS: {
{ id: "roadmap", label: "Roadmap", icon: MapIcon },
];
+// Temporary rollback until the client portal invite flow is ready to be used again.
+const USE_CLIENT_MAGIC_LINK_INVITES = false;
+
// ─── Page ─────────────────────────────────────────────────────────────────────
export const TabloDetailsPage = () => {
@@ -202,6 +205,7 @@ export const TabloDetailsPage = () => {
);
const [showAllOverviewTasks, setShowAllOverviewTasks] = useState(false);
const [isShareDialogOpen, setIsShareDialogOpen] = useState(false);
+ const [inviteEmail, setInviteEmail] = useState("");
const [clientInviteEmail, setClientInviteEmail] = useState("");
const [isLayoutEditMode, setIsLayoutEditMode] = useState(false);
const [draggedOverviewBlock, setDraggedOverviewBlock] = useState<{
@@ -255,6 +259,13 @@ export const TabloDetailsPage = () => {
(member) => !pendingInvites?.some((invite) => invite.invited_email === member.email)
);
+ const handleSendInvite = () => {
+ if (!tabloId || !inviteEmail.trim()) return;
+
+ inviteUser({ email: inviteEmail, tablo_id: tabloId });
+ setInviteEmail("");
+ };
+
const openTaskModal = (dueDate?: Date) => {
setTaskModalInitialDueDate(dueDate ? new Date(dueDate) : undefined);
setIsTaskModalOpen(true);
@@ -1002,117 +1013,205 @@ export const TabloDetailsPage = () => {
{/* Separator */}
- {/* Client Access Section */}
-
-
Accès client
-
- Invitez des clients externes via un lien magique
-
-
-
- {/* Client Invite Input */}
-
-
setClientInviteEmail(e.target.value)}
- placeholder="Email du client"
- className="flex-1 min-h-[44px]"
- />
- {isCreatingClientInvite ? (
-
-
+ {USE_CLIENT_MAGIC_LINK_INVITES ? (
+ <>
+
+
Accès client
+
+ Invitez des clients externes via un lien magique
+
- ) : (
-
- )}
-
- {/* Pending Client Invites */}
- {pendingClientInvites && pendingClientInvites.length > 0 && (
-
-
- Invitations client en attente ({pendingClientInvites.length})
-
-
- {pendingClientInvites.map((invite) => {
- const daysUntilExpiry = Math.ceil(
- (new Date(invite.expires_at).getTime() - Date.now()) / (1000 * 60 * 60 * 24)
- );
- const isExpiringSoon = daysUntilExpiry < 5;
- return (
-
-
+
+ {pendingClientInvites && pendingClientInvites.length > 0 && (
+
+
+ Invitations client en attente ({pendingClientInvites.length})
+
+
+ {pendingClientInvites.map((invite) => {
+ const daysUntilExpiry = Math.ceil(
+ (new Date(invite.expires_at).getTime() - Date.now()) /
+ (1000 * 60 * 60 * 24)
+ );
+ const isExpiringSoon = daysUntilExpiry < 5;
+ return (
+
-
-
- {invite.invited_email}
-
-
- {isExpiringSoon && "⚠ "}
- Expire dans {daysUntilExpiry} jour{daysUntilExpiry !== 1 ? "s" : ""}
-
-
- {isExpiringSoon && (
-
- Bientôt expiré
-
- )}
-
+ );
+ })}
+
+
+ )}
+ >
+ ) : (
+ <>
+
+
Inviter un utilisateur
+
+ Utilisez le système d'invitation standard par email pour le moment
+
+
+
+
+
setInviteEmail(e.target.value)}
+ placeholder="Email de l'utilisateur à inviter"
+ className="flex-1 min-h-[44px]"
+ />
+ {isInvitingUser ? (
+
+ ) : (
+
+ Inviter
+
+ )}
+
+
+ {pendingInvites && pendingInvites.length > 0 && (
+
+
+ Invitations en attente ({pendingInvites.length})
+
+
+ {pendingInvites.map((invite) => (
+
-
-
-
- );
- })}
-
-
+
+
+
+ {invite.invited_email}
+
+ (En attente)
+
+
+ cancelInvite({
+ tabloId: tabloId ?? "",
+ inviteId: invite.id,
+ })
+ }
+ disabled={isCancellingInvite || !tabloId}
+ title="Retirer l'invitation"
+ >
+
+
+
+ ))}
+
+
+ )}
+ >
)}