feat(core): improve invite link (#9111)

This commit is contained in:
JimmFly
2024-12-12 17:43:19 +08:00
committed by GitHub
parent cdb55a3393
commit dd39d049fe
5 changed files with 50 additions and 29 deletions
@@ -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}
@@ -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"
@@ -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}
@@ -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;
}), }),