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) {