feat(core): optimize team workspace member management (#9737)
close AF-2106 AF-2077 AF-2089 feat(core): handle need more seat status feat(core): prevent invite members when team plan is canceled
This commit is contained in:
@@ -22,7 +22,7 @@ export const AuthPageContainer: FC<
|
|||||||
<div className="wrapper">
|
<div className="wrapper">
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<p className="title">{title}</p>
|
<p className="title">{title}</p>
|
||||||
<p className="subtitle">{subtitle}</p>
|
<div className="subtitle">{subtitle}</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
<div className={hideInSmallScreen}>
|
<div className={hideInSmallScreen}>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from './accept-invite-page';
|
export * from './accept-invite-page';
|
||||||
export * from './invite-modal';
|
export * from './invite-modal';
|
||||||
export * from './invite-team-modal';
|
export * from './invite-team-modal';
|
||||||
|
export * from './join-failed-page';
|
||||||
export * from './member-limit-modal';
|
export * from './member-limit-modal';
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { AuthPageContainer } from '@affine/component/auth-components';
|
||||||
|
import {
|
||||||
|
ErrorNames,
|
||||||
|
type GetInviteInfoQuery,
|
||||||
|
UserFriendlyError,
|
||||||
|
} from '@affine/graphql';
|
||||||
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
|
|
||||||
|
import { Avatar } from '../../ui/avatar';
|
||||||
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
|
export const JoinFailedPage = ({
|
||||||
|
inviteInfo,
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
inviteInfo: GetInviteInfoQuery['getInviteInfo'];
|
||||||
|
error?: any;
|
||||||
|
}) => {
|
||||||
|
const userFriendlyError = UserFriendlyError.fromAnyError(error);
|
||||||
|
const t = useI18n();
|
||||||
|
return (
|
||||||
|
<AuthPageContainer
|
||||||
|
title={t['com.affine.fail-to-join-workspace.title']()}
|
||||||
|
subtitle={
|
||||||
|
userFriendlyError.name === ErrorNames.MEMBER_QUOTA_EXCEEDED ? (
|
||||||
|
<div className={styles.content}>
|
||||||
|
<Trans
|
||||||
|
i18nKey={'com.affine.fail-to-join-workspace.description-1'}
|
||||||
|
components={{
|
||||||
|
1: (
|
||||||
|
<Avatar
|
||||||
|
url={`data:image/png;base64,${inviteInfo.workspace.avatar}`}
|
||||||
|
name={inviteInfo.workspace.name}
|
||||||
|
size={20}
|
||||||
|
style={{ marginLeft: 4 }}
|
||||||
|
colorfulFallback
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
2: <span className={styles.inviteName} />,
|
||||||
|
}}
|
||||||
|
values={{
|
||||||
|
workspaceName: inviteInfo.workspace.name,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div>{t['com.affine.fail-to-join-workspace.description-2']()}</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>{t['error.' + userFriendlyError.name]()}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -11,6 +11,7 @@ import illustrationLight from '../affine-other-page-layout/assets/other-page.lig
|
|||||||
import type { User } from '../auth-components';
|
import type { User } from '../auth-components';
|
||||||
import {
|
import {
|
||||||
illustration,
|
illustration,
|
||||||
|
info,
|
||||||
largeButtonEffect,
|
largeButtonEffect,
|
||||||
notFoundPageContainer,
|
notFoundPageContainer,
|
||||||
wrapper,
|
wrapper,
|
||||||
@@ -35,14 +36,7 @@ export const NoPermissionOrNotFound = ({
|
|||||||
<div className={notFoundPageContainer} data-testid="not-found">
|
<div className={notFoundPageContainer} data-testid="not-found">
|
||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
<div className={wrapper}>
|
<div className={info}>
|
||||||
<ThemedImg
|
|
||||||
draggable={false}
|
|
||||||
className={illustration}
|
|
||||||
lightSrc={illustrationLight}
|
|
||||||
darkSrc={illustrationDark}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p className={wrapper}>{t['404.hint']()}</p>
|
<p className={wrapper}>{t['404.hint']()}</p>
|
||||||
<div className={wrapper}>
|
<div className={wrapper}>
|
||||||
<Button
|
<Button
|
||||||
@@ -65,6 +59,15 @@ export const NoPermissionOrNotFound = ({
|
|||||||
<SignOutIcon />
|
<SignOutIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={wrapper}>
|
||||||
|
<ThemedImg
|
||||||
|
draggable={false}
|
||||||
|
className={illustration}
|
||||||
|
lightSrc={illustrationLight}
|
||||||
|
darkSrc={illustrationDark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
signInComponent
|
signInComponent
|
||||||
@@ -84,14 +87,7 @@ export const NotFoundPage = ({
|
|||||||
return (
|
return (
|
||||||
<AffineOtherPageLayout>
|
<AffineOtherPageLayout>
|
||||||
<div className={notFoundPageContainer} data-testid="not-found">
|
<div className={notFoundPageContainer} data-testid="not-found">
|
||||||
<div className={wrapper}>
|
<div className={info}>
|
||||||
<ThemedImg
|
|
||||||
draggable={false}
|
|
||||||
className={illustration}
|
|
||||||
lightSrc={illustrationLight}
|
|
||||||
darkSrc={illustrationDark}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p className={wrapper}>{t['404.hint']()}</p>
|
<p className={wrapper}>{t['404.hint']()}</p>
|
||||||
<div className={wrapper}>
|
<div className={wrapper}>
|
||||||
<Button
|
<Button
|
||||||
@@ -103,7 +99,6 @@ export const NotFoundPage = ({
|
|||||||
{t['404.back']()}
|
{t['404.back']()}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{user ? (
|
{user ? (
|
||||||
<div className={wrapper}>
|
<div className={wrapper}>
|
||||||
<Avatar url={user.avatar ?? user.image} name={user.label} />
|
<Avatar url={user.avatar ?? user.image} name={user.label} />
|
||||||
@@ -118,6 +113,15 @@ export const NotFoundPage = ({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
<div className={wrapper}>
|
||||||
|
<ThemedImg
|
||||||
|
draggable={false}
|
||||||
|
className={illustration}
|
||||||
|
lightSrc={illustrationLight}
|
||||||
|
darkSrc={illustrationDark}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</AffineOtherPageLayout>
|
</AffineOtherPageLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ import { style } from '@vanilla-extract/css';
|
|||||||
export const notFoundPageContainer = style({
|
export const notFoundPageContainer = style({
|
||||||
fontSize: cssVar('fontBase'),
|
fontSize: cssVar('fontBase'),
|
||||||
color: cssVar('textPrimaryColor'),
|
color: cssVar('textPrimaryColor'),
|
||||||
height: '100vh',
|
height: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '0 20px',
|
padding: '0 20px',
|
||||||
});
|
});
|
||||||
@@ -15,7 +14,18 @@ export const wrapper = style({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
margin: '24px auto 0',
|
margin: '0 auto',
|
||||||
|
});
|
||||||
|
export const info = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '24px',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginTop: 'auto',
|
||||||
|
paddingTop: '120px',
|
||||||
|
marginBottom: 'auto',
|
||||||
});
|
});
|
||||||
export const largeButtonEffect = style({
|
export const largeButtonEffect = style({
|
||||||
boxShadow: `${cssVar('largeButtonEffect')} !important`,
|
boxShadow: `${cssVar('largeButtonEffect')} !important`,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { notify } from '@affine/component';
|
||||||
import { useDowngradeNotify } from '@affine/core/components/affine/subscription-landing/notify';
|
import { useDowngradeNotify } from '@affine/core/components/affine/subscription-landing/notify';
|
||||||
import { getDowngradeQuestionnaireLink } from '@affine/core/components/hooks/affine/use-subscription-notify';
|
import { getDowngradeQuestionnaireLink } from '@affine/core/components/hooks/affine/use-subscription-notify';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { SubscriptionPlan } from '@affine/graphql';
|
import { SubscriptionPlan } from '@affine/graphql';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
@@ -231,6 +233,7 @@ export const TeamResumeAction = ({
|
|||||||
const [idempotencyKey, setIdempotencyKey] = useState(nanoid());
|
const [idempotencyKey, setIdempotencyKey] = useState(nanoid());
|
||||||
const [isMutating, setIsMutating] = useState(false);
|
const [isMutating, setIsMutating] = useState(false);
|
||||||
const subscription = useService(WorkspaceSubscriptionService).subscription;
|
const subscription = useService(WorkspaceSubscriptionService).subscription;
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
const resume = useAsyncCallback(async () => {
|
const resume = useAsyncCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -243,10 +246,14 @@ export const TeamResumeAction = ({
|
|||||||
// refresh idempotency key
|
// refresh idempotency key
|
||||||
setIdempotencyKey(nanoid());
|
setIdempotencyKey(nanoid());
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
|
notify.success({
|
||||||
|
title: t['com.affine.payment.resume.success.title'](),
|
||||||
|
message: t['com.affine.payment.resume.success.team.message'](),
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsMutating(false);
|
setIsMutating(false);
|
||||||
}
|
}
|
||||||
}, [subscription, idempotencyKey, onOpenChange]);
|
}, [subscription, idempotencyKey, onOpenChange, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, Loading, notify } from '@affine/component';
|
import { Button, Loading, notify, useConfirmModal } from '@affine/component';
|
||||||
import {
|
import {
|
||||||
InviteTeamMemberModal,
|
InviteTeamMemberModal,
|
||||||
type InviteTeamMemberModalProps,
|
type InviteTeamMemberModalProps,
|
||||||
@@ -7,7 +7,11 @@ import {
|
|||||||
import { SettingRow } from '@affine/component/setting-components';
|
import { SettingRow } from '@affine/component/setting-components';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { Upload } from '@affine/core/components/pure/file-upload';
|
import { Upload } from '@affine/core/components/pure/file-upload';
|
||||||
import { ServerService, SubscriptionService } from '@affine/core/modules/cloud';
|
import {
|
||||||
|
ServerService,
|
||||||
|
SubscriptionService,
|
||||||
|
WorkspaceSubscriptionService,
|
||||||
|
} from '@affine/core/modules/cloud';
|
||||||
import {
|
import {
|
||||||
WorkspaceMembersService,
|
WorkspaceMembersService,
|
||||||
WorkspacePermissionService,
|
WorkspacePermissionService,
|
||||||
@@ -17,11 +21,12 @@ 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';
|
||||||
import { UserFriendlyError } from '@affine/graphql';
|
import { SubscriptionPlan, UserFriendlyError } from '@affine/graphql';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { ExportIcon } from '@blocksuite/icons/rc';
|
import { ExportIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import type { SettingState } from '../../types';
|
import type { SettingState } from '../../types';
|
||||||
@@ -51,6 +56,8 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
isTeam?: boolean;
|
isTeam?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const workspaceShareSettingService = useService(WorkspaceShareSettingService);
|
const workspaceShareSettingService = useService(WorkspaceShareSettingService);
|
||||||
|
const subscription = useService(WorkspaceSubscriptionService).subscription;
|
||||||
|
const workspaceSubscription = useLiveData(subscription.subscription$);
|
||||||
const inviteLink = useLiveData(
|
const inviteLink = useLiveData(
|
||||||
workspaceShareSettingService.sharePreview.inviteLink$
|
workspaceShareSettingService.sharePreview.inviteLink$
|
||||||
);
|
);
|
||||||
@@ -89,9 +96,77 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
const [openMemberLimit, setOpenMemberLimit] = useState(false);
|
const [openMemberLimit, setOpenMemberLimit] = useState(false);
|
||||||
const [isMutating, setIsMutating] = useState(false);
|
const [isMutating, setIsMutating] = useState(false);
|
||||||
|
|
||||||
|
const { openConfirmModal, closeConfirmModal } = useConfirmModal();
|
||||||
|
const goToTeamBilling = useCallback(() => {
|
||||||
|
onChangeSettingState({
|
||||||
|
activeTab: 'workspace:billing',
|
||||||
|
});
|
||||||
|
}, [onChangeSettingState]);
|
||||||
|
const [idempotencyKey, setIdempotencyKey] = useState(nanoid());
|
||||||
|
const resume = useAsyncCallback(async () => {
|
||||||
|
try {
|
||||||
|
setIsMutating(true);
|
||||||
|
await subscription.resumeSubscription(
|
||||||
|
idempotencyKey,
|
||||||
|
SubscriptionPlan.Team
|
||||||
|
);
|
||||||
|
await subscription.waitForRevalidation();
|
||||||
|
// refresh idempotency key
|
||||||
|
setIdempotencyKey(nanoid());
|
||||||
|
closeConfirmModal();
|
||||||
|
notify.success({
|
||||||
|
title: t['com.affine.payment.resume.success.title'](),
|
||||||
|
message: t['com.affine.payment.resume.success.team.message'](),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const error = UserFriendlyError.fromAnyError(err);
|
||||||
|
notify.error({
|
||||||
|
title: error.name,
|
||||||
|
message: error.message,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsMutating(false);
|
||||||
|
}
|
||||||
|
}, [subscription, idempotencyKey, closeConfirmModal, t]);
|
||||||
const openInviteModal = useCallback(() => {
|
const openInviteModal = useCallback(() => {
|
||||||
|
if (isTeam && workspaceSubscription?.canceledAt) {
|
||||||
|
openConfirmModal({
|
||||||
|
title: t['com.affine.payment.member.team.retry-payment.title'](),
|
||||||
|
description:
|
||||||
|
t[
|
||||||
|
`com.affine.payment.member.team.disabled-subscription.${isOwner ? 'owner' : 'admin'}.description`
|
||||||
|
](),
|
||||||
|
confirmText:
|
||||||
|
t[
|
||||||
|
isOwner
|
||||||
|
? 'com.affine.payment.member.team.disabled-subscription.resume-subscription'
|
||||||
|
: 'Got it'
|
||||||
|
](),
|
||||||
|
cancelText: t['Cancel'](),
|
||||||
|
cancelButtonOptions: {
|
||||||
|
style: {
|
||||||
|
visibility: isOwner ? 'visible' : 'hidden',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onConfirm: isOwner ? resume : undefined,
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'primary',
|
||||||
|
loading: isMutating,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
setOpenInvite(true);
|
setOpenInvite(true);
|
||||||
}, []);
|
}, [
|
||||||
|
isMutating,
|
||||||
|
isOwner,
|
||||||
|
isTeam,
|
||||||
|
openConfirmModal,
|
||||||
|
resume,
|
||||||
|
t,
|
||||||
|
workspaceSubscription?.canceledAt,
|
||||||
|
]);
|
||||||
|
|
||||||
const onGenerateInviteLink = useCallback(
|
const onGenerateInviteLink = useCallback(
|
||||||
async (expireTime: WorkspaceInviteLinkExpireTime) => {
|
async (expireTime: WorkspaceInviteLinkExpireTime) => {
|
||||||
@@ -253,7 +328,11 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<div className={styles.membersPanel}>
|
<div className={styles.membersPanel}>
|
||||||
<MemberList isOwner={!!isOwner} isAdmin={!!isAdmin} />
|
<MemberList
|
||||||
|
isOwner={!!isOwner}
|
||||||
|
isAdmin={!!isAdmin}
|
||||||
|
goToTeamBilling={goToTeamBilling}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,9 +29,11 @@ import * as styles from './styles.css';
|
|||||||
export const MemberList = ({
|
export const MemberList = ({
|
||||||
isOwner,
|
isOwner,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
|
goToTeamBilling,
|
||||||
}: {
|
}: {
|
||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
goToTeamBilling: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const membersService = useService(WorkspaceMembersService);
|
const membersService = useService(WorkspaceMembersService);
|
||||||
const memberCount = useLiveData(membersService.members.memberCount$);
|
const memberCount = useLiveData(membersService.members.memberCount$);
|
||||||
@@ -85,6 +87,7 @@ export const MemberList = ({
|
|||||||
member={member}
|
member={member}
|
||||||
isOwner={isOwner}
|
isOwner={isOwner}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
|
goToTeamBilling={goToTeamBilling}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@@ -130,11 +133,13 @@ const MemberItem = ({
|
|||||||
isOwner,
|
isOwner,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
currentAccount,
|
currentAccount,
|
||||||
|
goToTeamBilling,
|
||||||
}: {
|
}: {
|
||||||
member: Member;
|
member: Member;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
currentAccount: AuthAccountInfo;
|
currentAccount: AuthAccountInfo;
|
||||||
|
goToTeamBilling: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -208,7 +213,10 @@ const MemberItem = ({
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.roleOrStatus, {
|
className={clsx(styles.roleOrStatus, {
|
||||||
pending: member.status !== WorkspaceMemberStatus.Accepted,
|
pending:
|
||||||
|
member.status !== WorkspaceMemberStatus.Accepted &&
|
||||||
|
member.status !== WorkspaceMemberStatus.NeedMoreSeat,
|
||||||
|
error: member.status === WorkspaceMemberStatus.NeedMoreSeat,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{t.t(memberStatus)}
|
{t.t(memberStatus)}
|
||||||
@@ -220,6 +228,7 @@ const MemberItem = ({
|
|||||||
openAssignModal={handleOpenAssignModal}
|
openAssignModal={handleOpenAssignModal}
|
||||||
isAdmin={isAdmin}
|
isAdmin={isAdmin}
|
||||||
isOwner={isOwner}
|
isOwner={isOwner}
|
||||||
|
goToTeamBilling={goToTeamBilling}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -249,9 +258,10 @@ const MemberItem = ({
|
|||||||
const getMemberStatus = (member: Member): I18nString => {
|
const getMemberStatus = (member: Member): I18nString => {
|
||||||
switch (member.status) {
|
switch (member.status) {
|
||||||
case WorkspaceMemberStatus.NeedMoreSeat:
|
case WorkspaceMemberStatus.NeedMoreSeat:
|
||||||
case WorkspaceMemberStatus.NeedMoreSeatAndReview:
|
return 'insufficient-team-seat';
|
||||||
case WorkspaceMemberStatus.Pending:
|
case WorkspaceMemberStatus.Pending:
|
||||||
return 'Pending';
|
return 'Pending';
|
||||||
|
case WorkspaceMemberStatus.NeedMoreSeatAndReview:
|
||||||
case WorkspaceMemberStatus.UnderReview:
|
case WorkspaceMemberStatus.UnderReview:
|
||||||
return 'Under-Review';
|
return 'Under-Review';
|
||||||
case WorkspaceMemberStatus.Accepted:
|
case WorkspaceMemberStatus.Accepted:
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ export const MemberOptions = ({
|
|||||||
isOwner,
|
isOwner,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
openAssignModal,
|
openAssignModal,
|
||||||
|
goToTeamBilling,
|
||||||
}: {
|
}: {
|
||||||
member: Member;
|
member: Member;
|
||||||
isOwner: boolean;
|
isOwner: boolean;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
openAssignModal: () => void;
|
openAssignModal: () => void;
|
||||||
|
goToTeamBilling: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const membersService = useService(WorkspaceMembersService);
|
const membersService = useService(WorkspaceMembersService);
|
||||||
@@ -163,13 +165,49 @@ export const MemberOptions = ({
|
|||||||
});
|
});
|
||||||
}, [member, membersService, t]);
|
}, [member, membersService, t]);
|
||||||
|
|
||||||
|
const handleRetryPayment = useCallback(() => {
|
||||||
|
openConfirmModal({
|
||||||
|
title: t['com.affine.payment.member.team.retry-payment.title'](),
|
||||||
|
description:
|
||||||
|
t[
|
||||||
|
`com.affine.payment.member.team.retry-payment.${isOwner ? 'owner' : 'admin'}.description`
|
||||||
|
](),
|
||||||
|
confirmText:
|
||||||
|
t[
|
||||||
|
isOwner
|
||||||
|
? 'com.affine.payment.member.team.retry-payment.update-payment'
|
||||||
|
: 'Got it'
|
||||||
|
](),
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'primary',
|
||||||
|
},
|
||||||
|
onConfirm: isOwner ? goToTeamBilling : undefined,
|
||||||
|
cancelText: t['Cancel'](),
|
||||||
|
cancelButtonOptions: {
|
||||||
|
style: {
|
||||||
|
visibility: isOwner ? 'visible' : 'hidden',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [goToTeamBilling, isOwner, openConfirmModal, t]);
|
||||||
|
|
||||||
const operationButtonInfo = useMemo(() => {
|
const operationButtonInfo = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
label: t['com.affine.payment.member.team.retry-payment'](),
|
||||||
|
onClick: handleRetryPayment,
|
||||||
|
show: member.status === WorkspaceMemberStatus.NeedMoreSeat,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t['com.affine.payment.member.team.approve'](),
|
label: t['com.affine.payment.member.team.approve'](),
|
||||||
onClick: handleApprove,
|
onClick: handleApprove,
|
||||||
show: member.status === WorkspaceMemberStatus.UnderReview,
|
show: member.status === WorkspaceMemberStatus.UnderReview,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t['com.affine.payment.member.team.approve'](),
|
||||||
|
onClick: handleRetryPayment,
|
||||||
|
show: member.status === WorkspaceMemberStatus.NeedMoreSeatAndReview,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t['com.affine.payment.member.team.decline'](),
|
label: t['com.affine.payment.member.team.decline'](),
|
||||||
onClick: handleDecline,
|
onClick: handleDecline,
|
||||||
@@ -230,11 +268,13 @@ export const MemberOptions = ({
|
|||||||
handleChangeToCollaborator,
|
handleChangeToCollaborator,
|
||||||
handleDecline,
|
handleDecline,
|
||||||
handleRemove,
|
handleRemove,
|
||||||
|
handleRetryPayment,
|
||||||
handleRevoke,
|
handleRevoke,
|
||||||
isAdmin,
|
isAdmin,
|
||||||
isOwner,
|
isOwner,
|
||||||
isTeam,
|
isTeam,
|
||||||
member,
|
member.permission,
|
||||||
|
member.status,
|
||||||
t,
|
t,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ export const roleOrStatus = style({
|
|||||||
'&.pending': {
|
'&.pending': {
|
||||||
color: cssVarV2('text/emphasis'),
|
color: cssVarV2('text/emphasis'),
|
||||||
},
|
},
|
||||||
|
'&.error': {
|
||||||
|
color: cssVarV2('status/error'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,80 @@
|
|||||||
import { AcceptInvitePage } from '@affine/component/member-components';
|
|
||||||
import type { GetInviteInfoQuery } from '@affine/graphql';
|
|
||||||
import {
|
import {
|
||||||
acceptInviteByInviteIdMutation,
|
AcceptInvitePage,
|
||||||
ErrorNames,
|
JoinFailedPage,
|
||||||
getInviteInfoQuery,
|
} from '@affine/component/member-components';
|
||||||
UserFriendlyError,
|
import { ErrorNames, UserFriendlyError } from '@affine/graphql';
|
||||||
} from '@affine/graphql';
|
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { Navigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RouteLogic,
|
RouteLogic,
|
||||||
useNavigateHelper,
|
useNavigateHelper,
|
||||||
} from '../../../components/hooks/use-navigate-helper';
|
} from '../../../components/hooks/use-navigate-helper';
|
||||||
import { AuthService, GraphQLService } from '../../../modules/cloud';
|
import { AcceptInviteService, AuthService } from '../../../modules/cloud';
|
||||||
import { AppContainer } from '../../components/app-container';
|
|
||||||
|
const AcceptInvite = ({ inviteId }: { inviteId: string }) => {
|
||||||
|
const { jumpToPage } = useNavigateHelper();
|
||||||
|
const acceptInviteService = useService(AcceptInviteService);
|
||||||
|
const error = useLiveData(acceptInviteService.error$);
|
||||||
|
const inviteInfo = useLiveData(acceptInviteService.inviteInfo$);
|
||||||
|
const accepted = useLiveData(acceptInviteService.accepted$);
|
||||||
|
const loading = useLiveData(acceptInviteService.loading$);
|
||||||
|
const navigateHelper = useNavigateHelper();
|
||||||
|
|
||||||
|
const openWorkspace = useCallback(() => {
|
||||||
|
if (!inviteInfo?.workspace.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
jumpToPage(inviteInfo.workspace.id, 'all', RouteLogic.REPLACE);
|
||||||
|
}, [jumpToPage, inviteInfo]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
acceptInviteService.revalidate({
|
||||||
|
inviteId,
|
||||||
|
});
|
||||||
|
}, [acceptInviteService, inviteId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error) {
|
||||||
|
const err = UserFriendlyError.fromAnyError(error);
|
||||||
|
if (err.name === ErrorNames.ALREADY_IN_SPACE) {
|
||||||
|
return navigateHelper.jumpToIndex();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [error, navigateHelper]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inviteInfo) {
|
||||||
|
// if invite is expired
|
||||||
|
return <Navigate to="/expired" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <JoinFailedPage inviteInfo={inviteInfo} error={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accepted) {
|
||||||
|
return (
|
||||||
|
<AcceptInvitePage
|
||||||
|
inviteInfo={inviteInfo}
|
||||||
|
onOpenWorkspace={openWorkspace}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// invite is expired
|
||||||
|
return <Navigate to="/expired" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* /invite/:inviteId page
|
* /invite/:inviteId page
|
||||||
*
|
*
|
||||||
* only for web
|
* only for web
|
||||||
*/
|
*/
|
||||||
const AcceptInvite = ({
|
|
||||||
inviteInfo,
|
|
||||||
}: {
|
|
||||||
inviteInfo: GetInviteInfoQuery['getInviteInfo'];
|
|
||||||
}) => {
|
|
||||||
const { jumpToPage } = useNavigateHelper();
|
|
||||||
|
|
||||||
const openWorkspace = useCallback(() => {
|
|
||||||
jumpToPage(inviteInfo.workspace.id, 'all', RouteLogic.REPLACE);
|
|
||||||
}, [inviteInfo.workspace.id, jumpToPage]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AcceptInvitePage inviteInfo={inviteInfo} onOpenWorkspace={openWorkspace} />
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
const authService = useService(AuthService);
|
const authService = useService(AuthService);
|
||||||
const isRevalidating = useLiveData(authService.session.isRevalidating$);
|
const isRevalidating = useLiveData(authService.session.isRevalidating$);
|
||||||
@@ -57,73 +94,13 @@ export const Component = () => {
|
|||||||
}
|
}
|
||||||
}, [isRevalidating, jumpToSignIn, loginStatus, params.inviteId]);
|
}, [isRevalidating, jumpToSignIn, loginStatus, params.inviteId]);
|
||||||
|
|
||||||
|
if (!params.inviteId) {
|
||||||
|
return <Navigate to="/expired" />;
|
||||||
|
}
|
||||||
|
|
||||||
if (loginStatus === 'authenticated') {
|
if (loginStatus === 'authenticated') {
|
||||||
return <Middle />;
|
return <AcceptInvite inviteId={params.inviteId} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Middle = () => {
|
|
||||||
const graphqlService = useService(GraphQLService);
|
|
||||||
const params = useParams<{ inviteId: string }>();
|
|
||||||
const navigateHelper = useNavigateHelper();
|
|
||||||
|
|
||||||
const [data, setData] = useState<{
|
|
||||||
inviteId: string;
|
|
||||||
inviteInfo: GetInviteInfoQuery['getInviteInfo'];
|
|
||||||
} | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
setData(null);
|
|
||||||
const inviteId = params.inviteId || '';
|
|
||||||
const res = await graphqlService.gql({
|
|
||||||
query: getInviteInfoQuery,
|
|
||||||
variables: {
|
|
||||||
inviteId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the inviteId is invalid, redirect to 404 page
|
|
||||||
if (!res || !res?.getInviteInfo) {
|
|
||||||
return navigateHelper.jumpTo404();
|
|
||||||
}
|
|
||||||
|
|
||||||
// No mater sign in or not, we need to accept the invite
|
|
||||||
await graphqlService.gql({
|
|
||||||
query: acceptInviteByInviteIdMutation,
|
|
||||||
variables: {
|
|
||||||
workspaceId: res.getInviteInfo.workspace.id,
|
|
||||||
inviteId,
|
|
||||||
sendAcceptMail: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
setData({
|
|
||||||
inviteId,
|
|
||||||
inviteInfo: res.getInviteInfo,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
})().catch(error => {
|
|
||||||
const userFriendlyError = UserFriendlyError.fromAnyError(error);
|
|
||||||
console.error(userFriendlyError);
|
|
||||||
if (userFriendlyError.name === ErrorNames.ALREADY_IN_SPACE) {
|
|
||||||
return navigateHelper.jumpToIndex();
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
userFriendlyError.name === ErrorNames.USER_NOT_FOUND ||
|
|
||||||
userFriendlyError.name === ErrorNames.SPACE_OWNER_NOT_FOUND
|
|
||||||
) {
|
|
||||||
return navigateHelper.jumpToExpired();
|
|
||||||
}
|
|
||||||
return navigateHelper.jumpTo404();
|
|
||||||
});
|
|
||||||
}, [graphqlService, navigateHelper, params.inviteId]);
|
|
||||||
|
|
||||||
if (!data) {
|
|
||||||
return <AppContainer fallback />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <AcceptInvite inviteInfo={data.inviteInfo} />;
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export { AccountLoggedIn } from './events/account-logged-in';
|
|||||||
export { AccountLoggedOut } from './events/account-logged-out';
|
export { AccountLoggedOut } from './events/account-logged-out';
|
||||||
export { ServerInitialized } from './events/server-initialized';
|
export { ServerInitialized } from './events/server-initialized';
|
||||||
export { ValidatorProvider } from './provider/validator';
|
export { ValidatorProvider } from './provider/validator';
|
||||||
|
export { AcceptInviteService } from './services/accept-invite';
|
||||||
export { AuthService } from './services/auth';
|
export { AuthService } from './services/auth';
|
||||||
export { CaptchaService } from './services/captcha';
|
export { CaptchaService } from './services/captcha';
|
||||||
export { DefaultServerService } from './services/default-server';
|
export { DefaultServerService } from './services/default-server';
|
||||||
@@ -52,6 +53,7 @@ import { WorkspaceInvoices } from './entities/workspace-invoices';
|
|||||||
import { WorkspaceSubscription } from './entities/workspace-subscription';
|
import { WorkspaceSubscription } from './entities/workspace-subscription';
|
||||||
import { ValidatorProvider } from './provider/validator';
|
import { ValidatorProvider } from './provider/validator';
|
||||||
import { ServerScope } from './scopes/server';
|
import { ServerScope } from './scopes/server';
|
||||||
|
import { AcceptInviteService } from './services/accept-invite';
|
||||||
import { AuthService } from './services/auth';
|
import { AuthService } from './services/auth';
|
||||||
import { CaptchaService } from './services/captcha';
|
import { CaptchaService } from './services/captcha';
|
||||||
import { CloudDocMetaService } from './services/cloud-doc-meta';
|
import { CloudDocMetaService } from './services/cloud-doc-meta';
|
||||||
@@ -71,8 +73,10 @@ import { UserQuotaService } from './services/user-quota';
|
|||||||
import { WorkspaceInvoicesService } from './services/workspace-invoices';
|
import { WorkspaceInvoicesService } from './services/workspace-invoices';
|
||||||
import { WorkspaceServerService } from './services/workspace-server';
|
import { WorkspaceServerService } from './services/workspace-server';
|
||||||
import { WorkspaceSubscriptionService } from './services/workspace-subscription';
|
import { WorkspaceSubscriptionService } from './services/workspace-subscription';
|
||||||
|
import { AcceptInviteStore } from './stores/accept-invite';
|
||||||
import { AuthStore } from './stores/auth';
|
import { AuthStore } from './stores/auth';
|
||||||
import { CloudDocMetaStore } from './stores/cloud-doc-meta';
|
import { CloudDocMetaStore } from './stores/cloud-doc-meta';
|
||||||
|
import { InviteInfoStore } from './stores/invite-info';
|
||||||
import { InvoicesStore } from './stores/invoices';
|
import { InvoicesStore } from './stores/invoices';
|
||||||
import { SelfhostGenerateLicenseStore } from './stores/selfhost-generate-license';
|
import { SelfhostGenerateLicenseStore } from './stores/selfhost-generate-license';
|
||||||
import { SelfhostLicenseStore } from './stores/selfhost-license';
|
import { SelfhostLicenseStore } from './stores/selfhost-license';
|
||||||
@@ -136,7 +140,10 @@ export function configureCloudModule(framework: Framework) {
|
|||||||
.store(InvoicesStore, [GraphQLService])
|
.store(InvoicesStore, [GraphQLService])
|
||||||
.entity(Invoices, [InvoicesStore])
|
.entity(Invoices, [InvoicesStore])
|
||||||
.service(SelfhostGenerateLicenseService, [SelfhostGenerateLicenseStore])
|
.service(SelfhostGenerateLicenseService, [SelfhostGenerateLicenseStore])
|
||||||
.store(SelfhostGenerateLicenseStore, [GraphQLService]);
|
.store(SelfhostGenerateLicenseStore, [GraphQLService])
|
||||||
|
.store(InviteInfoStore, [GraphQLService])
|
||||||
|
.service(AcceptInviteService, [AcceptInviteStore, InviteInfoStore])
|
||||||
|
.store(AcceptInviteStore, [GraphQLService]);
|
||||||
|
|
||||||
framework
|
framework
|
||||||
.scope(WorkspaceScope)
|
.scope(WorkspaceScope)
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import type { GetInviteInfoQuery } from '@affine/graphql';
|
||||||
|
import {
|
||||||
|
backoffRetry,
|
||||||
|
catchErrorInto,
|
||||||
|
effect,
|
||||||
|
fromPromise,
|
||||||
|
LiveData,
|
||||||
|
onComplete,
|
||||||
|
onStart,
|
||||||
|
Service,
|
||||||
|
} from '@toeverything/infra';
|
||||||
|
import { EMPTY, exhaustMap, mergeMap } from 'rxjs';
|
||||||
|
|
||||||
|
import { isBackendError, isNetworkError } from '../error';
|
||||||
|
import type { AcceptInviteStore } from '../stores/accept-invite';
|
||||||
|
import type { InviteInfoStore } from '../stores/invite-info';
|
||||||
|
|
||||||
|
export type InviteInfo = GetInviteInfoQuery['getInviteInfo'];
|
||||||
|
|
||||||
|
export class AcceptInviteService extends Service {
|
||||||
|
constructor(
|
||||||
|
private readonly store: AcceptInviteStore,
|
||||||
|
private readonly inviteInfoStore: InviteInfoStore
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
inviteInfo$ = new LiveData<InviteInfo | undefined>(undefined);
|
||||||
|
accepted$ = new LiveData<boolean>(false);
|
||||||
|
loading$ = new LiveData(false);
|
||||||
|
error$ = new LiveData<any>(null);
|
||||||
|
|
||||||
|
readonly revalidate = effect(
|
||||||
|
exhaustMap(({ inviteId }: { inviteId: string }) => {
|
||||||
|
if (!inviteId) {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
return fromPromise(async () => {
|
||||||
|
return await this.inviteInfoStore.getInviteInfo(inviteId);
|
||||||
|
}).pipe(
|
||||||
|
mergeMap(res => {
|
||||||
|
this.inviteInfo$.setValue(res);
|
||||||
|
return fromPromise(async () => {
|
||||||
|
return await this.store.acceptInvite(
|
||||||
|
res.workspace.id,
|
||||||
|
inviteId,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
mergeMap(res => {
|
||||||
|
this.accepted$.next(res);
|
||||||
|
return EMPTY;
|
||||||
|
}),
|
||||||
|
backoffRetry({
|
||||||
|
when: isNetworkError,
|
||||||
|
count: Infinity,
|
||||||
|
}),
|
||||||
|
backoffRetry({
|
||||||
|
when: isBackendError,
|
||||||
|
count: 3,
|
||||||
|
}),
|
||||||
|
catchErrorInto(this.error$),
|
||||||
|
onStart(() => {
|
||||||
|
this.loading$.setValue(true);
|
||||||
|
this.inviteInfo$.setValue(undefined);
|
||||||
|
this.accepted$.setValue(false);
|
||||||
|
}),
|
||||||
|
onComplete(() => {
|
||||||
|
this.loading$.setValue(false);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
override dispose(): void {
|
||||||
|
this.revalidate.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { acceptInviteByInviteIdMutation } from '@affine/graphql';
|
||||||
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { GraphQLService } from '../services/graphql';
|
||||||
|
|
||||||
|
export class AcceptInviteStore extends Store {
|
||||||
|
constructor(private readonly gqlService: GraphQLService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async acceptInvite(
|
||||||
|
workspaceId: string,
|
||||||
|
inviteId: string,
|
||||||
|
sendAcceptMail?: boolean,
|
||||||
|
signal?: AbortSignal
|
||||||
|
) {
|
||||||
|
const data = await this.gqlService.gql({
|
||||||
|
query: acceptInviteByInviteIdMutation,
|
||||||
|
|
||||||
|
variables: {
|
||||||
|
workspaceId,
|
||||||
|
inviteId,
|
||||||
|
sendAcceptMail,
|
||||||
|
},
|
||||||
|
context: { signal },
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.acceptInviteById;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { getInviteInfoQuery } from '@affine/graphql';
|
||||||
|
import { Store } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import type { GraphQLService } from '../services/graphql';
|
||||||
|
|
||||||
|
export class InviteInfoStore extends Store {
|
||||||
|
constructor(private readonly gqlService: GraphQLService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInviteInfo(inviteId?: string, signal?: AbortSignal) {
|
||||||
|
if (!inviteId) {
|
||||||
|
throw new Error('No inviteId');
|
||||||
|
}
|
||||||
|
const data = await this.gqlService.gql({
|
||||||
|
query: getInviteInfoQuery,
|
||||||
|
variables: {
|
||||||
|
inviteId,
|
||||||
|
},
|
||||||
|
context: { signal },
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.getInviteInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
"el-GR": 90,
|
"el-GR": 90,
|
||||||
"en": 100,
|
"en": 100,
|
||||||
"es-AR": 90,
|
"es-AR": 90,
|
||||||
"es-CL": 92,
|
"es-CL": 91,
|
||||||
"es": 90,
|
"es": 90,
|
||||||
"fa": 90,
|
"fa": 90,
|
||||||
"fr": 90,
|
"fr": 90,
|
||||||
|
|||||||
@@ -289,6 +289,10 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Invited members will collaborate with you in current workspace`
|
* `Invited members will collaborate with you in current workspace`
|
||||||
*/
|
*/
|
||||||
["Invite Members Message"](): string;
|
["Invite Members Message"](): string;
|
||||||
|
/**
|
||||||
|
* `Insufficient team seat`
|
||||||
|
*/
|
||||||
|
["insufficient-team-seat"](): string;
|
||||||
/**
|
/**
|
||||||
* `Joined workspace`
|
* `Joined workspace`
|
||||||
*/
|
*/
|
||||||
@@ -3881,6 +3885,10 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Remove member`
|
* `Remove member`
|
||||||
*/
|
*/
|
||||||
["com.affine.payment.member.team.remove"](): string;
|
["com.affine.payment.member.team.remove"](): string;
|
||||||
|
/**
|
||||||
|
* `Retry payment`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.member.team.retry-payment"](): string;
|
||||||
/**
|
/**
|
||||||
* `Change role to admin`
|
* `Change role to admin`
|
||||||
*/
|
*/
|
||||||
@@ -3893,6 +3901,34 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Assign as owner`
|
* `Assign as owner`
|
||||||
*/
|
*/
|
||||||
["com.affine.payment.member.team.assign"](): string;
|
["com.affine.payment.member.team.assign"](): string;
|
||||||
|
/**
|
||||||
|
* `Insufficient Team Seats`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.member.team.retry-payment.title"](): string;
|
||||||
|
/**
|
||||||
|
* `The payment for adding new team members has failed. To add more seats, please update your payment method and process unpaid invoices.`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.member.team.retry-payment.owner.description"](): string;
|
||||||
|
/**
|
||||||
|
* `The payment for adding new team members has failed. Please contact your workspace owner to update the payment method and process unpaid invoices.`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.member.team.retry-payment.admin.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Update Payment`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.member.team.retry-payment.update-payment"](): string;
|
||||||
|
/**
|
||||||
|
* `Subscription has been disabled for your team workspace. To add more seats, you'll need to resume subscription first.`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.member.team.disabled-subscription.owner.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Your team workspace has subscription disabled, which prevents adding more seats. Please contact your workspace owner to enable subscription.`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.member.team.disabled-subscription.admin.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Resume Subscription`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.member.team.disabled-subscription.resume-subscription"](): string;
|
||||||
/**
|
/**
|
||||||
* `Invitation Revoked`
|
* `Invitation Revoked`
|
||||||
*/
|
*/
|
||||||
@@ -4081,6 +4117,14 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Resume`
|
* `Resume`
|
||||||
*/
|
*/
|
||||||
["com.affine.payment.resume"](): string;
|
["com.affine.payment.resume"](): string;
|
||||||
|
/**
|
||||||
|
* `Subscription Resumed`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.resume.success.title"](): string;
|
||||||
|
/**
|
||||||
|
* `Your team workspace subscription has been enabled successfully. Changes will take effect immediately.`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.resume.success.team.message"](): string;
|
||||||
/**
|
/**
|
||||||
* `Resume auto-renewal`
|
* `Resume auto-renewal`
|
||||||
*/
|
*/
|
||||||
@@ -4241,6 +4285,10 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Open in center peek`
|
* `Open in center peek`
|
||||||
*/
|
*/
|
||||||
["com.affine.peek-view-controls.open-doc-in-center-peek"](): string;
|
["com.affine.peek-view-controls.open-doc-in-center-peek"](): string;
|
||||||
|
/**
|
||||||
|
* `Click or drag`
|
||||||
|
*/
|
||||||
|
["com.affine.split-view-drag-handle.tooltip"](): string;
|
||||||
/**
|
/**
|
||||||
* `New`
|
* `New`
|
||||||
*/
|
*/
|
||||||
@@ -6820,6 +6868,14 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Are you sure you want to delete these blob files? This action cannot be undone. Make sure you no longer need them before proceeding.`
|
* `Are you sure you want to delete these blob files? This action cannot be undone. Make sure you no longer need them before proceeding.`
|
||||||
*/
|
*/
|
||||||
["com.affine.settings.workspace.storage.unused-blobs.delete.warning"](): string;
|
["com.affine.settings.workspace.storage.unused-blobs.delete.warning"](): string;
|
||||||
|
/**
|
||||||
|
* `Join Failed`
|
||||||
|
*/
|
||||||
|
["com.affine.fail-to-join-workspace.title"](): string;
|
||||||
|
/**
|
||||||
|
* `Please contact your workspace owner to add more seats.`
|
||||||
|
*/
|
||||||
|
["com.affine.fail-to-join-workspace.description-2"](): string;
|
||||||
/**
|
/**
|
||||||
* `An internal error occurred.`
|
* `An internal error occurred.`
|
||||||
*/
|
*/
|
||||||
@@ -7072,6 +7128,10 @@ export function useAFFiNEI18N(): {
|
|||||||
* `A Team workspace is required to perform this action.`
|
* `A Team workspace is required to perform this action.`
|
||||||
*/
|
*/
|
||||||
["error.ACTION_FORBIDDEN_ON_NON_TEAM_WORKSPACE"](): string;
|
["error.ACTION_FORBIDDEN_ON_NON_TEAM_WORKSPACE"](): string;
|
||||||
|
/**
|
||||||
|
* `Page default role can not be owner.`
|
||||||
|
*/
|
||||||
|
["error.PAGE_DEFAULT_ROLE_CAN_NOT_BE_OWNER"](): string;
|
||||||
/**
|
/**
|
||||||
* `Unsupported subscription plan: {{plan}}.`
|
* `Unsupported subscription plan: {{plan}}.`
|
||||||
*/
|
*/
|
||||||
@@ -7690,4 +7750,13 @@ export const TypedTrans: {
|
|||||||
}, {
|
}, {
|
||||||
["1"]: JSX.Element;
|
["1"]: JSX.Element;
|
||||||
}>>;
|
}>>;
|
||||||
|
/**
|
||||||
|
* `Unable to join <1/> <2>{{workspaceName}}</2> due to insufficient seats available.`
|
||||||
|
*/
|
||||||
|
["com.affine.fail-to-join-workspace.description-1"]: ComponentType<TypedTransProps<{
|
||||||
|
readonly workspaceName: string;
|
||||||
|
}, {
|
||||||
|
["1"]: JSX.Element;
|
||||||
|
["2"]: JSX.Element;
|
||||||
|
}>>;
|
||||||
} = /*#__PURE__*/ createProxy(createComponent);
|
} = /*#__PURE__*/ createProxy(createComponent);
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
"Invite": "Invite",
|
"Invite": "Invite",
|
||||||
"Invite Members": "Invite members",
|
"Invite Members": "Invite members",
|
||||||
"Invite Members Message": "Invited members will collaborate with you in current workspace",
|
"Invite Members Message": "Invited members will collaborate with you in current workspace",
|
||||||
|
"insufficient-team-seat": "Insufficient team seat",
|
||||||
"Joined Workspace": "Joined workspace",
|
"Joined Workspace": "Joined workspace",
|
||||||
"Leave": "Leave",
|
"Leave": "Leave",
|
||||||
"Link": "Hyperlink (with selected text)",
|
"Link": "Hyperlink (with selected text)",
|
||||||
@@ -965,9 +966,17 @@
|
|||||||
"com.affine.payment.member.team.approve": "Approve",
|
"com.affine.payment.member.team.approve": "Approve",
|
||||||
"com.affine.payment.member.team.decline": "Decline",
|
"com.affine.payment.member.team.decline": "Decline",
|
||||||
"com.affine.payment.member.team.remove": "Remove member",
|
"com.affine.payment.member.team.remove": "Remove member",
|
||||||
|
"com.affine.payment.member.team.retry-payment": "Retry payment",
|
||||||
"com.affine.payment.member.team.change.admin": "Change role to admin",
|
"com.affine.payment.member.team.change.admin": "Change role to admin",
|
||||||
"com.affine.payment.member.team.change.collaborator": "Change role to collaborator",
|
"com.affine.payment.member.team.change.collaborator": "Change role to collaborator",
|
||||||
"com.affine.payment.member.team.assign": "Assign as owner",
|
"com.affine.payment.member.team.assign": "Assign as owner",
|
||||||
|
"com.affine.payment.member.team.retry-payment.title": "Insufficient Team Seats",
|
||||||
|
"com.affine.payment.member.team.retry-payment.owner.description": "The payment for adding new team members has failed. To add more seats, please update your payment method and process unpaid invoices.",
|
||||||
|
"com.affine.payment.member.team.retry-payment.admin.description": "The payment for adding new team members has failed. Please contact your workspace owner to update the payment method and process unpaid invoices.",
|
||||||
|
"com.affine.payment.member.team.retry-payment.update-payment": "Update Payment",
|
||||||
|
"com.affine.payment.member.team.disabled-subscription.owner.description": "Subscription has been disabled for your team workspace. To add more seats, you'll need to resume subscription first.",
|
||||||
|
"com.affine.payment.member.team.disabled-subscription.admin.description": "Your team workspace has subscription disabled, which prevents adding more seats. Please contact your workspace owner to enable subscription.",
|
||||||
|
"com.affine.payment.member.team.disabled-subscription.resume-subscription": "Resume Subscription",
|
||||||
"com.affine.payment.member.team.revoke.notify.title": "Invitation Revoked",
|
"com.affine.payment.member.team.revoke.notify.title": "Invitation Revoked",
|
||||||
"com.affine.payment.member.team.revoke.notify.message": "You have canceled the invitation for {{name}}",
|
"com.affine.payment.member.team.revoke.notify.message": "You have canceled the invitation for {{name}}",
|
||||||
"com.affine.payment.member.team.approve.notify.title": "Request approved",
|
"com.affine.payment.member.team.approve.notify.title": "Request approved",
|
||||||
@@ -1011,6 +1020,8 @@
|
|||||||
"com.affine.payment.recurring-monthly": "monthly",
|
"com.affine.payment.recurring-monthly": "monthly",
|
||||||
"com.affine.payment.recurring-yearly": "annually",
|
"com.affine.payment.recurring-yearly": "annually",
|
||||||
"com.affine.payment.resume": "Resume",
|
"com.affine.payment.resume": "Resume",
|
||||||
|
"com.affine.payment.resume.success.title": "Subscription Resumed",
|
||||||
|
"com.affine.payment.resume.success.team.message": "Your team workspace subscription has been enabled successfully. Changes will take effect immediately.",
|
||||||
"com.affine.payment.resume-renewal": "Resume auto-renewal",
|
"com.affine.payment.resume-renewal": "Resume auto-renewal",
|
||||||
"com.affine.payment.see-all-plans": "See all plans",
|
"com.affine.payment.see-all-plans": "See all plans",
|
||||||
"com.affine.payment.sign-up-free": "Sign up free",
|
"com.affine.payment.sign-up-free": "Sign up free",
|
||||||
@@ -1701,6 +1712,9 @@
|
|||||||
"com.affine.settings.workspace.storage.unused-blobs.selected": "Selected",
|
"com.affine.settings.workspace.storage.unused-blobs.selected": "Selected",
|
||||||
"com.affine.settings.workspace.storage.unused-blobs.delete.title": "Delete blob files",
|
"com.affine.settings.workspace.storage.unused-blobs.delete.title": "Delete blob files",
|
||||||
"com.affine.settings.workspace.storage.unused-blobs.delete.warning": "Are you sure you want to delete these blob files? This action cannot be undone. Make sure you no longer need them before proceeding.",
|
"com.affine.settings.workspace.storage.unused-blobs.delete.warning": "Are you sure you want to delete these blob files? This action cannot be undone. Make sure you no longer need them before proceeding.",
|
||||||
|
"com.affine.fail-to-join-workspace.title": "Join Failed",
|
||||||
|
"com.affine.fail-to-join-workspace.description-1": "Unable to join <1/> <2>{{workspaceName}}</2> due to insufficient seats available.",
|
||||||
|
"com.affine.fail-to-join-workspace.description-2": "Please contact your workspace owner to add more seats.",
|
||||||
"error.INTERNAL_SERVER_ERROR": "An internal error occurred.",
|
"error.INTERNAL_SERVER_ERROR": "An internal error occurred.",
|
||||||
"error.TOO_MANY_REQUEST": "Too many requests.",
|
"error.TOO_MANY_REQUEST": "Too many requests.",
|
||||||
"error.NOT_FOUND": "Resource not found.",
|
"error.NOT_FOUND": "Resource not found.",
|
||||||
@@ -1751,6 +1765,7 @@
|
|||||||
"error.FAILED_TO_SAVE_UPDATES": "Failed to store doc updates.",
|
"error.FAILED_TO_SAVE_UPDATES": "Failed to store doc updates.",
|
||||||
"error.FAILED_TO_UPSERT_SNAPSHOT": "Failed to store doc snapshot.",
|
"error.FAILED_TO_UPSERT_SNAPSHOT": "Failed to store doc snapshot.",
|
||||||
"error.ACTION_FORBIDDEN_ON_NON_TEAM_WORKSPACE": "A Team workspace is required to perform this action.",
|
"error.ACTION_FORBIDDEN_ON_NON_TEAM_WORKSPACE": "A Team workspace is required to perform this action.",
|
||||||
|
"error.PAGE_DEFAULT_ROLE_CAN_NOT_BE_OWNER": "Page default role can not be owner.",
|
||||||
"error.UNSUPPORTED_SUBSCRIPTION_PLAN": "Unsupported subscription plan: {{plan}}.",
|
"error.UNSUPPORTED_SUBSCRIPTION_PLAN": "Unsupported subscription plan: {{plan}}.",
|
||||||
"error.FAILED_TO_CHECKOUT": "Failed to create checkout session.",
|
"error.FAILED_TO_CHECKOUT": "Failed to create checkout session.",
|
||||||
"error.INVALID_CHECKOUT_PARAMETERS": "Invalid checkout parameters provided.",
|
"error.INVALID_CHECKOUT_PARAMETERS": "Invalid checkout parameters provided.",
|
||||||
|
|||||||
Reference in New Issue
Block a user