From 124b2a7e83a46edfd22747278d6ba61cbec41283 Mon Sep 17 00:00:00 2001 From: Peter Karman Date: Wed, 23 Apr 2025 12:19:00 -0700 Subject: [PATCH] feat(subscriptions): add a subscription --- app/api/subscription/route.ts | 46 +++++++++++++ app/components/SubscriptionForm.tsx | 102 ++++++++++++++++++++++++++++ app/lib/subscription-utils.ts | 39 +++++++++++ app/page.tsx | 17 +++++ firestore.rules | 6 ++ 5 files changed, 210 insertions(+) create mode 100644 app/api/subscription/route.ts create mode 100644 app/components/SubscriptionForm.tsx create mode 100644 app/lib/subscription-utils.ts diff --git a/app/api/subscription/route.ts b/app/api/subscription/route.ts new file mode 100644 index 0000000..1f8ca87 --- /dev/null +++ b/app/api/subscription/route.ts @@ -0,0 +1,46 @@ +import { NextResponse } from 'next/server'; +import * as admin from 'firebase-admin'; +import serviceAccount from '../../../dog-voicenator.json'; + +if (!admin.apps.length) { + admin.initializeApp({ + credential: admin.credential.cert(serviceAccount) + }); +} + +export async function POST(request: Request) { + const data = await request.json(); + const { userId, cardNumber, expiryDate, cvv, plan } = data; + + const db = admin.firestore(); + const subscriptionRef = db.collection('subscriptions').doc(userId); + + await subscriptionRef.set({ + cardNumber, + expiryDate, + cvv, + plan, + createdAt: new Date(), + active: true + }); + + return NextResponse.json({ success: true }); +} + +export async function GET(request: Request) { + const url = new URL(request.url); + const userId = url.searchParams.get('userId'); + + if (!userId) { + return NextResponse.json({ error: 'Missing userId' }, { status: 400 }); + } + + const db = admin.firestore(); + const subscriptionDoc = await db.collection('subscriptions').doc(userId).get(); + + if (!subscriptionDoc.exists) { + return NextResponse.json({ error: 'No subscription found' }, { status: 404 }); + } + + return NextResponse.json(subscriptionDoc.data()); +} diff --git a/app/components/SubscriptionForm.tsx b/app/components/SubscriptionForm.tsx new file mode 100644 index 0000000..9c8cec9 --- /dev/null +++ b/app/components/SubscriptionForm.tsx @@ -0,0 +1,102 @@ +'use client' + +import { useState } from 'react' +import { Card, CardContent, CardHeader, CardTitle } from './ui/card' +import { Button } from './ui/button' +import { useAuth } from '../contexts/AuthContext' +import { createSubscription, getSubscription } from '../lib/subscription-utils' + +export default function SubscriptionForm() { + const { user } = useAuth() + const [cardNumber, setCardNumber] = useState('') + const [expiryDate, setExpiryDate] = useState('') + const [cvv, setCvv] = useState('') + const [plan, setPlan] = useState('basic') + const [loading, setLoading] = useState(false) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + + try { + await createSubscription({ + userId: user!.uid, + cardNumber, + expiryDate, + cvv, + plan + }) + + window.location.reload() + } catch (error) { + console.error('Error creating subscription:', error) + } finally { + setLoading(false) + } + } + + const checkSubscription = async () => { + const data = await getSubscription(user!.uid) + console.log('Subscription data:', data) + } + + return ( + + + Subscribe to Premium + + +
+
+ + setCardNumber(e.target.value)} + className="w-full p-2 border rounded" + placeholder="1234 5678 9012 3456" + /> +
+
+ + setExpiryDate(e.target.value)} + className="w-full p-2 border rounded" + placeholder="MM/YY" + /> +
+
+ + setCvv(e.target.value)} + className="w-full p-2 border rounded" + placeholder="123" + /> +
+
+ + +
+ + +
+
+
+ ) +} diff --git a/app/lib/subscription-utils.ts b/app/lib/subscription-utils.ts new file mode 100644 index 0000000..2d11a7b --- /dev/null +++ b/app/lib/subscription-utils.ts @@ -0,0 +1,39 @@ +interface SubscriptionData { + userId: string; + cardNumber: string; + expiryDate: string; + cvv: string; + plan: string; +} + +export async function createSubscription(data: SubscriptionData) { + const response = await fetch('/api/subscription', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error('Failed to create subscription'); + } + + return response.json(); +} + +export async function getSubscription(userId: string) { + const response = await fetch(`/api/subscription?userId=${userId}`); + + if (!response.ok) { + throw new Error('Failed to get subscription'); + } + + return response.json(); +} + +export function isSubscribed(userId: string): Promise { + return fetch(`/api/subscription?userId=${userId}`) + .then(response => response.ok) + .catch(() => false); +} diff --git a/app/page.tsx b/app/page.tsx index c96c191..5c3e5eb 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -12,6 +12,8 @@ import { useToast } from './hooks/use-toast' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from './components/ui/dialog' import LoginForm from './components/LoginForm' import RateLimitInfo from './components/RateLimitInfo' +import SubscriptionForm from './components/SubscriptionForm' +import { isSubscribed } from './lib/subscription-utils' export default function Home() { const { user, signIn, signOut } = useAuth() @@ -26,6 +28,7 @@ export default function Home() { const [error, setError] = useState(null) const [showConsentDialog, setShowConsentDialog] = useState(false) const [showLoginDialog, setShowLoginDialog] = useState(false) + const [isUserSubscribed, setIsUserSubscribed] = useState(false) useEffect(() => { if (user) { @@ -33,6 +36,8 @@ export default function Home() { try { const voices = await getVoices(user.uid) setVoices(voices) + const subscribed = await isSubscribed(user.uid) + setIsUserSubscribed(subscribed) } catch (error) { console.error('Error loading voices:', error) toast({ @@ -171,6 +176,12 @@ export default function Home() { Share the giggles with your friends and family as your furry friend becomes the star of their own viral videos! 🌟

+ {user && !isUserSubscribed && ( +
+

Upgrade to Premium! 🌟

+ +
+ )}
🔒 @@ -359,6 +370,12 @@ export default function Home() { 😿 {error}
)} + {user && !isUserSubscribed && ( +
+

Upgrade to Premium! 🌟

+ +
+ )}
)} diff --git a/firestore.rules b/firestore.rules index 2de7404..5cb67b7 100644 --- a/firestore.rules +++ b/firestore.rules @@ -22,6 +22,12 @@ service cloud.firestore { allow write: if request.auth != null && request.auth.uid == userId; } + // Allow anyone to read subscription data + match /subscriptions/{userId} { + allow read: if true; + allow write: if true; + } + // Default deny match /{document=**} { allow read, write: if false;