diff --git a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx
index f97402e2d0..167d516310 100644
--- a/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx
+++ b/packages/frontend/core/src/components/affine/setting-modal/general-setting/billing/index.tsx
@@ -10,31 +10,33 @@ import { Loading } from '@affine/component/ui/loading';
import { getUpgradeQuestionnaireLink } from '@affine/core/hooks/affine/use-subscription-notify';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { track } from '@affine/core/mixpanel';
+import {
+ AuthService,
+ InvoicesService,
+ SubscriptionService,
+} from '@affine/core/modules/cloud';
import type { InvoicesQuery } from '@affine/graphql';
import {
createCustomerPortalMutation,
- getInvoicesCountQuery,
- invoicesQuery,
InvoiceStatus,
SubscriptionPlan,
SubscriptionRecurring,
SubscriptionStatus,
+ UserFriendlyError,
} from '@affine/graphql';
import { i18nTime, Trans, useI18n } from '@affine/i18n';
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
+import { cssVar } from '@toeverything/theme';
import { useSetAtom } from 'jotai';
-import { Suspense, useCallback, useEffect, useState } from 'react';
+import { useCallback, useEffect, useState } from 'react';
import {
openSettingModalAtom,
type PlansScrollAnchor,
} from '../../../../../atoms';
import { useMutation } from '../../../../../hooks/use-mutation';
-import { useQuery } from '../../../../../hooks/use-query';
-import { AuthService, SubscriptionService } from '../../../../../modules/cloud';
import { popupWindow } from '../../../../../utils';
-import { SWRErrorBoundary } from '../../../../pure/swr-error-bundary';
import { CancelAction, ResumeAction } from '../plans/actions';
import { AICancel, AIResume, AISubscribe } from '../plans/ai/actions';
import { BelieverCard } from '../plans/lifetime/believer-card';
@@ -48,8 +50,6 @@ enum DescriptionI18NKey {
Lifetime = 'com.affine.payment.billing-setting.current-plan.description.lifetime',
}
-const INVOICE_PAGE_SIZE = 12;
-
const getMessageKey = (
plan: SubscriptionPlan,
recurring: SubscriptionRecurring
@@ -69,24 +69,14 @@ export const BillingSettings = () => {
title={t['com.affine.payment.billing-setting.title']()}
subtitle={t['com.affine.payment.billing-setting.subtitle']()}
/>
-
- }>
-
-
-
-
-
-
- }>
-
-
-
-
-
+
+
+
+
+
+
>
);
};
@@ -485,39 +475,60 @@ const CancelSubscription = ({ loading }: { loading?: boolean }) => {
const BillingHistory = () => {
const t = useI18n();
- const { data: invoicesCountQueryResult } = useQuery({
- query: getInvoicesCountQuery,
- });
- const [skip, setSkip] = useState(0);
+ const invoicesService = useService(InvoicesService);
+ const pageInvoices = useLiveData(invoicesService.invoices.pageInvoices$);
+ const invoiceCount = useLiveData(invoicesService.invoices.invoiceCount$);
+ const isLoading = useLiveData(invoicesService.invoices.isLoading$);
+ const error = useLiveData(invoicesService.invoices.error$);
+ const pageNum = useLiveData(invoicesService.invoices.pageNum$);
- const { data: invoicesQueryResult } = useQuery({
- query: invoicesQuery,
- variables: { skip, take: INVOICE_PAGE_SIZE },
- });
+ useEffect(() => {
+ invoicesService.invoices.revalidate();
+ }, [invoicesService]);
- const invoices = invoicesQueryResult.currentUser?.invoices ?? [];
- const invoiceCount = invoicesCountQueryResult.currentUser?.invoiceCount ?? 0;
+ const handlePageChange = useCallback(
+ (_: number, pageNum: number) => {
+ invoicesService.invoices.setPageNum(pageNum);
+ invoicesService.invoices.revalidate();
+ },
+ [invoicesService]
+ );
+
+ if (invoiceCount === undefined) {
+ if (isLoading) {
+ return ;
+ } else {
+ return (
+
+ {error
+ ? UserFriendlyError.fromAnyError(error).message
+ : 'Failed to load members'}
+
+ );
+ }
+ }
return (
- {invoices.length === 0 ? (
+ {invoiceCount === 0 ? (
{t['com.affine.payment.billing-setting.no-invoice']()}
) : (
- invoices.map(invoice => (
+ pageInvoices?.map(invoice => (
))
)}
- {invoiceCount > INVOICE_PAGE_SIZE && (
+ {invoiceCount > invoicesService.invoices.PAGE_SIZE && (
setSkip(skip)}
+ countPerPage={invoicesService.invoices.PAGE_SIZE}
+ pageNum={pageNum}
+ onPageChange={handlePageChange}
/>
)}
diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx
index 5634fd4ec1..64a6f24fc6 100644
--- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx
+++ b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx
@@ -162,15 +162,15 @@ export const CloudWorkspaceMembersPanel = () => {
if (workspaceQuota === null) {
if (isLoading) {
return ;
- }
- if (error) {
+ } else {
return (
- {UserFriendlyError.fromAnyError(error).message}
+ {error
+ ? UserFriendlyError.fromAnyError(error).message
+ : 'Failed to load members'}
);
}
- return; // never reach here
}
return (
@@ -261,6 +261,7 @@ const MemberList = ({
const memberCount = useLiveData(membersService.members.memberCount$);
const pageNum = useLiveData(membersService.members.pageNum$);
const isLoading = useLiveData(membersService.members.isLoading$);
+ const error = useLiveData(membersService.members.error$);
const pageMembers = useLiveData(membersService.members.pageMembers$);
useEffect(() => {
@@ -280,17 +281,25 @@ const MemberList = ({
return (
- {isLoading && pageMembers === undefined ? (
-
+ {pageMembers === undefined ? (
+ isLoading ? (
+
+ ) : (
+
+ {error
+ ? UserFriendlyError.fromAnyError(error).message
+ : 'Failed to load members'}
+
+ )
) : (
pageMembers?.map(member => (
;
-
-export function useIsSharedPage(
- workspaceId: string,
- pageId: string
-): {
- isSharedPage: boolean;
- changeShare: (mode: DocMode) => void;
- disableShare: () => void;
- currentShareMode: DocMode;
- enableShare: (mode: DocMode) => void;
-} {
- const t = useI18n();
- const { data, mutate } = useQuery({
- query: getWorkspacePublicPagesQuery,
- variables: {
- workspaceId,
- },
- });
-
- const { trigger: enableSharePage } = useMutation({
- mutation: publishPageMutation,
- });
- const { trigger: disableSharePage } = useMutation({
- mutation: revokePublicPageMutation,
- });
-
- const [isSharedPage, currentShareMode] = useMemo(() => {
- const publicPage = data?.workspace.publicPages.find(
- publicPage => publicPage.id === pageId
- );
- const isPageShared = !!publicPage;
-
- const currentShareMode: DocMode =
- publicPage?.mode === PublicPageMode.Edgeless ? 'edgeless' : 'page';
-
- return [isPageShared, currentShareMode];
- }, [data?.workspace.publicPages, pageId]);
-
- const enableShare = useCallback(
- (mode: DocMode) => {
- const publishMode =
- mode === 'edgeless' ? PublicPageMode.Edgeless : PublicPageMode.Page;
-
- enableSharePage({ workspaceId, pageId, mode: publishMode })
- .then(() => {
- notify.success({
- title: t[notificationToI18nKey['enableSuccessTitle']](),
- message: t[notificationToI18nKey['enableSuccessMessage']](),
- style: 'normal',
- icon: (
-
- ),
- });
- return mutate();
- })
- .catch(e => {
- notify.error({
- title: t[notificationToI18nKey['enableErrorTitle']](),
- message: t[notificationToI18nKey['enableErrorMessage']](),
- });
- console.error(e);
- });
- },
- [enableSharePage, mutate, pageId, t, workspaceId]
- );
-
- const changeShare = useCallback(
- (mode: DocMode) => {
- const publishMode =
- mode === 'edgeless' ? PublicPageMode.Edgeless : PublicPageMode.Page;
-
- enableSharePage({ workspaceId, pageId, mode: publishMode })
- .then(() => {
- notify.success({
- title: t[notificationToI18nKey['changeSuccessTitle']](),
- message: t[
- 'com.affine.share-menu.confirm-modify-mode.notification.success.message'
- ]({
- preMode:
- publishMode === PublicPageMode.Edgeless
- ? t['Page']()
- : t['Edgeless'](),
- currentMode:
- publishMode === PublicPageMode.Edgeless
- ? t['Edgeless']()
- : t['Page'](),
- }),
- style: 'normal',
- icon: (
-
- ),
- });
- return mutate();
- })
- .catch(e => {
- notify.error({
- title: t[notificationToI18nKey['changeErrorTitle']](),
- message: t[notificationToI18nKey['changeErrorMessage']](),
- });
- console.error(e);
- });
- },
- [enableSharePage, mutate, pageId, t, workspaceId]
- );
-
- const disableShare = useCallback(() => {
- disableSharePage({ workspaceId, pageId })
- .then(() => {
- notify.success({
- title: t[notificationToI18nKey['disableSuccessTitle']](),
- message: t[notificationToI18nKey['disableSuccessMessage']](),
- style: 'normal',
- icon: ,
- });
- return mutate();
- })
- .catch(e => {
- notify.error({
- title: t[notificationToI18nKey['disableErrorTitle']](),
- message: t[notificationToI18nKey['disableErrorMessage']](),
- });
- console.error(e);
- });
- }, [disableSharePage, mutate, pageId, t, workspaceId]);
-
- return useMemo(
- () => ({
- isSharedPage,
- currentShareMode,
- enableShare,
- disableShare,
- changeShare,
- }),
- [isSharedPage, currentShareMode, enableShare, disableShare, changeShare]
- );
-}
-
-export function usePublicPages(workspace: Workspace) {
- const isLocalWorkspace = workspace.flavour === WorkspaceFlavour.LOCAL;
- const { data } = useQuery(
- isLocalWorkspace
- ? undefined
- : {
- query: getWorkspacePublicPagesQuery,
- variables: {
- workspaceId: workspace.id,
- },
- }
- );
- const maybeData = data as typeof data | undefined;
-
- const publicPages: {
- id: string;
- mode: DocMode;
- }[] = useMemo(
- () =>
- maybeData?.workspace.publicPages.map(i => ({
- id: i.id,
- mode: i.mode === PublicPageMode.Edgeless ? 'edgeless' : 'page',
- })) ?? [],
- [maybeData?.workspace.publicPages]
- );
-
- /**
- * Return `undefined` if the page is not public.
- */
- const getPublicMode = useCallback(
- (pageId: string) => {
- return publicPages.find(i => i.id === pageId)?.mode;
- },
- [publicPages]
- );
- return {
- publicPages,
- getPublicMode,
- };
-}
diff --git a/packages/frontend/core/src/hooks/use-workspace-features.ts b/packages/frontend/core/src/hooks/use-workspace-features.ts
deleted file mode 100644
index 3587a4e30e..0000000000
--- a/packages/frontend/core/src/hooks/use-workspace-features.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-import { WorkspaceFlavour } from '@affine/env/workspace';
-import type { FeatureType } from '@affine/graphql';
-import {
- availableFeaturesQuery,
- enabledFeaturesQuery,
- setWorkspaceExperimentalFeatureMutation,
-} from '@affine/graphql';
-import type { WorkspaceMetadata } from '@toeverything/infra';
-
-import { useAsyncCallback } from './affine-async-hooks';
-import { useMutateQueryResource, useMutation } from './use-mutation';
-import { useQueryImmutable } from './use-query';
-
-const emptyFeatures: FeatureType[] = [];
-
-export const useWorkspaceAvailableFeatures = (
- workspaceMetadata: WorkspaceMetadata
-) => {
- const isCloudWorkspace =
- workspaceMetadata.flavour === WorkspaceFlavour.AFFINE_CLOUD;
- const { data } = useQueryImmutable(
- isCloudWorkspace
- ? {
- query: availableFeaturesQuery,
- variables: {
- id: workspaceMetadata.id,
- },
- }
- : undefined
- );
- return data?.workspace.availableFeatures ?? emptyFeatures;
-};
-
-export const useWorkspaceEnabledFeatures = (
- workspaceMetadata: WorkspaceMetadata
-) => {
- const isCloudWorkspace =
- workspaceMetadata.flavour === WorkspaceFlavour.AFFINE_CLOUD;
- const { data } = useQueryImmutable(
- isCloudWorkspace
- ? {
- query: enabledFeaturesQuery,
- variables: {
- id: workspaceMetadata.id,
- },
- }
- : undefined
- );
- return data?.workspace.features ?? emptyFeatures;
-};
-
-export const useSetWorkspaceFeature = (
- workspaceMetadata: WorkspaceMetadata
-) => {
- const { trigger, isMutating } = useMutation({
- mutation: setWorkspaceExperimentalFeatureMutation,
- });
- const revalidate = useMutateQueryResource();
-
- return {
- trigger: useAsyncCallback(
- async (feature: FeatureType, enable: boolean) => {
- await trigger({
- workspaceId: workspaceMetadata.id,
- feature,
- enable,
- });
- await revalidate(enabledFeaturesQuery, vars => {
- return vars.id === workspaceMetadata.id;
- });
- },
- [workspaceMetadata.id, revalidate, trigger]
- ),
- isMutating,
- };
-};
diff --git a/packages/frontend/core/src/modules/cloud/entities/invoices.ts b/packages/frontend/core/src/modules/cloud/entities/invoices.ts
new file mode 100644
index 0000000000..04d6b26c9b
--- /dev/null
+++ b/packages/frontend/core/src/modules/cloud/entities/invoices.ts
@@ -0,0 +1,78 @@
+import type { InvoicesQuery } from '@affine/graphql';
+import {
+ backoffRetry,
+ catchErrorInto,
+ effect,
+ Entity,
+ exhaustMapSwitchUntilChanged,
+ fromPromise,
+ LiveData,
+ onComplete,
+ onStart,
+} from '@toeverything/infra';
+import { EMPTY, map, mergeMap } from 'rxjs';
+
+import { isBackendError, isNetworkError } from '../error';
+import type { InvoicesStore } from '../stores/invoices';
+
+export type Invoice = NonNullable<
+ InvoicesQuery['currentUser']
+>['invoices'][number];
+
+export class Invoices extends Entity {
+ constructor(private readonly store: InvoicesStore) {
+ super();
+ }
+
+ pageNum$ = new LiveData(0);
+ invoiceCount$ = new LiveData(undefined);
+ pageInvoices$ = new LiveData(undefined);
+
+ isLoading$ = new LiveData(false);
+ error$ = new LiveData(null);
+
+ readonly PAGE_SIZE = 8;
+
+ readonly revalidate = effect(
+ map(() => this.pageNum$.value),
+ exhaustMapSwitchUntilChanged(
+ (a, b) => a === b,
+ pageNum => {
+ return fromPromise(async signal => {
+ return this.store.fetchInvoices(
+ pageNum * this.PAGE_SIZE,
+ this.PAGE_SIZE,
+ signal
+ );
+ }).pipe(
+ mergeMap(data => {
+ this.invoiceCount$.setValue(data.invoiceCount);
+ this.pageInvoices$.setValue(data.invoices);
+ return EMPTY;
+ }),
+ backoffRetry({
+ when: isNetworkError,
+ count: Infinity,
+ }),
+ backoffRetry({
+ when: isBackendError,
+ }),
+ catchErrorInto(this.error$),
+ onStart(() => {
+ this.pageInvoices$.setValue(undefined);
+ this.isLoading$.setValue(true);
+ }),
+ onComplete(() => this.isLoading$.setValue(false))
+ );
+ }
+ )
+ );
+
+ setPageNum(pageNum: number) {
+ this.pageNum$.setValue(pageNum);
+ }
+
+ override dispose(): void {
+ this.revalidate.unsubscribe();
+ }
+}
diff --git a/packages/frontend/core/src/modules/cloud/index.ts b/packages/frontend/core/src/modules/cloud/index.ts
index add141d3cc..8360ef699e 100644
--- a/packages/frontend/core/src/modules/cloud/index.ts
+++ b/packages/frontend/core/src/modules/cloud/index.ts
@@ -1,3 +1,4 @@
+export type { Invoice } from './entities/invoices';
export type { AuthAccountInfo } from './entities/session';
export {
BackendError,
@@ -8,6 +9,7 @@ export {
export { AccountChanged, AuthService } from './services/auth';
export { FetchService } from './services/fetch';
export { GraphQLService } from './services/graphql';
+export { InvoicesService } from './services/invoices';
export { ServerConfigService } from './services/server-config';
export { SubscriptionService } from './services/subscription';
export { UserCopilotQuotaService } from './services/user-copilot-quota';
@@ -25,6 +27,7 @@ import {
} from '@toeverything/infra';
import { CloudDocMeta } from './entities/cloud-doc-meta';
+import { Invoices } from './entities/invoices';
import { ServerConfig } from './entities/server-config';
import { AuthSession } from './entities/session';
import { Subscription } from './entities/subscription';
@@ -36,6 +39,7 @@ import { AuthService } from './services/auth';
import { CloudDocMetaService } from './services/cloud-doc-meta';
import { FetchService } from './services/fetch';
import { GraphQLService } from './services/graphql';
+import { InvoicesService } from './services/invoices';
import { ServerConfigService } from './services/server-config';
import { SubscriptionService } from './services/subscription';
import { UserCopilotQuotaService } from './services/user-copilot-quota';
@@ -44,6 +48,7 @@ import { UserQuotaService } from './services/user-quota';
import { WebSocketService } from './services/websocket';
import { AuthStore } from './stores/auth';
import { CloudDocMetaStore } from './stores/cloud-doc-meta';
+import { InvoicesStore } from './stores/invoices';
import { ServerConfigStore } from './stores/server-config';
import { SubscriptionStore } from './stores/subscription';
import { UserCopilotQuotaStore } from './stores/user-copilot-quota';
@@ -78,6 +83,9 @@ export function configureCloudModule(framework: Framework) {
.service(UserFeatureService)
.entity(UserFeature, [AuthService, UserFeatureStore])
.store(UserFeatureStore, [GraphQLService])
+ .service(InvoicesService)
+ .store(InvoicesStore, [GraphQLService])
+ .entity(Invoices, [InvoicesStore])
.scope(WorkspaceScope)
.scope(DocScope)
.service(CloudDocMetaService)
diff --git a/packages/frontend/core/src/modules/cloud/services/invoices.ts b/packages/frontend/core/src/modules/cloud/services/invoices.ts
new file mode 100644
index 0000000000..6e1dd2306c
--- /dev/null
+++ b/packages/frontend/core/src/modules/cloud/services/invoices.ts
@@ -0,0 +1,7 @@
+import { Service } from '@toeverything/infra';
+
+import { Invoices } from '../entities/invoices';
+
+export class InvoicesService extends Service {
+ invoices = this.framework.createEntity(Invoices);
+}
diff --git a/packages/frontend/core/src/modules/cloud/stores/invoices.ts b/packages/frontend/core/src/modules/cloud/stores/invoices.ts
new file mode 100644
index 0000000000..96192b3c3f
--- /dev/null
+++ b/packages/frontend/core/src/modules/cloud/stores/invoices.ts
@@ -0,0 +1,24 @@
+import { invoicesQuery } from '@affine/graphql';
+import { Store } from '@toeverything/infra';
+
+import type { GraphQLService } from '../services/graphql';
+
+export class InvoicesStore extends Store {
+ constructor(private readonly graphqlService: GraphQLService) {
+ super();
+ }
+
+ async fetchInvoices(skip: number, take: number, signal?: AbortSignal) {
+ const data = await this.graphqlService.gql({
+ query: invoicesQuery,
+ variables: { skip, take },
+ context: { signal },
+ });
+
+ if (!data.currentUser) {
+ throw new Error('No logged in');
+ }
+
+ return data.currentUser;
+ }
+}
diff --git a/packages/frontend/core/src/modules/permissions/entities/members.ts b/packages/frontend/core/src/modules/permissions/entities/members.ts
index 5064fe38e1..966cb71446 100644
--- a/packages/frontend/core/src/modules/permissions/entities/members.ts
+++ b/packages/frontend/core/src/modules/permissions/entities/members.ts
@@ -5,12 +5,13 @@ import {
catchErrorInto,
effect,
Entity,
+ exhaustMapSwitchUntilChanged,
fromPromise,
LiveData,
onComplete,
onStart,
} from '@toeverything/infra';
-import { distinctUntilChanged, EMPTY, map, mergeMap, switchMap } from 'rxjs';
+import { EMPTY, map, mergeMap } from 'rxjs';
import { isBackendError, isNetworkError } from '../../cloud';
import type { WorkspaceMembersStore } from '../stores/members';
@@ -37,36 +38,38 @@ export class WorkspaceMembers extends Entity {
readonly revalidate = effect(
map(() => this.pageNum$.value),
- distinctUntilChanged(),
- switchMap(pageNum => {
- return fromPromise(async signal => {
- return this.store.fetchMembers(
- this.workspaceService.workspace.id,
- pageNum * this.PAGE_SIZE,
- this.PAGE_SIZE,
- signal
+ exhaustMapSwitchUntilChanged(
+ (a, b) => a === b,
+ pageNum => {
+ return fromPromise(async signal => {
+ return this.store.fetchMembers(
+ this.workspaceService.workspace.id,
+ pageNum * this.PAGE_SIZE,
+ this.PAGE_SIZE,
+ signal
+ );
+ }).pipe(
+ mergeMap(data => {
+ this.memberCount$.setValue(data.memberCount);
+ this.pageMembers$.setValue(data.members);
+ return EMPTY;
+ }),
+ backoffRetry({
+ when: isNetworkError,
+ count: Infinity,
+ }),
+ backoffRetry({
+ when: isBackendError,
+ }),
+ catchErrorInto(this.error$),
+ onStart(() => {
+ this.pageMembers$.setValue(undefined);
+ this.isLoading$.setValue(true);
+ }),
+ onComplete(() => this.isLoading$.setValue(false))
);
- }).pipe(
- mergeMap(data => {
- this.memberCount$.setValue(data.memberCount);
- this.pageMembers$.setValue(data.members);
- return EMPTY;
- }),
- backoffRetry({
- when: isNetworkError,
- count: Infinity,
- }),
- backoffRetry({
- when: isBackendError,
- }),
- catchErrorInto(this.error$),
- onStart(() => {
- this.pageMembers$.setValue(undefined);
- this.isLoading$.setValue(true);
- }),
- onComplete(() => this.isLoading$.setValue(false))
- );
- })
+ }
+ )
);
setPageNum(pageNum: number) {
diff --git a/packages/frontend/graphql/src/graphql/index.ts b/packages/frontend/graphql/src/graphql/index.ts
index 43206d73bd..47a97eb632 100644
--- a/packages/frontend/graphql/src/graphql/index.ts
+++ b/packages/frontend/graphql/src/graphql/index.ts
@@ -750,6 +750,7 @@ export const invoicesQuery = {
query: `
query invoices($take: Int!, $skip: Int!) {
currentUser {
+ invoiceCount
invoices(take: $take, skip: $skip) {
id
status
diff --git a/packages/frontend/graphql/src/graphql/invoices.gql b/packages/frontend/graphql/src/graphql/invoices.gql
index bd26f0896a..0645d6a3b8 100644
--- a/packages/frontend/graphql/src/graphql/invoices.gql
+++ b/packages/frontend/graphql/src/graphql/invoices.gql
@@ -1,5 +1,6 @@
query invoices($take: Int!, $skip: Int!) {
currentUser {
+ invoiceCount
invoices(take: $take, skip: $skip) {
id
status
diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts
index 0ffe888349..2dcfd2233e 100644
--- a/packages/frontend/graphql/src/schema.ts
+++ b/packages/frontend/graphql/src/schema.ts
@@ -1941,6 +1941,7 @@ export type InvoicesQuery = {
__typename?: 'Query';
currentUser: {
__typename?: 'UserType';
+ invoiceCount: number;
invoices: Array<{
__typename?: 'UserInvoice';
id: string;