Skip to content

Commit

Permalink
Merge pull request #129 from evgongora/feat/add-payments-fix-auth
Browse files Browse the repository at this point in the history
feat/fix: payments implementation && fix auth
  • Loading branch information
evgongora authored Feb 3, 2025
2 parents ee531f1 + e44247f commit 66582fd
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 58 deletions.
181 changes: 167 additions & 14 deletions frontend/src/app/awaken-pro/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,66 @@
"use client"

import { FilledButton } from "@/components/ui/FilledButton"
import { Crown, CheckCircle2 } from "lucide-react"
import { Crown, CheckCircle2, Sparkles } from "lucide-react"
import { cn } from "@/lib/utils"
import { MiniKit, tokenToDecimals, Tokens, PayCommandInput } from '@worldcoin/minikit-js'
import { useRouter } from "next/navigation"
import { useState } from "react"
import { motion } from "framer-motion"

export default function AwakenProPage() {
const router = useRouter()
const [isProcessing, setIsProcessing] = useState(false)

const handleUpgrade = async () => {
setIsProcessing(true)
try {
if (!MiniKit.isInstalled()) {
window.open('https://worldcoin.org/download-app', '_blank')
return
}

// Initiate payment
const res = await fetch('/api/initiate-payment', {
method: 'POST',
})
const { id } = await res.json()

// Configure payment
const payload: PayCommandInput = {
reference: id,
to: process.env.NEXT_PUBLIC_PAYMENT_ADDRESS!, // Your whitelisted address
tokens: [
{
symbol: Tokens.WLD,
token_amount: tokenToDecimals(3.50, Tokens.WLD).toString(),
}
],
description: 'Upgrade to Awaken Pro - 1 Month Subscription'
}

const { finalPayload } = await MiniKit.commandsAsync.pay(payload)

if (finalPayload.status === 'success') {
// Verify payment
const confirmRes = await fetch('/api/confirm-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ payload: finalPayload }),
})

const payment = await confirmRes.json()
if (payment.success) {
router.push('/settings?upgrade=success')
}
}
} catch (error) {
console.error('Payment error:', error)
} finally {
setIsProcessing(false)
}
}

return (
<div className="min-h-screen bg-neutral-bg">

Expand All @@ -20,11 +76,16 @@ export default function AwakenProPage() {
</div>


<div className="max-w-md mx-auto px-6">
<div className={cn(
"bg-brand-secondary rounded-[30px] p-8 relative overflow-hidden",
"shadow-[0_10px_20px_rgba(0,0,0,0.2),_0_6px_6px_rgba(0,0,0,0.25)]"
)}>
<div className="max-w-md mx-auto px-6 mb-8">
<motion.div
className={cn(
"bg-brand-secondary rounded-[30px] p-8 relative overflow-hidden",
"shadow-[0_10px_20px_rgba(0,0,0,0.2),_0_6px_6px_rgba(0,0,0,0.25)]"
)}
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.5 }}
>

<div className="flex items-center justify-between mb-8">
<div className="flex items-center gap-4">
Expand All @@ -37,7 +98,7 @@ export default function AwakenProPage() {
</div>

<div className="mb-8">
<div className="text-4xl font-bold text-white mb-2">$4.99</div>
<div className="text-4xl font-bold text-white mb-2">3.50 WLD</div>
<div className="text-slate-300 text-sm">Per month, billed monthly</div>
</div>

Expand All @@ -57,14 +118,106 @@ export default function AwakenProPage() {
))}
</div>

