Skip to content

Commit 18a91b4

Browse files
committed
Add confirmation dialog to auto top-up if it would place a charge immediately
1 parent b625411 commit 18a91b4

File tree

5 files changed

+120
-53
lines changed

5 files changed

+120
-53
lines changed

web/src/app/payment-success/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ function PaymentSuccessContent() {
2828
} = useAutoTopup()
2929

3030
const enableMinimumAutoTopup = async () => {
31-
const { MIN_THRESHOLD_CREDITS, DEFAULT_TOPUP_DOLLARS } =
31+
const { DEFAULT_THRESHOLD_CREDITS, DEFAULT_TOPUP_DOLLARS } =
3232
AUTO_TOPUP_CONSTANTS
3333

3434
await handleToggleAutoTopup(true)
35-
handleThresholdChange(MIN_THRESHOLD_CREDITS)
35+
handleThresholdChange(DEFAULT_THRESHOLD_CREDITS)
3636
handleTopUpAmountChange(DEFAULT_TOPUP_DOLLARS)
3737
}
3838

web/src/components/auto-topup/AutoTopupSettings.tsx

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AutoTopupSettingsForm } from './AutoTopupSettingsForm'
22
import { AutoTopupSwitch } from './AutoTopupSwitch'
33
import { BaseAutoTopupSettings } from './BaseAutoTopupSettings'
44

5+
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog'
56
import { useAutoTopup } from '@/hooks/use-auto-topup'
67

78
export function AutoTopupSettings() {
@@ -15,31 +16,45 @@ export function AutoTopupSettings() {
1516
handleToggleAutoTopup,
1617
handleThresholdChange,
1718
handleTopUpAmountChange,
19+
showConfirmDialog,
20+
confirmDialogBalance,
21+
confirmEnableAutoTopup,
22+
cancelEnableAutoTopup,
1823
} = useAutoTopup()
1924

2025
return (
21-
<BaseAutoTopupSettings
22-
isLoading={isLoadingProfile}
23-
switchComponent={
24-
<AutoTopupSwitch
25-
isEnabled={isEnabled}
26-
onToggle={handleToggleAutoTopup}
27-
isPending={isPending}
28-
autoTopupBlockedReason={
29-
userProfile?.auto_topup_blocked_reason ?? null
30-
}
31-
/>
32-
}
33-
formComponent={
34-
<AutoTopupSettingsForm
35-
isEnabled={isEnabled}
36-
threshold={threshold}
37-
topUpAmountDollars={topUpAmountDollars}
38-
onThresholdChange={handleThresholdChange}
39-
onTopUpAmountChange={handleTopUpAmountChange}
40-
isPending={isPending}
41-
/>
42-
}
43-
/>
26+
<>
27+
<BaseAutoTopupSettings
28+
isLoading={isLoadingProfile}
29+
switchComponent={
30+
<AutoTopupSwitch
31+
isEnabled={isEnabled}
32+
onToggle={handleToggleAutoTopup}
33+
isPending={isPending}
34+
autoTopupBlockedReason={
35+
userProfile?.auto_topup_blocked_reason ?? null
36+
}
37+
/>
38+
}
39+
formComponent={
40+
<AutoTopupSettingsForm
41+
isEnabled={isEnabled}
42+
threshold={threshold}
43+
topUpAmountDollars={topUpAmountDollars}
44+
onThresholdChange={handleThresholdChange}
45+
onTopUpAmountChange={handleTopUpAmountChange}
46+
isPending={isPending}
47+
/>
48+
}
49+
/>
50+
<ConfirmationDialog
51+
isOpen={showConfirmDialog}
52+
onClose={cancelEnableAutoTopup}
53+
onConfirm={confirmEnableAutoTopup}
54+
title="Enable Auto Top-up?"
55+
description={`Your current balance (${(confirmDialogBalance ?? 0).toLocaleString()} credits) is below your threshold (${threshold.toLocaleString()} credits). Enabling auto top-up will charge your payment method ~$${topUpAmountDollars.toFixed(2)} on your next usage.`}
56+
confirmText="Enable Anyway"
57+
/>
58+
</>
4459
)
4560
}

