From 6e50f4cc506bc10433afcbde8df0258611cce7da Mon Sep 17 00:00:00 2001 From: hailin Date: Sat, 7 Mar 2026 05:37:30 -0800 Subject: [PATCH] feat(web-admin): show copyable invite link after sending invite After a platform admin sends an invite, the generated invite URL is displayed inline with a one-click copy button so it can be shared via any channel (email, WeChat, etc.). Link auto-dismisses when the invite form is reopened. Also adds i18n keys for invite link UI in en/zh. Co-Authored-By: Claude Sonnet 4.6 --- .../src/app/(admin)/tenants/[id]/page.tsx | 37 +++++++++++++++++-- .../src/i18n/locales/en/tenants.json | 4 ++ .../src/i18n/locales/zh/tenants.json | 4 ++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/it0-web-admin/src/app/(admin)/tenants/[id]/page.tsx b/it0-web-admin/src/app/(admin)/tenants/[id]/page.tsx index a4b1717..eaed148 100644 --- a/it0-web-admin/src/app/(admin)/tenants/[id]/page.tsx +++ b/it0-web-admin/src/app/(admin)/tenants/[id]/page.tsx @@ -225,6 +225,8 @@ export default function TenantDetailPage() { const [showInviteForm, setShowInviteForm] = useState(false); const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState('viewer'); + const [inviteLink, setInviteLink] = useState(null); + const [linkCopied, setLinkCopied] = useState(false); const [form, setForm] = useState({ name: '', plan: 'free', @@ -297,12 +299,15 @@ export default function TenantDetailPage() { const sendInviteMutation = useMutation({ mutationFn: (body: { email: string; role: string }) => - apiClient(`/api/v1/admin/tenants/${id}/invites`, { method: 'POST', body }), - onSuccess: () => { + apiClient<{ token: string }>(`/api/v1/admin/tenants/${id}/invites`, { method: 'POST', body }), + onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: queryKeys.tenants.invites(id) }); setShowInviteForm(false); setInviteEmail(''); setInviteRole('viewer'); + const baseUrl = typeof window !== 'undefined' ? window.location.origin : ''; + setInviteLink(`${baseUrl}/invite/${data.token}`); + setLinkCopied(false); }, }); @@ -686,13 +691,39 @@ export default function TenantDetailPage() {

{t('detail.invitations')}

+ {inviteLink && ( +
+

+ {t('detail.inviteLinkReady')} +

+
+ + +
+

{t('detail.inviteLinkHint')}

+
+ )} + {showInviteForm && (
diff --git a/it0-web-admin/src/i18n/locales/en/tenants.json b/it0-web-admin/src/i18n/locales/en/tenants.json index 3d8625c..a87f1f7 100644 --- a/it0-web-admin/src/i18n/locales/en/tenants.json +++ b/it0-web-admin/src/i18n/locales/en/tenants.json @@ -66,6 +66,10 @@ "sendInvite": "Send Invite", "sending": "Sending...", "revoke": "Revoke", + "inviteLinkReady": "Invitation link generated — share it with the user:", + "copy": "Copy", + "copied": "Copied!", + "inviteLinkHint": "This link is valid for 7 days. The user can use it to create their account.", "suspendTenant": "Suspend Tenant", "activateTenant": "Activate Tenant", "viewAuditLog": "View Audit Log", diff --git a/it0-web-admin/src/i18n/locales/zh/tenants.json b/it0-web-admin/src/i18n/locales/zh/tenants.json index c57d63b..729e492 100644 --- a/it0-web-admin/src/i18n/locales/zh/tenants.json +++ b/it0-web-admin/src/i18n/locales/zh/tenants.json @@ -66,6 +66,10 @@ "sendInvite": "发送邀请", "sending": "正在发送...", "revoke": "撤销", + "inviteLinkReady": "邀请链接已生成,将其发送给用户:", + "copy": "复制", + "copied": "已复制!", + "inviteLinkHint": "此链接有效期 7 天,用户通过该链接创建账号后即可登录。", "suspendTenant": "停用租户", "activateTenant": "激活租户", "viewAuditLog": "查看审计日志",