fix(core): optimize upgrade to team page (#9099)
This commit is contained in:
@@ -4,7 +4,6 @@ import { useI18n } from '@affine/i18n';
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { ConfirmModal } from '../../../ui/modal';
|
import { ConfirmModal } from '../../../ui/modal';
|
||||||
import { notify } from '../../../ui/notification';
|
|
||||||
import { type InviteMethodType, ModalContent } from './modal-content';
|
import { type InviteMethodType, ModalContent } from './modal-content';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
@@ -61,11 +60,7 @@ export const InviteTeamMemberModal = ({
|
|||||||
onConfirm({
|
onConfirm({
|
||||||
emails: inviteEmailsArray,
|
emails: inviteEmailsArray,
|
||||||
});
|
});
|
||||||
notify.success({
|
}, [inviteEmails, inviteMethod, onConfirm, setOpen]);
|
||||||
title: t['com.affine.payment.member.team.invite.notify.title'](),
|
|
||||||
message: t['com.affine.payment.member.team.invite.notify.message'](),
|
|
||||||
});
|
|
||||||
}, [inviteEmails, inviteMethod, onConfirm, setOpen, t]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import {
|
|||||||
SubscriptionService,
|
SubscriptionService,
|
||||||
WorkspaceSubscriptionService,
|
WorkspaceSubscriptionService,
|
||||||
} from '../../../../../modules/cloud';
|
} from '../../../../../modules/cloud';
|
||||||
import { ConfirmLoadingModal, DowngradeModal } from './modals';
|
import {
|
||||||
|
ConfirmLoadingModal,
|
||||||
|
DowngradeModal,
|
||||||
|
DowngradeTeamModal,
|
||||||
|
} from './modals';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel action with modal & request
|
* Cancel action with modal & request
|
||||||
@@ -115,7 +119,10 @@ export const CancelTeamAction = ({
|
|||||||
const account = authService.session.account$.value;
|
const account = authService.session.account$.value;
|
||||||
const prevRecurring = workspaceSubscription?.recurring;
|
const prevRecurring = workspaceSubscription?.recurring;
|
||||||
setIsMutating(true);
|
setIsMutating(true);
|
||||||
await subscription.cancelSubscription(idempotencyKey);
|
await subscription.cancelSubscription(
|
||||||
|
idempotencyKey,
|
||||||
|
SubscriptionPlan.Team
|
||||||
|
);
|
||||||
await subscription.waitForRevalidation();
|
await subscription.waitForRevalidation();
|
||||||
// refresh idempotency key
|
// refresh idempotency key
|
||||||
setIdempotencyKey(nanoid());
|
setIdempotencyKey(nanoid());
|
||||||
@@ -136,18 +143,22 @@ export const CancelTeamAction = ({
|
|||||||
setIsMutating(false);
|
setIsMutating(false);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
authService.session.account$.value,
|
authService,
|
||||||
workspaceSubscription,
|
workspaceSubscription?.recurring,
|
||||||
subscription,
|
subscription,
|
||||||
idempotencyKey,
|
idempotencyKey,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
downgradeNotify,
|
downgradeNotify,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (workspaceSubscription?.canceledAt) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
<DowngradeModal
|
<DowngradeTeamModal
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={downgrade}
|
onCancel={downgrade}
|
||||||
onOpenChange={onOpenChange}
|
onOpenChange={onOpenChange}
|
||||||
@@ -208,3 +219,45 @@ export const ResumeAction = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export const TeamResumeAction = ({
|
||||||
|
children,
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
} & PropsWithChildren) => {
|
||||||
|
// allow replay request on network error until component unmount or success
|
||||||
|
const [idempotencyKey, setIdempotencyKey] = useState(nanoid());
|
||||||
|
const [isMutating, setIsMutating] = useState(false);
|
||||||
|
const subscription = useService(WorkspaceSubscriptionService).subscription;
|
||||||
|
|
||||||
|
const resume = useAsyncCallback(async () => {
|
||||||
|
try {
|
||||||
|
setIsMutating(true);
|
||||||
|
await subscription.resumeSubscription(
|
||||||
|
idempotencyKey,
|
||||||
|
SubscriptionPlan.Team
|
||||||
|
);
|
||||||
|
await subscription.waitForRevalidation();
|
||||||
|
// refresh idempotency key
|
||||||
|
setIdempotencyKey(nanoid());
|
||||||
|
onOpenChange(false);
|
||||||
|
} finally {
|
||||||
|
setIsMutating(false);
|
||||||
|
}
|
||||||
|
}, [subscription, idempotencyKey, onOpenChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{children}
|
||||||
|
<ConfirmLoadingModal
|
||||||
|
type={'resume'}
|
||||||
|
open={open}
|
||||||
|
onConfirm={resume}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
loading={isMutating}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -129,3 +129,62 @@ export const DowngradeModal = ({
|
|||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DowngradeTeamModal = ({
|
||||||
|
open,
|
||||||
|
loading,
|
||||||
|
onOpenChange,
|
||||||
|
onCancel,
|
||||||
|
}: {
|
||||||
|
loading?: boolean;
|
||||||
|
open?: boolean;
|
||||||
|
onOpenChange?: (open: boolean) => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
}) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const canceled = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!loading && open && canceled.current) {
|
||||||
|
onOpenChange?.(false);
|
||||||
|
canceled.current = false;
|
||||||
|
}
|
||||||
|
}, [loading, open, onOpenChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={t['com.affine.payment.modal.downgrade.title']()}
|
||||||
|
open={open}
|
||||||
|
contentOptions={{}}
|
||||||
|
width={480}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
<div className={styles.downgradeContentWrapper}>
|
||||||
|
<p className={styles.downgradeContent}>
|
||||||
|
{t['com.affine.payment.modal.downgrade.content']()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer className={styles.downgradeFooter}>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
canceled.current = true;
|
||||||
|
onCancel?.();
|
||||||
|
}}
|
||||||
|
loading={loading}
|
||||||
|
>
|
||||||
|
{t['com.affine.payment.modal.downgrade.cancel']()}
|
||||||
|
</Button>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
disabled={loading}
|
||||||
|
onClick={() => onOpenChange?.(false)}
|
||||||
|
variant="primary"
|
||||||
|
>
|
||||||
|
{t['com.affine.payment.modal.downgrade.team-confirm']()}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
</footer>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ const ActionButton = ({ detail, recurring }: PlanCardProps) => {
|
|||||||
|
|
||||||
// team
|
// team
|
||||||
if (detail.plan === SubscriptionPlan.Team) {
|
if (detail.plan === SubscriptionPlan.Team) {
|
||||||
return <UpgradeToTeam />;
|
return <UpgradeToTeam recurring={recurring} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lifetime
|
// lifetime
|
||||||
@@ -247,10 +247,11 @@ const Downgrade = ({ disabled }: { disabled?: boolean }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const UpgradeToTeam = () => {
|
const UpgradeToTeam = ({ recurring }: { recurring: SubscriptionRecurring }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const serverService = useService(ServerService);
|
const serverService = useService(ServerService);
|
||||||
const url = `${serverService.server.baseUrl}/upgrade-to-team`;
|
const url = `${serverService.server.baseUrl}/upgrade-to-team?recurring=${recurring}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className={styles.planAction}
|
className={styles.planAction}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
WorkspaceInvoicesService,
|
WorkspaceInvoicesService,
|
||||||
WorkspaceSubscriptionService,
|
WorkspaceSubscriptionService,
|
||||||
} from '@affine/core/modules/cloud';
|
} from '@affine/core/modules/cloud';
|
||||||
|
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
|
||||||
import { UrlService } from '@affine/core/modules/url';
|
import { UrlService } from '@affine/core/modules/url';
|
||||||
import {
|
import {
|
||||||
createCustomerPortalMutation,
|
createCustomerPortalMutation,
|
||||||
@@ -36,7 +37,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CancelTeamAction,
|
CancelTeamAction,
|
||||||
ResumeAction,
|
TeamResumeAction,
|
||||||
} from '../../general-setting/plans/actions';
|
} from '../../general-setting/plans/actions';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
@@ -60,7 +61,7 @@ export const WorkspaceSettingBilling = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
subscriptionService?.subscription.revalidate();
|
subscriptionService?.subscription.revalidate();
|
||||||
}, [subscriptionService]);
|
}, [subscriptionService?.subscription]);
|
||||||
|
|
||||||
if (workspace === null) {
|
if (workspace === null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -97,8 +98,10 @@ export const WorkspaceSettingBilling = ({
|
|||||||
const TeamCard = () => {
|
const TeamCard = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const workspaceSubscriptionService = useService(WorkspaceSubscriptionService);
|
const workspaceSubscriptionService = useService(WorkspaceSubscriptionService);
|
||||||
|
const workspaceQuotaService = useService(WorkspaceQuotaService);
|
||||||
const subscriptionService = useService(SubscriptionService);
|
const subscriptionService = useService(SubscriptionService);
|
||||||
|
const workspaceQuota = useLiveData(workspaceQuotaService.quota.quota$);
|
||||||
|
const workspaceMemberCount = workspaceQuota?.memberCount;
|
||||||
const teamSubscription = useLiveData(
|
const teamSubscription = useLiveData(
|
||||||
workspaceSubscriptionService.subscription.subscription$
|
workspaceSubscriptionService.subscription.subscription$
|
||||||
);
|
);
|
||||||
@@ -108,7 +111,13 @@ const TeamCard = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
workspaceSubscriptionService.subscription.revalidate();
|
workspaceSubscriptionService.subscription.revalidate();
|
||||||
}, [workspaceSubscriptionService.subscription]);
|
workspaceQuotaService.quota.revalidate();
|
||||||
|
subscriptionService.prices.revalidate();
|
||||||
|
}, [
|
||||||
|
subscriptionService,
|
||||||
|
workspaceQuotaService,
|
||||||
|
workspaceSubscriptionService,
|
||||||
|
]);
|
||||||
|
|
||||||
const expiration = teamSubscription?.end;
|
const expiration = teamSubscription?.end;
|
||||||
const nextBillingDate = teamSubscription?.nextBillAt;
|
const nextBillingDate = teamSubscription?.nextBillAt;
|
||||||
@@ -147,12 +156,22 @@ const TeamCard = () => {
|
|||||||
}, [expiration, nextBillingDate, t]);
|
}, [expiration, nextBillingDate, t]);
|
||||||
|
|
||||||
const amount = teamSubscription
|
const amount = teamSubscription
|
||||||
? teamPrices
|
? teamPrices && workspaceMemberCount
|
||||||
? teamSubscription.recurring === SubscriptionRecurring.Monthly
|
? teamSubscription.recurring === SubscriptionRecurring.Monthly
|
||||||
? String((teamPrices.amount ?? 0) / 100)
|
? String(
|
||||||
: String((teamPrices.yearlyAmount ?? 0) / 100)
|
(teamPrices.amount ? teamPrices.amount * workspaceMemberCount : 0) /
|
||||||
|
100
|
||||||
|
)
|
||||||
|
: String(
|
||||||
|
(teamPrices.yearlyAmount
|
||||||
|
? teamPrices.yearlyAmount * workspaceMemberCount
|
||||||
|
: 0) / 100
|
||||||
|
)
|
||||||
: '?'
|
: '?'
|
||||||
: '0';
|
: '0';
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
setOpenCancelModal(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.planCard}>
|
<div className={styles.planCard}>
|
||||||
@@ -171,7 +190,11 @@ const TeamCard = () => {
|
|||||||
open={openCancelModal}
|
open={openCancelModal}
|
||||||
onOpenChange={setOpenCancelModal}
|
onOpenChange={setOpenCancelModal}
|
||||||
>
|
>
|
||||||
<Button variant="primary" className={styles.cancelPlanButton}>
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
className={styles.cancelPlanButton}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
{t[
|
{t[
|
||||||
'com.affine.settings.workspace.billing.team-workspace.cancel-plan'
|
'com.affine.settings.workspace.billing.team-workspace.cancel-plan'
|
||||||
]()}
|
]()}
|
||||||
@@ -207,11 +230,11 @@ const ResumeSubscription = ({ expirationDate }: { expirationDate: string }) => {
|
|||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<ResumeAction open={open} onOpenChange={setOpen}>
|
<TeamResumeAction open={open} onOpenChange={setOpen}>
|
||||||
<Button onClick={handleClick}>
|
<Button onClick={handleClick}>
|
||||||
{t['com.affine.payment.billing-setting.resume-subscription']()}
|
{t['com.affine.payment.billing-setting.resume-subscription']()}
|
||||||
</Button>
|
</Button>
|
||||||
</ResumeAction>
|
</TeamResumeAction>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MenuItem, notify } from '@affine/component';
|
import { MenuItem, notify, useConfirmModal } from '@affine/component';
|
||||||
import {
|
import {
|
||||||
type Member,
|
type Member,
|
||||||
WorkspacePermissionService,
|
WorkspacePermissionService,
|
||||||
@@ -21,21 +21,28 @@ export const MemberOptions = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const permission = useService(WorkspacePermissionService).permission;
|
const permission = useService(WorkspacePermissionService).permission;
|
||||||
|
const { openConfirmModal } = useConfirmModal();
|
||||||
|
|
||||||
const handleAssignOwner = useCallback(() => {
|
const openRemoveConfirmModal = useCallback(
|
||||||
openAssignModal();
|
(successNotify: { title: string; message: string }) => {
|
||||||
}, [openAssignModal]);
|
openConfirmModal({
|
||||||
|
title: t['com.affine.payment.member.team.remove.confirm.title'](),
|
||||||
const handleRevoke = useCallback(() => {
|
description:
|
||||||
|
t['com.affine.payment.member.team.remove.confirm.description'](),
|
||||||
|
confirmText:
|
||||||
|
t['com.affine.payment.member.team.remove.confirm.confirm-button'](),
|
||||||
|
cancelText: t['com.affine.payment.member.team.remove.confirm.cancel'](),
|
||||||
|
confirmButtonOptions: {
|
||||||
|
variant: 'error',
|
||||||
|
},
|
||||||
|
onConfirm: () =>
|
||||||
permission
|
permission
|
||||||
.revokeMember(member.id)
|
.revokeMember(member.id)
|
||||||
.then(result => {
|
.then(result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
notify.success({
|
notify.success({
|
||||||
title: t['com.affine.payment.member.team.revoke.notify.title'](),
|
title: successNotify.title,
|
||||||
message: t['com.affine.payment.member.team.revoke.notify.message']({
|
message: successNotify.message,
|
||||||
name: member.name || member.email || member.id,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -44,8 +51,25 @@ export const MemberOptions = ({
|
|||||||
title: 'Operation failed',
|
title: 'Operation failed',
|
||||||
message: error.message,
|
message: error.message,
|
||||||
});
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}, [permission, member, t]);
|
},
|
||||||
|
[member.id, openConfirmModal, permission, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleAssignOwner = useCallback(() => {
|
||||||
|
openAssignModal();
|
||||||
|
}, [openAssignModal]);
|
||||||
|
|
||||||
|
const handleRevoke = useCallback(() => {
|
||||||
|
openRemoveConfirmModal({
|
||||||
|
title: t['com.affine.payment.member.team.revoke.notify.title'](),
|
||||||
|
message: t['com.affine.payment.member.team.revoke.notify.message']({
|
||||||
|
name: member.name || member.email || member.id,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}, [openRemoveConfirmModal, member, t]);
|
||||||
|
|
||||||
const handleApprove = useCallback(() => {
|
const handleApprove = useCallback(() => {
|
||||||
permission
|
permission
|
||||||
.approveMember(member.id)
|
.approveMember(member.id)
|
||||||
@@ -70,48 +94,22 @@ export const MemberOptions = ({
|
|||||||
}, [member, permission, t]);
|
}, [member, permission, t]);
|
||||||
|
|
||||||
const handleDecline = useCallback(() => {
|
const handleDecline = useCallback(() => {
|
||||||
permission
|
openRemoveConfirmModal({
|
||||||
.revokeMember(member.id)
|
|
||||||
.then(result => {
|
|
||||||
if (result) {
|
|
||||||
notify.success({
|
|
||||||
title: t['com.affine.payment.member.team.decline.notify.title'](),
|
title: t['com.affine.payment.member.team.decline.notify.title'](),
|
||||||
message: t['com.affine.payment.member.team.decline.notify.message'](
|
message: t['com.affine.payment.member.team.decline.notify.message']({
|
||||||
{
|
|
||||||
name: member.name || member.email || member.id,
|
name: member.name || member.email || member.id,
|
||||||
}
|
}),
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}, [member, openRemoveConfirmModal, t]);
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
notify.error({
|
|
||||||
title: 'Operation failed',
|
|
||||||
message: error.message,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [member, permission, t]);
|
|
||||||
|
|
||||||
const handleRemove = useCallback(() => {
|
const handleRemove = useCallback(() => {
|
||||||
permission
|
openRemoveConfirmModal({
|
||||||
.revokeMember(member.id)
|
|
||||||
.then(result => {
|
|
||||||
if (result) {
|
|
||||||
notify.success({
|
|
||||||
title: t['com.affine.payment.member.team.remove.notify.title'](),
|
title: t['com.affine.payment.member.team.remove.notify.title'](),
|
||||||
message: t['com.affine.payment.member.team.remove.notify.message']({
|
message: t['com.affine.payment.member.team.remove.notify.message']({
|
||||||
name: member.name || member.email || member.id,
|
name: member.name || member.email || member.id,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
}, [member, openRemoveConfirmModal, t]);
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
notify.error({
|
|
||||||
title: 'Operation failed',
|
|
||||||
message: error.message,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [member, permission, t]);
|
|
||||||
|
|
||||||
const handleChangeToAdmin = useCallback(() => {
|
const handleChangeToAdmin = useCallback(() => {
|
||||||
permission
|
permission
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { AuthPageContainer } from '@affine/component/auth-components';
|
|||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
|
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
|
||||||
import { PureWorkspaceCard } from '@affine/core/components/workspace-selector/workspace-card';
|
import { PureWorkspaceCard } from '@affine/core/components/workspace-selector/workspace-card';
|
||||||
|
import { AuthService } from '@affine/core/modules/cloud';
|
||||||
import { buildShowcaseWorkspace } from '@affine/core/utils/first-app-data';
|
import { buildShowcaseWorkspace } from '@affine/core/utils/first-app-data';
|
||||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||||
import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql';
|
import { SubscriptionPlan, SubscriptionRecurring } from '@affine/graphql';
|
||||||
@@ -24,8 +25,10 @@ import {
|
|||||||
WorkspacesService,
|
WorkspacesService,
|
||||||
} from '@toeverything/infra';
|
} from '@toeverything/infra';
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { Upgrade } from '../../dialogs/setting/general-setting/plans/plan-card';
|
import { Upgrade } from '../../dialogs/setting/general-setting/plans/plan-card';
|
||||||
|
import { PageNotFound } from '../404';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
const benefitList: I18nString[] = [
|
const benefitList: I18nString[] = [
|
||||||
@@ -36,6 +39,20 @@ const benefitList: I18nString[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
|
const authService = useService(AuthService);
|
||||||
|
const authStatus = useLiveData(authService.session.status$);
|
||||||
|
|
||||||
|
const [params] = useSearchParams();
|
||||||
|
const recurring = params.get('recurring');
|
||||||
|
|
||||||
|
const authIsRevalidating = useLiveData(authService.session.isRevalidating$);
|
||||||
|
if (authStatus === 'unauthenticated' && !authIsRevalidating) {
|
||||||
|
return <PageNotFound noPermission />;
|
||||||
|
}
|
||||||
|
return <UpgradeToTeam recurring={recurring} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UpgradeToTeam = ({ recurring }: { recurring: string | null }) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const workspacesList = useService(WorkspacesService).list;
|
const workspacesList = useService(WorkspacesService).list;
|
||||||
const workspaces = useLiveData(workspacesList.workspaces$);
|
const workspaces = useLiveData(workspacesList.workspaces$);
|
||||||
@@ -111,6 +128,7 @@ export const Component = () => {
|
|||||||
{t['com.affine.upgrade-to-team-page.benefit.description']()}
|
{t['com.affine.upgrade-to-team-page.benefit.description']()}
|
||||||
</div>
|
</div>
|
||||||
<UpgradeDialog
|
<UpgradeDialog
|
||||||
|
recurring={recurring}
|
||||||
open={openUpgrade}
|
open={openUpgrade}
|
||||||
onOpenChange={setOpenUpgrade}
|
onOpenChange={setOpenUpgrade}
|
||||||
workspaceName={name}
|
workspaceName={name}
|
||||||
@@ -132,16 +150,25 @@ const UpgradeDialog = ({
|
|||||||
onOpenChange,
|
onOpenChange,
|
||||||
workspaceName,
|
workspaceName,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
recurring,
|
||||||
}: {
|
}: {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
workspaceName: string;
|
workspaceName: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
|
recurring: string | null;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
onOpenChange(false);
|
onOpenChange(false);
|
||||||
}, [onOpenChange]);
|
}, [onOpenChange]);
|
||||||
|
|
||||||
|
const currentRecurring =
|
||||||
|
recurring &&
|
||||||
|
recurring.toLowerCase() === SubscriptionRecurring.Yearly.toLowerCase()
|
||||||
|
? SubscriptionRecurring.Yearly
|
||||||
|
: SubscriptionRecurring.Monthly;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal width={480} open={open} onOpenChange={onOpenChange}>
|
<Modal width={480} open={open} onOpenChange={onOpenChange}>
|
||||||
<div className={styles.dialogTitle}>
|
<div className={styles.dialogTitle}>
|
||||||
@@ -163,7 +190,7 @@ const UpgradeDialog = ({
|
|||||||
<Button onClick={onClose}>{t['Cancel']()}</Button>
|
<Button onClick={onClose}>{t['Cancel']()}</Button>
|
||||||
<Upgrade
|
<Upgrade
|
||||||
className={styles.upgradeButtonInDialog}
|
className={styles.upgradeButtonInDialog}
|
||||||
recurring={SubscriptionRecurring.Monthly}
|
recurring={currentRecurring}
|
||||||
plan={SubscriptionPlan.Team}
|
plan={SubscriptionPlan.Team}
|
||||||
onCheckoutSuccess={onClose}
|
onCheckoutSuccess={onClose}
|
||||||
checkoutInput={{
|
checkoutInput={{
|
||||||
|
|||||||
@@ -44,7 +44,12 @@ export class WorkspaceSubscription extends Entity {
|
|||||||
if (!this.store) {
|
if (!this.store) {
|
||||||
throw new Error('Subscription store not available');
|
throw new Error('Subscription store not available');
|
||||||
}
|
}
|
||||||
await this.store.mutateResumeSubscription(idempotencyKey, plan);
|
await this.store.mutateResumeSubscription(
|
||||||
|
idempotencyKey,
|
||||||
|
plan,
|
||||||
|
undefined,
|
||||||
|
this.workspaceService.workspace.id
|
||||||
|
);
|
||||||
await this.waitForRevalidation();
|
await this.waitForRevalidation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +57,12 @@ export class WorkspaceSubscription extends Entity {
|
|||||||
if (!this.store) {
|
if (!this.store) {
|
||||||
throw new Error('Subscription store not available');
|
throw new Error('Subscription store not available');
|
||||||
}
|
}
|
||||||
await this.store.mutateCancelSubscription(idempotencyKey, plan);
|
await this.store.mutateCancelSubscription(
|
||||||
|
idempotencyKey,
|
||||||
|
plan,
|
||||||
|
undefined,
|
||||||
|
this.workspaceService.workspace.id
|
||||||
|
);
|
||||||
await this.waitForRevalidation();
|
await this.waitForRevalidation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +74,12 @@ export class WorkspaceSubscription extends Entity {
|
|||||||
if (!this.store) {
|
if (!this.store) {
|
||||||
throw new Error('Subscription store not available');
|
throw new Error('Subscription store not available');
|
||||||
}
|
}
|
||||||
await this.store.setSubscriptionRecurring(idempotencyKey, recurring, plan);
|
await this.store.setSubscriptionRecurring(
|
||||||
|
idempotencyKey,
|
||||||
|
recurring,
|
||||||
|
plan,
|
||||||
|
this.workspaceService.workspace.id
|
||||||
|
);
|
||||||
await this.waitForRevalidation();
|
await this.waitForRevalidation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,12 +96,14 @@ export class SubscriptionStore extends Store {
|
|||||||
async mutateResumeSubscription(
|
async mutateResumeSubscription(
|
||||||
idempotencyKey: string,
|
idempotencyKey: string,
|
||||||
plan?: SubscriptionPlan,
|
plan?: SubscriptionPlan,
|
||||||
abortSignal?: AbortSignal
|
abortSignal?: AbortSignal,
|
||||||
|
workspaceId?: string
|
||||||
) {
|
) {
|
||||||
const data = await this.gqlService.gql({
|
const data = await this.gqlService.gql({
|
||||||
query: resumeSubscriptionMutation,
|
query: resumeSubscriptionMutation,
|
||||||
variables: {
|
variables: {
|
||||||
plan,
|
plan,
|
||||||
|
workspaceId,
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
signal: abortSignal,
|
signal: abortSignal,
|
||||||
@@ -116,12 +118,14 @@ export class SubscriptionStore extends Store {
|
|||||||
async mutateCancelSubscription(
|
async mutateCancelSubscription(
|
||||||
idempotencyKey: string,
|
idempotencyKey: string,
|
||||||
plan?: SubscriptionPlan,
|
plan?: SubscriptionPlan,
|
||||||
abortSignal?: AbortSignal
|
abortSignal?: AbortSignal,
|
||||||
|
workspaceId?: string
|
||||||
) {
|
) {
|
||||||
const data = await this.gqlService.gql({
|
const data = await this.gqlService.gql({
|
||||||
query: cancelSubscriptionMutation,
|
query: cancelSubscriptionMutation,
|
||||||
variables: {
|
variables: {
|
||||||
plan,
|
plan,
|
||||||
|
workspaceId,
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
signal: abortSignal,
|
signal: abortSignal,
|
||||||
@@ -162,13 +166,15 @@ export class SubscriptionStore extends Store {
|
|||||||
setSubscriptionRecurring(
|
setSubscriptionRecurring(
|
||||||
idempotencyKey: string,
|
idempotencyKey: string,
|
||||||
recurring: SubscriptionRecurring,
|
recurring: SubscriptionRecurring,
|
||||||
plan?: SubscriptionPlan
|
plan?: SubscriptionPlan,
|
||||||
|
workspaceId?: string
|
||||||
) {
|
) {
|
||||||
return this.gqlService.gql({
|
return this.gqlService.gql({
|
||||||
query: updateSubscriptionMutation,
|
query: updateSubscriptionMutation,
|
||||||
variables: {
|
variables: {
|
||||||
plan,
|
plan,
|
||||||
recurring,
|
recurring,
|
||||||
|
workspaceId,
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
mutation cancelSubscription($plan: SubscriptionPlan = Pro) {
|
mutation cancelSubscription(
|
||||||
cancelSubscription(plan: $plan) {
|
$plan: SubscriptionPlan = Pro
|
||||||
|
$workspaceId: String
|
||||||
|
) {
|
||||||
|
cancelSubscription(plan: $plan, workspaceId: $workspaceId) {
|
||||||
id
|
id
|
||||||
status
|
status
|
||||||
nextBillAt
|
nextBillAt
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ export const cancelSubscriptionMutation = {
|
|||||||
definitionName: 'cancelSubscription',
|
definitionName: 'cancelSubscription',
|
||||||
containsFile: false,
|
containsFile: false,
|
||||||
query: `
|
query: `
|
||||||
mutation cancelSubscription($plan: SubscriptionPlan = Pro) {
|
mutation cancelSubscription($plan: SubscriptionPlan = Pro, $workspaceId: String) {
|
||||||
cancelSubscription(plan: $plan) {
|
cancelSubscription(plan: $plan, workspaceId: $workspaceId) {
|
||||||
id
|
id
|
||||||
status
|
status
|
||||||
nextBillAt
|
nextBillAt
|
||||||
@@ -188,7 +188,7 @@ export const createCopilotMessageMutation = {
|
|||||||
id: 'createCopilotMessageMutation' as const,
|
id: 'createCopilotMessageMutation' as const,
|
||||||
operationName: 'createCopilotMessage',
|
operationName: 'createCopilotMessage',
|
||||||
definitionName: 'createCopilotMessage',
|
definitionName: 'createCopilotMessage',
|
||||||
containsFile: true,
|
containsFile: false,
|
||||||
query: `
|
query: `
|
||||||
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
mutation createCopilotMessage($options: CreateChatMessageInput!) {
|
||||||
createCopilotMessage(options: $options)
|
createCopilotMessage(options: $options)
|
||||||
@@ -960,8 +960,8 @@ export const resumeSubscriptionMutation = {
|
|||||||
definitionName: 'resumeSubscription',
|
definitionName: 'resumeSubscription',
|
||||||
containsFile: false,
|
containsFile: false,
|
||||||
query: `
|
query: `
|
||||||
mutation resumeSubscription($plan: SubscriptionPlan = Pro) {
|
mutation resumeSubscription($plan: SubscriptionPlan = Pro, $workspaceId: String) {
|
||||||
resumeSubscription(plan: $plan) {
|
resumeSubscription(plan: $plan, workspaceId: $workspaceId) {
|
||||||
id
|
id
|
||||||
status
|
status
|
||||||
nextBillAt
|
nextBillAt
|
||||||
@@ -1184,8 +1184,12 @@ export const updateSubscriptionMutation = {
|
|||||||
definitionName: 'updateSubscriptionRecurring',
|
definitionName: 'updateSubscriptionRecurring',
|
||||||
containsFile: false,
|
containsFile: false,
|
||||||
query: `
|
query: `
|
||||||
mutation updateSubscription($plan: SubscriptionPlan = Pro, $recurring: SubscriptionRecurring!) {
|
mutation updateSubscription($plan: SubscriptionPlan = Pro, $recurring: SubscriptionRecurring!, $workspaceId: String) {
|
||||||
updateSubscriptionRecurring(plan: $plan, recurring: $recurring) {
|
updateSubscriptionRecurring(
|
||||||
|
plan: $plan
|
||||||
|
recurring: $recurring
|
||||||
|
workspaceId: $workspaceId
|
||||||
|
) {
|
||||||
id
|
id
|
||||||
plan
|
plan
|
||||||
recurring
|
recurring
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
mutation resumeSubscription($plan: SubscriptionPlan = Pro) {
|
mutation resumeSubscription(
|
||||||
resumeSubscription(plan: $plan) {
|
$plan: SubscriptionPlan = Pro
|
||||||
|
$workspaceId: String
|
||||||
|
) {
|
||||||
|
resumeSubscription(plan: $plan, workspaceId: $workspaceId) {
|
||||||
id
|
id
|
||||||
status
|
status
|
||||||
nextBillAt
|
nextBillAt
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
mutation updateSubscription(
|
mutation updateSubscription(
|
||||||
$plan: SubscriptionPlan = Pro
|
$plan: SubscriptionPlan = Pro
|
||||||
$recurring: SubscriptionRecurring!
|
$recurring: SubscriptionRecurring!
|
||||||
|
$workspaceId: String
|
||||||
) {
|
) {
|
||||||
updateSubscriptionRecurring(plan: $plan, recurring: $recurring) {
|
updateSubscriptionRecurring(
|
||||||
|
plan: $plan
|
||||||
|
recurring: $recurring
|
||||||
|
workspaceId: $workspaceId
|
||||||
|
) {
|
||||||
id
|
id
|
||||||
plan
|
plan
|
||||||
recurring
|
recurring
|
||||||
|
|||||||
@@ -1476,6 +1476,7 @@ export type SetBlobMutation = { __typename?: 'Mutation'; setBlob: string };
|
|||||||
|
|
||||||
export type CancelSubscriptionMutationVariables = Exact<{
|
export type CancelSubscriptionMutationVariables = Exact<{
|
||||||
plan?: InputMaybe<SubscriptionPlan>;
|
plan?: InputMaybe<SubscriptionPlan>;
|
||||||
|
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type CancelSubscriptionMutation = {
|
export type CancelSubscriptionMutation = {
|
||||||
@@ -2256,6 +2257,7 @@ export type RemoveAvatarMutation = {
|
|||||||
|
|
||||||
export type ResumeSubscriptionMutationVariables = Exact<{
|
export type ResumeSubscriptionMutationVariables = Exact<{
|
||||||
plan?: InputMaybe<SubscriptionPlan>;
|
plan?: InputMaybe<SubscriptionPlan>;
|
||||||
|
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type ResumeSubscriptionMutation = {
|
export type ResumeSubscriptionMutation = {
|
||||||
@@ -2467,6 +2469,7 @@ export type UpdateServerRuntimeConfigsMutation = {
|
|||||||
export type UpdateSubscriptionMutationVariables = Exact<{
|
export type UpdateSubscriptionMutationVariables = Exact<{
|
||||||
plan?: InputMaybe<SubscriptionPlan>;
|
plan?: InputMaybe<SubscriptionPlan>;
|
||||||
recurring: SubscriptionRecurring;
|
recurring: SubscriptionRecurring;
|
||||||
|
workspaceId?: InputMaybe<Scalars['String']['input']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type UpdateSubscriptionMutation = {
|
export type UpdateSubscriptionMutation = {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"ar": 69,
|
"ar": 68,
|
||||||
"ca": 5,
|
"ca": 5,
|
||||||
"da": 5,
|
"da": 5,
|
||||||
"de": 26,
|
"de": 26,
|
||||||
@@ -12,13 +12,13 @@
|
|||||||
"hi": 2,
|
"hi": 2,
|
||||||
"it-IT": 1,
|
"it-IT": 1,
|
||||||
"it": 1,
|
"it": 1,
|
||||||
"ja": 92,
|
"ja": 91,
|
||||||
"ko": 73,
|
"ko": 73,
|
||||||
"pl": 0,
|
"pl": 0,
|
||||||
"pt-BR": 79,
|
"pt-BR": 78,
|
||||||
"ru": 67,
|
"ru": 67,
|
||||||
"sv-SE": 4,
|
"sv-SE": 4,
|
||||||
"ur": 2,
|
"ur": 2,
|
||||||
"zh-Hans": 92,
|
"zh-Hans": 92,
|
||||||
"zh-Hant": 92
|
"zh-Hant": 91
|
||||||
}
|
}
|
||||||
@@ -982,12 +982,17 @@
|
|||||||
"com.affine.payment.member.team.assign.confirm.description-4": "To confirm this transfer, please type the workspace name",
|
"com.affine.payment.member.team.assign.confirm.description-4": "To confirm this transfer, please type the workspace name",
|
||||||
"com.affine.payment.member.team.assign.confirm.placeholder": "Type workspace name to confirm",
|
"com.affine.payment.member.team.assign.confirm.placeholder": "Type workspace name to confirm",
|
||||||
"com.affine.payment.member.team.assign.confirm.button": "Transfer Ownership",
|
"com.affine.payment.member.team.assign.confirm.button": "Transfer Ownership",
|
||||||
|
"com.affine.payment.member.team.remove.confirm.title": "Remove member from workspace?",
|
||||||
|
"com.affine.payment.member.team.remove.confirm.description": "This action will revoke their access to all workspace resources immediately.",
|
||||||
|
"com.affine.payment.member.team.remove.confirm.confirm-button": "Remove Member",
|
||||||
|
"com.affine.payment.member.team.remove.confirm.cancel": "Cancel",
|
||||||
"com.affine.payment.modal.change.cancel": "Cancel",
|
"com.affine.payment.modal.change.cancel": "Cancel",
|
||||||
"com.affine.payment.modal.change.confirm": "Change",
|
"com.affine.payment.modal.change.confirm": "Change",
|
||||||
"com.affine.payment.modal.change.title": "Change your subscription",
|
"com.affine.payment.modal.change.title": "Change your subscription",
|
||||||
"com.affine.payment.modal.downgrade.cancel": "Cancel subscription",
|
"com.affine.payment.modal.downgrade.cancel": "Cancel subscription",
|
||||||
"com.affine.payment.modal.downgrade.caption": "You can still use AFFiNE Cloud Pro until the end of this billing period :)",
|
"com.affine.payment.modal.downgrade.caption": "You can still use AFFiNE Cloud Pro until the end of this billing period :)",
|
||||||
"com.affine.payment.modal.downgrade.confirm": "Keep AFFiNE Cloud Pro",
|
"com.affine.payment.modal.downgrade.confirm": "Keep AFFiNE Cloud Pro",
|
||||||
|
"com.affine.payment.modal.downgrade.team-confirm": "Keep Team plan",
|
||||||
"com.affine.payment.modal.downgrade.content": "We're sorry to see you go, but we're always working to improve, and your feedback is welcome. We hope to see you return in the future.",
|
"com.affine.payment.modal.downgrade.content": "We're sorry to see you go, but we're always working to improve, and your feedback is welcome. We hope to see you return in the future.",
|
||||||
"com.affine.payment.modal.downgrade.title": "Are you sure?",
|
"com.affine.payment.modal.downgrade.title": "Are you sure?",
|
||||||
"com.affine.payment.modal.resume.cancel": "Cancel",
|
"com.affine.payment.modal.resume.cancel": "Cancel",
|
||||||
|
|||||||
Reference in New Issue
Block a user