Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
new-interface/added-payment
  • Loading branch information
pruthvi-yb committed Sep 25, 2024
commit 164b4c86af6f4bf202bb037eb8d05f6ab3e6f6e2
59 changes: 59 additions & 0 deletions app/api/create-topup-session/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { NextRequest, NextResponse } from "next/server";
import { withAuth } from "@/utils/withAuth";
import { createClient } from "@/utils/supabase/server";
import { User } from "@supabase/supabase-js";
import { STRIPE_PRICE_IDS } from "@/utils/constants";

const PEARAI_SERVER_URL = process.env.PEARAI_SERVER_URL;

async function createTopUpSession(request: NextRequest & { user: User }) {
const supabase = createClient();

try {
const { amount } = await request.json();
const priceId = STRIPE_PRICE_IDS.TOP_UP_CREDITS[amount];
if (!priceId) {
return NextResponse.json({ error: "Invalid amount" }, { status: 400 });
}
const {
data: { session },
} = await supabase.auth.getSession();

if (!session) {
return NextResponse.json(
{ error: "Failed to get session" },
{ status: 401 },
);
}

const token = session.access_token;
const url = `${PEARAI_SERVER_URL}/payment/create-topup-session`;

const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ priceId, amount }),
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.error || `HTTP error! status: ${response.status}`,
);
}

const data = await response.json();
return NextResponse.json({ url: data.url });
} catch (error) {
console.error("Error creating top-up session:", error);
return NextResponse.json(
{ error: "Failed to create top-up session" },
{ status: 500 },
);
}
}

