Skip to content

Commit 97bc094

Browse files
authored
Merge pull request #474 from Merit-Systems/master
[Release] payouts fix
2 parents 54583f2 + 9accc4a commit 97bc094

19 files changed

Lines changed: 987 additions & 510 deletions

File tree

‎packages/app/control/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@radix-ui/react-accordion": "^1.2.12",
5050
"@radix-ui/react-alert-dialog": "^1.1.15",
5151
"@radix-ui/react-avatar": "^1.1.10",
52+
"@radix-ui/react-collapsible": "^1.1.12",
5253
"@radix-ui/react-dialog": "^1.1.14",
5354
"@radix-ui/react-dropdown-menu": "^2.1.15",
5455
"@radix-ui/react-label": "^2.1.7",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
Warnings:
3+
4+
- Changed the type of `status` on the `payouts` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
5+
6+
*/
7+
-- CreateEnum
8+
CREATE TYPE "public"."EnumPayoutStatus" AS ENUM ('PENDING', 'STARTED', 'COMPLETED');
9+
10+
-- AlterTable
11+
ALTER TABLE "public"."payouts" ADD COLUMN "payoutBatchId" UUID;
12+
13+
ALTER TABLE "public"."payouts"
14+
ALTER COLUMN "status" TYPE "public"."EnumPayoutStatus"
15+
USING CASE
16+
WHEN lower("status") = 'completed' THEN 'COMPLETED'::"public"."EnumPayoutStatus"
17+
WHEN lower("status") = 'pending' THEN 'PENDING'::"public"."EnumPayoutStatus"
18+
ELSE upper("status")::"public"."EnumPayoutStatus"
19+
END;
20+
21+
ALTER TABLE "public"."payouts" ALTER COLUMN "status" SET NOT NULL;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
Warnings:
3+
4+
- Changed the type of `type` on the `payouts` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required.
5+
6+
*/
7+
-- CreateEnum
8+
CREATE TYPE "public"."EnumPayoutType" AS ENUM ('MARKUP', 'REFERRAL');
9+
10+
-- AlterTable
11+
ALTER TABLE "public"."payouts"
12+
ALTER COLUMN "type" TYPE "public"."EnumPayoutType"
13+
USING CASE
14+
WHEN lower("type") = 'markup' THEN 'MARKUP'::"public"."EnumPayoutType"
15+
WHEN lower("type") = 'referral' THEN 'REFERRAL'::"public"."EnumPayoutType"
16+
ELSE upper("type")::"public"."EnumPayoutType"
17+
END;
18+
19+
ALTER TABLE "public"."payouts" ALTER COLUMN "type" SET NOT NULL;

‎packages/app/control/prisma/schema.prisma‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,23 @@ model Payment {
219219
@@map("payments")
220220
}
221221