<FilledButton
variant="default"
size="lg"
className="w-full bg-accent-red hover:bg-accent-red/90"
<motion.div
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
animate={{
y: [0, -4, 0],
boxShadow: [
"0 8px 16px rgba(227,108,89,0.3)",
"0 12px 24px rgba(227,108,89,0.4)",
"0 8px 16px rgba(227,108,89,0.3)"
]
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
className="relative"
>
Upgrade to Pro
</FilledButton>
</div>
{/* Pulsing background effect */}
<div className="absolute -inset-1 bg-gradient-to-r from-accent-red/20 to-[#FF8066]/20 rounded-xl blur-xl animate-pulse" />

{/* Floating particles effect */}
<div className="absolute inset-0 overflow-hidden">
{[...Array(3)].map((_, i) => (
<motion.div
key={i}
className="absolute w-2 h-2 bg-white/30 rounded-full"
animate={{
y: [-10, -40],
x: Math.sin(i * 45) * 20,
opacity: [0, 1, 0],
scale: [0, 1.5, 0]
}}
transition={{
duration: 2,
repeat: Infinity,
delay: i * 0.4,
ease: "easeOut"
}}
style={{
left: `${25 + (i * 25)}%`,
bottom: "0"
}}
/>
))}
</div>

<FilledButton
variant="default"
size="lg"
onClick={handleUpgrade}
disabled={isProcessing}
className={cn(
"w-full bg-gradient-to-r from-accent-red to-[#FF8066]",
"hover:from-accent-red/90 hover:to-[#FF8066]/90",
"shadow-[0_8px_16px_rgba(227,108,89,0.3)]",
"hover:shadow-[0_12px_24px_rgba(227,108,89,0.4)]",
"transform transition-all duration-300",
"relative overflow-hidden",
"border border-white/10",
"h-16" // Increased height for better visibility
)}
>
{/* Shimmer effect */}
<div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent"
style={{
animation: "shimmer 2s infinite",
backgroundSize: "200% 100%"
}}
/>

<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/5 to-transparent animate-shimmer" />

<div className="relative z-10 flex items-center justify-center gap-3">
<Sparkles className="w-6 h-6 animate-pulse" />
<span className="font-bold text-lg tracking-wide">
{isProcessing ? (
<div className="flex items-center gap-2">
<span>Processing</span>
<motion.div
animate={{ opacity: [1, 0.5, 1] }}
transition={{ duration: 1.5, repeat: Infinity }}
>
...
</motion.div>
</div>
) : (
<motion.span
animate={{ scale: [1, 1.02, 1] }}
transition={{ duration: 2, repeat: Infinity }}
>
Upgrade to Pro
</motion.span>
)}
</span>
</div>
</FilledButton>
</motion.div>
</motion.div>
</div>
</div>
)
Expand Down
16 changes: 13 additions & 3 deletions frontend/src/app/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export default function Register() {
lastName: "",
email: "",
age: "",
country: "CR" as CountryCode
country: "CR" as CountryCode,
wallet_address: ""
});

