diff --git a/apps/mesh/src/auth/index.ts b/apps/mesh/src/auth/index.ts index 4cdeed645e..bc2c821816 100644 --- a/apps/mesh/src/auth/index.ts +++ b/apps/mesh/src/auth/index.ts @@ -130,17 +130,58 @@ if ( const sendEmail = createEmailSender(inviteProvider); sendInvitationEmail = async (data) => { - const inviterName = data.inviter.user?.name || data.inviter.user?.email; - const acceptUrl = `${getBaseUrl()}/auth/accept-invitation?invitationId=${data.invitation.id}&redirectTo=/`; + const esc = (s: string) => + s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + + const inviterName = + data.inviter.user?.name || data.inviter.user?.email || ""; + const orgSlug = data.organization.slug ?? ""; + const acceptUrl = `${getBaseUrl()}/auth/accept-invitation?invitationId=${encodeURIComponent(data.invitation.id)}&redirectTo=/${encodeURIComponent(orgSlug)}`; await sendEmail({ to: data.email, - subject: `Invitation to join ${data.organization.name}`, - html: ` -

You've been invited!

-

${inviterName} has invited you to join ${data.organization.name}.

-

Click here to accept the invitation

- `, + subject: `You've been invited to join ${data.organization.name}`, + html: ` + + + + + + + +
+ + + + + + + + + + +
+

Invitation

+

You've been invited to join
${esc(data.organization.name)}

+
+

+ ${esc(inviterName)} has invited you to collaborate on ${esc(data.organization.name)}. +

+ Accept invitation +
+

+ Or copy this link into your browser:
+ ${esc(acceptUrl)} +

+
+
+ +`, }); }; } diff --git a/apps/mesh/src/web/components/sidebar/footer/inbox.tsx b/apps/mesh/src/web/components/sidebar/footer/inbox.tsx index 414b00cc29..9e5fa03bd5 100644 --- a/apps/mesh/src/web/components/sidebar/footer/inbox.tsx +++ b/apps/mesh/src/web/components/sidebar/footer/inbox.tsx @@ -53,11 +53,17 @@ function InvitationItem({ invitation }: { invitation: Invitation }) { toast.error(result.error.message); setIsAccepting(false); } else { + // Get org slug — prefer setActive response, fall back to org list lookup. + // setActive may not always return data if the session hasn't refreshed yet. const setActiveResult = await authClient.organization.setActive({ organizationId: invitation.organizationId, }); + let slug = setActiveResult?.data?.slug; + if (!slug) { + const { data: orgs } = await authClient.organization.list(); + slug = orgs?.find((o) => o.id === invitation.organizationId)?.slug; + } toast.success("Invitation accepted!"); - const slug = setActiveResult?.data?.slug; window.location.href = slug ? `/${slug}` : "/"; } } catch { diff --git a/apps/mesh/src/web/layouts/shell-layout.tsx b/apps/mesh/src/web/layouts/shell-layout.tsx index 3d78391362..fbde6e3764 100644 --- a/apps/mesh/src/web/layouts/shell-layout.tsx +++ b/apps/mesh/src/web/layouts/shell-layout.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; -import { SplashScreen } from "@/web/components/splash-screen"; +import { Button } from "@deco/ui/components/button.tsx"; +import { EmptyState } from "@/web/components/empty-state"; import { KeyboardShortcutsDialog } from "@/web/components/keyboard-shortcuts-dialog"; import { isModKey } from "@/web/lib/keyboard-shortcuts"; import RequiredAuthLayout from "@/web/layouts/required-auth-layout"; @@ -238,7 +239,24 @@ function ShellLayoutContent() { const { data: ssoStatus } = useOrgSsoStatus(orgId); if (!activeOrg) { - return ; + return ( + { + localStorage.removeItem(LOCALSTORAGE_KEYS.lastOrgSlug()); + window.location.href = "/"; + }} + > + Go to your account + + } + /> + ); } if (ssoStatus?.ssoRequired && !ssoStatus.authenticated) {