Skip to content

Commit 9adf6e6

Browse files
committed
Prefill Promo code on coupon card from search param + scroll into view (#4968)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview This PR introduces functionality for pre-filling a coupon code in the `ApplyCouponCard` component based on URL search parameters and enhances the user experience by scrolling into view when a coupon code is present. ### Detailed summary - Added `BadgeContainer` for pre-filled coupon code display in `ApplyCouponCard.stories.tsx`. - Utilized `useSearchParams` to retrieve the coupon code from the URL in `CouponCard.tsx`. - Enhanced `ApplyCouponCardUI` to accept `prefillPromoCode` and `scrollIntoView` props. - Implemented scrolling behavior to focus on the form when a coupon code is present. - Wrapped `ApplyCouponCard` in a `Suspense` component to manage loading states effectively. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 9330401 commit 9adf6e6

File tree

2 files changed

+55
-20
lines changed

2 files changed

+55
-20
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ function statusStub(status: number) {
5454
function Story() {
5555
return (
5656
<div className="container flex max-w-[1100px] flex-col gap-10 py-10">
57+
<BadgeContainer label="Prefill code - XYZ, Success - 200">
58+
<ApplyCouponCardUI
59+
submit={statusStub(200)}
60+
onCouponApplied={undefined}
61+
prefillPromoCode="XYZ"
62+
/>
63+
</BadgeContainer>
64+
5765
<BadgeContainer label="Success - 200">
5866
<ApplyCouponCardUI
5967
submit={statusStub(200)}

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

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { zodResolver } from "@hookform/resolvers/zod";
1616
import { useMutation, useQuery } from "@tanstack/react-query";
1717
import { format, fromUnixTime } from "date-fns";
1818
import { TagIcon } from "lucide-react";
19-
import { useState } from "react";
19+
import { useSearchParams } from "next/navigation";
20+
import { Suspense, useEffect, useRef, useState } from "react";
2021
import { useForm } from "react-hook-form";
2122
import { toast } from "sonner";
2223
import { z } from "zod";
@@ -37,9 +38,13 @@ function ApplyCouponCard(props: {
3738
teamId: string | undefined;
3839
onCouponApplied: (data: ActiveCouponResponse) => void;
3940
}) {
41+
const searchParams = useSearchParams();
42+
const couponCode = searchParams?.get("coupon");
4043
return (
4144
<ApplyCouponCardUI
4245
onCouponApplied={props.onCouponApplied}
46+
prefillPromoCode={couponCode || undefined}
47+
scrollIntoView={!!couponCode}
4348
submit={async (promoCode: string) => {
4449
const res = await fetch("/api/server-proxy/api/v1/coupons/redeem", {
4550
method: "POST",
@@ -79,14 +84,30 @@ export function ApplyCouponCardUI(props: {
7984
data: null | ActiveCouponResponse;
8085
}>;
8186
onCouponApplied: ((data: ActiveCouponResponse) => void) | undefined;
87+
prefillPromoCode?: string;
88+
scrollIntoView?: boolean;
8289
}) {
90+
const containerRef = useRef<HTMLFormElement | null>(null);
8391
const form = useForm<z.infer<typeof couponFormSchema>>({
8492
resolver: zodResolver(couponFormSchema),
8593
defaultValues: {
86-
promoCode: "",
94+
promoCode: props.prefillPromoCode,
8795
},
8896
});
8997

98+
const scrolled = useRef(false);
99+
// eslint-disable-next-line no-restricted-syntax
100+
useEffect(() => {
101+
if (props.scrollIntoView && !scrolled.current) {
102+
const el = containerRef.current;
103+
if (el) {
104+
el.scrollIntoView({ behavior: "smooth", block: "start" });
105+
el.querySelector("input")?.focus();
106+
scrolled.current = true;
107+
}
108+
}
109+
}, [props.scrollIntoView]);
110+
90111
const applyCoupon = useMutation({
91112
mutationFn: (promoCode: string) => props.submit(promoCode),
92113
});
@@ -133,7 +154,7 @@ export function ApplyCouponCardUI(props: {
133154

134155
return (
135156
<Form {...form}>
136-
<form onSubmit={form.handleSubmit(onSubmit)}>
157+
<form onSubmit={form.handleSubmit(onSubmit)} ref={containerRef}>
137158
<SettingsCard
138159
header={{
139160
title: "Apply Coupon",
@@ -272,11 +293,7 @@ export function CouponSection(props: { teamId: string | undefined }) {
272293
});
273294

274295
if (activeCoupon.isPending) {
275-
return (
276-
<div className="flex h-[300px] items-center justify-center rounded-lg border border-border bg-muted/50">
277-
<Spinner className="size-6" />
278-
</div>
279-
);
296+
return <LoadingCouponSection />;
280297
}
281298

282299
const couponData = optimisticCouponData
@@ -296,17 +313,27 @@ export function CouponSection(props: { teamId: string | undefined }) {
296313
}
297314

298315
return (
299-
<ApplyCouponCard
300-
teamId={props.teamId}
301-
onCouponApplied={(coupon) => {
302-
setOptimisticCouponData({
303-
type: "added",
304-
data: coupon,
305-
});
306-
activeCoupon.refetch().then(() => {
307-
setOptimisticCouponData(undefined);
308-
});
309-
}}
310-
/>
316+
<Suspense fallback={<LoadingCouponSection />}>
317+
<ApplyCouponCard
318+
teamId={props.teamId}
319+
onCouponApplied={(coupon) => {
320+
setOptimisticCouponData({
321+
type: "added",
322+
data: coupon,
323+
});
324+
activeCoupon.refetch().then(() => {
325+
setOptimisticCouponData(undefined);
326+
});
327+
}}
328+
/>
329+
</Suspense>
330+
);
331+
}
332+
333+
function LoadingCouponSection() {
334+
return (
335+
<div className="flex h-[300px] items-center justify-center rounded-lg border border-border bg-muted/50">
336+
<Spinner className="size-6" />
337+
</div>
311338
);
312339
}

0 commit comments

Comments
 (0)