const [error, setError] = useState("");
Expand All @@ -26,17 +27,26 @@ export default function Register() {
useEffect(() => {
if (!userId) {
router.push('/sign-in');
return;
}

// Set the wallet address from URL parameter
setFormData(prev => ({
...prev,
wallet_address: userId
}));
}, [userId, router]);

const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setIsSubmitting(true);
try {
// Get wallet address and username from MiniKit
const walletAddress = MiniKit.user?.walletAddress;
// Get username from MiniKit if available
const username = MiniKit.user?.username;

// Use the wallet address from form data (set from URL parameter)
const walletAddress = formData.wallet_address;

if (!walletAddress) {
throw new Error('No wallet address provided');
}
Expand Down
99 changes: 60 additions & 39 deletions frontend/src/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,40 @@ interface SettingItem {
export default function SettingsPage() {
const router = useRouter()
const [loading, setLoading] = useState(true)
const [subscriptionData, setSubscriptionData] = useState<{
next_payment_date: string | null;
isPro: boolean;
}>({
next_payment_date: null,
isPro: false
});

useEffect(() => {
const fetchSettings = async () => {
try {
const response = await fetch('/api/settings')
await response.json()
// Fetch subscription data
const subscriptionResponse = await fetch('/api/user/subscription');
if (subscriptionResponse.ok) {
const data = await subscriptionResponse.json();
setSubscriptionData({
next_payment_date: data.next_payment_date || null,
isPro: data.isPro || false
});
}
} catch (error) {
console.error('Error fetching settings:', error)
console.error('Error fetching settings:', error);
} finally {
setLoading(false)
setLoading(false);
}
}

fetchSettings()
}, [])

const handleUpgradeClick = () => {
router.push('/awaken-pro');
};

const handleLogout = async () => {
try {
// Clear verification session data
Expand Down Expand Up @@ -75,7 +93,7 @@ export default function SettingsPage() {
>
<div className="text-center space-y-3">
<Settings className="h-10 w-10 mx-auto text-[#E36C59]" />
<h1 className="text-center text-white text-3xl sm:text-4xl md:text-5xl font-bold font-spaceGrotesk leading-tight sm:leading-[50px] mb-3 sm:mb-4">
<h1 className="text-3xl sm:text-4xl font-bold text-slate-100 tracking-tight">
Settings
</h1>
</div>
Expand All @@ -87,8 +105,10 @@ export default function SettingsPage() {
transition={{ delay: 0.3, duration: 0.4 }}
className="inline-flex items-center gap-2 bg-white/10 backdrop-blur-sm px-6 py-2 rounded-full"
>
<Crown className="w-5 h-5 text-[#e36c59]" />
<span className="text-white/90 font-medium">Premium Member</span>
{subscriptionData.isPro && <Crown className="w-5 h-5 text-[#e36c59]" />}
<span className="text-white/90 font-medium">
{subscriptionData.isPro ? 'Premium Member' : 'Basic Member'}
</span>
</motion.div>
</p>
</motion.div>
Expand All @@ -108,44 +128,45 @@ export default function SettingsPage() {
transition={{ duration: 0.3, delay: 0.3 }}
>
<MembershipCard
expiryDate="March 15, 2024"
isActive={true}
cost={3}
expiryDate={subscriptionData.next_payment_date || '0 days'}
isActive={subscriptionData.isPro}
cost={3.50}
/>

{/* Upgrade Button with Enhanced Styling */}
<div className="mt-4 relative">
<div className="absolute -inset-3 bg-accent-red/20 blur-xl rounded-2xl animate-pulse"></div>
<FilledButton
variant="default"
size="lg"
className={cn(
"w-full bg-accent-red hover:bg-accent-red/90",
"transform transition-all duration-300 hover:scale-[1.02]",
"shadow-[0_10px_20px_rgba(227,108,89,0.3)]",
"hover:shadow-[0_14px_28px_rgba(227,108,89,0.4)]",
"relative z-10"
)}
onClick={() => router.push('/awaken-pro')}
>
<div className="flex items-center justify-center gap-2">
<Crown className="w-5 h-5" />
<span>Upgrade to Awaken Pro</span>
{!subscriptionData.isPro && (
<div className="mt-4 relative">
<div className="absolute -inset-3 bg-accent-red/20 blur-xl rounded-2xl animate-pulse"></div>
<FilledButton
variant="default"
size="lg"
className={cn(
"w-full bg-accent-red hover:bg-accent-red/90",
"transform transition-all duration-300 hover:scale-[1.02]",
"shadow-[0_10px_20px_rgba(227,108,89,0.3)]",
"hover:shadow-[0_14px_28px_rgba(227,108,89,0.4)]",
"relative z-10"
)}
onClick={handleUpgradeClick}
>
<div className="flex items-center justify-center gap-2">
<Crown className="w-5 h-5" />
<span>Upgrade to Awaken Pro</span>
</div>
</FilledButton>

<div className="relative z-10 mt-3 mb-4 text-center py-2 px-4">
<p className="text-sm font-medium">
<span className="text-neutral-black">Unlock</span>
<span className="text-accent-red"> advanced features </span>
<span className="text-neutral-black">and</span>
<span className="text-accent-red"> exclusive content </span>
</p>
</div>
</FilledButton>

{/* Enhanced Promotional Text */}
<div className="relative z-10 mt-3 mb-4 text-center py-2 px-4">
<p className="text-sm font-medium">
<span className="text-neutral-black">Unlock</span>
<span className="text-accent-red"> advanced features </span>
<span className="text-neutral-black">and</span>
<span className="text-accent-red"> exclusive content </span>
</p>
</div>
</div>
)}
</motion.div>

{/* Settings Items */}
<motion.div
className="space-y-4"
initial={{ y: 20, opacity: 0 }}
Expand Down
Loading

0 comments on commit 66582fd

Please sign in to comment.