Skip to content

Commit 6d8ff0c

Browse files
cleanup
1 parent b9ff2e2 commit 6d8ff0c

File tree

7 files changed

+142
-50
lines changed

7 files changed

+142
-50
lines changed

src/pages/PlanPage/PlanPage.jsx renamed to src/pages/PlanPage/PlanPage.tsx

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import config from 'config'
99
import { SentryRoute } from 'sentry'
1010

1111
import { useAccountDetails } from 'services/account'
12+
import { Provider } from 'shared/api/helpers'
1213
import { Theme, useThemeContext } from 'shared/ThemeContext'
1314
import A from 'ui/A'
1415
import { Alert } from 'ui/Alert'
@@ -19,10 +20,8 @@ import PlanBreadcrumb from './PlanBreadcrumb'
1920
import { PlanPageDataQueryOpts } from './queries/PlanPageDataQueryOpts'
2021
import Tabs from './Tabs'
2122

22-
2323
import { StripeAppearance } from '../../stripe'
2424

25-
2625
const CancelPlanPage = lazy(() => import('./subRoutes/CancelPlanPage'))
2726
const CurrentOrgPlan = lazy(() => import('./subRoutes/CurrentOrgPlan'))
2827
const InvoicesPage = lazy(() => import('./subRoutes/InvoicesPage'))
@@ -40,8 +39,13 @@ const Loader = () => (
4039
</div>
4140
)
4241

