Skip to content

Commit

Permalink
Merge pull request #144 from wizelineacademy/stripe-payments
Browse files Browse the repository at this point in the history
feat: Added subscription with stripe
  • Loading branch information
JulioEmmmanuel authored Jun 6, 2024
2 parents eae7ebd + d80cea6 commit ecb4bd8
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 86 deletions.
67 changes: 35 additions & 32 deletions src/app/(pages)/(main)/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,21 @@ import { useRouter } from 'next/navigation'

const Home = () => {
const router = useRouter()

// Define an array of suggestions
const suggestions = [
'¡Come más frutas y verduras!',
'¡Sal a dar un paseo!',
'¡Toma más agua!',
'¡Hoy practica Mindfullness por 10 minutos!',
'¡Recuerda dormir 7-8 horas diarias!',
]

const [isOpen, setIsOpen] = useState(true)
const [isOpen2, setIsOpen2] = useState(true)
const [userData, setUserData] = useState<HealthData | null>(null)
const [user, setUser] = useState<UserAdmin[] | null>(null)
const [randomSuggestion, setRandomSuggestion] = useState(suggestions[0])

const getData = async () => {
try {
Expand Down Expand Up @@ -84,26 +95,12 @@ const Home = () => {
setIsOpen2(!isOpen2)
}

// Define an array of suggestions
const suggestions = [
'¡Come más frutas y verduras!',
'¡Sal a dar un paseo!',
'¡Toma más agua!',
'¡Hoy practica Mindfullness por 10 minutos!',
'¡Recuerda dormir 7-8 horas diarias!',
]

// Function to generate a random suggestion
const generateRandomSuggestion = () => {
const randomIndex = Math.floor(Math.random() * suggestions.length)
return suggestions[randomIndex]
}

// State to hold the current random suggestion
const [randomSuggestion, setRandomSuggestion] = useState(
generateRandomSuggestion(),
)

// Function to generate a new random suggestion
const handleGenerateSuggestion = () => {
const newRandomSuggestion = generateRandomSuggestion()
Expand All @@ -129,7 +126,9 @@ const Home = () => {
Recomendación del Día
</h2>
<div className='mx-4 mt-4 flex h-[120px] w-[185px] items-center justify-between rounded-3xl bg-color-home3 px-5'>
<p className='text-lg text-[#1D154A]'>{randomSuggestion}</p>
<p className='text-lg text-[#1D154A]'>
{randomSuggestion && randomSuggestion}
</p>
<button
onClick={handleGenerateSuggestion}
className='cursor-pointer border-none bg-transparent'
Expand Down Expand Up @@ -161,25 +160,18 @@ const Home = () => {
<div id='Centro' className='flex flex-col'>
<Link href='/home/generalData'>
<div
className={`mx-4 mt-4 w-[190px] rounded-3xl bg-color-home6 transition-colors duration-300 ease-in-out hover:bg-color-home5`}
className={`mx-4 mt-4 w-56 rounded-3xl bg-color-home6 transition-colors duration-300 ease-in-out hover:bg-color-home5`}
>
<div className='flex flex-col items-center justify-center'>
<span className='flex flex-row'>
<h2 className='ml-4 mt-4 w-[120px] text-2xl font-bold text-white'>
<h2 className='w-34 ml-4 mt-4 text-2xl font-bold text-white'>
Mis datos de salud
</h2>
<div className='mt-4 lg:hidden'>
<button
onClick={toggleContent}
className='focus:outline-none'
>
<FontAwesomeIcon
icon={isOpen ? faAngleDown : faAngleRight}
size='2x'
color='white'
className='transform transition-transform duration-300'
/>
</button>
></button>
</div>
</span>

Expand Down Expand Up @@ -236,15 +228,26 @@ const Home = () => {
<Link
href='/home/dashboard'
id='Dashboard'
className='mt-4 flex h-[120px] w-[232px] flex-row justify-between rounded-3xl bg-color-home7 transition-colors duration-300 ease-in-out hover:cursor-pointer hover:bg-color-home2'
className='z-10 mt-4 flex h-12 w-[232px] flex-row justify-between rounded-3xl bg-color-home7 transition-colors duration-300 ease-in-out hover:cursor-pointer hover:bg-color-home2'
>
<h2 className='mt-2 w-[120px] pl-4 text-2xl font-bold text-color-home6'>
Mi Dashboard de Salud
Dashboard
</h2>
<div className='mr-8 mt-4 flex flex-col'>
<FaAngleRight size={48} color='#144154' />
<div className='mr-2 mt-3 flex flex-col'>
<FaAngleRight size={24} color='#144154' />
</div>
</Link>

<FaCircle color='white' size={32} />
<Link
href='/home/subscription'
id='Dashboard'
className='z-10 mt-4 flex h-12 w-[232px] flex-row justify-between rounded-3xl bg-color-home6 transition-colors duration-300 ease-in-out hover:cursor-pointer hover:bg-color-home2'
>
<h2 className='mt-2 w-[200px] pl-4 text-2xl font-bold text-white'>
Mi suscripción
</h2>
<div className='mr-2 mt-3 flex flex-col'>
<FaAngleRight size={24} color='#fff' />
</div>
</Link>

Expand Down Expand Up @@ -299,13 +302,13 @@ const Home = () => {

<button
id='Challenge'
className='ml-4 mt-4 flex h-16 w-56 items-center justify-between rounded-full bg-color-home6 px-4 transition-colors duration-300 ease-in-out hover:cursor-pointer hover:bg-color-home5'
className='mt-4 flex h-16 w-60 items-center justify-between rounded-full bg-color-home6 px-4 transition-colors duration-300 ease-in-out hover:cursor-pointer hover:bg-color-home5'
onClick={handleChallengeLink}
>
<span className='ml-3 text-lg font-bold text-white'>
Retos y Logros
</span>
<FaAngleRight size={48} color='#fff' className='mb-2 ml-4' />
<FaAngleRight size={28} color='#fff' className='mb-2 ml-4' />
</button>
</div>
</div>
Expand Down
128 changes: 128 additions & 0 deletions src/app/(pages)/(main)/home/subscription/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
'use client'
import { Plan } from '@/src/data/datatypes/payment'
import { plans } from '@/src/data/plans'
import axios from 'axios'
import { NextPage } from 'next'
import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'

const PricingPage: NextPage = () => {
interface Subscription {
plan: string
status: string
end?: number
}

const [subscription, setSubscription] = useState<Subscription | null>(null)

const getSubscriptionData = async () => {
try {
const res = await axios.get('/api/subscription')
const data = res.data
if (data.message === 'No subscription') {
setSubscription(null)
} else {
setSubscription(data)
}
} catch (error) {
console.log(error)
}
}

const formatExpirationDate = (date: number) => {
const dateObject = new Date(date * 1000)
const dateString = dateObject.toLocaleString()
const dateDay = dateString.split(',')[0]
return dateDay
}

useEffect(() => {
getSubscriptionData()
}, [])

return (
<div className='flex min-h-screen w-full flex-col items-center justify-center'>
<div className='mx-auto max-w-4xl py-8'>
{!subscription && (
<h2 className='mb-4 text-center text-2xl font-bold text-color-home6'>
No tienes una suscripción activa
</h2>
)}
{subscription && (
<h2 className='mb-4 text-center text-2xl font-bold text-color-home6'>
Tienes una subscripción de {subscription.plan}
</h2>
)}
{subscription &&
subscription?.status === 'trialing' &&
subscription.end && (
<>
<h3 className='mx-4 mb-4 text-center text-lg text-black'>
Actualmente te encuentras en modo de prueba, contrata un plan
para no perder el acceso a la aplicación
</h3>
<h3 className='mx-4 mb-4 text-center text-lg text-black'>
Tu subscripción expira el{' '}
{formatExpirationDate(subscription.end)}
</h3>
</>
)}

<h1 className='mx-4 mb-4 text-center text-2xl font-bold text-color-home6'>
Contrata una subscripción a Vita
</h1>
<div className='grid grid-cols-1 gap-4 md:grid-cols-3'>
{plans.map((plan, index) => (
<PlanCard key={index} plan={plan} />
))}
</div>
</div>
</div>
)
}

const PlanCard: React.FC<{ plan: Plan }> = ({ plan }) => {
const router = useRouter()

const processPayment = async () => {
try {
const res = await axios.post('/api/stripe/payment', {
priceId: plan.priceId,
allowTrial: plan.allowTrial,
})
const data = res.data
router.push(data.url)
} catch (error) {
console.log(error)
}
}

return (
<div className='flex h-full transform flex-col justify-between rounded-md border bg-color-home6 p-4 text-white shadow-md transition duration-300 ease-in-out hover:scale-105'>
<div>
<h2 className='mb-2 text-xl font-semibold text-white'>{plan.name}</h2>
<p className='mb-4 text-gray-300'>${plan.price}/mes</p>
<ul className='mb-4 list-disc pl-5 text-white'>
{plan.features.map((feature, index) => (
<li key={index} className='mb-2'>
{feature}
</li>
))}
</ul>
{plan.name === 'Bienestar Plus' && (
<h2 className='mb-5 text-lg font-bold'>Pruébalo gratis por 7 días</h2>
)}
</div>
<div className='mt-auto'>
<button
className={`w-full transform rounded-md bg-blue-500 px-4 py-2 text-white transition duration-300 hover:scale-105 hover:bg-green-600 focus:outline-none focus:ring focus:ring-green-400`}
onClick={processPayment}
>
Seleccionar
</button>
</div>
</div>
)
}

export default PricingPage
54 changes: 2 additions & 52 deletions src/app/(pages)/plan/page.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,10 @@
'use client'
import { Plan } from '@/src/data/datatypes/payment'
import { plans } from '@/src/data/plans'
import axios from 'axios'
import { NextPage } from 'next'
import { useRouter } from 'next/navigation'

interface Plan {
name: string
price: number
features: string[]
priceId: string
allowTrial: boolean
}

const plans: Plan[] = [
{
name: 'Bienestar Básico',
price: 199,
features: [
'31 rutinas de ejercicio y 300 recetas de comida al mes.',
'90 detecciones de comida al mes.',
'Metas de ejercicio, nutrición y sueño.',
'Blog de salud generado por inteligencia artificial',
'Red Social para compartir progreso y apoyar a otros.',
'Perfil Médico',
],
priceId: 'price_1PODCEA5dyQt5UTQT1A5yBVQ',
allowTrial: false,
},
{
name: 'Bienestar Plus',
price: 299,
features: [
'100 rutinas de ejercicio y 300 recetas de comida al mes.',
'300 detecciones de comida al mes.',
'Todos los beneficios del plan básico',
'Chatbot de salud en la app.',
'Recordatorios automáticos en Whatsapp.',
'Retos mensuales e insignias por logros.',
],
priceId: 'price_1PODQFA5dyQt5UTQ9ejvVNjG',
allowTrial: true,
},
{
name: 'Bienestar Total',
price: 699,
features: [
'300 rutinas de ejercicio y 300 recetas de comida al mes.',
'1200 detecciones de comida al mes.',
'Todos los beneficios del plan plus.',
'Chatbot por Whatsapp.',
'Acceso prioritario a las nuevas funciones en desarrollo.',
],
priceId: 'price_1PODRUA5dyQt5UTQqsDYIfuQ',
allowTrial: false,
},
]

const PricingPage: NextPage = () => {
return (
<div className='flex min-h-screen flex-col items-center justify-center bg-gradient-custom'>
Expand Down
53 changes: 53 additions & 0 deletions src/app/api/subscription/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { NextResponse } from 'next/server'
import { db } from '@/src/db/drizzle'
import { user } from '@/src/db/schema/schema'
import { eq } from 'drizzle-orm'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/src/lib/auth/authOptions'
import { stripe } from '@/src/lib/stripe/stripe'
import { plans } from '@/src/data/plans'

export async function GET() {
const session = await getServerSession(authOptions)
if (!session) {
return NextResponse.json('Unauthorized', { status: 404 })
}

try {
const existingUser = await db
.select()
.from(user)
.where(eq(user.idUser, session.user?.id))
.limit(1)

//find stripe subscription
const subscriptionId = existingUser[0].membership
if (!subscriptionId) {
return NextResponse.json({ message: 'No subscription' }, { status: 200 })
}

const subscription = await stripe.subscriptions.retrieve(subscriptionId)

if (
subscription.status !== 'active' &&
subscription.status !== 'trialing'
) {
return NextResponse.json({ message: 'No subscription' }, { status: 200 })
}

const subscriptionData = subscription.items.data[0].price.id
const planType = plans.find((plan) => plan.priceId === subscriptionData)

return NextResponse.json(
{
plan: planType?.name,
status: subscription.status,
end: subscription.trial_end,
},
{ status: 200 },
)
} catch (error) {
console.log(error)
return NextResponse.json('Error processing registration', { status: 400 })
}
}
2 changes: 1 addition & 1 deletion src/components/landing/Landing_About.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const About: React.FC = () => {
<div className='mt-2 h-0.5 w-60 bg-rose-300 md:w-80'></div>
</h1>

<h2 className='mb-5 w-[350px] text-center text-2xl font-light leading-loose md:w-[450px] md:text-4xl'>
<h2 className='mb-5 w-full px-4 text-center text-2xl font-light leading-loose sm:w-[350px] md:w-[500px] md:text-4xl'>
<span className='text-7xl font-bold text-red-300'>VITA </span>
es una aplicación para monitorear tu salud a todo momento y recibir
asesoramiento a través de inteligencia artificial y expertos en la
Expand Down
Loading

0 comments on commit ecb4bd8

Please sign in to comment.