1
1
'use client' ;
2
-
2
+ import {
3
+ AlertDialog ,
4
+ AlertDialogContent ,
5
+ AlertDialogDescription ,
6
+ AlertDialogFooter ,
7
+ AlertDialogHeader ,
8
+ AlertDialogTitle
9
+ } from '@/src/components/shadcn-ui/alert-dialog' ;
3
10
import {
4
11
Card ,
5
12
CardContent ,
@@ -8,29 +15,14 @@ import {
8
15
CardHeader ,
9
16
CardTitle
10
17
} from '@/src/components/shadcn-ui/card' ;
11
- import {
12
- Dialog ,
13
- DialogContent ,
14
- DialogDescription ,
15
- DialogHeader ,
16
- DialogTitle
17
- } from '@/src/components/shadcn-ui/dialog' ;
18
- import {
19
- EmbeddedCheckoutProvider ,
20
- EmbeddedCheckout
21
- } from '@stripe/react-stripe-js' ;
22
- import {
23
- loadStripe ,
24
- type StripeEmbeddedCheckoutOptions
25
- } from '@stripe/stripe-js' ;
26
18
import { Tabs , TabsList , TabsTrigger } from '@/src/components/shadcn-ui/tabs' ;
27
19
import { Button } from '@/src/components/shadcn-ui/button' ;
20
+ import { Check , SpinnerGap } from '@phosphor-icons/react' ;
28
21
import { useOrgShortcode } from '@/src/hooks/use-params' ;
29
- import { useCallback , useRef , useState } from 'react' ;
30
- import { Check } from '@phosphor-icons/react' ;
22
+ import { useEffect , useState } from 'react' ;
31
23
import { platform } from '@/src/lib/trpc' ;
32
24
import { cn } from '@/src/lib/utils' ;
33
- import { env } from '@/src/env ' ;
25
+ import { ms } from '@u22n/utils/ms ' ;
34
26
35
27
type PricingSwitchProps = {
36
28
onSwitch : ( value : string ) => void ;
@@ -302,60 +294,104 @@ type StripeModalProps = {
302
294
} ;
303
295
304
296
function StripeModal ( { open, isYearly, plan, setOpen } : StripeModalProps ) {
305
- if ( ! env . NEXT_PUBLIC_BILLING_STRIPE_PUBLISHABLE_KEY ) {
306
- throw new Error (
307
- 'Stripe publishable key not set, cannot render Stripe modal'
308
- ) ;
309
- }
310
297
const orgShortcode = useOrgShortcode ( ) ;
311
298
const utils = platform . useUtils ( ) ;
312
- const stripePromise = useRef (
313
- loadStripe ( env . NEXT_PUBLIC_BILLING_STRIPE_PUBLISHABLE_KEY )
314
- ) ;
315
299
316
- const fetchClientSecret = useCallback (
317
- ( ) =>
318
- utils . org . setup . billing . createCheckoutSession
319
- . fetch ( {
320
- orgShortcode,
321
- plan,
322
- period : isYearly ? 'yearly' : 'monthly'
323
- } )
324
- . then ( ( res ) => res . checkoutSessionClientSecret ) ,
325
- [
326
- isYearly ,
300
+ const {
301
+ data : paymentLink ,
302
+ isLoading : paymentLinkLoading ,
303
+ error : paymentLinkError
304
+ } = platform . org . setup . billing . getOrgSubscriptionPaymentLink . useQuery (
305
+ {
327
306
orgShortcode,
328
307
plan,
329
- utils . org . setup . billing . createCheckoutSession
330
- ]
308
+ period : isYearly ? 'yearly' : 'monthly'
309
+ } ,
310
+ {
311
+ enabled : open
312
+ }
331
313
) ;
332
- const onComplete = useCallback ( ( ) => {
333
- setOpen ( false ) ;
334
- setTimeout ( ( ) => void utils . org . setup . billing . invalidate ( ) , 1000 ) ;
335
- } , [ setOpen , utils . org . setup . billing ] ) ;
336
314
337
- const options = {
338
- fetchClientSecret,
339
- onComplete
340
- } satisfies StripeEmbeddedCheckoutOptions ;
315
+ const { data : overview } =
316
+ platform . org . setup . billing . getOrgBillingOverview . useQuery (
317
+ { orgShortcode } ,
318
+ {
319
+ enabled : open && paymentLink && ! paymentLinkLoading ,
320
+ refetchOnWindowFocus : true ,
321
+ refetchInterval : ms ( '15 seconds' )
322
+ }
323
+ ) ;
324
+
325
+ // Open payment link once payment link is generated
326
+ useEffect ( ( ) => {
327
+ if ( ! open || paymentLinkLoading || ! paymentLink ) return ;
328
+ window . open ( paymentLink . subLink , '_blank' ) ;
329
+ } , [ open , paymentLink , paymentLinkLoading ] ) ;
330
+
331
+ // handle payment info update
332
+ useEffect ( ( ) => {
333
+ if ( overview ?. currentPlan === 'pro' ) {
334
+ void utils . org . setup . billing . getOrgBillingOverview . invalidate ( {
335
+ orgShortcode
336
+ } ) ;
337
+ setOpen ( false ) ;
338
+ }
339
+ } , [
340
+ orgShortcode ,
341
+ overview ,
342
+ setOpen ,
343
+ utils . org . setup . billing . getOrgBillingOverview
344
+ ] ) ;
341
345
342
346
return (
343
- < Dialog
344
- open = { open }
345
- onOpenChange = { setOpen } >
346
- < DialogContent className = "w-[90vw] max-w-screen-lg p-0" >
347
- < DialogHeader className = "sr-only" >
348
- < DialogTitle > Stripe Checkout</ DialogTitle >
349
- < DialogDescription > Checkout with Stripe</ DialogDescription >
350
- </ DialogHeader >
351
- { open && (
352
- < EmbeddedCheckoutProvider
353
- options = { options }
354
- stripe = { stripePromise . current } >
355
- < EmbeddedCheckout className = "*:rounded-lg" />
356
- </ EmbeddedCheckoutProvider >
357
- ) }
358
- </ DialogContent >
359
- </ Dialog >
347
+ < AlertDialog open = { open } >
348
+ < AlertDialogContent >
349
+ < AlertDialogHeader >
350
+ < AlertDialogTitle > Upgrade to Pro</ AlertDialogTitle >
351
+ < AlertDialogDescription className = "space-y-2 p-2" >
352
+ { paymentLinkLoading ? (
353
+ < span className = "flex items-center gap-2" >
354
+ < SpinnerGap className = "size-4 animate-spin" />
355
+ Generating Payment Link
356
+ </ span >
357
+ ) : paymentLink ? (
358
+ 'Waiting for Payment (This may take a few seconds)'
359
+ ) : (
360
+ < span className = "text-red-9" > { paymentLinkError ?. message } </ span >
361
+ ) }
362
+ </ AlertDialogDescription >
363
+ </ AlertDialogHeader >
364
+ < div className = "flex flex-col gap-2 p-2" >
365
+ < span >
366
+ We are waiting for your payment to be processed. It may take a few
367
+ seconds for the payment to reflect in app.
368
+ </ span >
369
+ { paymentLink && (
370
+ < span >
371
+ If a new tab was not opened,{ ' ' }
372
+ < a
373
+ target = "_blank"
374
+ href = { paymentLink . subLink }
375
+ className = "underline" >
376
+ open it manually.
377
+ </ a >
378
+ </ span >
379
+ ) }
380
+ < span >
381
+ { `If your payment hasn't been detected correctly, please try refreshing
382
+ the page.` }
383
+ </ span >
384
+ < span > If the issue persists, please contact support.</ span >
385
+ </ div >
386
+
387
+ < AlertDialogFooter >
388
+ < Button
389
+ onClick = { ( ) => setOpen ( false ) }
390
+ className = "w-full" >
391
+ Close
392
+ </ Button >
393
+ </ AlertDialogFooter >
394
+ </ AlertDialogContent >
395
+ </ AlertDialog >
360
396
) ;
361
397
}
0 commit comments