export const POST = withAuth(createTopUpSession);
13 changes: 6 additions & 7 deletions components/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,20 @@ export default function DashboardPage({
</div>
<div className="grid gap-6 lg:grid-cols-2">
<ProfileCard user={user} />
{/* Below commented out until we implement Free Trial */}
{subscription ? (
<FreeTrialCard
loading={loading}
usage={usage}
openAppQueryParams={openAppQueryParams}
/>
) : (
<SubscriptionCard
subscription={subscription}
usage={usage}
openAppQueryParams={openAppQueryParams}
user={user}
loading={loading}
/>
) : (
<FreeTrialCard
loading={loading}
usage={usage}
openAppQueryParams={openAppQueryParams}
/>
)}
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions components/dashboard/subscription-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { Info } from "lucide-react";
import { UsageType } from "../dashboard";
import { toast } from "sonner";
import { useUpgradeSubscription } from "@/hooks/useUpgradeSubscription";
import BuyCreditsModal from "../buy-credits-modal";
import TopUpModal from "../topup-modal";

type SubscriptionCardProps = {
subscription: Subscription | null;
Expand Down Expand Up @@ -313,7 +313,7 @@ export default function SubscriptionCard({
</p>
</div>
<div className="mt-4">
<BuyCreditsModal />
<TopUpModal />
</div>
</CardContent>
</div>
Expand Down
5 changes: 0 additions & 5 deletions components/pricing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
} from "./ui/dropdown-menu";
import { Info } from "lucide-react";
import Spinner from "./ui/spinner";
import Link from "next/link";
import Footer from "./footer";

interface ExtendedPricingTierProps extends PricingTierProps {
Expand Down Expand Up @@ -328,10 +327,6 @@ const PricingTier: React.FC<ExtendedPricingTierProps> = ({
)}
</>
)}

{/* <button className="bg-primary-800 py-2 px-4 w-full rounded-full text-sm font-medium hover:bg-primary-800"> */}
{/* Get Started */}
{/* </button> */}
</CardFooter>
</div>
</Card>
Expand Down
28 changes: 13 additions & 15 deletions components/buy-credits-modal.tsx → components/topup-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,36 @@ import {
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { CreditCard, Zap, Check } from "lucide-react";
import { CreditCard, Check } from "lucide-react";
import { useTopUpCheckout } from "@/hooks/useTopUpCheckout";
import { useUser } from "@/hooks/useUser";

const REQUEST_OPTIONS = [
{ amount: 5, requests: 233, popular: false },
{ amount: 10, requests: 466, popular: true },
{ amount: 15, requests: 700, popular: false },
];

export default function BuyRequestsModal() {
export default function TopUpModal() {
const [selectedAmount, setSelectedAmount] = useState(10);
const [isOpen, setIsOpen] = useState(false);
const { user } = useUser();
const { handleTopUpCheckout, isSubmitting } = useTopUpCheckout(user);

const handleBuyRequests = () => {
const selectedOption = REQUEST_OPTIONS.find(
(option) => option.amount === selectedAmount,
);
if (selectedOption) {
console.log(
`Purchased ${selectedOption.requests} requests for $${selectedOption.amount}`,
);
setIsOpen(false);
}
const handleBuyRequests = async () => {
await handleTopUpCheckout(selectedAmount);
setIsOpen(false);
};

return (
<AlertDialog open={isOpen} onOpenChange={setIsOpen}>
<AlertDialogTrigger asChild>
<Button variant="outline">Buy Extra Requests</Button>
<Button variant="outline">Top Up Credits</Button>
</AlertDialogTrigger>
<AlertDialogContent className="max-w-md select-none sm:max-w-[425px]">
<AlertDialogHeader>
<AlertDialogTitle className="text-2xl font-bold text-secondary-800 dark:text-white-50">
Buy Extra Requests
Top Up Credits
</AlertDialogTitle>
<AlertDialogDescription className="text-base text-gray-700 dark:text-gray-600">
Enhance your AI experience with additional requests. Choose a
Expand Down Expand Up @@ -113,8 +110,9 @@ export default function BuyRequestsModal() {
<AlertDialogAction
className="bg-secondary-600 hover:bg-secondary-600/90 sm:w-1/2"
onClick={handleBuyRequests}
disabled={isSubmitting}
>
Continue
{isSubmitting ? "Processing..." : "Continue"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
Expand Down
49 changes: 49 additions & 0 deletions hooks/useTopUpCheckout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useState } from "react";
import { User } from "@supabase/supabase-js";
import { toast } from "sonner";
import { useRouter } from "next/navigation";

export const useTopUpCheckout = (user: User | null) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter();

const handleTopUpCheckout = async (amount: number) => {
if (!user) {
toast.error("Please log in to top up credits.");
router.push("/signin");
return;
}

if (isSubmitting) return;

setIsSubmitting(true);
try {
const response = await fetch("/api/create-topup-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ amount }),
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData?.error || `HTTP error! status: ${response.status}`,
);
}

const { url } = await response.json();

if (url) {
window.location.href = url;
} else {
toast.error("Failed to start top-up process. Please try again.");
}
} catch (error) {
toast.error("An error occurred. Please try again.");
} finally {
setIsSubmitting(false);
}
};

return { handleTopUpCheckout, isSubmitting };
};
26 changes: 26 additions & 0 deletions hooks/useUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect, useState } from "react";
import { User } from "@supabase/supabase-js";
import { createClient } from "@/utils/supabase/client";

export const useUser = () => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchUser = async () => {
const supabase = createClient();
const { data, error } = await supabase.auth.getUser();
if (error) {
console.error("Error fetching user:", error);
setUser(null);
} else {
setUser(data.user);
}
setLoading(false);
};

fetchUser();
}, []);

return { user, loading };
};
3 changes: 1 addition & 2 deletions next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
21 changes: 21 additions & 0 deletions utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ const NEXT_PUBLIC_STRIPE_ANNUAL_PRICE_ID = "price_1PpZUO08N4O93LU5FYFUyh43";
const NEXT_PUBLIC_STRIPE_ANNUAL_PRICE_ID_TEST =
"price_1PZUSi08N4O93LU5UVdlkfp2";

const NEXT_PUBLIC_STRIPE_TOP_UP_5_CREDITS_ID = "price_1Q2nvy09pmf2gJFPOCmPjfmB";
const NEXT_PUBLIC_STRIPE_TOP_UP_5_CREDITS_ID_TEST =
"price_1Q2nvy09pmf2gJFPOCmPjfmB";
const NEXT_PUBLIC_STRIPE_TOP_UP_10_CREDITS_ID = "price_id_for_10_credits";
const NEXT_PUBLIC_STRIPE_TOP_UP_10_CREDITS_ID_TEST =
"price_id_for_10_credits_test";
const NEXT_PUBLIC_STRIPE_TOP_UP_15_CREDITS_ID = "price_id_for_15_credits";
const NEXT_PUBLIC_STRIPE_TOP_UP_15_CREDITS_ID_TEST =
"price_id_for_15_credits_test";

export const STRIPE_PRICE_IDS = {
WAITLIST: TEST_MODE_ENABLED
? NEXT_PUBLIC_STRIPE_WAITLIST_PRICE_ID_TEST
Expand All @@ -32,6 +42,17 @@ export const STRIPE_PRICE_IDS = {
ANNUAL: TEST_MODE_ENABLED
? NEXT_PUBLIC_STRIPE_ANNUAL_PRICE_ID_TEST
: NEXT_PUBLIC_STRIPE_ANNUAL_PRICE_ID,
TOP_UP_CREDITS: {
5: TEST_MODE_ENABLED
? NEXT_PUBLIC_STRIPE_TOP_UP_5_CREDITS_ID_TEST
: NEXT_PUBLIC_STRIPE_TOP_UP_5_CREDITS_ID,
10: TEST_MODE_ENABLED
? NEXT_PUBLIC_STRIPE_TOP_UP_10_CREDITS_ID_TEST
: NEXT_PUBLIC_STRIPE_TOP_UP_10_CREDITS_ID,
15: TEST_MODE_ENABLED
? NEXT_PUBLIC_STRIPE_TOP_UP_15_CREDITS_ID_TEST
: NEXT_PUBLIC_STRIPE_TOP_UP_15_CREDITS_ID,
} as Record<number, string>,
};

export const PRICING_TIERS: {
Expand Down