Skip to content

Commit 76ab41a

Browse files
authored
stripe 2 - pricing (#137)
* Trigger Build * prettier dep * plan * link * check user * link * css * fix welcome text * vars * copy
1 parent c457f78 commit 76ab41a

File tree

8 files changed

+236
-192
lines changed

8 files changed

+236
-192
lines changed

web/app/blog/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default async function Blog() {
3737
<CardTitle>
3838
<Link
3939
href={link}
40-
className='text-blue-600 hover:text-blue-800'
40+
className='hover:underline'
4141
>
4242
{post.title}
4343
</Link>

web/app/pricing/page.tsx

+41-19
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,53 @@
11
import { Suspense } from 'react';
22

33
import { PricingBody } from '@/app/pricing/PricingBody';
4-
import CheckoutButton from '@/components/CheckoutButton';
5-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
4+
import { Plan, PricingCard } from '@/components/PricingCard';
5+
6+
const FREE_SUMMARY_LIMIT = 50;
7+
const PRO_PLAN_MONTHLY_PRICE = 1;
8+
9+
const pricingOptions: Plan[] = [
10+
{
11+
id: 'free',
12+
title: 'Free Plan',
13+
description:
14+
'Get the essentials for free, perfect for quick summaries without breaking the bank (or... actually, just free!).',
15+
features: [
16+
`Store up to ${FREE_SUMMARY_LIMIT} brilliant summaries`,
17+
'Standard-speed summary magic 🪄',
18+
'Access to basic models',
19+
],
20+
note: 'Great for casually curious users or folks who just love the word "free".',
21+
isPremium: false,
22+
},
23+
{
24+
id: 'pro',
25+
title: 'Pro Plan',
26+
description:
27+
'Step it up a notch! Get faster, better summaries with fancy premium models and priority support that actually answers you.',
28+
features: [
29+
'Limitless summary vault',
30+
'Lightning-fast processing speed (say goodbye to waiting) ⚡️',
31+
'Access to pro models for enhanced accuracy',
32+
],
33+
note: 'Perfect for power-users and professionals who need summaries on the fly!',
34+
isPremium: true,
35+
price: `$${PRO_PLAN_MONTHLY_PRICE}/month (yep, just a buck!)`,
36+
},
37+
];
638

739
export default function Pricing() {
840
return (
9-
<div className='flex min-h-screen flex-col items-center justify-center bg-gray-100'>
41+
<div className='flex min-h-screen flex-col items-center py-8 sm:py-16'>
1042
<Suspense fallback={null}>
1143
<PricingBody />
1244
</Suspense>
13-
<Card className='mx-auto w-full max-w-lg rounded-lg bg-white p-6 shadow-lg'>
14-
<CardHeader>
15-
<CardTitle className='text-center text-2xl font-semibold'>
16-
Premium Plan
17-
</CardTitle>
18-
</CardHeader>
19-
<CardContent>
20-
<p className='mb-4 text-center text-gray-600'>
21-
Unlock exclusive features and support by subscribing to our premium
22-
plan.
23-
</p>
24-
<div className='flex justify-center'>
25-
<CheckoutButton priceId={process.env.NEXT_PUBLIC_TEST_ID} />
26-
</div>
27-
</CardContent>
28-
</Card>
45+
46+
<div className='mx-auto flex max-w-4xl flex-wrap justify-center gap-8'>
47+
{pricingOptions.map((plan) => (
48+
<PricingCard key={plan.id} plan={plan} />
49+
))}
50+
</div>
2951
</div>
3052
);
3153
}

web/components/CheckoutButton.tsx

+24-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,24 @@ import { Loader2 } from 'lucide-react';
66
import { useState } from 'react';
77

88
import { Button } from '@/components/ui/button';
9+
import { useUser } from '@/lib/hooks';
10+
import { cn } from '@/lib/utils';
911

1012
const stripePromise = loadStripe(
1113
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string
1214
) as Promise<Stripe>;
1315

14-
export default function CheckoutButton({ priceId }) {
16+
interface CheckoutButtonProps {
17+
priceId: string;
18+
className?: string;
19+
}
20+
21+
export default function CheckoutButton({
22+
priceId,
23+
className,
24+
}: CheckoutButtonProps) {
1525
const [loading, setLoading] = useState(false);
26+
const user = useUser();
1627

1728
const handleCheckout = async () => {
1829
setLoading(true);
@@ -33,8 +44,18 @@ export default function CheckoutButton({ priceId }) {
3344
};
3445

3546
return (
36-
<Button onClick={handleCheckout} disabled={loading} className='w-full'>
37-
{loading ? <Loader2 className='animate-spin' /> : 'Subscribe Now'}
47+
<Button
48+
onClick={handleCheckout}
49+
disabled={loading || !user}
50+
className={cn('w-full', className)}
51+
>
52+
{loading ? (
53+
<Loader2 className='animate-spin' />
54+
) : user ? (
55+
'Checkout'
56+
) : (
57+
'Sign In to Purchase'
58+
)}
3859
</Button>
3960
);
4061
}

web/components/PricingCard.tsx

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import CheckoutButton from '@/components/CheckoutButton';
2+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
3+
4+
export type Plan = {
5+
id: string;
6+
title: string;
7+
description: string;
8+
features: string[];
9+
note: string;
10+
isPremium: boolean;
11+
price?: string;
12+
};
13+
14+
type PricingCardProps = {
15+
plan: Plan;
16+
};
17+
18+
export function PricingCard({ plan }: PricingCardProps) {
19+
const { title, description, features, note, isPremium, price } = plan;
20+
21+
return (
22+
<Card className='flex w-full max-w-sm flex-col rounded-lg bg-white p-6 shadow-lg'>
23+
<CardHeader>
24+
<CardTitle className='text-center text-2xl font-semibold'>
25+
{title}
26+
</CardTitle>
27+
</CardHeader>
28+
<CardContent className='flex flex-col'>
29+
<p className='mb-4 text-gray-600'>{description}</p>
30+
<ul className='mb-4 space-y-2 text-gray-600'>
31+
{features.map((feature, index) => (
32+
<li key={index} className='flex items-center'>
33+
<span className='mr-2'>✔️</span>
34+
<span>{feature}</span>
35+
</li>
36+
))}
37+
</ul>
38+
<p className='mb-4 text-sm text-gray-500'>{note}</p>
39+
{isPremium && (
40+
<div className='flex flex-col'>
41+
<span className='mb-2 text-lg font-bold text-gray-700'>
42+
{price}
43+
</span>
44+
<CheckoutButton
45+
priceId={process.env.NEXT_PUBLIC_TEST_ID as string}
46+
className='w-full max-w-xs rounded-lg py-2 text-white hover:underline'
47+
/>
48+
</div>
49+
)}
50+
</CardContent>
51+
</Card>
52+
);
53+
}

web/components/auth/LoginDialog.tsx

+30-36
Original file line numberDiff line numberDiff line change
@@ -35,42 +35,36 @@ export default function LoginDialog() {
3535

3636
return (
3737
<Dialog>
38-
{user ? (
39-
<p>Welcome, {user.email}</p>
40-
) : (
41-
<>
42-
<DialogTrigger asChild>
43-
<Button variant='outline'>Sign In</Button>
44-
</DialogTrigger>
45-
<DialogContent className='sm:max-w-[425px]'>
46-
<DialogHeader>
47-
<DialogTitle>Sign In</DialogTitle>
48-
<DialogDescription>
49-
Sign in with your Google account to continue.
50-
</DialogDescription>
51-
</DialogHeader>
52-
<div className='flex items-center justify-center py-4'>
53-
<Button
54-
onClick={handleGoogleLogin}
55-
className='flex w-full items-center justify-center gap-2'
56-
variant='outline'
57-
>
58-
<GoogleIcon className='h-6 w-6' />
59-
Sign in with Google
60-
</Button>
61-
</div>
62-
<DialogFooter className='mt-4 text-center'>
63-
<Link
64-
href={AppConfig.SITE_MAP.TERMS}
65-
target='_blank'
66-
className='text-sm hover:underline'
67-
>
68-
By signing in, you agree to our terms and conditions.
69-
</Link>
70-
</DialogFooter>
71-
</DialogContent>
72-
</>
73-
)}
38+
<DialogTrigger asChild>
39+
<Button variant='outline'>Sign In</Button>
40+
</DialogTrigger>
41+
<DialogContent className='sm:max-w-[425px]'>
42+
<DialogHeader>
43+
<DialogTitle>Sign In</DialogTitle>
44+
<DialogDescription>
45+
Sign in with your Google account to continue.
46+
</DialogDescription>
47+
</DialogHeader>
48+
<div className='flex items-center justify-center py-4'>
49+
<Button
50+
onClick={handleGoogleLogin}
51+
className='flex w-full items-center justify-center gap-2'
52+
variant='outline'
53+
>
54+
<GoogleIcon className='h-6 w-6' />
55+
Sign in with Google
56+
</Button>
57+
</div>
58+
<DialogFooter className='mt-4 text-center'>
59+
<Link
60+
href={AppConfig.SITE_MAP.TERMS}
61+
target='_blank'
62+
className='text-sm hover:underline'
63+
>
64+
By signing in, you agree to our terms and conditions.
65+
</Link>
66+
</DialogFooter>
67+
</DialogContent>
7468
</Dialog>
7569
);
7670
}

0 commit comments

Comments
 (0)