retry-skipped-form.tsx96 lines · main
1'use client';
2
3import { useRouter } from 'next/navigation';
4import { useState, useTransition } from 'react';
5
6import { StepUpPrompt } from '@/components/step-up-prompt';
7
8interface Props {
9 apiOrigin: string;
10}
11
12export function RetrySkippedForm({ apiOrigin }: Props) {
13 const router = useRouter();
14 const [sinceDays, setSinceDays] = useState('7');
15 const [busy, setBusy] = useState(false);
16 const [error, setError] = useState<string | null>(null);
17 const [pending, setPending] = useState<number | null>(null);
18 const [, startTransition] = useTransition();
19
20 async function run(days: number) {
21 setBusy(true);
22 setError(null);
23 try {
24 const res = await fetch(`${apiOrigin}/v1/admin/usage-events/retry-skipped`, {
25 method: 'POST',
26 credentials: 'include',
27 headers: { 'content-type': 'application/json' },
28 body: JSON.stringify({ sinceDays: days }),
29 });
30 if (res.status === 403) {
31 const body = (await res.json().catch(() => null)) as { code?: string } | null;
32 if (body?.code === 'step_up_required') {
33 setPending(days);
34 return;
35 }
36 }
37 if (!res.ok) {
38 const body = await res.text().catch(() => '');
39 throw new Error(body || `retry failed: ${res.status}`);
40 }
41 startTransition(() => router.refresh());
42 } catch (err) {
43 setError(err instanceof Error ? err.message : 'retry failed');
44 } finally {
45 setBusy(false);
46 }
47 }
48
49 function onSubmit(e: React.FormEvent) {
50 e.preventDefault();
51 void run(Number(sinceDays));
52 }
53
54 return (
55 <>
56 <form onSubmit={onSubmit} className="flex items-center gap-2">
57 <label className="font-mono text-[10px] text-[var(--color-text-muted)]">
58 retry skipped within
59 </label>
60 <select
61 value={sinceDays}
62 onChange={(e) => setSinceDays(e.target.value)}
63 disabled={busy}
64 className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-2 py-1 font-mono text-xs text-[var(--color-text)] disabled:opacity-50"
65 >
66 <option value="1">1d</option>
67 <option value="7">7d</option>
68 <option value="30">30d</option>
69 <option value="90">90d</option>
70 </select>
71 <button
72 type="submit"
73 disabled={busy}
74 className="rounded-md border border-[var(--color-primary)] bg-[var(--color-primary-subtle)] px-3 py-1 font-mono text-xs text-[var(--color-primary)] hover:bg-[var(--color-primary)]/15 disabled:opacity-50"
75 >
76 {busy ? 'retrying…' : 'retry → pending'}
77 </button>
78 </form>
79 {error ? (
80 <p className="font-mono text-[10px] text-[var(--color-error)]">{error}</p>
81 ) : null}
82 {pending != null ? (
83 <StepUpPrompt
84 apiOrigin={apiOrigin}
85 reason="re-queueing skipped usage events for polar push requires fresh step-up auth."
86 onSuccess={async () => {
87 const d = pending;
88 setPending(null);
89 await run(d);
90 }}
91 onCancel={() => setPending(null)}
92 />
93 ) : null}
94 </>
95 );
96}