Skip to content

Commit 5a310df

Browse files
committed
payments screen
1 parent fb230de commit 5a310df

7 files changed

Lines changed: 120 additions & 98 deletions

File tree

packages/dashboard/src/app/(layout)/subscription/components/PlansComparison.tsx

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,22 @@ import {useState} from "react";
44
import {Button} from "@/components/ui/button";
55
import {Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle} from "@/components/ui/card";
66
import {CheckCircle, XCircle} from "lucide-react";
7-
import {usePaddle} from "@/hooks/usePaddle";
8-
import {createClient} from "@/utils/supabase/server";
7+
import {pro_monthly_id, pro_yearly_id, usePaddle} from "@/hooks/usePaddle";
8+
import {Tables} from "@/utils/supabase/database.types";
99

10-
const pro_monthly_id = "pri_01jv5ewwzjg4ab4dzzgm5xc1d5"
11-
const pro_yearly_id = "pri_01jv5ey9ahq6xb8es0v14z741p"
1210

1311
type Props = {
14-
initialSubscription: any
12+
subscription: (Tables<'subscriptions'> & Tables<'customers'>) | null;
1513
customerData: {
1614
email: string
1715
}
1816
}
1917

20-
export default async function PlansComparison({initialSubscription, customerData}:Props) {
18+
export default function PlansComparison({subscription, customerData}: Props) {
2119

22-
const [subscription, setSubscription] = useState(initialSubscription);
23-
const [billingCycle, setBillingCycle] = useState(initialSubscription.billingCycle);
24-
const {paddle, error, openCheckout} = usePaddle();
20+
const [billingCycle, setBillingCycle] = useState("monthly");
21+
const {paddle, error, handleUpgrade} = usePaddle();
2522

26-
// Handle upgrade
27-
const handleUpgrade = async (priceId: string) => {
28-
try {
29-
openCheckout({
30-
customer: customerData,
31-
items: [{
32-
quantity:1,
33-
priceId
34-
}]
35-
})
36-
} catch (error) {
37-
console.error("Error upgrading subscription:", error);
38-
}
39-
};
4023

4124
// Toggle billing cycle
4225
const changeBillingCycle = (cycle: any) => {
@@ -65,7 +48,6 @@ export default async function PlansComparison({initialSubscription, customerData
6548
</div>
6649

6750
<div className="grid md:grid-cols-2 gap-6">
68-
{/* Free Plan */}
6951
<Card>
7052
<CardHeader>
7153
<CardTitle>Free Plan</CardTitle>
@@ -118,7 +100,7 @@ export default async function PlansComparison({initialSubscription, customerData
118100
<ul className="space-y-2">
119101
<li className="flex items-center">
120102
<CheckCircle className="mr-2 h-5 w-5 text-green-500"/>
121-
<span>10,000 API requests per month</span>
103+
<span><span className="font-semibold">10,000</span> API requests per month</span>
122104
</li>
123105
<li className="flex items-center">
124106
<CheckCircle className="mr-2 h-5 w-5 text-green-500"/>
@@ -135,10 +117,12 @@ export default async function PlansComparison({initialSubscription, customerData
135117
</ul>
136118
</CardContent>
137119
<CardFooter>
138-
{subscription.type === "pro" ? (
120+
{subscription?.subscription_status === "active" ? (
139121
<Button disabled variant="outline" className="w-full">Current Plan</Button>
140122
) : (
141-
<Button onClick={()=>handleUpgrade(billingCycle === "yearly" ? pro_yearly_id: pro_monthly_id)} className="w-full">Upgrade Now</Button>
123+
<Button
124+
onClick={() => handleUpgrade(billingCycle === "yearly" ? pro_yearly_id : pro_monthly_id, customerData.email)}
125+
className="w-full">Upgrade Now</Button>
142126
)}
143127
</CardFooter>
144128
</Card>

packages/dashboard/src/app/(layout)/subscription/components/SubscriptionCard.tsx

Lines changed: 29 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,35 @@ import { Badge } from "@/components/ui/badge";
55
import { Progress } from "@/components/ui/progress";
66
import { Button } from "@/components/ui/button";
77
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
8+
import {Tables} from "@/utils/supabase/database.types";
9+
import {PLANS} from "@/utils/constants";
10+
import {format} from "date-fns";
11+
import {pro_monthly_id, pro_yearly_id, usePaddle} from "@/hooks/usePaddle";
812

9-
export default function SubscriptionCard({ initialSubscription }) {
10-
const [subscription, setSubscription] = useState(initialSubscription);
13+
type Props = {
14+
subscription: (Tables<'subscriptions'> & Tables<'customers'>) | null;
15+
usages: number
16+
customerData: {
17+
email: string
18+
}
19+
}
1120

12-
// Calculate usage percentage
13-
const usagePercentage = Math.round((subscription.requests.used / subscription.requests.total) * 100);
21+
export default function SubscriptionCard({ subscription, usages, customerData }: Props) {
22+
const {paddle, error, handleUpgrade} = usePaddle();
1423

15-
// Handle upgrade
16-
const handleUpgrade = async () => {
17-
try {
18-
// In a real app, you would call an API endpoint
19-
// await fetch('/api/subscription/upgrade', { method: 'POST' });
24+
const isPro = subscription?.subscription_status === "active";
25+
const maxRequestsPerMonth = isPro ? PLANS.PRO.REQUESTS_PER_MONTH : PLANS.BASIC.REQUESTS_PER_MONTH
2026

21-
setSubscription({
22-
...subscription,
23-
type: "pro",
24-
requests: {
25-
used: subscription.requests.used,
26-
total: 10000
27-
},
28-
renewalDate: "June 14, 2025",
29-
billingCycle: "monthly"
30-
});
31-
} catch (error) {
32-
console.error("Error upgrading subscription:", error);
33-
}
34-
};
27+
const usagePercentage = Math.min((usages / maxRequestsPerMonth) * 100, 100)
3528

3629
// Handle cancel
3730
const handleCancel = async () => {
38-
if (window.confirm("Are you sure you want to cancel your subscription?")) {
39-
try {
40-
// In a real app, you would call an API endpoint
41-
// await fetch('/api/subscription/cancel', { method: 'POST' });
42-
43-
setSubscription({
44-
...subscription,
45-
type: "free",
46-
requests: {
47-
used: subscription.requests.used,
48-
total: 100
49-
}
50-
});
51-
} catch (error) {
52-
console.error("Error canceling subscription:", error);
53-
}
31+
try {
32+
paddle?.Retain.initCancellationFlow({
33+
subscriptionId: subscription?.subscription_id!,
34+
})
35+
} catch (error) {
36+
console.error("Error canceling subscription:", error);
5437
}
5538
};
5639

@@ -60,35 +43,35 @@ export default function SubscriptionCard({ initialSubscription }) {
6043
<div>
6144
<CardTitle className="text-xl flex items-center">
6245
<div>
63-
{subscription.type === "pro" ? "Pro Plan" : "Free Plan"}
46+
{isPro ? "Pro Plan" : "Free Plan"}
6447
</div>
6548
<div className="flex items-center">
66-
{subscription.type === "pro" && (
49+
{isPro && (
6750
<Badge className="ml-3 bg-green-500 hover:bg-green-600">Active</Badge>
6851
)}
6952
</div>
7053
</CardTitle>
7154
<CardDescription>
72-
{subscription.type === "pro"
73-
? `Renews on ${subscription.renewalDate} (${subscription.billingCycle === "monthly" ? "Monthly" : "Yearly"})`
55+
{isPro
56+
? `Renews on ${format(new Date(subscription?.next_billed_at), "dd MMM yyyy")} (${subscription?.billing_cycle ? "Monthly" : "Yearly"})`
7457
: "Limited features"}
7558
</CardDescription>
7659
</div>
7760
<div className="flex space-x-2">
78-
{subscription.type === "pro" ? (
61+
{isPro ? (
7962
<Button variant="outline" onClick={handleCancel}>
8063
Cancel Subscription
8164
</Button>
8265
) : (
83-
<Button onClick={handleUpgrade}>Upgrade</Button>
66+
<Button onClick={() => handleUpgrade(pro_monthly_id, customerData.email)}>Upgrade</Button>
8467
)}
8568
</div>
8669
</CardHeader>
8770
<CardContent className="pt-4">
8871
<div className="mb-2 flex justify-between items-center">
8972
<div className="text-sm text-gray-500">API Requests</div>
9073
<div className="text-sm font-medium">
91-
{subscription.requests.used} / {subscription.requests.total}
74+
{usages} / {maxRequestsPerMonth}
9275
</div>
9376
</div>
9477
<Progress value={usagePercentage} className="h-2" />

packages/dashboard/src/app/(layout)/subscription/page.tsx

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,15 @@ import { Separator } from "@/components/ui/separator";
22
import SubscriptionCard from "./components/SubscriptionCard";
33
import PlansComparison from "./components/PlansComparison";
44
import {createClient} from "@/utils/supabase/server";
5+
import { getSubscription } from "@/utils/paddle/getSubscription";
56

67
export default async function SubscriptionPage() {
78
const supabase = await createClient()
89
const {data: {user}} = await supabase.auth.getUser()
9-
// In a real app, you would fetch this data server-side
10-
const userData = {
11-
subscription: {
12-
type: "free", // "free" or "pro"
13-
requests: {
14-
used: 2345,
15-
total: 10000
16-
},
17-
renewalDate: "June 14, 2025",
18-
billingCycle: "yearly" // "monthly" or "yearly"
19-
}
20-
};
10+
const { data } = await supabase.from('usages').select().eq('user_id',user?.id!).maybeSingle()
11+
const usages = data?.calls_count
12+
const subscription = await getSubscription()
13+
2114

2215
return (
2316
<div className="container mx-auto">
@@ -28,16 +21,16 @@ export default async function SubscriptionPage() {
2821
</div>
2922
<Separator className="my-4" />
3023
<div className="space-y-6">
31-
{/* Current Subscription Section */}
3224
<div>
3325
<h2 className="text-xl font-semibold mb-4">Current Subscription</h2>
34-
<SubscriptionCard initialSubscription={userData.subscription} />
26+
<SubscriptionCard usages={usages} subscription={subscription} customerData={{
27+
email: user?.email!,
28+
}} />
3529
</div>
3630

37-
{/* Plans Comparison Section */}
3831
<div>
3932
<h2 className="text-xl font-semibold mb-4">Plans Comparison</h2>
40-
<PlansComparison initialSubscription={userData.subscription} customerData={{
33+
<PlansComparison subscription={subscription} customerData={{
4134
email: user?.email!,
4235
}} />
4336
</div>

packages/dashboard/src/hooks/usePaddle.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import {initializePaddle, Paddle} from "@paddle/paddle-js";
66
const environment = env('NEXT_PUBLIC_PADDLE_ENV') as "sandbox";
77
const token = env('NEXT_PUBLIC_PADDLE_CLIENT_TOKEN')!;
88

9+
export const pro_monthly_id = "pri_01jvj6fb5bmpvyke0c6v23sv2a"
10+
export const pro_yearly_id = "pri_01jvj6gh0wenwg2drzdk3dd6y2"
11+
12+
913
export function usePaddle() {
1014
const [paddle, setPaddle] = useState<Paddle | null>(null);
1115
const [error, setError] = useState<Error | null>(null);
@@ -42,5 +46,24 @@ export function usePaddle() {
4246
[paddle]
4347
);
4448

45-
return { paddle, openCheckout, error } as const;
49+
const handleUpgrade = async (priceId: string, email: string) => {
50+
try {
51+
openCheckout({
52+
customer: {
53+
email
54+
},
55+
items: [{
56+
quantity: 1,
57+
priceId
58+
}],
59+
settings:{
60+
successUrl: window.location.href
61+
}
62+
})
63+
} catch (error) {
64+
console.error("Error upgrading subscription:", error);
65+
}
66+
};
67+
68+
return { paddle, handleUpgrade, error } as const;
4669
}

packages/dashboard/src/utils/data/plans.ts

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import {createClient} from "@/utils/supabase/server";
2+
3+
export async function getSubscription() {
4+
// Create a Supabase client
5+
const supabase = await createClient()
6+
7+
// Get the current authenticated user
8+
const { data: { user } } = await supabase.auth.getUser();
9+
10+
// If no user is found, return null
11+
if (!user) {
12+
return null;
13+
}
14+
15+
// First get the customer that matches the user's email
16+
const { data: customer } = await supabase
17+
.from('customers')
18+
.select('*')
19+
.eq('email', user.email)
20+
.single();
21+
22+
// If no customer is found, return null
23+
if (!customer) {
24+
return null;
25+
}
26+
27+
// Get the subscription for this customer
28+
const { data: subscription } = await supabase
29+
.from('subscriptions')
30+
.select('*')
31+
.eq('customer_id', customer.customer_id)
32+
.maybeSingle();
33+
34+
// If no subscription is found, return null
35+
if (!subscription) {
36+
return null;
37+
}
38+
39+
// Return the subscription with the customer data
40+
return {
41+
...subscription,
42+
customer,
43+
};
44+
}

packages/dashboard/src/utils/supabase/database.types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ export type Database = {
337337
}
338338
subscriptions: {
339339
Row: {
340+
billing_cycle: string
340341
created_at: string
341342
customer_id: string
342343
next_billed_at: string
@@ -348,6 +349,7 @@ export type Database = {
348349
updated_at: string
349350
}
350351
Insert: {
352+
billing_cycle: string
351353
created_at?: string
352354
customer_id: string
353355
next_billed_at: string
@@ -359,6 +361,7 @@ export type Database = {
359361
updated_at?: string
360362
}
361363
Update: {
364+
billing_cycle?: string
362365
created_at?: string
363366
customer_id?: string
364367
next_billed_at?: string

0 commit comments

Comments
 (0)