UpgradeModal.tsx128 lines · main
1import { useParams } from 'common'
2import { includes, without } from 'lodash'
3import { useReducer, useState } from 'react'
4import { toast } from 'sonner'
5import { Modal, TextArea } from 'ui'
6
7import { generateUpgradeReasons } from '../helpers'
8import { useSendUpgradeFeedbackMutation } from '@/data/feedback/upgrade-survey-send'
9import type { OrgSubscription } from '@/data/subscriptions/types'
10
11export interface UpgradeSurveyModalProps {
12 visible: boolean
13 originalPlan?: string
14 subscription?: OrgSubscription
15 onClose: (success?: boolean) => void
16}
17
18// Upgrade survey is from Free Plan to Pro/Team Plan and from Pro to Team Plan
19const UpgradeSurveyModal = ({
20 visible,
21 originalPlan,
22 subscription,
23 onClose,
24}: UpgradeSurveyModalProps) => {
25 const { slug } = useParams()
26 const [message, setMessage] = useState('')
27 const [selectedReasons, dispatchSelectedReasons] = useReducer(reducer, [])
28
29 const upgradeReasons = generateUpgradeReasons(originalPlan, subscription?.plan.id)
30
31 const { mutate: sendUpgradeSurvey, isPending: isSubmitting } = useSendUpgradeFeedbackMutation({
32 onError: (error) => {
33 toast.error(`Failed to submit survey: ${error.message}`)
34 },
35 onSuccess: () => {
36 onClose(true)
37 window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
38 },
39 })
40
41 function reducer(state: any, action: any) {
42 if (includes(state, action.target.value)) {
43 return without(state, action.target.value)
44 } else {
45 return [...state, action.target.value]
46 }
47 }
48
49 const onSubmit = async () => {
50 if (selectedReasons.length === 0) {
51 return toast.error('Please select at least one reason for upgrading your subscription')
52 }
53 sendUpgradeSurvey({
54 orgSlug: slug,
55 prevPlan: originalPlan,
56 currentPlan: subscription?.plan?.id,
57 reasons: selectedReasons,
58 message: message.trim() || undefined,
59 })
60 }
61
62 return (
63 <>
64 <Modal
65 alignFooter="right"
66 size="xlarge"
67 loading={isSubmitting}
68 visible={visible}
69 onCancel={onClose}
70 onConfirm={onSubmit}
71 cancelText="Skip"
72 header="We're excited for your upgrade"
73 >
74 <Modal.Content className="space-y-4">
75 <p className="text-sm text-foreground-light">
76 What reasons motivated your decision to upgrade? Your feedback helps us improve Briven
77 as much as we can.
78 </p>
79 <div className="space-y-8 mt-6">
80 <div className="flex flex-wrap gap-2" data-toggle="buttons">
81 {upgradeReasons.map((option) => {
82 const active = selectedReasons.find((x) => x === option)
83 return (
84 <label
85 key={option}
86 className={`
87 flex cursor-pointer items-center space-x-2 rounded-md py-1
88 pl-2 pr-3 text-center text-sm
89 shadow-xs transition-all duration-100
90 ${
91 active
92 ? ` bg-foreground text-background opacity-100 hover:bg-foreground/75`
93 : ` bg-border-strong text-foreground opacity-25 hover:opacity-50`
94 }
95 `}
96 >
97 <input
98 type="checkbox"
99 name="options"
100 value={option}
101 className="hidden"
102 onClick={dispatchSelectedReasons}
103 />
104 <div>{option}</div>
105 </label>
106 )
107 })}
108 </div>
109 <div className="text-area-text-sm flex flex-col gap-y-2">
110 <label htmlFor="message" className="text-sm whitespace-pre-line wrap-break-word">
111 Anything else that we can improve on?
112 </label>
113 <TextArea
114 id="message"
115 name="message"
116 value={message}
117 onChange={(event: any) => setMessage(event.target.value)}
118 rows={3}
119 />
120 </div>
121 </div>
122 </Modal.Content>
123 </Modal>
124 </>
125 )
126}
127
128export default UpgradeSurveyModal