42+
interface URLParams {
43+
owner: string
44+
provider: Provider
45+
}
46+
4347
function PlanPage() {
44-
const { owner, provider } = useParams()
48+
const { owner, provider } = useParams<URLParams>()
4549
const { data: ownerData } = useSuspenseQueryV5(
4650
PlanPageDataQueryOpts({ owner, provider })
4751
)
@@ -56,15 +60,10 @@ function PlanPage() {
5660
if (config.IS_SELF_HOSTED || !ownerData?.isCurrentUserPartOfOrg) {
5761
return <Redirect to={`/${provider}/${owner}`} />
5862
}
59-
60-
const isAwaitingVerification =
61-
accountDetails?.unverifiedPaymentMethods?.length
62-
// const isAwaitingFirstPaymentMethodVerification =
63-
// !accountDetails?.subscriptionDetail?.defaultPaymentMethod &&
64-
// isAwaitingVerification
65-
66-
// const hasSuccessfulDefaultPaymentMethod =
67-
// accountDetails?.subscriptionDetail?.defaultPaymentMethod
63+
const hasUnverifiedPaymentMethods = Boolean(
64+
accountDetails?.unverifiedPaymentMethods &&
65+
accountDetails.unverifiedPaymentMethods.length >= 1
66+
)
6867

6968
return (
7069
<div className="flex flex-col gap-4">
@@ -80,7 +79,7 @@ function PlanPage() {
8079
>
8180
<PlanProvider>
8281
<PlanBreadcrumb />
83-
{isAwaitingVerification ? (
82+
{hasUnverifiedPaymentMethods ? (
8483
<UnverifiedPaymentMethodAlert
8584
url={
8685
accountDetails?.unverifiedPaymentMethods?.[0]
@@ -117,10 +116,7 @@ function PlanPage() {
117116
)
118117
}
119118

120-
export default PlanPage
121-
122-
// eslint-disable-next-line react/prop-types
123-
const UnverifiedPaymentMethodAlert = ({ url }) => {
119+
const UnverifiedPaymentMethodAlert = ({ url }: { url?: string }) => {
124120
return (
125121
<>
126122
<Alert variant={'warning'}>
@@ -142,3 +138,5 @@ const UnverifiedPaymentMethodAlert = ({ url }) => {
142138
</>
143139
)
144140
}
141+
142+
export default PlanPage

src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentCard.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ function PaymentCard({ accountDetails, provider, owner }) {
1515
const subscriptionDetail = accountDetails?.subscriptionDetail
1616
const card = subscriptionDetail?.defaultPaymentMethod?.card
1717
const usBankAccount = subscriptionDetail?.defaultPaymentMethod?.usBankAccount
18-
// const isAwaitingDelayedPaymentMethodVerification = true
1918

2019
let nextBillingDisplayDate = null
2120
if (!subscriptionDetail?.cancelAtPeriodEnd) {

src/pages/PlanPage/subRoutes/CurrentOrgPlan/BillingDetails/PaymentCard/PaymentMethodForm.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,15 @@ const PaymentMethodForm = ({
4040
mutate: updatePaymentMethod,
4141
isLoading,
4242
error,
43+
reset,
4344
} = useUpdatePaymentMethod({
4445
provider,
4546
owner,
4647
name,
4748
email,
4849
address: stripeAddress(billingDetails) || undefined,
4950
})
51+
5052
async function submit(e: React.FormEvent) {
5153
e.preventDefault()
5254

@@ -67,6 +69,8 @@ const PaymentMethodForm = ({
6769
})
6870
}
6971

72+
const showError = error && !reset
73+
7074
return (
7175
<form onSubmit={submit} aria-label="form">
7276
<div className={cs('flex flex-col gap-3')}>
@@ -83,7 +87,7 @@ const PaymentMethodForm = ({
8387
}}
8488
/>
8589
<p className="mt-1 text-ds-primary-red">
86-
{error ? getErrorMessage(error) : null}
90+
{showError ? getErrorMessage(error) : null}
8791
</p>
8892
<div className="mb-8 mt-4 flex gap-1">
8993
<Button

src/pages/PlanPage/subRoutes/CurrentOrgPlan/CurrentOrgPlan.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,22 @@ function CurrentOrgPlan() {
4040
})
4141
)
4242

43-
const isAwaitingVerification =
44-
accountDetails?.unverifiedPaymentMethods?.length
45-
const isAwaitingFirstPaymentMethodVerification =
43+
// awaitingInitialPaymentMethodVerification is true if the
44+
// customer needs to verify a delayed notification payment method
45+
// like ACH for their first subscription
46+
const awaitingInitialPaymentMethodVerification =
4647
!accountDetails?.subscriptionDetail?.defaultPaymentMethod &&
47-
isAwaitingVerification
48+
accountDetails?.unverifiedPaymentMethods?.length
4849

4950
const scheduledPhase = accountDetails?.scheduleDetail?.scheduledPhase
50-
// customer is delinquent until their first payment method is verified
5151
const isDelinquent =
52-
accountDetails?.delinquent && !isAwaitingFirstPaymentMethodVerification
52+
accountDetails?.delinquent && !awaitingInitialPaymentMethodVerification
5353
const scheduleStart = scheduledPhase
5454
? getScheduleStart(scheduledPhase)
5555
: undefined
5656

5757
const shouldRenderBillingDetails =
58-
!isAwaitingFirstPaymentMethodVerification &&
58+
!awaitingInitialPaymentMethodVerification &&
5959
((accountDetails?.planProvider !== 'github' &&
6060
!accountDetails?.rootOrganization) ||
6161
accountDetails?.usesInvoice)
@@ -64,21 +64,14 @@ function CurrentOrgPlan() {
6464

6565
const account = enterpriseDetails?.owner?.account
6666

67-
const hasSuccessfulDefaultPaymentMethod =
68-
accountDetails?.subscriptionDetail?.defaultPaymentMethod
69-
70-
const hideSuccessBanner = isAwaitingVerification
71-
? hasSuccessfulDefaultPaymentMethod
72-
: true
73-
7467
return (
7568
<div className="w-full lg:w-4/5">
7669
{planUpdatedNotification.isCancellation ? (
7770
<InfoAlertCancellation
7871
subscriptionDetail={accountDetails?.subscriptionDetail}
7972
/>
8073
) : null}
81-
{hideSuccessBanner ? <InfoMessageStripeCallback /> : null}
74+
<InfoMessageStripeCallback accountDetails={accountDetails} />
8275
{isDelinquent ? <DelinquentAlert /> : null}
8376
{planData?.plan ? (
8477
<div className="flex flex-col gap-4 sm:mr-4 sm:flex-initial md:w-2/3 lg:w-3/4">

src/pages/PlanPage/subRoutes/CurrentOrgPlan/InfoMessageStripeCallback/InfoMessageStripeCallback.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import qs from 'qs'
22
import { useLocation } from 'react-router-dom'
3+
import { z } from 'zod'
34

45
import Message from 'old_ui/Message'
6+
import { AccountDetailsSchema } from 'services/account'
57

68
// Stripe redirects to this page with ?success or ?cancel in the URL
79
// this component takes care of rendering a message if it is successful
8-
function InfoMessageStripeCallback() {
10+
function InfoMessageStripeCallback({
11+
accountDetails,
12+
}: {
13+
accountDetails?: z.infer<typeof AccountDetailsSchema>
14+
}) {
915
const urlParams = qs.parse(useLocation().search, {
1016
ignoreQueryPrefix: true,
1117
})
18+
const isAwaitingVerification =
19+
accountDetails?.unverifiedPaymentMethods?.length
1220

13-
if ('success' in urlParams)
21+
if ('success' in urlParams && !isAwaitingVerification)
1422
return (
1523
<div className="col-start-1 col-end-13 mb-4">
1624
<Message variant="success">

src/pages/PlanPage/subRoutes/UpgradePlanPage/UpgradeForm/UpgradeForm.tsx

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { zodResolver } from '@hookform/resolvers/zod'
2-
import { useEffect } from 'react'
2+
import { useEffect, useState } from 'react'
33
import { useForm } from 'react-hook-form'
44
import { useParams } from 'react-router-dom'
55

@@ -23,6 +23,7 @@ import { useUpgradeControls } from './hooks'
2323
import PlanTypeOptions from './PlanTypeOptions'
2424
import UpdateBlurb from './UpdateBlurb/UpdateBlurb'
2525
import UpdateButton from './UpdateButton'
26+
import UpgradeFormModal from './UpgradeFormModal'
2627

2728
type URLParams = {
2829
provider: Provider
@@ -45,6 +46,9 @@ function UpgradeForm({ selectedPlan, setSelectedPlan }: UpgradeFormProps) {
4546
const { data: plans } = useAvailablePlans({ provider, owner })
4647
const { data: planData } = usePlanData({ owner, provider })
4748
const { upgradePlan } = useUpgradeControls()
49+
const [showModal, setShowModal] = useState(false)
50+
const [formData, setFormData] = useState<UpgradeFormFields>()
51+
const [isUpgrading, setIsUpgrading] = useState(false)
4852
const isSentryUpgrade = canApplySentryUpgrade({
4953
isEnterprisePlan: planData?.plan?.isEnterprisePlan,
5054
plans,
@@ -90,22 +94,20 @@ function UpgradeForm({ selectedPlan, setSelectedPlan }: UpgradeFormProps) {
9094
trigger('seats')
9195
}, [newPlan, trigger])
9296

97+
const onSubmit = handleSubmit((data) => {
98+
if (accountDetails?.unverifiedPaymentMethods?.length) {
99+
setFormData(data)
100+
setShowModal(true)
101+
} else {
102+
setIsUpgrading(true)
103+
upgradePlan(data)
104+
}
105+
})
106+
93107
return (
94108
<form
95109
className="flex flex-col gap-6 border p-4 text-ds-gray-default md:w-2/3"
96-
onSubmit={handleSubmit((data) => {
97-
if (accountDetails?.unverifiedPaymentMethods?.length) {
98-
if (
99-
window.confirm(
100-
'You have a payment method awaiting verification. Are you sure you want to proceed with upgrading your plan? This will cancel the existing action.'
101-
)
102-
) {
103-
upgradePlan(data)
104-
}
105-
} else {
106-
upgradePlan(data)
107-
}
108-
})}
110+
onSubmit={onSubmit}
109111
>
110112
<div className="flex flex-col gap-1">
111113
<h3 className="font-semibold">Organization</h3>
@@ -131,6 +133,21 @@ function UpgradeForm({ selectedPlan, setSelectedPlan }: UpgradeFormProps) {
131133
nextBillingDate={getNextBillingDate(accountDetails)!}
132134
/>
133135
<UpdateButton isValid={isValid} newPlan={newPlan} seats={seats} />
136+
{showModal && formData && (
137+
<UpgradeFormModal
138+
isOpen={showModal}
139+
onClose={() => setShowModal(false)}
140+
onConfirm={() => {
141+
setIsUpgrading(true)
142+
upgradePlan(formData)
143+
}}
144+
url={
145+
accountDetails?.unverifiedPaymentMethods?.[0]
146+
?.hostedVerificationLink || ''
147+
}
148+
isUpgrading={isUpgrading}
149+
/>
150+
)}
134151
</form>
135152
)
136153
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import A from 'ui/A'
2+
import Button from 'ui/Button'
3+
import Icon from 'ui/Icon'
4+
import Modal from 'ui/Modal'
5+
6+
interface UpgradeModalProps {
7+
isOpen: boolean
8+
onClose: () => void
9+
onConfirm: () => void
10+
url: string
11+
isUpgrading?: boolean
12+
}
13+
14+
const UpgradeFormModal = ({
15+
isOpen,
16+
onClose,
17+
onConfirm,
18+
url,
19+
isUpgrading = false,
20+
}: UpgradeModalProps) => (
21+
<Modal
22+
isOpen={isOpen}
23+
onClose={onClose}
24+
title={
25+
<p className="flex items-center gap-2 text-base">
26+
<Icon
27+
name="exclamationTriangle"
28+
size="sm"
29+
className="fill-ds-primary-yellow"
30+
/>
31+
Incomplete Plan Upgrade
32+
</p>
33+
}
34+
body={
35+
<div className="flex flex-col gap-4">
36+
<div>
37+
You have an incomplete plan upgrade that is awaiting payment
38+
verification{' '}
39+
<A
40+
href={url}
41+
isExternal
42+
hook={'verify-payment-method'}
43+
to={undefined}
44+
>
45+
here
46+
</A>
47+
.
48+
</div>
49+
<p>
50+
Are you sure you wish to abandon the pending upgrade and start a new
51+
one?
52+
</p>
53+
</div>
54+
}
55+
footer={
56+
<div className="flex gap-2">
57+
<Button hook="cancel-upgrade" onClick={onClose} disabled={isUpgrading}>
58+
Cancel
59+
</Button>
60+
<Button
61+
hook="confirm-upgrade"
62+
variant="primary"
63+
onClick={onConfirm}
64+
disabled={isUpgrading}
65+
>
66+
{isUpgrading ? 'Processing...' : 'Yes, Start New Upgrade'}
67+
</Button>
68+
</div>
69+
}
70+
/>
71+
)
72+
73+
export default UpgradeFormModal

0 commit comments

Comments
 (0)