222+
enum EnumPayoutStatus {
223+
PENDING
224+
STARTED
225+
COMPLETED
226+
}
227+
228+
enum EnumPayoutType {
229+
MARKUP
230+
REFERRAL
231+
}
232+
222233
model Payout {
223234
id String @id @default(uuid()) @db.Uuid
224235
amount Decimal @db.Decimal(65, 14)
225-
status String
226-
type String // "markup" or "referral"
236+
status EnumPayoutStatus
237+
payoutBatchId String? @db.Uuid
238+
type EnumPayoutType
227239
createdAt DateTime @default(now()) @db.Timestamptz(6)
228240
updatedAt DateTime @updatedAt @db.Timestamptz(6)
229241
description String?

‎packages/app/control/src/app/(app)/admin/_components/completedPayouts.tsx‎

Lines changed: 0 additions & 124 deletions
This file was deleted.

‎packages/app/control/src/app/(app)/admin/_components/index.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
export { PendingPayoutsTable } from './pendingPayouts';
2-
export { CompletedPayoutsTable } from './completedPayouts';
1+
export { CompletedPayoutsTable } from './payouts/CompletedPayouts';
2+
export { PendingPayoutsTable } from './payouts/PendingPayouts';
33
export { UserLink } from './UserLink';
44
export { AppLink } from './AppLink';
55
export { UserAppsOverview } from './overview/UserApps';
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { api } from '@/trpc/client';
5+
import {
6+
CollapsibleTable,
7+
type CollapsibleTableColumn,
8+
} from '@/components/collapsible-table/CollapsibleTable';
9+
10+
type Props = {
11+
pageSize?: number;
12+
};
13+
14+
export function CompletedPayoutsTable({ pageSize = 10 }: Props) {
15+
const [page, setPage] = useState(0);
16+
17+
const { data, isLoading, isFetching, refetch } =
18+
api.admin.payouts.list.completed.useQuery({
19+
cursor: page,
20+
page_size: pageSize,
21+
});
22+
23+
type Row = NonNullable<typeof data>['items'][number];
24+
type InnerRow = Row['payouts'][number];
25+
26+
const columns: CollapsibleTableColumn<Row>[] = [
27+
{
28+
id: 'user',
29+
header: 'User',
30+
cell: row => (
31+
<div className="flex flex-col">
32+
<span>{row.user.name || '—'}</span>
33+
<span className="text-muted-foreground">{row.user.email}</span>
34+
</div>
35+
),
36+
},
37+
{
38+
id: 'total',
39+
header: 'Total Completed',
40+
className: 'text-right',
41+
cell: row => <span>${row.totalOutstanding.toFixed(2)}</span>,
42+
},
43+
];
44+
45+
const innerColumns: CollapsibleTableColumn<InnerRow>[] = [
46+
{
47+
id: 'payout-id',
48+
header: 'Payout ID',
49+
cell: r => <span className="font-mono text-xs">{r.id}</span>,
50+
},
51+
{
52+
id: 'echo-app',
53+
header: 'Echo App',
54+
cell: r => <span className="font-mono text-xs">{r.echoApp.name}</span>,
55+
},
56+
{
57+
id: 'amount',
58+
header: 'Amount',
59+
className: 'text-right',
60+
cell: r => <span>${r.amount.toFixed(2)}</span>,
61+
},
62+
{
63+
id: 'github-type',
64+
header: 'GitHub Type',
65+
cell: r => (
66+
<span className="capitalize">
67+
{r.recipientGithubLink.githubType.toLowerCase()}
68+
</span>
69+
),
70+
},
71+
{
72+
id: 'github-id',
73+
header: 'GitHub ID',
74+
cell: r => (
75+
<span className="font-mono text-xs">
76+
{r.recipientGithubLink.githubId}
77+
</span>
78+
),
79+
},
80+
{
81+
id: 'github-url',
82+
header: 'GitHub URL',
83+
cell: r =>
84+
r.recipientGithubLink.githubUrl ? (
85+
<a
86+
href={r.recipientGithubLink.githubUrl}
87+
target="_blank"
88+
rel="noreferrer"
89+
className="text-primary underline"
90+
>
91+
Link
92+
</a>
93+
) : (
94+
<span>—</span>
95+
),
96+
},
97+
];
98+
99+
return (
100+
<div className="space-y-4">
101+
<div className="flex items-center justify-between">
102+
<h2 className="text-xl font-semibold">Completed Payouts</h2>
103+
<div className="flex items-center gap-3">
104+
{isFetching && (
105+
<span className="text-sm text-muted-foreground">Refreshing…</span>
106+
)}
107+
<button
108+
className="px-3 py-1 border rounded disabled:opacity-50"
109+
onClick={() => refetch()}
110+
disabled={isFetching}
111+
>
112+
Refresh
113+
</button>
114+
</div>
115+
</div>
116+
117+
<CollapsibleTable<Row, InnerRow>
118+
title="Completed Payouts by User"
119+
columns={columns}
120+
items={(data?.items ?? []).map(group => ({
121+
id: group.user.id,
122+
row: group,
123+
innerTable: { columns: innerColumns, rows: group.payouts },
124+
}))}
125+
getRowId={row => row.user.id}
126+
emptyState={isLoading ? 'Loading…' : 'No completed payouts.'}
127+
/>
128+
129+
<div className="flex items-center justify-between">
130+
<button
131+
className="px-3 py-2 border rounded disabled:opacity-50"
132+
onClick={() => setPage(p => Math.max(0, p - 1))}
133+
disabled={page === 0}
134+
>
135+
Previous
136+
</button>
137+
<div className="text-sm">
138+
Page {page + 1}
139+
{data?.has_next ? ' (more available)' : ''}
140+
</div>
141+
<button
142+
className="px-3 py-2 border rounded disabled:opacity-50"
143+
onClick={() => setPage(p => (data?.has_next ? p + 1 : p))}
144+
disabled={!data?.has_next}
145+
>
146+
Next
147+
</button>
148+
</div>
149+
</div>
150+
);
151+
}

0 commit comments

Comments
 (0)