web/src/components/auto-topup/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const AUTO_TOPUP_CONSTANTS = {
22
MIN_THRESHOLD_CREDITS: 150,
3-
MAX_THRESHOLD_CREDITS: 1000,
3+
DEFAULT_THRESHOLD_CREDITS: 500,
4+
MAX_THRESHOLD_CREDITS: 5000,
45
MIN_TOPUP_DOLLARS: 10.0,
56
DEFAULT_TOPUP_DOLLARS: 20.0,
67
MAX_TOPUP_DOLLARS: 100.0,

web/src/components/auto-topup/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export interface AutoTopupState {
1010
handleToggleAutoTopup: (checked: boolean) => void
1111
handleThresholdChange: (value: number) => void
1212
handleTopUpAmountChange: (value: number) => void
13+
showConfirmDialog: boolean
14+
confirmDialogBalance: number | null
15+
confirmEnableAutoTopup: () => void
16+
cancelEnableAutoTopup: () => void
1317
}
1418

1519
export interface AutoTopupSwitchProps {

web/src/hooks/use-auto-topup.ts

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,16 @@ import { AUTO_TOPUP_CONSTANTS } from '@/components/auto-topup/constants'
1010
import { toast } from '@/components/ui/use-toast'
1111
import { clamp } from '@/lib/utils'
1212

13+
async function fetchCurrentBalance(): Promise<number> {
14+
const response = await fetch('/api/user/usage')
15+
if (!response.ok) throw new Error('Failed to fetch balance')
16+
const data = await response.json()
17+
return data.balance?.totalRemaining ?? 0
18+
}
19+
1320
const {
1421
MIN_THRESHOLD_CREDITS,
22+
DEFAULT_THRESHOLD_CREDITS,
1523
MAX_THRESHOLD_CREDITS,
1624
MIN_TOPUP_DOLLARS,
1725
DEFAULT_TOPUP_DOLLARS,
@@ -22,14 +30,19 @@ const {
2230
export function useAutoTopup(): AutoTopupState {
2331
const queryClient = useQueryClient()
2432
const [isEnabled, setIsEnabled] = useState(false)
25-
const [threshold, setThreshold] = useState<number>(MIN_THRESHOLD_CREDITS)
33+
const [threshold, setThreshold] = useState<number>(DEFAULT_THRESHOLD_CREDITS)
2634
const [topUpAmountDollars, setTopUpAmountDollars] =
2735
useState<number>(DEFAULT_TOPUP_DOLLARS)
2836
const isInitialLoad = useRef(true)
2937
const pendingSettings = useRef<{
3038
threshold: number
3139
topUpAmountDollars: number
3240
} | null>(null)
41+
const [isCheckingBalance, setIsCheckingBalance] = useState(false)
42+
const [showConfirmDialog, setShowConfirmDialog] = useState(false)
43+
const [confirmDialogBalance, setConfirmDialogBalance] = useState<
44+
number | null
45+
>(null)
3346

3447
const { data: userProfile, isLoading: isLoadingProfile } = useQuery<
3548
UserProfile & { initialTopUpDollars?: number }
@@ -40,7 +53,7 @@ export function useAutoTopup(): AutoTopupState {
4053
if (!response.ok) throw new Error('Failed to fetch profile')
4154
const data = await response.json()
4255
const thresholdCredits =
43-
data.auto_topup_threshold ?? MIN_THRESHOLD_CREDITS
56+
data.auto_topup_threshold ?? DEFAULT_THRESHOLD_CREDITS
4457
const topUpAmount = data.auto_topup_amount ?? DEFAULT_TOPUP_DOLLARS * 100
4558
const topUpDollars = topUpAmount / 100
4659

@@ -75,7 +88,7 @@ export function useAutoTopup(): AutoTopupState {
7588
useEffect(() => {
7689
if (userProfile) {
7790
setIsEnabled(userProfile.auto_topup_enabled ?? false)
78-
setThreshold(userProfile.auto_topup_threshold ?? MIN_THRESHOLD_CREDITS)
91+
setThreshold(userProfile.auto_topup_threshold ?? DEFAULT_THRESHOLD_CREDITS)
7992
setTopUpAmountDollars(
8093
userProfile.initialTopUpDollars ?? DEFAULT_TOPUP_DOLLARS,
8194
)
@@ -176,7 +189,7 @@ export function useAutoTopup(): AutoTopupState {
176189
const savedThreshold =
177190
data?.auto_topup_threshold ??
178191
variables.auto_topup_threshold ??
179-
MIN_THRESHOLD_CREDITS
192+
DEFAULT_THRESHOLD_CREDITS
180193
const savedAmountCents =
181194
data?.auto_topup_amount ??
182195
(variables.auto_topup_amount
@@ -195,7 +208,7 @@ export function useAutoTopup(): AutoTopupState {
195208
}
196209

197210
setIsEnabled(updatedData.auto_topup_enabled ?? false)
198-
setThreshold(updatedData.auto_topup_threshold ?? MIN_THRESHOLD_CREDITS)
211+
setThreshold(updatedData.auto_topup_threshold ?? DEFAULT_THRESHOLD_CREDITS)
199212
setTopUpAmountDollars(
200213
updatedData.initialTopUpDollars ?? DEFAULT_TOPUP_DOLLARS,
201214
)
@@ -213,7 +226,7 @@ export function useAutoTopup(): AutoTopupState {
213226
})
214227
if (userProfile) {
215228
setIsEnabled(userProfile.auto_topup_enabled ?? false)
216-
setThreshold(userProfile.auto_topup_threshold ?? MIN_THRESHOLD_CREDITS)
229+
setThreshold(userProfile.auto_topup_threshold ?? DEFAULT_THRESHOLD_CREDITS)
217230
setTopUpAmountDollars(
218231
userProfile.initialTopUpDollars ?? DEFAULT_TOPUP_DOLLARS,
219232
)
@@ -289,6 +302,39 @@ export function useAutoTopup(): AutoTopupState {
289302
}
290303
}
291304

305+
const enableAutoTopup = useCallback(() => {
306+
setIsEnabled(true)
307+
autoTopupMutation.mutate(
308+
{
309+
auto_topup_enabled: true,
310+
auto_topup_threshold: threshold,
311+
auto_topup_amount: topUpAmountDollars,
312+
},
313+
{
314+
onSuccess: () => {
315+
toast({
316+
title: 'Auto Top-up enabled!',
317+
description: `We'll automatically add credits when your balance falls below ${threshold.toLocaleString()} credits.`,
318+
})
319+
},
320+
onError: () => {
321+
setIsEnabled(false)
322+
},
323+
},
324+
)
325+
}, [autoTopupMutation, threshold, topUpAmountDollars])
326+
327+
const confirmEnableAutoTopup = useCallback(() => {
328+
setShowConfirmDialog(false)
329+
setConfirmDialogBalance(null)
330+
enableAutoTopup()
331+
}, [enableAutoTopup])
332+
333+
const cancelEnableAutoTopup = useCallback(() => {
334+
setShowConfirmDialog(false)
335+
setConfirmDialogBalance(null)
336+
}, [])
337+
292338
const handleToggleAutoTopup = (checked: boolean) => {
293339
if (checked && userProfile?.auto_topup_blocked_reason) {
294340
toast({
@@ -299,7 +345,6 @@ export function useAutoTopup(): AutoTopupState {
299345
return
300346
}
301347

302-
setIsEnabled(checked)
303348
debouncedSaveSettings.cancel()
304349
pendingSettings.current = null
305350

@@ -316,29 +361,27 @@ export function useAutoTopup(): AutoTopupState {
316361
'Cannot enable auto top-up with current values. Please ensure they are within limits.',
317362
variant: 'destructive',
318363
})
319-
setIsEnabled(false)
320364
return
321365
}
322366

323-
autoTopupMutation.mutate(
324-
{
325-
auto_topup_enabled: true,
326-
auto_topup_threshold: threshold,
327-
auto_topup_amount: topUpAmountDollars,
328-
},
329-
{
330-
onSuccess: () => {
331-
toast({
332-
title: 'Auto Top-up enabled!',
333-
description: `We'll automatically add credits when your balance falls below ${threshold.toLocaleString()} credits.`,
334-
})
335-
},
336-
onError: () => {
337-
setIsEnabled(false)
338-
},
339-
},
340-
)
367+
setIsCheckingBalance(true)
368+
fetchCurrentBalance()
369+
.then((balance) => {
370+
if (balance < threshold) {
371+
setConfirmDialogBalance(balance)
372+
setShowConfirmDialog(true)
373+
} else {
374+
enableAutoTopup()
375+
}
376+
})
377+
.catch(() => {
378+
enableAutoTopup()
379+
})
380+
.finally(() => {
381+
setIsCheckingBalance(false)
382+
})
341383
} else {
384+
setIsEnabled(false)
342385
autoTopupMutation.mutate(
343386
{
344387
auto_topup_enabled: false,
@@ -362,10 +405,14 @@ export function useAutoTopup(): AutoTopupState {
362405
threshold,
363406
topUpAmountDollars,
364407
isLoadingProfile,
365-
isPending: autoTopupMutation.isPending,
408+
isPending: autoTopupMutation.isPending || isCheckingBalance,
366409
userProfile: userProfile ?? null,
367410
handleToggleAutoTopup,
368411
handleThresholdChange,
369412
handleTopUpAmountChange,
413+
showConfirmDialog,
414+
confirmDialogBalance,
415+
confirmEnableAutoTopup,
416+
cancelEnableAutoTopup,
370417
}
371418
}

0 commit comments

Comments
 (0)