mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-23 21:06:22 +08:00
feat(core): improve invite link (#9111)
This commit is contained in:
+7
-1
@@ -1,5 +1,8 @@
|
|||||||
import { emailRegex } from '@affine/component/auth-components';
|
import { emailRegex } from '@affine/component/auth-components';
|
||||||
import type { WorkspaceInviteLinkExpireTime } from '@affine/graphql';
|
import type {
|
||||||
|
InviteLink,
|
||||||
|
WorkspaceInviteLinkExpireTime,
|
||||||
|
} from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
@@ -18,6 +21,7 @@ export interface InviteTeamMemberModalProps {
|
|||||||
) => Promise<string>;
|
) => Promise<string>;
|
||||||
onRevokeInviteLink: () => Promise<boolean>;
|
onRevokeInviteLink: () => Promise<boolean>;
|
||||||
importCSV: React.ReactNode;
|
importCSV: React.ReactNode;
|
||||||
|
invitationLink: InviteLink | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseEmailString = (emailString: string): string[] => {
|
const parseEmailString = (emailString: string): string[] => {
|
||||||
@@ -36,6 +40,7 @@ export const InviteTeamMemberModal = ({
|
|||||||
onGenerateInviteLink,
|
onGenerateInviteLink,
|
||||||
onRevokeInviteLink,
|
onRevokeInviteLink,
|
||||||
importCSV,
|
importCSV,
|
||||||
|
invitationLink,
|
||||||
}: InviteTeamMemberModalProps) => {
|
}: InviteTeamMemberModalProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [inviteEmails, setInviteEmails] = useState('');
|
const [inviteEmails, setInviteEmails] = useState('');
|
||||||
@@ -95,6 +100,7 @@ export const InviteTeamMemberModal = ({
|
|||||||
childrenContentClassName={styles.contentStyle}
|
childrenContentClassName={styles.contentStyle}
|
||||||
>
|
>
|
||||||
<ModalContent
|
<ModalContent
|
||||||
|
invitationLink={invitationLink}
|
||||||
inviteEmail={inviteEmails}
|
inviteEmail={inviteEmails}
|
||||||
setInviteEmail={setInviteEmails}
|
setInviteEmail={setInviteEmails}
|
||||||
handleConfirm={handleConfirm}
|
handleConfirm={handleConfirm}
|
||||||
|
|||||||
+23
-24
@@ -1,4 +1,7 @@
|
|||||||
import { WorkspaceInviteLinkExpireTime } from '@affine/graphql';
|
import {
|
||||||
|
type InviteLink,
|
||||||
|
WorkspaceInviteLinkExpireTime,
|
||||||
|
} from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { CloseIcon } from '@blocksuite/icons/rc';
|
import { CloseIcon } from '@blocksuite/icons/rc';
|
||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
@@ -39,10 +42,12 @@ const getMenuItems = (t: ReturnType<typeof useI18n>) => [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const LinkInvite = ({
|
export const LinkInvite = ({
|
||||||
|
invitationLink,
|
||||||
copyTextToClipboard,
|
copyTextToClipboard,
|
||||||
generateInvitationLink,
|
generateInvitationLink,
|
||||||
revokeInvitationLink,
|
revokeInvitationLink,
|
||||||
}: {
|
}: {
|
||||||
|
invitationLink: InviteLink | null;
|
||||||
generateInvitationLink: (
|
generateInvitationLink: (
|
||||||
expireTime: WorkspaceInviteLinkExpireTime
|
expireTime: WorkspaceInviteLinkExpireTime
|
||||||
) => Promise<string>;
|
) => Promise<string>;
|
||||||
@@ -53,7 +58,6 @@ export const LinkInvite = ({
|
|||||||
const [selectedValue, setSelectedValue] = useState(
|
const [selectedValue, setSelectedValue] = useState(
|
||||||
WorkspaceInviteLinkExpireTime.OneWeek
|
WorkspaceInviteLinkExpireTime.OneWeek
|
||||||
);
|
);
|
||||||
const [invitationLink, setInvitationLink] = useState('');
|
|
||||||
const menuItems = getMenuItems(t);
|
const menuItems = getMenuItems(t);
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
return menuItems.map(item => (
|
return menuItems.map(item => (
|
||||||
@@ -69,21 +73,20 @@ export const LinkInvite = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const onGenerate = useCallback(() => {
|
const onGenerate = useCallback(() => {
|
||||||
generateInvitationLink(selectedValue)
|
generateInvitationLink(selectedValue).catch(err => {
|
||||||
.then(link => {
|
console.error('Failed to generate invitation link: ', err);
|
||||||
setInvitationLink(link);
|
notify.error({
|
||||||
})
|
title: 'Failed to generate invitation link',
|
||||||
.catch(err => {
|
message: err.message,
|
||||||
console.error('Failed to generate invitation link: ', err);
|
|
||||||
notify.error({
|
|
||||||
title: 'Failed to generate invitation link',
|
|
||||||
message: err.message,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}, [generateInvitationLink, selectedValue]);
|
}, [generateInvitationLink, selectedValue]);
|
||||||
|
|
||||||
const onCopy = useCallback(() => {
|
const onCopy = useCallback(() => {
|
||||||
copyTextToClipboard(invitationLink)
|
if (!invitationLink) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
copyTextToClipboard(invitationLink.link)
|
||||||
.then(() =>
|
.then(() =>
|
||||||
notify.success({
|
notify.success({
|
||||||
title: t['Copied link to clipboard'](),
|
title: t['Copied link to clipboard'](),
|
||||||
@@ -99,17 +102,13 @@ export const LinkInvite = ({
|
|||||||
}, [copyTextToClipboard, invitationLink, t]);
|
}, [copyTextToClipboard, invitationLink, t]);
|
||||||
|
|
||||||
const onReset = useCallback(() => {
|
const onReset = useCallback(() => {
|
||||||
revokeInvitationLink()
|
revokeInvitationLink().catch(err => {
|
||||||
.then(() => {
|
console.error('Failed to revoke invitation link: ', err);
|
||||||
setInvitationLink('');
|
notify.error({
|
||||||
})
|
title: 'Failed to revoke invitation link',
|
||||||
.catch(err => {
|
message: err.message,
|
||||||
console.error('Failed to revoke invitation link: ', err);
|
|
||||||
notify.error({
|
|
||||||
title: 'Failed to revoke invitation link',
|
|
||||||
message: err.message,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}, [revokeInvitationLink]);
|
}, [revokeInvitationLink]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -136,7 +135,7 @@ export const LinkInvite = ({
|
|||||||
<Input
|
<Input
|
||||||
value={
|
value={
|
||||||
invitationLink
|
invitationLink
|
||||||
? invitationLink
|
? invitationLink.link
|
||||||
: 'https://your-app.com/invite/xxxxxxxx'
|
: 'https://your-app.com/invite/xxxxxxxx'
|
||||||
}
|
}
|
||||||
inputMode="none"
|
inputMode="none"
|
||||||
|
|||||||
+7
-1
@@ -1,4 +1,7 @@
|
|||||||
import type { WorkspaceInviteLinkExpireTime } from '@affine/graphql';
|
import type {
|
||||||
|
InviteLink,
|
||||||
|
WorkspaceInviteLinkExpireTime,
|
||||||
|
} from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { EmailIcon, LinkIcon } from '@blocksuite/icons/rc';
|
import { EmailIcon, LinkIcon } from '@blocksuite/icons/rc';
|
||||||
|
|
||||||
@@ -20,9 +23,11 @@ export const ModalContent = ({
|
|||||||
onGenerateInviteLink,
|
onGenerateInviteLink,
|
||||||
onRevokeInviteLink,
|
onRevokeInviteLink,
|
||||||
importCSV,
|
importCSV,
|
||||||
|
invitationLink,
|
||||||
}: {
|
}: {
|
||||||
inviteEmail: string;
|
inviteEmail: string;
|
||||||
importCSV: React.ReactNode;
|
importCSV: React.ReactNode;
|
||||||
|
invitationLink: InviteLink | null;
|
||||||
setInviteEmail: (value: string) => void;
|
setInviteEmail: (value: string) => void;
|
||||||
inviteMethod: InviteMethodType;
|
inviteMethod: InviteMethodType;
|
||||||
onInviteMethodChange: (value: InviteMethodType) => void;
|
onInviteMethodChange: (value: InviteMethodType) => void;
|
||||||
@@ -78,6 +83,7 @@ export const ModalContent = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<LinkInvite
|
<LinkInvite
|
||||||
|
invitationLink={invitationLink}
|
||||||
copyTextToClipboard={copyTextToClipboard}
|
copyTextToClipboard={copyTextToClipboard}
|
||||||
generateInvitationLink={onGenerateInviteLink}
|
generateInvitationLink={onGenerateInviteLink}
|
||||||
revokeInvitationLink={onRevokeInviteLink}
|
revokeInvitationLink={onRevokeInviteLink}
|
||||||
|
|||||||
+10
-2
@@ -13,6 +13,7 @@ import {
|
|||||||
WorkspacePermissionService,
|
WorkspacePermissionService,
|
||||||
} from '@affine/core/modules/permissions';
|
} from '@affine/core/modules/permissions';
|
||||||
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||||
|
import { WorkspaceShareSettingService } from '@affine/core/modules/share-setting';
|
||||||
import { copyTextToClipboard } from '@affine/core/utils/clipboard';
|
import { copyTextToClipboard } from '@affine/core/utils/clipboard';
|
||||||
import { emailRegex } from '@affine/core/utils/email-regex';
|
import { emailRegex } from '@affine/core/utils/email-regex';
|
||||||
import type { WorkspaceInviteLinkExpireTime } from '@affine/graphql';
|
import type { WorkspaceInviteLinkExpireTime } from '@affine/graphql';
|
||||||
@@ -49,6 +50,10 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
onChangeSettingState: (settingState: SettingState) => void;
|
onChangeSettingState: (settingState: SettingState) => void;
|
||||||
isTeam?: boolean;
|
isTeam?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
|
const workspaceShareSettingService = useService(WorkspaceShareSettingService);
|
||||||
|
const inviteLink = useLiveData(
|
||||||
|
workspaceShareSettingService.sharePreview.inviteLink$
|
||||||
|
);
|
||||||
const serverService = useService(ServerService);
|
const serverService = useService(ServerService);
|
||||||
const hasPaymentFeature = useLiveData(
|
const hasPaymentFeature = useLiveData(
|
||||||
serverService.server.features$.map(f => f?.payment)
|
serverService.server.features$.map(f => f?.payment)
|
||||||
@@ -90,15 +95,17 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
async (expireTime: WorkspaceInviteLinkExpireTime) => {
|
async (expireTime: WorkspaceInviteLinkExpireTime) => {
|
||||||
const { link } =
|
const { link } =
|
||||||
await permissionService.permission.generateInviteLink(expireTime);
|
await permissionService.permission.generateInviteLink(expireTime);
|
||||||
|
workspaceShareSettingService.sharePreview.revalidate();
|
||||||
return link;
|
return link;
|
||||||
},
|
},
|
||||||
[permissionService.permission]
|
[permissionService.permission, workspaceShareSettingService.sharePreview]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onRevokeInviteLink = useCallback(async () => {
|
const onRevokeInviteLink = useCallback(async () => {
|
||||||
const success = await permissionService.permission.revokeInviteLink();
|
const success = await permissionService.permission.revokeInviteLink();
|
||||||
|
workspaceShareSettingService.sharePreview.revalidate();
|
||||||
return success;
|
return success;
|
||||||
}, [permissionService.permission]);
|
}, [permissionService.permission, workspaceShareSettingService.sharePreview]);
|
||||||
|
|
||||||
const onInviteBatchConfirm = useCallback<
|
const onInviteBatchConfirm = useCallback<
|
||||||
InviteTeamMemberModalProps['onConfirm']
|
InviteTeamMemberModalProps['onConfirm']
|
||||||
@@ -218,6 +225,7 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
onGenerateInviteLink={onGenerateInviteLink}
|
onGenerateInviteLink={onGenerateInviteLink}
|
||||||
onRevokeInviteLink={onRevokeInviteLink}
|
onRevokeInviteLink={onRevokeInviteLink}
|
||||||
importCSV={<ImportCSV onImport={onImportCSV} />}
|
importCSV={<ImportCSV onImport={onImportCSV} />}
|
||||||
|
invitationLink={inviteLink}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
import type { GetWorkspaceConfigQuery } from '@affine/graphql';
|
import type { GetWorkspaceConfigQuery, InviteLink } from '@affine/graphql';
|
||||||
import type { WorkspaceService } from '@toeverything/infra';
|
import type { WorkspaceService } from '@toeverything/infra';
|
||||||
import {
|
import {
|
||||||
backoffRetry,
|
backoffRetry,
|
||||||
@@ -25,6 +25,7 @@ const logger = new DebugLogger('affine:workspace-permission');
|
|||||||
export class WorkspaceShareSetting extends Entity {
|
export class WorkspaceShareSetting extends Entity {
|
||||||
enableAi$ = new LiveData<EnableAi | null>(null);
|
enableAi$ = new LiveData<EnableAi | null>(null);
|
||||||
enableUrlPreview$ = new LiveData<EnableUrlPreview | null>(null);
|
enableUrlPreview$ = new LiveData<EnableUrlPreview | null>(null);
|
||||||
|
inviteLink$ = new LiveData<InviteLink | null>(null);
|
||||||
isLoading$ = new LiveData(false);
|
isLoading$ = new LiveData(false);
|
||||||
error$ = new LiveData<any>(null);
|
error$ = new LiveData<any>(null);
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ export class WorkspaceShareSetting extends Entity {
|
|||||||
if (value) {
|
if (value) {
|
||||||
this.enableAi$.next(value.enableAi);
|
this.enableAi$.next(value.enableAi);
|
||||||
this.enableUrlPreview$.next(value.enableUrlPreview);
|
this.enableUrlPreview$.next(value.enableUrlPreview);
|
||||||
|
this.inviteLink$.next(value.inviteLink);
|
||||||
}
|
}
|
||||||
return EMPTY;
|
return EMPTY;
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user