Skip to content

Commit cf7e4e9

Browse files
committed
Show Payment Modal when applying a coupon if account has no validPayment method setup (#5011)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview This PR introduces a payment setup feature in the `ApplyCouponCard` component, allowing users to add payment methods before applying coupons. It enhances the UI and functionality related to payment methods and coupon application. ### Detailed summary - Added `isPaymentSetup` and `onAddPayment` props to `ApplyCouponCardUI` and `CouponSection`. - Updated `ApplyCouponCard` to handle payment setup checks. - Modified `onSubmit` in `ApplyCouponCardUI` to prompt for payment if not set up. - Adjusted modal handling for payment methods in `Billing` component. - Updated stories to reflect changes in payment setup logic. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent f0d6212 commit cf7e4e9

File tree

3 files changed

+68
-20
lines changed

3 files changed

+68
-20
lines changed

apps/dashboard/src/components/settings/Account/Billing/ApplyCouponCard.stories.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,48 +59,73 @@ function Story() {
5959
submit={statusStub(200)}
6060
onCouponApplied={undefined}
6161
prefillPromoCode="XYZ"
62+
isPaymentSetup={true}
63+
onAddPayment={() => {}}
64+
/>
65+
</BadgeContainer>
66+
67+
<BadgeContainer label="No Valid Payment setup">
68+
<ApplyCouponCardUI
69+
submit={statusStub(200)}
70+
onCouponApplied={undefined}
71+
isPaymentSetup={false}
72+
onAddPayment={() => {
73+
console.log("show payment modal");
74+
}}
6275
/>
6376
</BadgeContainer>
6477

6578
<BadgeContainer label="Success - 200">
6679
<ApplyCouponCardUI
6780
submit={statusStub(200)}
6881
onCouponApplied={undefined}
82+
isPaymentSetup={true}
83+
onAddPayment={() => {}}
6984
/>
7085
</BadgeContainer>
7186

7287
<BadgeContainer label="Invalid - 400">
7388
<ApplyCouponCardUI
7489
submit={statusStub(400)}
7590
onCouponApplied={undefined}
91+
isPaymentSetup={true}
92+
onAddPayment={() => {}}
7693
/>
7794
</BadgeContainer>
7895

7996
<BadgeContainer label="Not Authorized - 401">
8097
<ApplyCouponCardUI
8198
submit={statusStub(401)}
8299
onCouponApplied={undefined}
100+
isPaymentSetup={true}
101+
onAddPayment={() => {}}
83102
/>
84103
</BadgeContainer>
85104

86105
<BadgeContainer label="Already applied - 409">
87106
<ApplyCouponCardUI
88107
submit={statusStub(409)}
89108
onCouponApplied={undefined}
109+
isPaymentSetup={true}
110+
onAddPayment={() => {}}
90111
/>
91112
</BadgeContainer>
92113

93114
<BadgeContainer label="Rate Limited - 429">
94115
<ApplyCouponCardUI
95116
submit={statusStub(429)}
96117
onCouponApplied={undefined}
118+
isPaymentSetup={true}
119+
onAddPayment={() => {}}
97120
/>
98121
</BadgeContainer>
99122

100123
<BadgeContainer label="Other - 500">
101124
<ApplyCouponCardUI
102125
submit={statusStub(500)}
103126
onCouponApplied={undefined}
127+
isPaymentSetup={true}
128+
onAddPayment={() => {}}
104129
/>
105130
</BadgeContainer>
106131
<Toaster richColors />

apps/dashboard/src/components/settings/Account/Billing/CouponCard.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export type ActiveCouponResponse = {
3737
function ApplyCouponCard(props: {
3838
teamId: string | undefined;
3939
onCouponApplied: (data: ActiveCouponResponse) => void;
40+
isPaymentSetup: boolean;
41+
onAddPayment: () => void;
4042
}) {
4143
const searchParams = useSearchParams();
4244
const couponCode = searchParams?.get("coupon");
@@ -45,6 +47,8 @@ function ApplyCouponCard(props: {
4547
onCouponApplied={props.onCouponApplied}
4648
prefillPromoCode={couponCode || undefined}
4749
scrollIntoView={!!couponCode}
50+
isPaymentSetup={props.isPaymentSetup}
51+
onAddPayment={props.onAddPayment}
4852
submit={async (promoCode: string) => {
4953
const res = await fetch("/api/server-proxy/api/v1/coupons/redeem", {
5054
method: "POST",
@@ -86,6 +90,8 @@ export function ApplyCouponCardUI(props: {
8690
onCouponApplied: ((data: ActiveCouponResponse) => void) | undefined;
8791
prefillPromoCode?: string;
8892
scrollIntoView?: boolean;
93+
isPaymentSetup: boolean;
94+
onAddPayment: () => void;
8995
}) {
9096
const containerRef = useRef<HTMLFormElement | null>(null);
9197
const form = useForm<z.infer<typeof couponFormSchema>>({
@@ -113,6 +119,11 @@ export function ApplyCouponCardUI(props: {
113119
});
114120

115121
async function onSubmit(values: z.infer<typeof couponFormSchema>) {
122+
if (!props.isPaymentSetup) {
123+
props.onAddPayment();
124+
return;
125+
}
126+
116127
try {
117128
const res = await applyCoupon.mutateAsync(values.promoCode);
118129
switch (res.status) {
@@ -161,7 +172,11 @@ export function ApplyCouponCardUI(props: {
161172
description:
162173
"Enter your coupon code to apply discounts or free trials on thirdweb products",
163174
}}
164-
bottomText=""
175+
bottomText={
176+
props.isPaymentSetup
177+
? ""
178+
: "A valid payment method must be added to apply a coupon"
179+
}
165180
saveButton={{
166181
variant: "default",
167182
disabled: false,
@@ -238,7 +253,11 @@ export function CouponDetailsCardUI(props: {
238253
);
239254
}
240255

241-
export function CouponSection(props: { teamId: string | undefined }) {
256+
export function CouponSection(props: {
257+
teamId: string | undefined;
258+
isPaymentSetup: boolean;
259+
onAddPayment: () => void;
260+
}) {
242261
const loggedInUser = useLoggedInUser();
243262
const [optimisticCouponData, setOptimisticCouponData] = useState<
244263
| {
@@ -325,6 +344,8 @@ export function CouponSection(props: { teamId: string | undefined }) {
325344
setOptimisticCouponData(undefined);
326345
});
327346
}}
347+
isPaymentSetup={props.isPaymentSetup}
348+
onAddPayment={props.onAddPayment}
328349
/>
329350
</Suspense>
330351
);

apps/dashboard/src/components/settings/Account/Billing/index.tsx

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
AccountStatus,
66
useUpdateAccountPlan,
77
} from "@3rdweb-sdk/react/hooks/useApi";
8-
import { Flex, Icon, useDisclosure } from "@chakra-ui/react";
8+
import { Flex, Icon } from "@chakra-ui/react";
99
import { StepsCard } from "components/dashboard/StepsCard";
1010
import { OnboardingModal } from "components/onboarding/Modal";
1111
import { AccountForm } from "components/settings/Account/AccountForm";
@@ -32,11 +32,7 @@ export const Billing: React.FC<BillingProps> = ({ account, teamId }) => {
3232
const updatePlanMutation = useUpdateAccountPlan(
3333
account?.plan === AccountPlan.Free,
3434
);
35-
const {
36-
isOpen: isPaymentMethodOpen,
37-
onOpen: onPaymentMethodOpen,
38-
onClose: onPaymentMethodClose,
39-
} = useDisclosure();
35+
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
4036
const [paymentMethodSaving, setPaymentMethodSaving] = useState(false);
4137
const [selectedPlan, setSelectedPlan] = useState<AccountPlan | undefined>();
4238
const trackEvent = useTrack();
@@ -114,7 +110,7 @@ export const Billing: React.FC<BillingProps> = ({ account, teamId }) => {
114110

115111
if (!validPayment) {
116112
setSelectedPlan(plan);
117-
onPaymentMethodOpen();
113+
setIsPaymentModalOpen(true);
118114
return;
119115
}
120116
// downgrade from Growth to Free
@@ -127,7 +123,7 @@ export const Billing: React.FC<BillingProps> = ({ account, teamId }) => {
127123

128124
const handlePaymentAdded = () => {
129125
setPaymentMethodSaving(true);
130-
onPaymentMethodClose();
126+
setIsPaymentModalOpen(false);
131127
};
132128

133129
const handleDowngradeAlertClose = () => {
@@ -186,12 +182,12 @@ export const Billing: React.FC<BillingProps> = ({ account, teamId }) => {
186182
account={account}
187183
loading={paymentMethodSaving}
188184
loadingText="Verifying payment method"
189-
onClick={onPaymentMethodOpen}
185+
onClick={() => setIsPaymentModalOpen(true)}
190186
/>
191187
),
192188
},
193189
];
194-
}, [account, onPaymentMethodOpen, paymentMethodSaving, stepsCompleted]);
190+
}, [account, paymentMethodSaving, stepsCompleted]);
195191

196192
// FIXME: this entire flow needs to be re-worked
197193
// eslint-disable-next-line no-restricted-syntax
@@ -245,16 +241,16 @@ export const Billing: React.FC<BillingProps> = ({ account, teamId }) => {
245241

246242
return (
247243
<Flex flexDir="column" gap={8}>
244+
<OnboardingModal isOpen={isPaymentModalOpen}>
245+
<LazyOnboardingBilling
246+
onSave={handlePaymentAdded}
247+
onCancel={() => setIsPaymentModalOpen(false)}
248+
/>
249+
</OnboardingModal>
250+
248251
{showSteps ? (
249252
<>
250253
<StepsCard title="Get started with billing" steps={steps} />
251-
252-
<OnboardingModal isOpen={isPaymentMethodOpen}>
253-
<LazyOnboardingBilling
254-
onSave={handlePaymentAdded}
255-
onCancel={onPaymentMethodClose}
256-
/>
257-
</OnboardingModal>
258254
</>
259255
) : (
260256
<>
@@ -303,7 +299,13 @@ export const Billing: React.FC<BillingProps> = ({ account, teamId }) => {
303299
/>
304300
)}
305301

306-
<CouponSection teamId={teamId} />
302+
<CouponSection
303+
teamId={teamId}
304+
onAddPayment={() => {
305+
setIsPaymentModalOpen(true);
306+
}}
307+
isPaymentSetup={validPayment}
308+
/>
307309
</Flex>
308310
);
309311
};

0 commit comments

Comments
 (0)