feat(core): billing history pagination (#4787)
This commit is contained in:
@@ -8,10 +8,12 @@ import {
|
|||||||
Args,
|
Args,
|
||||||
Field,
|
Field,
|
||||||
ID,
|
ID,
|
||||||
|
Int,
|
||||||
Mutation,
|
Mutation,
|
||||||
ObjectType,
|
ObjectType,
|
||||||
Query,
|
Query,
|
||||||
registerEnumType,
|
registerEnumType,
|
||||||
|
ResolveField,
|
||||||
Resolver,
|
Resolver,
|
||||||
} from '@nestjs/graphql';
|
} from '@nestjs/graphql';
|
||||||
import type { User } from '@prisma/client';
|
import type { User } from '@prisma/client';
|
||||||
@@ -156,6 +158,17 @@ export class UserResolver {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throttle({ default: { limit: 10, ttl: 60 } })
|
||||||
|
@ResolveField(() => Int, {
|
||||||
|
name: 'invoiceCount',
|
||||||
|
description: 'Get user invoice count',
|
||||||
|
})
|
||||||
|
async invoiceCount(@CurrentUser() user: UserType) {
|
||||||
|
return this.prisma.userInvoice.count({
|
||||||
|
where: { userId: user.id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Throttle({
|
@Throttle({
|
||||||
default: {
|
default: {
|
||||||
limit: 10,
|
limit: 10,
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ type UserType {
|
|||||||
"""User password has been set"""
|
"""User password has been set"""
|
||||||
hasPassword: Boolean
|
hasPassword: Boolean
|
||||||
token: TokenType!
|
token: TokenType!
|
||||||
|
|
||||||
|
"""Get user invoice count"""
|
||||||
|
invoiceCount: Int!
|
||||||
subscription: UserSubscription
|
subscription: UserSubscription
|
||||||
invoices(take: Int = 8, skip: Int): [UserInvoice!]!
|
invoices(take: Int = 8, skip: Int): [UserInvoice!]!
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Pagination } from '@affine/component/member-components';
|
||||||
import {
|
import {
|
||||||
SettingHeader,
|
SettingHeader,
|
||||||
SettingRow,
|
SettingRow,
|
||||||
@@ -6,6 +7,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
cancelSubscriptionMutation,
|
cancelSubscriptionMutation,
|
||||||
createCustomerPortalMutation,
|
createCustomerPortalMutation,
|
||||||
|
getInvoicesCountQuery,
|
||||||
type InvoicesQuery,
|
type InvoicesQuery,
|
||||||
invoicesQuery,
|
invoicesQuery,
|
||||||
InvoiceStatus,
|
InvoiceStatus,
|
||||||
@@ -41,6 +43,8 @@ enum DescriptionI18NKey {
|
|||||||
Yearly = 'com.affine.payment.billing-setting.current-plan.description.yearly',
|
Yearly = 'com.affine.payment.billing-setting.current-plan.description.yearly',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INVOICE_PAGE_SIZE = 12;
|
||||||
|
|
||||||
const getMessageKey = (
|
const getMessageKey = (
|
||||||
plan: SubscriptionPlan,
|
plan: SubscriptionPlan,
|
||||||
recurring: SubscriptionRecurring
|
recurring: SubscriptionRecurring
|
||||||
@@ -334,27 +338,40 @@ const CancelSubscription = ({ loading }: { loading?: boolean }) => {
|
|||||||
|
|
||||||
const BillingHistory = () => {
|
const BillingHistory = () => {
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
|
const { data: invoicesCountQueryResult } = useQuery({
|
||||||
|
query: getInvoicesCountQuery,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [skip, setSkip] = useState(0);
|
||||||
|
|
||||||
const { data: invoicesQueryResult } = useQuery({
|
const { data: invoicesQueryResult } = useQuery({
|
||||||
query: invoicesQuery,
|
query: invoicesQuery,
|
||||||
variables: {
|
variables: { skip, take: INVOICE_PAGE_SIZE },
|
||||||
skip: 0,
|
|
||||||
take: 12,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const invoices = invoicesQueryResult.currentUser?.invoices ?? [];
|
const invoices = invoicesQueryResult.currentUser?.invoices ?? [];
|
||||||
|
const invoiceCount = invoicesCountQueryResult.currentUser?.invoiceCount ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.billingHistory}>
|
<div className={styles.history}>
|
||||||
{invoices.length === 0 ? (
|
<div className={styles.historyContent}>
|
||||||
<p className={styles.noInvoice}>
|
{invoices.length === 0 ? (
|
||||||
{t['com.affine.payment.billing-setting.no-invoice']()}
|
<p className={styles.noInvoice}>
|
||||||
</p>
|
{t['com.affine.payment.billing-setting.no-invoice']()}
|
||||||
) : (
|
</p>
|
||||||
// TODO: pagination
|
) : (
|
||||||
invoices.map(invoice => (
|
invoices.map(invoice => (
|
||||||
<InvoiceLine key={invoice.id} invoice={invoice} />
|
<InvoiceLine key={invoice.id} invoice={invoice} />
|
||||||
))
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{invoiceCount > INVOICE_PAGE_SIZE && (
|
||||||
|
<Pagination
|
||||||
|
totalCount={invoiceCount}
|
||||||
|
countPerPage={INVOICE_PAGE_SIZE}
|
||||||
|
onPageChange={skip => setSkip(skip)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,15 @@ import { globalStyle, style } from '@vanilla-extract/css';
|
|||||||
|
|
||||||
export const subscription = style({});
|
export const subscription = style({});
|
||||||
|
|
||||||
export const billingHistory = style({});
|
export const history = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '24px',
|
||||||
|
});
|
||||||
|
export const historyContent = style({
|
||||||
|
width: '100%',
|
||||||
|
});
|
||||||
|
|
||||||
export const planCard = style({
|
export const planCard = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -359,6 +359,19 @@ query getWorkspaces {
|
|||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getInvoicesCountQuery = {
|
||||||
|
id: 'getInvoicesCountQuery' as const,
|
||||||
|
operationName: 'getInvoicesCount',
|
||||||
|
definitionName: 'currentUser',
|
||||||
|
containsFile: false,
|
||||||
|
query: `
|
||||||
|
query getInvoicesCount {
|
||||||
|
currentUser {
|
||||||
|
invoiceCount
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
};
|
||||||
|
|
||||||
export const invoicesQuery = {
|
export const invoicesQuery = {
|
||||||
id: 'invoicesQuery' as const,
|
id: 'invoicesQuery' as const,
|
||||||
operationName: 'invoices',
|
operationName: 'invoices',
|
||||||
|
|||||||
5
packages/frontend/graphql/src/graphql/invoices-count.gql
Normal file
5
packages/frontend/graphql/src/graphql/invoices-count.gql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
query getInvoicesCount {
|
||||||
|
currentUser {
|
||||||
|
invoiceCount
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -359,6 +359,13 @@ export type GetWorkspacesQuery = {
|
|||||||
workspaces: Array<{ __typename?: 'WorkspaceType'; id: string }>;
|
workspaces: Array<{ __typename?: 'WorkspaceType'; id: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetInvoicesCountQueryVariables = Exact<{ [key: string]: never }>;
|
||||||
|
|
||||||
|
export type GetInvoicesCountQuery = {
|
||||||
|
__typename?: 'Query';
|
||||||
|
currentUser: { __typename?: 'UserType'; invoiceCount: number } | null;
|
||||||
|
};
|
||||||
|
|
||||||
export type InvoicesQueryVariables = Exact<{
|
export type InvoicesQueryVariables = Exact<{
|
||||||
take: Scalars['Int']['input'];
|
take: Scalars['Int']['input'];
|
||||||
skip: Scalars['Int']['input'];
|
skip: Scalars['Int']['input'];
|
||||||
@@ -693,6 +700,11 @@ export type Queries =
|
|||||||
variables: GetWorkspacesQueryVariables;
|
variables: GetWorkspacesQueryVariables;
|
||||||
response: GetWorkspacesQuery;
|
response: GetWorkspacesQuery;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
name: 'getInvoicesCountQuery';
|
||||||
|
variables: GetInvoicesCountQueryVariables;
|
||||||
|
response: GetInvoicesCountQuery;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
name: 'invoicesQuery';
|
name: 'invoicesQuery';
|
||||||
variables: InvoicesQueryVariables;
|
variables: InvoicesQueryVariables;
|
||||||
|
|||||||
Reference in New Issue
Block a user