Skip to content

Commit 9979756

Browse files
committed
feat(core): add self-host license tab to workspace setting
1 parent 98c5a13 commit 9979756

File tree

32 files changed

+1352
-165
lines changed

32 files changed

+1352
-165
lines changed

packages/backend/server/src/plugins/license/service.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class LicenseService {
5656
throw new WorkspaceLicenseAlreadyExists();
5757
}
5858

59-
const data = await this.fetch<License>(
59+
const data = await this.fetchAffinePro<License>(
6060
`/api/team/licenses/${licenseKey}/activate`,
6161
{
6262
method: 'POST',
@@ -106,7 +106,7 @@ export class LicenseService {
106106
throw new LicenseNotFound();
107107
}
108108

109-
await this.fetch(`/api/team/licenses/${license.key}/deactivate`, {
109+
await this.fetchAffinePro(`/api/team/licenses/${license.key}/deactivate`, {
110110
method: 'POST',
111111
});
112112

@@ -121,10 +121,11 @@ export class LicenseService {
121121
plan: SubscriptionPlan.SelfHostedTeam,
122122
recurring: SubscriptionRecurring.Monthly,
123123
});
124+
return true;
124125
}
125126

126127
async updateTeamRecurring(key: string, recurring: SubscriptionRecurring) {
127-
await this.fetch(`/api/team/licenses/${key}/recurring`, {
128+
await this.fetchAffinePro(`/api/team/licenses/${key}/recurring`, {
128129
method: 'POST',
129130
body: JSON.stringify({
130131
recurring,
@@ -143,7 +144,7 @@ export class LicenseService {
143144
throw new LicenseNotFound();
144145
}
145146

146-
return this.fetch<{ url: string }>(
147+
return this.fetchAffinePro<{ url: string }>(
147148
`/api/team/licenses/${license.key}/create-customer-portal`,
148149
{
149150
method: 'POST',
@@ -169,7 +170,7 @@ export class LicenseService {
169170
return;
170171
}
171172

172-
await this.fetch(`/api/team/licenses/${license.key}/seats`, {
173+
await this.fetchAffinePro(`/api/team/licenses/${license.key}/seats`, {
173174
method: 'POST',
174175
body: JSON.stringify({
175176
quantity: count,
@@ -223,7 +224,7 @@ export class LicenseService {
223224

224225
private async revalidateLicense(license: InstalledLicense) {
225226
try {
226-
const res = await this.fetch<License>(
227+
const res = await this.fetchAffinePro<License>(
227228
`/api/team/licenses/${license.key}/health`
228229
);
229230

@@ -267,7 +268,7 @@ export class LicenseService {
267268
}
268269
}
269270

270-
private async fetch<T = any>(
271+
private async fetchAffinePro<T = any>(
271272
path: string,
272273
init?: RequestInit
273274
): Promise<T & { res: Response }> {

packages/frontend/component/src/components/member-components/invite-modal.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const InviteModal = ({
2121
}: InviteModalProps) => {
2222
const t = useI18n();
2323
const [inviteEmail, setInviteEmail] = useState('');
24-
const [permission] = useState(Permission.Write);
24+
const [permission] = useState(Permission.Collaborator);
2525
const [isValidEmail, setIsValidEmail] = useState(true);
2626

2727
const handleConfirm = useCallback(() => {

packages/frontend/core/src/components/affine/quota-reached-modal/cloud-quota-modal.tsx

+11-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { ConfirmModal } from '@affine/component/ui/modal';
22
import { openQuotaModalAtom } from '@affine/core/components/atoms';
3-
import { UserQuotaService } from '@affine/core/modules/cloud';
43
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
54
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
65
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
@@ -30,18 +29,6 @@ export const CloudQuotaModal = () => {
3029
permissionService.permission.revalidate();
3130
}, [permissionService]);
3231

33-
const quotaService = useService(UserQuotaService);
34-
const userQuota = useLiveData(
35-
quotaService.quota.quota$.map(q =>
36-
q
37-
? {
38-
name: q.humanReadable.name,
39-
blobLimit: q.humanReadable.blobLimit,
40-
}
41-
: null
42-
)
43-
);
44-
4532
const workspaceDialogService = useService(WorkspaceDialogService);
4633
const handleUpgradeConfirm = useCallback(() => {
4734
workspaceDialogService.open('setting', {
@@ -54,18 +41,19 @@ export const CloudQuotaModal = () => {
5441
}, [workspaceDialogService, setOpen]);
5542

5643
const description = useMemo(() => {
57-
if (userQuota && isOwner) {
58-
return <OwnerDescription quota={userQuota.blobLimit} />;
59-
}
60-
if (workspaceQuota) {
61-
return t['com.affine.payment.blob-limit.description.member']({
62-
quota: workspaceQuota.humanReadable.blobLimit,
63-
});
64-
} else {
65-
// loading
44+
if (!workspaceQuota) {
6645
return null;
6746
}
68-
}, [userQuota, isOwner, workspaceQuota, t]);
47+
if (isOwner) {
48+
return (
49+
<OwnerDescription quota={workspaceQuota.humanReadable.blobLimit} />
50+
);
51+
}
52+
53+
return t['com.affine.payment.blob-limit.description.member']({
54+
quota: workspaceQuota.humanReadable.blobLimit,
55+
});
56+
}, [isOwner, workspaceQuota, t]);
6957

7058
const onAbortLargeBlob = useAsyncCallback(
7159
async (byteSize: number) => {

packages/frontend/core/src/components/hooks/affine/use-enable-cloud.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,17 @@ export const useEnableCloud = () => {
7171
// not logged in, open login modal
7272
if (loginStatus === 'unauthenticated') {
7373
openSignIn();
74+
closeConfirmModal();
75+
return;
7476
}
77+
// FIXME: session status is not updated after login,
78+
// the confirm modal can not get the latest status
7579

7680
if (loginStatus === 'authenticated') {
7781
await enableCloud(...args);
7882
}
7983
},
80-
[enableCloud, loginStatus, openSignIn]
84+
[closeConfirmModal, enableCloud, loginStatus, openSignIn]
8185
);
8286

8387
const confirmEnableCloud = useCallback(

packages/frontend/core/src/desktop/dialogs/setting/general-setting/plans/plan-card.tsx

-61
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
import { GlobalDialogService } from '@affine/core/modules/dialogs';
1111
import {
1212
type CreateCheckoutSessionInput,
13-
ServerDeploymentType,
1413
SubscriptionPlan,
1514
SubscriptionRecurring,
1615
SubscriptionStatus,
@@ -122,21 +121,13 @@ const ActionButton = ({ detail, recurring }: PlanCardProps) => {
122121
const isOnetime = useLiveData(subscriptionService.subscription.isOnetimePro$);
123122
const isFree = detail.plan === SubscriptionPlan.Free;
124123

125-
const serverService = useService(ServerService);
126-
const isSelfHosted = useLiveData(
127-
serverService.server.config$.selector(
128-
c => c.type === ServerDeploymentType.Selfhosted
129-
)
130-
);
131-
132124
const signUpText = useMemo(
133125
() => getSignUpText(detail.plan, t),
134126
[detail.plan, t]
135127
);
136128

137129
// branches:
138130
// if contact => 'Contact Sales'
139-
// if self-hosted team => 'Upgrade'
140131
// if not signed in:
141132
// if free => 'Sign up free'
142133
// if team => 'Upgrade'
@@ -154,13 +145,6 @@ const ActionButton = ({ detail, recurring }: PlanCardProps) => {
154145
// if currentRecurring !== recurring => 'Change to {recurring} Billing'
155146
// else => 'Upgrade'
156147

157-
// self-hosted team
158-
if (isSelfHosted || detail.plan === SubscriptionPlan.SelfHostedTeam) {
159-
return (
160-
<UpgradeToSelfHostTeam recurring={recurring as SubscriptionRecurring} />
161-
);
162-
}
163-
164148
// not signed in
165149
if (!loggedIn) {
166150
return <SignUpAction>{signUpText}</SignUpAction>;
@@ -284,51 +268,6 @@ const UpgradeToTeam = ({ recurring }: { recurring: SubscriptionRecurring }) => {
284268
</a>
285269
);
286270
};
287-
const UpgradeToSelfHostTeam = ({
288-
recurring,
289-
}: {
290-
recurring: SubscriptionRecurring;
291-
}) => {
292-
const t = useI18n();
293-
294-
const handleBeforeCheckout = useCallback(() => {
295-
track.$.settingsPanel.plans.checkout({
296-
plan: SubscriptionPlan.SelfHostedTeam,
297-
recurring: recurring,
298-
});
299-
}, [recurring]);
300-
301-
const checkoutOptions = useMemo(
302-
() => ({
303-
recurring,
304-
plan: SubscriptionPlan.SelfHostedTeam,
305-
variant: null,
306-
coupon: null,
307-
successCallbackLink: generateSubscriptionCallbackLink(
308-
null,
309-
SubscriptionPlan.SelfHostedTeam,
310-
recurring
311-
),
312-
}),
313-
[recurring]
314-
);
315-
316-
return (
317-
<CheckoutSlot
318-
onBeforeCheckout={handleBeforeCheckout}
319-
checkoutOptions={checkoutOptions}
320-
renderer={props => (
321-
<Button
322-
className={clsx(styles.planAction)}
323-
variant="primary"
324-
{...props}
325-
>
326-
{t['com.affine.payment.upgrade']()}
327-
</Button>
328-
)}
329-
/>
330-
);
331-
};
332271

333272
export const Upgrade = ({
334273
className,

packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
2+
import { ServerService } from '@affine/core/modules/cloud';
23
import type { SettingTab } from '@affine/core/modules/dialogs/constant';
34
import { WorkspaceService } from '@affine/core/modules/workspace';
5+
import { ServerDeploymentType } from '@affine/graphql';
46
import { useI18n } from '@affine/i18n';
57
import {
68
CollaborationIcon,
@@ -9,11 +11,12 @@ import {
911
SaveIcon,
1012
SettingsIcon,
1113
} from '@blocksuite/icons/rc';
12-
import { useService } from '@toeverything/infra';
14+
import { useLiveData, useService } from '@toeverything/infra';
1315
import { useMemo } from 'react';
1416

1517
import type { SettingSidebarItem, SettingState } from '../types';
1618
import { WorkspaceSettingBilling } from './billing';
19+
import { WorkspaceSettingLicense } from './license';
1720
import { MembersPanel } from './members';
1821
import { WorkspaceSettingDetail } from './preference';
1922
import { WorkspaceSettingProperties } from './properties';
@@ -44,6 +47,8 @@ export const WorkspaceSetting = ({
4447
return <WorkspaceSettingBilling />;
4548
case 'workspace:storage':
4649
return <WorkspaceSettingStorage onCloseSetting={onCloseSetting} />;
50+
case 'workspace:license':
51+
return <WorkspaceSettingLicense />;
4752
default:
4853
return null;
4954
}
@@ -52,10 +57,18 @@ export const WorkspaceSetting = ({
5257
export const useWorkspaceSettingList = (): SettingSidebarItem[] => {
5358
const workspaceService = useService(WorkspaceService);
5459
const information = useWorkspaceInfo(workspaceService.workspace);
60+
const serverService = useService(ServerService);
61+
62+
const isSelfhosted = useLiveData(
63+
serverService.server.config$.selector(
64+
c => c.type === ServerDeploymentType.Selfhosted
65+
)
66+
);
5567

5668
const t = useI18n();
5769

5870
const showBilling = information?.isTeam && information?.isOwner;
71+
const showLicense = information?.isOwner && isSelfhosted;
5972
const items = useMemo<SettingSidebarItem[]>(() => {
6073
return [
6174
{
@@ -88,10 +101,14 @@ export const useWorkspaceSettingList = (): SettingSidebarItem[] => {
88101
icon: <PaymentIcon />,
89102
testId: 'workspace-setting:billing',
90103
},
91-
92-
// todo(@pengx17): add selfhost's team license
104+
showLicense && {
105+
key: 'workspace:license' as SettingTab,
106+
title: t['com.affine.settings.workspace.license'](),
107+
icon: <PaymentIcon />,
108+
testId: 'workspace-setting:license',
109+
},
93110
].filter((item): item is SettingSidebarItem => !!item);
94-
}, [showBilling, t]);
111+
}, [showBilling, showLicense, t]);
95112

96113
return items;
97114
};

0 commit comments

Comments
 (0)