fix(core): revalidate quota and subscription when subscribing (#9220)

This commit is contained in:
EYHN 2024-12-20 09:31:52 +00:00
parent bdbefd3e28
commit f788fdd0a4
No known key found for this signature in database
GPG Key ID: 46C9E26A75AB276C
5 changed files with 47 additions and 28 deletions

View File

@ -7,10 +7,10 @@ import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { type I18nString, useI18n } from '@affine/i18n'; import { type I18nString, useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
import bytes from 'bytes';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useAsyncCallback } from '../../hooks/affine-async-hooks';
import * as styles from './cloud-quota-modal.css'; import * as styles from './cloud-quota-modal.css';
export const CloudQuotaModal = () => { export const CloudQuotaModal = () => {
@ -66,22 +66,32 @@ export const CloudQuotaModal = () => {
} }
}, [userQuota, isOwner, workspaceQuota, t]); }, [userQuota, isOwner, workspaceQuota, t]);
const onAbortLargeBlob = useAsyncCallback(
async (blob: Blob) => {
// wait for quota revalidation
await workspaceQuotaService.quota.waitForRevalidation();
if (
blob.size > (workspaceQuotaService.quota.quota$.value?.blobLimit ?? 0)
) {
setOpen(true);
}
},
[setOpen, workspaceQuotaService]
);
useEffect(() => { useEffect(() => {
if (!workspaceQuota) { if (!workspaceQuota) {
return; return;
} }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
currentWorkspace.engine.blob.singleBlobSizeLimit = bytes.parse(
workspaceQuota.blobLimit.toString()
)!;
const disposable = currentWorkspace.engine.blob.onAbortLargeBlob.on(() => { currentWorkspace.engine.blob.singleBlobSizeLimit = workspaceQuota.blobLimit;
setOpen(true);
}); const disposable =
currentWorkspace.engine.blob.onAbortLargeBlob.on(onAbortLargeBlob);
return () => { return () => {
disposable?.dispose(); disposable?.dispose();
}; };
}, [currentWorkspace.engine.blob, setOpen, workspaceQuota]); }, [currentWorkspace.engine.blob, onAbortLargeBlob, workspaceQuota]);
return ( return (
<ConfirmModal <ConfirmModal

View File

@ -1,5 +1,8 @@
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { SubscriptionService } from '@affine/core/modules/cloud'; import {
SubscriptionService,
UserQuotaService,
} from '@affine/core/modules/cloud';
import { UrlService } from '@affine/core/modules/url'; import { UrlService } from '@affine/core/modules/url';
import type { CreateCheckoutSessionInput } from '@affine/graphql'; import type { CreateCheckoutSessionInput } from '@affine/graphql';
import { useService } from '@toeverything/infra'; import { useService } from '@toeverything/infra';
@ -7,6 +10,7 @@ import { nanoid } from 'nanoid';
import { import {
type PropsWithChildren, type PropsWithChildren,
type ReactNode, type ReactNode,
useCallback,
useEffect, useEffect,
useState, useState,
} from 'react'; } from 'react';
@ -35,26 +39,23 @@ export const CheckoutSlot = ({
const urlService = useService(UrlService); const urlService = useService(UrlService);
const subscriptionService = useService(SubscriptionService); const subscriptionService = useService(SubscriptionService);
const userQuotaService = useService(UserQuotaService);
const revalidate = useCallback(() => {
subscriptionService.subscription.revalidate();
userQuotaService.quota.revalidate();
}, [subscriptionService, userQuotaService]);
useEffect(() => {
subscriptionService.prices.revalidate();
}, [subscriptionService]);
useEffect(() => { useEffect(() => {
if (isOpenedExternalWindow) { if (isOpenedExternalWindow) {
// when the external window is opened, revalidate the subscription when window get focus // when the external window is opened, revalidate the subscription when window get focus
window.addEventListener( window.addEventListener('focus', revalidate);
'focus',
subscriptionService.subscription.revalidate
);
return () => { return () => {
window.removeEventListener( window.removeEventListener('focus', revalidate);
'focus',
subscriptionService.subscription.revalidate
);
}; };
} }
return; return;
}, [isOpenedExternalWindow, subscriptionService]); }, [isOpenedExternalWindow, revalidate, subscriptionService]);
const subscribe = useAsyncCallback(async () => { const subscribe = useAsyncCallback(async () => {
setMutating(true); setMutating(true);

View File

@ -75,7 +75,7 @@ export const CloudWorkspaceMembersPanel = ({
useEffect(() => { useEffect(() => {
workspaceQuotaService.quota.revalidate(); workspaceQuotaService.quota.revalidate();
}, [workspaceQuotaService]); }, [workspaceQuotaService]);
const isLoading = useLiveData(workspaceQuotaService.quota.isLoading$); const isLoading = useLiveData(workspaceQuotaService.quota.isRevalidating$);
const error = useLiveData(workspaceQuotaService.quota.error$); const error = useLiveData(workspaceQuotaService.quota.error$);
const workspaceQuota = useLiveData(workspaceQuotaService.quota.quota$); const workspaceQuota = useLiveData(workspaceQuotaService.quota.quota$);
const subscriptionService = useService(SubscriptionService); const subscriptionService = useService(SubscriptionService);

View File

@ -29,7 +29,7 @@ export const StorageProgress = () => {
).permission; ).permission;
const workspaceQuotaService = useService(WorkspaceQuotaService).quota; const workspaceQuotaService = useService(WorkspaceQuotaService).quota;
const isTeam = useLiveData(workspacePermissionService.isTeam$); const isTeam = useLiveData(workspacePermissionService.isTeam$);
const isLoading = useLiveData(workspaceQuotaService.isLoading$); const isLoading = useLiveData(workspaceQuotaService.isRevalidating$);
const usedFormatted = useLiveData(workspaceQuotaService.usedFormatted$); const usedFormatted = useLiveData(workspaceQuotaService.usedFormatted$);
const maxFormatted = useLiveData(workspaceQuotaService.maxFormatted$); const maxFormatted = useLiveData(workspaceQuotaService.maxFormatted$);
const percent = useLiveData(workspaceQuotaService.percent$); const percent = useLiveData(workspaceQuotaService.percent$);

View File

@ -25,7 +25,7 @@ const logger = new DebugLogger('affine:workspace-permission');
export class WorkspaceQuota extends Entity { export class WorkspaceQuota extends Entity {
quota$ = new LiveData<QuotaType | null>(null); quota$ = new LiveData<QuotaType | null>(null);
isLoading$ = new LiveData(false); isRevalidating$ = new LiveData(false);
error$ = new LiveData<any>(null); error$ = new LiveData<any>(null);
/** Used storage in bytes */ /** Used storage in bytes */
@ -106,18 +106,26 @@ export class WorkspaceQuota extends Entity {
catchErrorInto(this.error$, error => { catchErrorInto(this.error$, error => {
logger.error('Failed to fetch workspace quota', error); logger.error('Failed to fetch workspace quota', error);
}), }),
onStart(() => this.isLoading$.setValue(true)), onStart(() => this.isRevalidating$.setValue(true)),
onComplete(() => this.isLoading$.setValue(false)) onComplete(() => this.isRevalidating$.setValue(false))
); );
} }
) )
); );
waitForRevalidation(signal?: AbortSignal) {
this.revalidate();
return this.isRevalidating$.waitFor(
isRevalidating => !isRevalidating,
signal
);
}
reset() { reset() {
this.quota$.next(null); this.quota$.next(null);
this.used$.next(null); this.used$.next(null);
this.error$.next(null); this.error$.next(null);
this.isLoading$.next(false); this.isRevalidating$.next(false);
} }
override dispose(): void { override dispose(): void {