feat: paginate customer orders and polish the dashboard hero

Add paged orders API and infinite scroll, short order numbers, and align the guest invite card with the logged-in hero shell.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
s.esmaeili
2026-10-17 13:11:42 +03:30
co-authored by Cursor
parent f550172567
commit afb068c643
89 changed files with 4518 additions and 1325 deletions
@@ -367,21 +367,26 @@ export class AccountingSyncService {
select: { accountingGoldBalance: true, accountingRialBalance: true, accountingBalanceAt: true },
});
const stale = !c?.accountingBalanceAt || Date.now() - c.accountingBalanceAt.getTime() > BALANCE_FRESH_MS;
if (stale) {
try {
const [b] = await a.provider.getBalances(
{ ...a.settings, timeoutMs: Math.min(a.settings.timeoutMs, BALANCE_ON_DEMAND_TIMEOUT_MS) },
[link.externalCode],
// Already have a stored figure: hand it back immediately and refresh in the
// background. Waiting on Tahesab/Zarrin here blocked GET /customer/me for up
// to BALANCE_ON_DEMAND_TIMEOUT_MS on almost every dashboard open (freshness
// is only a few seconds), so the app painted the guest invite while the
// token was still valid.
if (c?.accountingBalanceAt && stale) {
void this.refreshStoredBalance(a, customerId, link.externalCode).catch((err) => {
this.logger.warn(
`Background balance refresh for ${customerId} failed: ${(err as Error).message}`,
);
if (b) {
c = await this.prisma.customer.update({
where: { id: customerId },
data: { accountingGoldBalance: b.goldGrams, accountingRialBalance: b.rial, accountingBalanceAt: new Date() },
select: { accountingGoldBalance: true, accountingRialBalance: true, accountingBalanceAt: true },
});
}
});
} else if (stale) {
// First read — nothing to show yet, so wait (with the short timeout).
try {
c = (await this.refreshStoredBalance(a, customerId, link.externalCode)) ?? c;
} catch (err) {
this.logger.warn(`On-demand balance for ${customerId} failed, serving the stored one: ${(err as Error).message}`);
this.logger.warn(
`On-demand balance for ${customerId} failed, serving the stored one: ${(err as Error).message}`,
);
}
}
if (!c?.accountingBalanceAt) return null;
@@ -393,6 +398,32 @@ export class AccountingSyncService {
};
}
/** Pull one linked customer's balance from the provider and persist it. */
private async refreshStoredBalance(
a: ActiveProvider,
customerId: string,
externalCode: string,
) {
const [b] = await a.provider.getBalances(
{ ...a.settings, timeoutMs: Math.min(a.settings.timeoutMs, BALANCE_ON_DEMAND_TIMEOUT_MS) },
[externalCode],
);
if (!b) return null;
return this.prisma.customer.update({
where: { id: customerId },
data: {
accountingGoldBalance: b.goldGrams,
accountingRialBalance: b.rial,
accountingBalanceAt: new Date(),
},
select: {
accountingGoldBalance: true,
accountingRialBalance: true,
accountingBalanceAt: true,
},
});
}
// ════════════════════════════════════════════════════════════════════
// Real-time: change poll + live reads