@@ -53,11 +53,12 @@ const schema = z.object({
53
53
type : z . enum ( [ "free" , "paid" ] ) ,
54
54
planCode : z . string ( ) . optional ( ) ,
55
55
callerPath : z . string ( ) ,
56
- reason : z . string ( ) . optional ( ) ,
56
+ reasons : z . union ( [ z . string ( ) , z . array ( z . string ( ) ) ] ) . optional ( ) ,
57
57
message : z . string ( ) . optional ( ) ,
58
58
} ) ;
59
59
60
60
export async function action ( { request, params } : ActionFunctionArgs ) {
61
+ console . log ( "Action function called" ) ;
61
62
if ( request . method . toLowerCase ( ) !== "post" ) {
62
63
return new Response ( "Method not allowed" , { status : 405 } ) ;
63
64
}
@@ -68,13 +69,22 @@ export async function action({ request, params }: ActionFunctionArgs) {
68
69
69
70
const formData = await request . formData ( ) ;
70
71
71
- // Log the form data for debugging
72
- console . log ( "Form data:" , Object . fromEntries ( formData ) ) ;
72
+ console . log ( "All form data:" , Object . fromEntries ( formData ) ) ;
73
73
74
- const form = schema . parse ( Object . fromEntries ( formData ) ) ;
74
+ // Log the entire formData
75
+ console . log ( "Form Data:" ) ;
76
+ for ( const [ key , value ] of formData . entries ( ) ) {
77
+ console . log ( `${ key } : ${ value } ` ) ;
78
+ }
79
+
80
+ const reasons = formData . getAll ( "reasons" ) ;
81
+ const message = formData . get ( "message" ) ;
75
82
76
- // Log the parsed form data
77
- console . log ( "Parsed form data:" , form ) ;
83
+ const form = schema . parse ( {
84
+ ...Object . fromEntries ( formData ) ,
85
+ reasons : reasons . length > 1 ? reasons : reasons [ 0 ] || undefined ,
86
+ message : message || undefined ,
87
+ } ) ;
78
88
79
89
const organization = await prisma . organization . findUnique ( {
80
90
where : { slug : organizationSlug } ,
@@ -132,12 +142,8 @@ export async function action({ request, params }: ActionFunctionArgs) {
132
142
throw redirectWithErrorMessage ( form . callerPath , request , upsertCustomerRes . error . message ) ;
133
143
}
134
144
135
- const formData = await request . formData ( ) ;
136
- const reasons = formData . getAll ( "reason" ) as string [ ] ;
137
- const message = formData . get ( "message" ) as string | null ;
138
-
139
145
// Only create a thread if there are reasons or a message
140
- if ( reasons . length > 0 || message ) {
146
+ if ( reasons . length > 0 || ( message && message . toString ( ) . trim ( ) !== "" ) ) {
141
147
const createThreadRes = await client . createThread ( {
142
148
customerIdentifier : {
143
149
customerId : upsertCustomerRes . data . customer . id ,
@@ -157,7 +163,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
157
163
text : "Reasons:" ,
158
164
} ) ,
159
165
uiComponent . text ( {
160
- text : reasons . join ( ", " ) ,
166
+ text : Array . isArray ( reasons ) ? reasons . join ( ", " ) : reasons ,
161
167
} ) ,
162
168
]
163
169
: [ ] ) ,
@@ -170,7 +176,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
170
176
text : "Comment:" ,
171
177
} ) ,
172
178
uiComponent . text ( {
173
- text : message ,
179
+ text : message . toString ( ) ,
174
180
} ) ,
175
181
]
176
182
: [ ] ) ,
@@ -189,7 +195,6 @@ export async function action({ request, params }: ActionFunctionArgs) {
189
195
}
190
196
}
191
197
} catch ( e ) {
192
- console . error ( "Error in free case:" , e ) ;
193
198
logger . error ( "Failed to submit to Plain the unsubscribe reason" , { error : e } ) ;
194
199
}
195
200
payload = {
@@ -210,12 +215,10 @@ export async function action({ request, params }: ActionFunctionArgs) {
210
215
break ;
211
216
}
212
217
default : {
213
- console . error ( "Invalid form type:" , form . type ) ;
214
218
throw new Error ( "Invalid form type" ) ;
215
219
}
216
220
}
217
221
218
- console . log ( "Final payload:" , payload ) ;
219
222
return setPlan ( organization , request , form . callerPath , payload ) ;
220
223
}
221
224
@@ -262,7 +265,7 @@ type PricingPlansProps = {
262
265
organizationSlug : string ;
263
266
hasPromotedPlan : boolean ;
264
267
showGithubVerificationBadge ?: boolean ;
265
- periodEnd : Date ;
268
+ periodEnd ? : Date ;
266
269
} ;
267
270
268
271
export function PricingPlans ( {
@@ -281,7 +284,7 @@ export function PricingPlans({
281
284
subscription = { subscription }
282
285
organizationSlug = { organizationSlug }
283
286
showGithubVerificationBadge = { showGithubVerificationBadge }
284
- periodEnd = { periodEnd }
287
+ periodEnd = { periodEnd ?? new Date ( ) }
285
288
/>
286
289
< TierHobby
287
290
plan = { plans . hobby }
@@ -317,13 +320,15 @@ export function TierFree({
317
320
const isLoading = navigation . formAction === formAction ;
318
321
const [ isDialogOpen , setIsDialogOpen ] = useState ( false ) ;
319
322
const [ isLackingFeaturesChecked , setIsLackingFeaturesChecked ] = useState ( false ) ;
320
-
321
323
const status = subscription ?. freeTierStatus ?? "requires_connect" ;
322
324
323
325
return (
324
326
< TierContainer >
325
327
< div className = "relative" >
326
328
< PricingHeader title = { plan . title } cost = { 0 } />
329
+ < TierLimit href = "https://trigger.dev/pricing#computePricing" >
330
+ ${ plan . limits . includedUsage / 100 } free usage
331
+ </ TierLimit >
327
332
{ showGithubVerificationBadge && status === "approved" && (
328
333
< SimpleTooltip
329
334
buttonClassName = "absolute right-1 top-1"
@@ -377,157 +382,102 @@ export function TierFree({
377
382
</ div >
378
383
</ div >
379
384
) : (
380
- < Form action = { formAction } method = "post" id = "subscribe" >
381
- < input type = "hidden" name = "type" value = "free" />
382
- < input type = "hidden" name = "callerPath" value = { location . pathname } />
383
- < TierLimit href = "https://trigger.dev/pricing#computePricing" >
384
- ${ plan . limits . includedUsage / 100 } free usage
385
- </ TierLimit >
386
- < div className = "py-6" >
387
- { status === "requires_connect" ? (
388
- < Dialog >
389
- < DialogTrigger asChild >
390
- < Button
391
- type = "button"
392
- variant = "tertiary/large"
393
- fullWidth
394
- className = "text-md font-medium"
395
- disabled = { isLoading }
396
- LeadingIcon = { isLoading ? Spinner : undefined }
397
- >
398
- Unlock free plan
399
- </ Button >
400
- </ DialogTrigger >
401
- < DialogContent className = "sm:max-w-md" >
402
- < DialogHeader > Unlock the Free plan</ DialogHeader >
403
- < div className = "mb-3 mt-4 flex flex-col items-center gap-4 px-6" >
404
- < GitHubLightIcon className = "size-16" />
405
- < Paragraph variant = "base/bright" className = "text-center" >
406
- To unlock the Free plan, we need to verify that you have an active GitHub
407
- account.
408
- </ Paragraph >
409
- < Paragraph className = "text-center" >
410
- We do this to prevent malicious use of our platform. We only ask for the
411
- minimum permissions to verify your account.
385
+ < >
386
+ { subscription ?. plan ?. type !== "free" && subscription ?. canceledAt === undefined ? (
387
+ < Dialog open = { isDialogOpen } onOpenChange = { setIsDialogOpen } key = "cancel" >
388
+ < DialogTrigger asChild >
389
+ < Button variant = "tertiary/large" fullWidth className = "text-md my-6 font-medium" >
390
+ { `Downgrade to ${ plan . title } ` }
391
+ </ Button >
392
+ </ DialogTrigger >
393
+ < DialogContent className = "sm:max-w-md" >
394
+ < Form action = { formAction } method = "post" id = "subscribe" >
395
+ < input type = "hidden" name = "type" value = "free" />
396
+ < input type = "hidden" name = "callerPath" value = { location . pathname } />
397
+ < DialogHeader > Downgrade plan</ DialogHeader >
398
+ < div className = "flex items-start gap-3 pb-6 pr-4 pt-6" >
399
+ < ArrowDownCircleIcon className = "size-12 min-w-12 text-error" />
400
+ < Paragraph variant = "base/bright" className = "text-text-bright" >
401
+ Are you sure you want to downgrade? If you do, you will retain your current
402
+ plan's features until < DateTime includeTime = { false } date = { periodEnd } /> .
412
403
</ Paragraph >
413
404
</ div >
414
- < DialogFooter >
405
+ < div >
406
+ < div className = "mb-4" >
407
+ < Header2 className = "mb-1" > Why are you thinking of downgrading?</ Header2 >
408
+ < ul className = "space-y-1" >
409
+ { [
410
+ "Subscription or usage costs too expensive" ,
411
+ "Bugs or technical issues" ,
412
+ "No longer need the service" ,
413
+ "Found a better alternative" ,
414
+ "Lacking features I need" ,
415
+ ] . map ( ( label , index ) => (
416
+ < li key = { index } >
417
+ < CheckboxWithLabel
418
+ id = { `reason-${ index + 1 } ` }
419
+ name = "reasons"
420
+ value = { label }
421
+ variant = "simple"
422
+ label = { label }
423
+ labelClassName = "text-text-dimmed"
424
+ onChange = { ( isChecked : boolean ) => {
425
+ if ( label === "Lacking features I need" ) {
426
+ setIsLackingFeaturesChecked ( isChecked ) ;
427
+ }
428
+ } }
429
+ />
430
+ </ li >
431
+ ) ) }
432
+ </ ul >
433
+ </ div >
434
+ < div >
435
+ < Header2 className = "mb-1" >
436
+ { isLackingFeaturesChecked
437
+ ? "What features do you need? Or how can we improve?"
438
+ : "What can we do to improve?" }
439
+ </ Header2 >
440
+ < TextArea id = "improvement-suggestions" name = "message" />
441
+ </ div >
442
+ </ div >
443
+ < DialogFooter className = "mt-2" >
444
+ < Button variant = "tertiary/medium" onClick = { ( ) => setIsDialogOpen ( false ) } >
445
+ Dismiss
446
+ </ Button >
415
447
< Button
416
- variant = "primary/large"
417
- fullWidth
448
+ variant = "danger/medium"
418
449
disabled = { isLoading }
419
- LeadingIcon = { isLoading ? Spinner : undefined }
420
- form = "subscribe"
450
+ LeadingIcon = {
451
+ isLoading && "submitting" ? ( ) => < Spinner color = "white" /> : undefined
452
+ }
453
+ type = "submit"
421
454
>
422
- Connect to GitHub
455
+ Downgrade plan
423
456
</ Button >
424
457
</ DialogFooter >
425
- </ DialogContent >
426
- </ Dialog >
427
- ) : (
428
- < >
429
- { subscription ?. plan ?. type !== "free" && subscription ?. canceledAt === undefined ? (
430
- < Dialog open = { isDialogOpen } onOpenChange = { setIsDialogOpen } key = "cancel" >
431
- < DialogTrigger asChild >
432
- < Button variant = "tertiary/large" fullWidth className = "text-md font-medium" >
433
- { `Downgrade to ${ plan . title } ` }
434
- </ Button >
435
- </ DialogTrigger >
436
- < DialogContent className = "sm:max-w-md" >
437
- < DialogHeader > Downgrade plan</ DialogHeader >
438
- < div className = "mb-2 mt-4 flex items-start gap-3" >
439
- < span >
440
- < XCircleIcon className = "size-12 text-error" />
441
- </ span >
442
- < Paragraph variant = "base/bright" className = "text-text-bright" >
443
- Are you sure you want to downgrade? If you do, you will retain your
444
- current plan's features until{ " " }
445
- < DateTime includeTime = { false } date = { periodEnd } /> .
446
- </ Paragraph >
447
- </ div >
448
- < div >
449
- < input type = "hidden" name = "type" value = "free" />
450
- < input type = "hidden" name = "callerPath" value = { location . pathname } />
451
- < div className = "mb-4" >
452
- < Header2 className = "mb-1" > Why are you thinking of canceling?</ Header2 >
453
- < ul className = "space-y-1" >
454
- { [
455
- "Subscription or usage costs too expensive" ,
456
- "Bugs or technical issues" ,
457
- "No longer need the service" ,
458
- "Found a better alternative" ,
459
- "Lacking features I need" ,
460
- ] . map ( ( label , index ) => (
461
- < li key = { index } >
462
- < CheckboxWithLabel
463
- id = { `reason-${ index + 1 } ` }
464
- name = "reason"
465
- value = { label }
466
- variant = "simple"
467
- label = { label }
468
- labelClassName = "text-text-dimmed"
469
- onChange = { ( isChecked : boolean ) => {
470
- if ( label === "Lacking features I need" ) {
471
- setIsLackingFeaturesChecked ( isChecked ) ;
472
- }
473
- } }
474
- />
475
- </ li >
476
- ) ) }
477
- </ ul >
478
- </ div >
479
- < div >
480
- < Header2 className = "mb-1" >
481
- { isLackingFeaturesChecked
482
- ? "What features do you need? Or how can we improve?"
483
- : "What can we do to improve?" }
484
- </ Header2 >
485
- < TextArea id = "improvement-suggestions" name = "message" />
486
- </ div >
487
- </ div >
488
- < DialogFooter >
489
- < Button variant = "tertiary/medium" onClick = { ( ) => setIsDialogOpen ( false ) } >
490
- Dismiss
491
- </ Button >
492
- < Button
493
- variant = "danger/medium"
494
- disabled = { isLoading }
495
- LeadingIcon = {
496
- isLoading && "submitting" ? ( ) => < Spinner color = "white" /> : undefined
497
- }
498
- type = "submit"
499
- form = "subscribe"
500
- >
501
- Downgrade plan
502
- </ Button >
503
- </ DialogFooter >
504
- </ DialogContent >
505
- </ Dialog >
506
- ) : (
507
- < Button
508
- variant = "tertiary/large"
509
- fullWidth
510
- className = "text-md font-medium"
511
- disabled = {
512
- isLoading ||
513
- subscription ?. plan ?. type === plan . type ||
514
- subscription ?. canceledAt !== undefined
515
- }
516
- LeadingIcon = {
517
- isLoading && navigation . formData ?. get ( "planCode" ) === null
518
- ? Spinner
519
- : undefined
520
- }
521
- >
522
- { subscription ?. plan === undefined
523
- ? "Select plan"
524
- : subscription . plan . type === "free" ||
525
- ( subscription . canceledAt !== undefined && "Current plan" ) }
526
- </ Button >
527
- ) }
528
- </ >
529
- ) }
530
- </ div >
458
+ </ Form >
459
+ </ DialogContent >
460
+ </ Dialog >
461
+ ) : (
462
+ < Button
463
+ variant = "tertiary/large"
464
+ fullWidth
465
+ className = "text-md my-6 font-medium"
466
+ disabled = {
467
+ isLoading ||
468
+ subscription ?. plan ?. type === plan . type ||
469
+ subscription ?. canceledAt !== undefined
470
+ }
471
+ LeadingIcon = {
472
+ isLoading && navigation . formData ?. get ( "planCode" ) === null ? Spinner : undefined
473
+ }
474
+ >
475
+ { subscription ?. plan === undefined
476
+ ? "Select plan"
477
+ : subscription . plan . type === "free" ||
478
+ ( subscription . canceledAt !== undefined && "Current plan" ) }
479
+ </ Button >
480
+ ) }
531
481
< ul className = "flex flex-col gap-2.5" >
532
482
< ConcurrentRuns limits = { plan . limits } />
533
483
< FeatureItem checked >
@@ -546,7 +496,7 @@ export function TierFree({
546
496
< SupportLevel limits = { plan . limits } />
547
497
< Alerts limits = { plan . limits } />
548
498
</ ul >
549
- </ Form >
499
+ </ >
550
500
) }
551
501
</ TierContainer >
552
502
) ;
0 commit comments