diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/billing/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/billing/index.tsx index 4ba34cfeab..2eeb8ccb49 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/billing/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/billing/index.tsx @@ -8,8 +8,10 @@ import { import { getUpgradeQuestionnaireLink } from '@affine/core/components/hooks/affine/use-subscription-notify'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useMutation } from '@affine/core/components/hooks/use-mutation'; +import { useWorkspace } from '@affine/core/components/hooks/use-workspace'; import { AuthService, + SubscriptionService, WorkspaceInvoicesService, WorkspaceSubscriptionService, } from '@affine/core/modules/cloud'; @@ -27,7 +29,7 @@ import { FrameworkScope, useLiveData, useService, - WorkspaceService, + type WorkspaceMetadata, } from '@toeverything/infra'; import { cssVar } from '@toeverything/theme'; import { useCallback, useEffect, useMemo, useState } from 'react'; @@ -38,18 +40,29 @@ import { } from '../../general-setting/plans/actions'; import * as styles from './styles.css'; -export const WorkspaceSettingBilling = () => { +export const WorkspaceSettingBilling = ({ + workspaceMetadata, +}: { + workspaceMetadata: WorkspaceMetadata; +}) => { + // useWorkspace hook is a vary heavy operation here, but we need syncing name and avatar changes here, + // we don't have a better way to do this now + const workspace = useWorkspace(workspaceMetadata); + const t = useI18n(); - const workspace = useService(WorkspaceService).workspace; - const subscriptionService = useService(WorkspaceSubscriptionService); - const subscription = useLiveData( - subscriptionService.subscription.subscription$ + + const subscriptionService = workspace?.scope.get( + WorkspaceSubscriptionService ); - const title = useLiveData(workspace.name$) || 'untitled'; + const subscription = useLiveData( + subscriptionService?.subscription.subscription$ + ); + + useEffect(() => { + subscriptionService?.subscription.revalidate(); + }, [subscriptionService]); if (workspace === null) { - console.log('workspace is null', title); - return null; } @@ -83,17 +96,20 @@ export const WorkspaceSettingBilling = () => { const TeamCard = () => { const t = useI18n(); - const subscriptionService = useService(WorkspaceSubscriptionService); + const workspaceSubscriptionService = useService(WorkspaceSubscriptionService); + const subscriptionService = useService(SubscriptionService); + const teamSubscription = useLiveData( - subscriptionService.subscription.subscription$ + workspaceSubscriptionService.subscription.subscription$ ); const teamPrices = useLiveData(subscriptionService.prices.teamPrice$); const [openCancelModal, setOpenCancelModal] = useState(false); + useEffect(() => { - subscriptionService.subscription.revalidate(); - subscriptionService.prices.revalidate(); - }, [subscriptionService]); + workspaceSubscriptionService.subscription.revalidate(); + }, [workspaceSubscriptionService.subscription]); + const expiration = teamSubscription?.end; const nextBillingDate = teamSubscription?.nextBillAt; const recurring = teamSubscription?.recurring; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx index 56ff2a7a5e..79f5334d3e 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/index.tsx @@ -31,7 +31,7 @@ export const WorkspaceSetting = ({ ); case 'workspace:billing': - return ; + return ; } return null; }; diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx index 7eeb0d61b2..05b4026d59 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx @@ -1,7 +1,5 @@ import { Button, Loading, notify } from '@affine/component'; import { - InviteModal, - type InviteModalProps, InviteTeamMemberModal, type InviteTeamMemberModalProps, MemberLimitModal, @@ -98,25 +96,6 @@ export const CloudWorkspaceMembersPanel = ({ return success; }, [permissionService.permission]); - const onInviteConfirm = useCallback( - async ({ email, permission }) => { - setIsMutating(true); - const success = await permissionService.permission.inviteMember( - email, - permission, - true - ); - if (success) { - notify.success({ - title: t['Invitation sent'](), - message: t['Invitation sent hint'](), - }); - setOpen(false); - } - setIsMutating(false); - }, - [permissionService.permission, t] - ); const onInviteBatchConfirm = useCallback< InviteTeamMemberModalProps['onConfirm'] >( @@ -208,18 +187,7 @@ export const CloudWorkspaceMembersPanel = ({ {isOwner ? ( <> - {isTeam ? ( - } - /> - ) : isLimited ? ( + {isLimited && !isTeam ? ( ) : ( - } /> )} diff --git a/packages/frontend/core/src/modules/cloud/entities/workspace-invoices.ts b/packages/frontend/core/src/modules/cloud/entities/workspace-invoices.ts index ba6d8fe6dc..ab85f68245 100644 --- a/packages/frontend/core/src/modules/cloud/entities/workspace-invoices.ts +++ b/packages/frontend/core/src/modules/cloud/entities/workspace-invoices.ts @@ -14,7 +14,8 @@ import { import { EMPTY, map, mergeMap } from 'rxjs'; import { isBackendError, isNetworkError } from '../error'; -import type { InvoicesStore } from '../stores/invoices'; +import type { WorkspaceServerService } from '../services/workspace-server'; +import { InvoicesStore } from '../stores/invoices'; export type Invoice = NonNullable< InvoicesQuery['currentUser'] @@ -22,12 +23,14 @@ export type Invoice = NonNullable< export class WorkspaceInvoices extends Entity { constructor( - private readonly store: InvoicesStore, - private readonly workspaceService: WorkspaceService + private readonly workspaceService: WorkspaceService, + private readonly workspaceServerService: WorkspaceServerService ) { super(); } + store = this.workspaceServerService.server?.scope.get(InvoicesStore); + pageNum$ = new LiveData(0); invoiceCount$ = new LiveData(undefined); pageInvoices$ = new LiveData(undefined); @@ -43,6 +46,9 @@ export class WorkspaceInvoices extends Entity { (a, b) => a === b, pageNum => { return fromPromise(async signal => { + if (!this.store) { + throw new Error('No invoices store'); + } return this.store.fetchWorkspaceInvoices( pageNum * this.PAGE_SIZE, this.PAGE_SIZE, diff --git a/packages/frontend/core/src/modules/cloud/entities/workspace-subscription.ts b/packages/frontend/core/src/modules/cloud/entities/workspace-subscription.ts index e9f6a74825..7d86210328 100644 --- a/packages/frontend/core/src/modules/cloud/entities/workspace-subscription.ts +++ b/packages/frontend/core/src/modules/cloud/entities/workspace-subscription.ts @@ -15,8 +15,8 @@ import { import { EMPTY, mergeMap } from 'rxjs'; import { isBackendError, isNetworkError } from '../error'; -import type { ServerService } from '../services/server'; -import type { SubscriptionStore } from '../stores/subscription'; +import type { WorkspaceServerService } from '../services/workspace-server'; +import { SubscriptionStore } from '../stores/subscription'; export type SubscriptionType = NonNullable< SubscriptionQuery['currentUser'] @@ -33,18 +33,25 @@ export class WorkspaceSubscription extends Entity { constructor( private readonly workspaceService: WorkspaceService, - private readonly serverService: ServerService, - private readonly store: SubscriptionStore + private readonly workspaceServerService: WorkspaceServerService ) { super(); } + server = this.workspaceServerService.server; + store = this.workspaceServerService.server?.scope.get(SubscriptionStore); async resumeSubscription(idempotencyKey: string, plan?: SubscriptionPlan) { + if (!this.store) { + throw new Error('Subscription store not available'); + } await this.store.mutateResumeSubscription(idempotencyKey, plan); await this.waitForRevalidation(); } async cancelSubscription(idempotencyKey: string, plan?: SubscriptionPlan) { + if (!this.store) { + throw new Error('Subscription store not available'); + } await this.store.mutateCancelSubscription(idempotencyKey, plan); await this.waitForRevalidation(); } @@ -54,6 +61,9 @@ export class WorkspaceSubscription extends Entity { recurring: SubscriptionRecurring, plan?: SubscriptionPlan ) { + if (!this.store) { + throw new Error('Subscription store not available'); + } await this.store.setSubscriptionRecurring(idempotencyKey, recurring, plan); await this.waitForRevalidation(); } @@ -70,11 +80,11 @@ export class WorkspaceSubscription extends Entity { exhaustMapWithTrailing(() => { return fromPromise(async signal => { const currentWorkspaceId = this.workspaceService.workspace.id; - if (!currentWorkspaceId) { + if (!currentWorkspaceId || !this.server) { return undefined; // no subscription if no user } - const serverConfig = - await this.serverService.server.features$.waitForNonNull(signal); + + const serverConfig = await this.server.features$.waitForNonNull(signal); if (!serverConfig.payment) { // No payment feature, no subscription @@ -83,6 +93,12 @@ export class WorkspaceSubscription extends Entity { subscription: null, }; } + if (!this.store) { + return { + workspaceId: currentWorkspaceId, + subscription: null, + }; + } const { workspaceId, subscription } = await this.store.fetchWorkspaceSubscriptions( currentWorkspaceId, @@ -101,7 +117,7 @@ export class WorkspaceSubscription extends Entity { when: isBackendError, }), mergeMap(data => { - if (data && data.subscription && data.workspaceId) { + if (data && data.subscription && data.workspaceId && this.store) { this.store.setCachedWorkspaceSubscription( data.workspaceId, data.subscription diff --git a/packages/frontend/core/src/modules/cloud/index.ts b/packages/frontend/core/src/modules/cloud/index.ts index 852fbeb75c..ad32ebc1f9 100644 --- a/packages/frontend/core/src/modules/cloud/index.ts +++ b/packages/frontend/core/src/modules/cloud/index.ts @@ -149,16 +149,7 @@ export function configureCloudModule(framework: Framework) { .store(UserFeatureStore, [GraphQLService]) .service(InvoicesService) .store(InvoicesStore, [GraphQLService]) - .entity(Invoices, [InvoicesStore]) - .scope(WorkspaceScope) - .service(WorkspaceSubscriptionService, [SubscriptionStore]) - .entity(WorkspaceSubscription, [ - WorkspaceService, - ServerService, - SubscriptionStore, - ]) - .service(WorkspaceInvoicesService) - .entity(WorkspaceInvoices, [InvoicesStore, WorkspaceService]); + .entity(Invoices, [InvoicesStore]); framework .scope(WorkspaceScope) @@ -167,4 +158,10 @@ export function configureCloudModule(framework: Framework) { .service(CloudDocMetaService) .entity(CloudDocMeta, [CloudDocMetaStore, DocService, GlobalCache]) .store(CloudDocMetaStore, [WorkspaceServerService]); + framework + .scope(WorkspaceScope) + .service(WorkspaceSubscriptionService, [WorkspaceServerService]) + .entity(WorkspaceSubscription, [WorkspaceService, WorkspaceServerService]) + .service(WorkspaceInvoicesService) + .entity(WorkspaceInvoices, [WorkspaceService, WorkspaceServerService]); } diff --git a/packages/frontend/core/src/modules/cloud/services/workspace-subscription.ts b/packages/frontend/core/src/modules/cloud/services/workspace-subscription.ts index c9850da2c5..b247f1bdfb 100644 --- a/packages/frontend/core/src/modules/cloud/services/workspace-subscription.ts +++ b/packages/frontend/core/src/modules/cloud/services/workspace-subscription.ts @@ -1,19 +1,22 @@ import { type CreateCheckoutSessionInput } from '@affine/graphql'; import { Service } from '@toeverything/infra'; -import { SubscriptionPrices } from '../entities/subscription-prices'; import { WorkspaceSubscription } from '../entities/workspace-subscription'; -import type { SubscriptionStore } from '../stores/subscription'; +import { SubscriptionStore } from '../stores/subscription'; +import type { WorkspaceServerService } from './workspace-server'; export class WorkspaceSubscriptionService extends Service { subscription = this.framework.createEntity(WorkspaceSubscription); - prices = this.framework.createEntity(SubscriptionPrices); - constructor(private readonly store: SubscriptionStore) { + constructor(private readonly workspaceServerService: WorkspaceServerService) { super(); } + store = this.workspaceServerService.server?.scope.get(SubscriptionStore); async createCheckoutSession(input: CreateCheckoutSessionInput) { + if (!this.store) { + throw new Error('No subscription store'); + } return await this.store.createCheckoutSession(input); } }