Skip to content

Commit

Permalink
feat: Add pricing redirect after login
Browse files Browse the repository at this point in the history
  • Loading branch information
richiemcilroy committed Jan 22, 2025
1 parent 60b7c99 commit 2ed381f
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 26 deletions.
4 changes: 2 additions & 2 deletions apps/web/app/api/settings/billing/subscribe/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function POST(request: NextRequest) {
console.log("Starting subscription process");
const user = await getCurrentUser();
let customerId = user?.stripeCustomerId;
const { priceId } = await request.json();
const { priceId, quantity } = await request.json();

console.log("Received request with priceId:", priceId);
console.log("Current user:", user?.id);
Expand Down Expand Up @@ -59,7 +59,7 @@ export async function POST(request: NextRequest) {
console.log("Creating checkout session for customer:", customerId);
const checkoutSession = await stripe.checkout.sessions.create({
customer: customerId as string,
line_items: [{ price: priceId, quantity: 1 }],
line_items: [{ price: priceId, quantity: quantity }],
mode: "subscription",
success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard/caps?upgrade=true`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
Expand Down
10 changes: 8 additions & 2 deletions apps/web/app/login/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ export function LoginForm() {

useEffect(() => {
const pendingPriceId = localStorage.getItem("pendingPriceId");
const pendingQuantity = localStorage.getItem("pendingQuantity") ?? "1";
if (emailSent && pendingPriceId) {
// Clear the pending price ID
localStorage.removeItem("pendingPriceId");
localStorage.removeItem("pendingQuantity");

// Wait a bit to ensure the user is created
setTimeout(async () => {
Expand All @@ -50,10 +51,15 @@ export function LoginForm() {
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ priceId: pendingPriceId }),
body: JSON.stringify({
priceId: pendingPriceId,
quantity: parseInt(pendingQuantity),
}),
});
const data = await response.json();

console.log(data);

if (data.url) {
window.location.href = data.url;
}
Expand Down
123 changes: 101 additions & 22 deletions apps/web/components/pages/PricingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,44 @@ import { LogoSection } from "./_components/LogoSection";
export const PricingPage = () => {
const [loading, setLoading] = useState(false);
const [isAnnual, setIsAnnual] = useState(true);
const [quantity, setQuantity] = useState(1);
const [initialRender, setInitialRender] = useState(true);
const { push } = useRouter();
const searchParams = useSearchParams();

useEffect(() => {
setInitialRender(false);
const planFromUrl = searchParams.get("plan");
if (planFromUrl) {
planCheckout(planFromUrl);
}
const init = async () => {
setInitialRender(false);
const planFromUrl = searchParams.get("plan");
const next = searchParams.get("next");
const pendingPriceId = localStorage.getItem("pendingPriceId");
const pendingQuantity = localStorage.getItem("pendingQuantity");

if (pendingPriceId && pendingQuantity) {
localStorage.removeItem("pendingPriceId");
localStorage.removeItem("pendingQuantity");

const response = await fetch(`/api/settings/billing/subscribe`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
priceId: pendingPriceId,
quantity: parseInt(pendingQuantity),
}),
});
const data = await response.json();

if (data.url) {
window.location.href = data.url;
}
} else if (planFromUrl) {
planCheckout(planFromUrl);
}
};

init();
}, []);

const planCheckout = async (planId?: string) => {
Expand All @@ -46,12 +74,13 @@ export const PricingPage = () => {
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ priceId: planId }),
body: JSON.stringify({ priceId: planId, quantity }),
});
const data = await response.json();

if (data.auth === false) {
localStorage.setItem("pendingPriceId", planId);
localStorage.setItem("pendingQuantity", quantity.toString());
push(`/login?next=/pricing`);
return;
}
Expand All @@ -68,6 +97,10 @@ export const PricingPage = () => {
};

const proList = [
{
text: "No watermark on videos",
available: true,
},
{
text: "Unlimited cloud storage & Shareable links",
available: true,
Expand Down Expand Up @@ -257,9 +290,10 @@ export const PricingPage = () => {
</p>
</div>
<p className="pl-8">
Screen & Window recording, Local video export, Powerful
video editor (custom background gradients, transitions,
etc) + many more.
Watermark on video, Screen & Window recording, Local video
export, 5 min shareable links, Powerful video editor
(custom background gradients, transitions, etc) + many
more.
</p>
</div>
</CardFooter>
Expand All @@ -274,34 +308,79 @@ export const PricingPage = () => {
<CardHeader>
<CardTitle className="text-3xl text-white">Cap Pro</CardTitle>
<CardDescription className="text-white/80">
For professional use and teams.
For the best Cap experience.
</CardDescription>
<div>
<div>
<h3 className="text-4xl text-white">
{isAnnual ? "$6/mo" : "$9/mo"}
{isAnnual
? `$${6 * quantity}/mo`
: `$${9 * quantity}/mo`}
</h3>
<div>
<p className="text-sm font-medium text-white/80">
{isAnnual
? "per user, billed annually."
: "per user, billed monthly."}
? quantity === 1
? "per user, billed annually."
: `for ${quantity} users, billed annually.`
: quantity === 1
? "per user, billed monthly."
: `for ${quantity} users, billed monthly.`}
</p>
{isAnnual && (
<p className="text-sm text-white/80">
or, $9/month, billed monthly.
<p
className="text-sm text-white/80 cursor-pointer hover:text-white transition-colors"
onClick={() => setIsAnnual(false)}
>
or, ${9 * quantity}/month,{" "}
{quantity === 1
? "per user, "
: `for ${quantity} users, `}
billed monthly.
</p>
)}
</div>
</div>
<div className="flex items-center mt-4 -mb-3 pt-4 border-t-2 border-white/20">
<div className="flex items-center border-t-2 border-white/20 pt-2 mt-2">
<span className="text-xs text-white/80 mr-2">
{isAnnual ? "Switch to monthly" : "Switch to annually"}
Number of users:
</span>
<Switch
checked={!isAnnual}
onCheckedChange={() => setIsAnnual(!isAnnual)}
/>
<div className="flex items-center gap-2">
<Button
variant="secondary"
size="sm"
onClick={() =>
quantity > 1 && setQuantity(quantity - 1)
}
className="px-2 py-0 h-6"
>
-
</Button>
<span className="text-white min-w-[20px] text-center">
{quantity}
</span>
<Button
variant="secondary"
size="sm"
onClick={() => setQuantity(quantity + 1)}
className="px-2 py-0 h-6"
>
+
</Button>
</div>
</div>
<div className="flex flex-col gap-4 mt-2 -mb-4 pt-4 border-t-2 border-white/20">
<div className="flex items-center">
<span className="text-xs text-white/80 mr-2">
{isAnnual
? "Switch to monthly"
: "Switch to annually"}
</span>
<Switch
checked={!isAnnual}
onCheckedChange={() => setIsAnnual(!isAnnual)}
/>
</div>
</div>
</div>
</CardHeader>
Expand All @@ -310,7 +389,7 @@ export const PricingPage = () => {
type="button"
spinner={loading}
onClick={() => planCheckout()}
className="w-full"
className="w-full -mb-4"
size="lg"
variant="white"
>
Expand Down

1 comment on commit 2ed381f

@vercel
Copy link

@vercel vercel bot commented on 2ed381f Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.