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
@@ -16,10 +16,11 @@ import { CustomersService } from './customers.service';
describe('CustomersService.getOrders — items carry their product', () => {
function setup() {
const findMany = jest.fn().mockResolvedValue([]);
const prisma = { order: { findMany } };
const count = jest.fn().mockResolvedValue(0);
const prisma = { order: { findMany, count } };
// getOrders touches neither the accounting sync nor anything on it.
const service = new CustomersService(prisma as never, {} as never);
return { service, findMany };
return { service, findMany, count };
}
it('includes the variant, its product and the first image', async () => {
@@ -39,6 +40,22 @@ describe('CustomersService.getOrders — items carry their product', () => {
});
});
it('includes status events oldest-first for the detail timeline', async () => {
const { service, findMany } = setup();
await service.getOrders('c1');
expect(findMany.mock.calls[0][0].include.statusEvents).toMatchObject({
orderBy: { createdAt: 'asc' },
select: {
id: true,
fromStatus: true,
toStatus: true,
note: true,
createdAt: true,
},
});
});
it('is scoped to the caller and newest first', async () => {
// Guards the two properties that were already here: one customer must never
// receive another's orders, and the list is reverse-chronological.
@@ -59,4 +76,34 @@ describe('CustomersService.getOrders — items carry their product', () => {
const product = findMany.mock.calls[0][0].include.items.include.productVariant.select.product;
expect(product.where).toBeUndefined();
});
it('pages with skip/take and returns count', async () => {
const { service, findMany, count } = setup();
findMany.mockResolvedValue([{ id: 'o1' }]);
count.mockResolvedValue(41);
await expect(service.getOrders('c1', { page: 2, limit: 20 })).resolves.toEqual({
result: [{ id: 'o1' }],
count: 41,
page: 2,
limit: 20,
});
expect(findMany.mock.calls[0][0]).toMatchObject({ skip: 20, take: 20 });
});
it('filters by status and orderNumber on the server', async () => {
const { service, findMany } = setup();
await service.getOrders('c1', { status: 'PLACED', q: '12' });
expect(findMany.mock.calls[0][0].where).toEqual({
customerId: 'c1',
status: 'PLACED',
orderNumber: { contains: '12', mode: 'insensitive' },
});
});
it('ignores an unknown status instead of throwing', async () => {
const { service, findMany } = setup();
await service.getOrders('c1', { status: 'NOT_A_STATUS' });
expect(findMany.mock.calls[0][0].where).toEqual({ customerId: 'c1' });
});
});