4
4
ShieldCheckIcon ,
5
5
XMarkIcon ,
6
6
} from "@heroicons/react/20/solid" ;
7
- import { Form , useLocation , useNavigation } from "@remix-run/react" ;
7
+ import { Form , useFetcher , useLocation , useNavigation } from "@remix-run/react" ;
8
8
import { ActionFunctionArgs } from "@remix-run/server-runtime" ;
9
9
import {
10
10
FreePlanDefinition ,
@@ -34,6 +34,12 @@ import { redirectWithErrorMessage } from "~/models/message.server";
34
34
import { setPlan } from "~/services/platform.v3.server" ;
35
35
import { requireUserId } from "~/services/session.server" ;
36
36
import { cn } from "~/utils/cn" ;
37
+ import { useState } from "react" ;
38
+ import { XCircleIcon } from "@heroicons/react/24/outline" ;
39
+ import { DateTime } from "~/components/primitives/DateTime" ;
40
+ import { Header2 } from "~/components/primitives/Headers" ;
41
+ import { CheckboxWithLabel } from "~/components/primitives/Checkbox" ;
42
+ import { TextArea } from "~/components/primitives/TextArea" ;
37
43
38
44
const Params = z . object ( {
39
45
organizationSlug : z . string ( ) ,
@@ -134,6 +140,7 @@ type PricingPlansProps = {
134
140
organizationSlug : string ;
135
141
hasPromotedPlan : boolean ;
136
142
showGithubVerificationBadge ?: boolean ;
143
+ periodEnd : Date ; // Add this line
137
144
} ;
138
145
139
146
export function PricingPlans ( {
@@ -142,6 +149,7 @@ export function PricingPlans({
142
149
organizationSlug,
143
150
hasPromotedPlan,
144
151
showGithubVerificationBadge,
152
+ periodEnd, // Add this line
145
153
} : PricingPlansProps ) {
146
154
return (
147
155
< div className = "flex w-full flex-col" >
@@ -151,6 +159,7 @@ export function PricingPlans({
151
159
subscription = { subscription }
152
160
organizationSlug = { organizationSlug }
153
161
showGithubVerificationBadge = { showGithubVerificationBadge }
162
+ periodEnd = { periodEnd } // Add this line
154
163
/>
155
164
< TierHobby
156
165
plan = { plans . hobby }
@@ -172,16 +181,19 @@ export function TierFree({
172
181
subscription,
173
182
organizationSlug,
174
183
showGithubVerificationBadge,
184
+ periodEnd, // Add this line
175
185
} : {
176
186
plan : FreePlanDefinition ;
177
187
subscription ?: SubscriptionResult ;
178
188
organizationSlug : string ;
179
189
showGithubVerificationBadge ?: boolean ;
190
+ periodEnd : Date ; // Add this line
180
191
} ) {
181
192
const location = useLocation ( ) ;
182
193
const navigation = useNavigation ( ) ;
183
194
const formAction = `/resources/orgs/${ organizationSlug } /select-plan` ;
184
195
const isLoading = navigation . formAction === formAction ;
196
+ const [ isDialogOpen , setIsDialogOpen ] = useState ( false ) ;
185
197
186
198
const status = subscription ?. freeTierStatus ?? "requires_connect" ;
187
199
@@ -290,25 +302,99 @@ export function TierFree({
290
302
</ DialogContent >
291
303
</ Dialog >
292
304
) : (
293
- < Button
294
- variant = "tertiary/large"
295
- fullWidth
296
- className = "text-md font-medium"
297
- disabled = {
298
- isLoading ||
299
- subscription ?. plan ?. type === plan . type ||
300
- subscription ?. canceledAt !== undefined
301
- }
302
- LeadingIcon = {
303
- isLoading && navigation . formData ?. get ( "planCode" ) === null ? Spinner : undefined
304
- }
305
- >
306
- { subscription ?. plan === undefined
307
- ? "Select plan"
308
- : subscription . plan . type === "free" || subscription . canceledAt !== undefined
309
- ? "Current plan"
310
- : `Downgrade to ${ plan . title } ` }
311
- </ Button >
305
+ < >
306
+ { subscription ?. plan ?. type !== "free" && subscription ?. canceledAt === undefined ? (
307
+ < >
308
+ < Button
309
+ variant = "tertiary/large"
310
+ fullWidth
311
+ className = "text-md font-medium"
312
+ onClick = { ( ) => setIsDialogOpen ( true ) }
313
+ >
314
+ { `Downgrade to ${ plan . title } ` }
315
+ </ Button >
316
+ < Dialog open = { isDialogOpen } onOpenChange = { setIsDialogOpen } >
317
+ < DialogContent className = "max-w-md" >
318
+ < DialogHeader > Cancel plan</ DialogHeader >
319
+ < div className = "mb-2 mt-4 flex items-start gap-3" >
320
+ < span >
321
+ < XCircleIcon className = "size-12 text-error" />
322
+ </ span >
323
+ < Paragraph variant = "base/bright" className = "text-text-bright" >
324
+ Are you sure you want to cancel? If you do, you will retain your current
325
+ plan's features until < DateTime includeTime = { false } date = { periodEnd } /> .
326
+ </ Paragraph >
327
+ </ div >
328
+ < div >
329
+ < input type = "hidden" name = "type" value = "free" />
330
+ < input type = "hidden" name = "callerPath" value = { location . pathname } />
331
+ < div className = "mb-4" >
332
+ < Header2 className = "mb-1" > Why are you thinking of canceling?</ Header2 >
333
+ < ul className = "space-y-1" >
334
+ { [
335
+ "Subscription or usage costs too expensive" ,
336
+ "Bugs or technical issues" ,
337
+ "No longer need the service" ,
338
+ "Found a better alternative" ,
339
+ "Lacking features I need" ,
340
+ ] . map ( ( label , index ) => (
341
+ < li key = { index } >
342
+ < CheckboxWithLabel
343
+ id = { `reason-${ index + 1 } ` }
344
+ name = "reason"
345
+ value = { label }
346
+ variant = "simple"
347
+ label = { label }
348
+ labelClassName = "text-text-dimmed"
349
+ />
350
+ </ li >
351
+ ) ) }
352
+ </ ul >
353
+ </ div >
354
+ < div className = "mb-2" >
355
+ < Header2 className = "mb-1" > What can we do to improve?</ Header2 >
356
+ < TextArea id = "improvement-suggestions" name = "message" />
357
+ </ div >
358
+ </ div >
359
+ < DialogFooter >
360
+ < Button variant = "tertiary/medium" onClick = { ( ) => setIsDialogOpen ( false ) } >
361
+ Dismiss
362
+ </ Button >
363
+ < Button
364
+ variant = "danger/medium"
365
+ type = "submit"
366
+ disabled = { isLoading }
367
+ LeadingIcon = { isLoading ? Spinner : undefined }
368
+ >
369
+ Confirm downgrade
370
+ </ Button >
371
+ </ DialogFooter >
372
+ </ DialogContent >
373
+ </ Dialog >
374
+ </ >
375
+ ) : (
376
+ < Button
377
+ variant = "tertiary/large"
378
+ fullWidth
379
+ className = "text-md font-medium"
380
+ disabled = {
381
+ isLoading ||
382
+ subscription ?. plan ?. type === plan . type ||
383
+ subscription ?. canceledAt !== undefined
384
+ }
385
+ LeadingIcon = {
386
+ isLoading && navigation . formData ?. get ( "planCode" ) === null
387
+ ? Spinner
388
+ : undefined
389
+ }
390
+ >
391
+ { subscription ?. plan === undefined
392
+ ? "Select plan"
393
+ : subscription . plan . type === "free" ||
394
+ ( subscription . canceledAt !== undefined && "Current plan" ) }
395
+ </ Button >
396
+ ) }
397
+ </ >
312
398
) }
313
399
</ div >
314
400
< ul className = "flex flex-col gap-2.5" >
0 commit comments