Skip to content

Commit e380fae

Browse files
committed
fix: loosen vote state schema
1 parent b3eb61b commit e380fae

File tree

3 files changed

+88
-49
lines changed

3 files changed

+88
-49
lines changed

public/docs/api/v0/openapi.json

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -421,17 +421,39 @@
421421
}
422422
},
423423
"ProposalState": {
424-
"type": "object",
425-
"required": ["status", "results", "authorName"],
426-
"properties": {
427-
"authorName": { "type": "string" },
428-
"status": { "type": "string", "enum": ["closed", "open"] },
429-
"userVote": { "$ref": "#/components/schemas/Vote" },
430-
"results": {
431-
"type": "array",
432-
"items": { "$ref": "#/components/schemas/Vote" }
424+
"oneOf": [
425+
{
426+
"type": "object",
427+
"required": ["status", "results", "authorName"],
428+
"properties": {
429+
"authorName": { "type": "string" },
430+
"status": { "type": "string", "enum": ["closed"] },
431+
"userVote": { "$ref": "#/components/schemas/Vote" },
432+
"results": {
433+
"type": "array",
434+
"items": { "$ref": "#/components/schemas/Vote" }
435+
}
436+
}
437+
},
438+
{
439+
"type": "object",
440+
"required": ["status", "authorName"],
441+
"properties": {
442+
"authorName": { "type": "string" },
443+
"status": { "type": "string", "enum": ["open"] },
444+
"userVote": { "$ref": "#/components/schemas/Vote" },
445+
"results": {
446+
"oneOf": [
447+
{
448+
"type": "array",
449+
"items": { "$ref": "#/components/schemas/Vote" }
450+
},
451+
{ "type": "null" }
452+
]
453+
}
454+
}
433455
}
434-
}
456+
]
435457
},
436458
"ProposalIndex": {
437459
"allOf": [

src/features/voting/ProposalList.tsx

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,55 @@ import { Button } from '../ui/button.tsx';
77
import { sdk, type Paginated } from '../../sdk.ts';
88
import { useClerk } from '../auth/hooks.ts';
99
import { LoadingSpinner } from '../ui/LoadingSpinner.tsx';
10+
import type { Clerk } from '../auth/clerk.ts';
1011

1112
// This component auto-loads proposals on scroll, so we hard-code a static limit
1213
const limit = 10;
1314

1415
export function ProposalList() {
15-
const clerk = useClerk();
16+
const clerkClient = useClerk();
1617
const [loading, setLoading] = useState(true);
1718
const [cursor, setCursor] = useState<Paginated['cursor']>();
1819
const [proposals, setProposals] = useState<ProposalCardProps[]>([]);
1920

20-
const load = useCallback(
21-
async (pagination: Paginated) => {
22-
if (!clerk) return;
23-
setLoading(true);
24-
const token = await clerk.session?.getToken();
21+
const load = useCallback(async (pagination: Paginated, clerk: Clerk) => {
22+
if (!clerk) return;
23+
setLoading(true);
24+
const token = await clerk.session?.getToken();
2525

26-
try {
27-
const result = await sdk.listProposals({
28-
queries: { pagination },
29-
headers: token ? { Authorization: `Bearer ${token}` } : {},
30-
});
31-
setCursor(result.cursor);
32-
setProposals((previous) => [...previous, ...result.proposals]);
33-
} finally {
34-
setLoading(false);
35-
}
36-
},
37-
[clerk],
38-
);
26+
try {
27+
const result = await sdk.listProposals({
28+
queries: { pagination },
29+
headers: token ? { Authorization: `Bearer ${token}` } : {},
30+
});
31+
setCursor(result.cursor);
32+
setProposals((previous) => [...previous, ...result.proposals]);
33+
} finally {
34+
setLoading(false);
35+
}
36+
}, []);
3937

4038
useEffect(() => {
41-
if (!clerk) return;
39+
if (!clerkClient) return;
4240
// Load initial proposals
43-
toast.promise(load({ limit }), {
41+
toast.promise(load({ limit }, clerkClient), {
4442
loading: 'Loading proposals...',
4543
success: 'Proposals loaded',
4644
error: 'Failed to load proposals',
4745
});
48-
}, [clerk, load]);
46+
}, [clerkClient, load]);
4947

5048
const onClick = useCallback(() => {
5149
if (loading) return;
5250
if (!cursor) return;
51+
if (!clerkClient) return;
5352

54-
toast.promise(load({ cursor, limit }), {
53+
toast.promise(load({ cursor, limit }, clerkClient), {
5554
loading: 'Loading more proposals...',
5655
success: 'More proposals loaded',
5756
error: 'Failed to load more proposals',
5857
});
59-
}, [loading, load, cursor]);
58+
}, [loading, load, cursor, clerkClient]);
6059

6160
return (
6261
<div>

src/sdk.ts

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,25 @@ export type DatabaseObject = {
3737
created: string;
3838
updated: string;
3939
};
40-
export type ProposalState = {
41-
authorName: string;
42-
/**
43-
* @enum closed, open
44-
*/
45-
status: 'closed' | 'open';
46-
userVote?: Vote | undefined;
47-
results: Array<Vote>;
48-
};
40+
export type ProposalState =
41+
| {
42+
authorName: string;
43+
/**
44+
* @enum closed
45+
*/
46+
status: 'closed';
47+
userVote?: Vote | undefined;
48+
results: Array<Vote>;
49+
}
50+
| {
51+
authorName: string;
52+
/**
53+
* @enum open
54+
*/
55+
status: 'open';
56+
userVote?: Vote | undefined;
57+
results?: (Array<Vote> | null) | undefined;
58+
};
4959
export type Vote = {
5060
/**
5161
* Ranking values: -2 (strong disinterest), -1 (slight disinterest), 0 (neutral), 1 (slight interest), 2 (strong interest)
@@ -143,12 +153,20 @@ const Vote: z.ZodType<Vote> = z.object({
143153
),
144154
comment: z.union([z.string(), z.null()]).optional(),
145155
});
146-
const ProposalState: z.ZodType<ProposalState> = z.object({
147-
authorName: z.string(),
148-
status: z.enum(['closed', 'open']),
149-
userVote: Vote.optional(),
150-
results: z.array(Vote),
151-
});
156+
const ProposalState: z.ZodType<ProposalState> = z.union([
157+
z.object({
158+
authorName: z.string(),
159+
status: z.literal('closed'),
160+
userVote: Vote.optional(),
161+
results: z.array(Vote),
162+
}),
163+
z.object({
164+
authorName: z.string(),
165+
status: z.literal('open'),
166+
userVote: Vote.optional(),
167+
results: z.union([z.array(Vote), z.null()]).optional(),
168+
}),
169+
]);
152170
const ProposalIndex: z.ZodType<ProposalIndex> = Paginated.and(
153171
z.object({
154172
proposals: z.array(Proposal.and(ProposalState).and(DatabaseObject)),

0 commit comments

Comments
 (0)