Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/app/(auth)/payment/success/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"use client";
import React, { useState, Suspense, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";

import ChefLoader from "@/components/shared/loaders/ChefLoader";
import NotificationModal from "@/components/shared/modal/NotificationModal";
import { useNotification } from "@/hooks/useNotification";
import { updateProfile } from "@/services/auth.service";
import { useAuthStore } from "@/store/useAuthStore";

function PaymentSuccessContent() {
const searchParams = useSearchParams();
const router = useRouter();
const { user, updateUser } = useAuthStore();

const externalReference = searchParams?.get('external_reference')

const {
message,
additionalMessage,
type,
show,
showSuccess,
showError,
clearNotification
} = useNotification();

const [loading, setLoading] = useState(true);

useEffect(() => {
handleSuccess();
}, []);

const handleSuccess = async () => {

if (!externalReference) {
showError("Error", "El link no es válido. Vuelve a intentarlo en unos instantes.");
return;
}

try {
setLoading(true);

const userData = {};

const userResponse = await updateProfile(userData);

if (userResponse) {
userResponse.premium = userResponse?.plan?.id == 2;
updateUser(userResponse)
}

showSuccess(
"¡Listo! Ya sos premium",
"Te enviaremos a la home para que puedas continuar con los beneficios de Cuoco Premium."
);

setTimeout(() => {
router.push("/home");
}, 4000);

} catch (error: any) {
const mainMessage = "Error al procesar el pago";
const backendMsg = error?.message || "Intenta nuevamente en unos instantes";

showError(mainMessage, backendMsg);
} finally {
setLoading(false);
}
}

return (
<div className="min-h-screen bg-[url('/auth/signup.png')] bg-cover bg-no-repeat bg-center flex items-center justify-center px-4 md:px-16">
<div className="flex flex-col items-center justify-start">
{loading && (
<div className="signin-loader-wrapper">
<ChefLoader text="" />
</div>
)}

<NotificationModal
show={show}
onClose={clearNotification}
message={message}
additionalMessage={additionalMessage}
type={type}
/>

</div>
</div>
);
}

export default function PaymentSuccess() {
return (
<Suspense fallback={
<div className="min-h-screen bg-[url('/auth/signup.png')] bg-cover bg-no-repeat bg-center flex items-center justify-center px-4 md:px-16">
<ChefLoader text="" />
</div>
}>
<PaymentSuccessContent />
</Suspense>
);
}
151 changes: 94 additions & 57 deletions src/components/shared/modal/SubscriptionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,47 @@
'use client';

import React from 'react';
import React, { useState } from 'react';
import Modal from '@/components/shared/modal/Modal';
import Button from '@/components/shared/form/Button';
import { SubscriptionModalProps } from '@/types';
import { paymentService } from '@/services/payment.service';
import { useNotification } from '@/hooks/useNotification';
import ChefLoader from '../loaders/ChefLoader';
import { PaymentResponse } from '@/types/payments/payment.types';

export default function SubscriptionModal({ isOpen, onClose,title }: SubscriptionModalProps) {

const {
showError,
} = useNotification();

const [loading, setLoading] = useState(false);

const handleUpgrade = async () => {
try {
setLoading(true);

const response: PaymentResponse = await paymentService.create();

response.checkout_url && window.open(response.checkout_url);
} catch (error: any) {
const mainMessage = "Error al generar el pago";
const backendMsg = "Vuelve a intentarlo en unos instantes";
showError(mainMessage, backendMsg);
} finally {
setLoading(false);
}
}

return (
<Modal
<>
{loading && (
<div className="signin-loader-wrapper">
<ChefLoader text="" />
</div>
)}

<Modal
isOpen={isOpen}
onClose={onClose}
maxWidth="max-w-3xl"
Expand All @@ -28,69 +62,72 @@ export default function SubscriptionModal({ isOpen, onClose,title }: Subscriptio
<h2 className="text-2xl font-semibold text-center text-gray-800">{title}</h2>
</div>
)}
<div className="grid md:grid-cols-2 gap-6">
{/* Plan Básico */}
<div className="bg-gray-100 rounded-xl p-6 flex flex-col gap-4">
<div className="text-center">
<h3 className="text-2xl font-semibold text-[#f37b6a]">Plan Básico</h3>
<p className="text-xl font-bold mt-2">$0 / mes</p>
<p className="text-gray-600 text-sm mt-1">
Ideal para probar la app y cocinar con lo que tenés.
</p>
</div>

<div className="space-y-3 flex-grow">
<div className="flex items-start gap-2">
<p>Acceso a recetas y funcionalidades básicas.</p>
<div className="grid md:grid-cols-2 gap-6">
{/* Plan Básico */}
<div className="bg-gray-100 rounded-xl p-6 flex flex-col gap-4">
<div className="text-center">
<h3 className="text-2xl font-semibold text-[#f37b6a]">Plan Básico</h3>
<p className="text-xl font-bold mt-2">$0 / mes</p>
<p className="text-gray-600 text-sm mt-1">
Ideal para probar la app y cocinar con lo que tenés.
</p>
</div>
<div className="flex items-start gap-2">
<p>Tipo de dieta y nivel de cocina.</p>
</div>
</div>

<Button
variant="secondary"
size="lg"
className="w-full mt-4"
>
Continuar
</Button>
</div>
<div className="space-y-3 flex-grow">
<div className="flex items-start gap-2">
<p>Acceso a recetas y funcionalidades básicas.</p>
</div>
<div className="flex items-start gap-2">
<p>Tipo de dieta y nivel de cocina.</p>
</div>
</div>

{/* Plan Premium */}
<div className="bg-[#ffd4ce] rounded-xl p-6 flex flex-col gap-4">
<div className="text-center">
<h3 className="text-2xl font-semibold text-[#f37b6a]">Plan Premium</h3>
<p className="text-xl font-bold mt-2">$3000 / mes</p>
<p className="text-gray-600 text-sm mt-1">
Para quienes quieren personalización y más recetas.
</p>
<Button
variant="secondary"
size="lg"
className="w-full mt-4"
>
Continuar
</Button>
</div>

<div className="space-y-3 flex-grow">
<div className="flex items-start gap-2">
<p>Acceso al Plan Básico</p>
</div>
<div className="flex items-start gap-2">
<p>Recetas ilimitadas</p>
{/* Plan Premium */}
<div className="bg-[#ffd4ce] rounded-xl p-6 flex flex-col gap-4">
<div className="text-center">
<h3 className="text-2xl font-semibold text-[#f37b6a]">Plan Premium</h3>
<p className="text-xl font-bold mt-2">$3000 / mes</p>
<p className="text-gray-600 text-sm mt-1">
Para quienes quieren personalización y más recetas.
</p>
</div>
<div className="flex items-start gap-2">
<p>Guardar recetas favoritas ilimitadas</p>
</div>
<div className="flex items-start gap-2">
<p>Guardar preferencias y filtro</p>

<div className="space-y-3 flex-grow">
<div className="flex items-start gap-2">
<p>Acceso al Plan Básico</p>
</div>
<div className="flex items-start gap-2">
<p>Recetas ilimitadas</p>
</div>
<div className="flex items-start gap-2">
<p>Guardar recetas favoritas ilimitadas</p>
</div>
<div className="flex items-start gap-2">
<p>Guardar preferencias y filtro</p>
</div>
</div>
</div>

<Button
variant="primary"
size="lg"
className="w-full mt-4"
>
Contratar
</Button>
<Button
variant="primary"
size="lg"
className="w-full mt-4"
onClick={handleUpgrade}
>
Contratar
</Button>
</div>
</div>
</div>
</Modal>
</Modal>

</>
);
}
14 changes: 14 additions & 0 deletions src/services/payment.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { apiClient } from '@/lib/axios.config';

export const paymentService = {

async create(): Promise<any> {
try {
const response = await apiClient.post('/payments');
return response.data;
} catch (error: any) {
const message = error.response?.data?.message || 'Error al crear el pago';
throw new Error(message);
}
}
}
5 changes: 5 additions & 0 deletions src/types/payments/payment.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface PaymentResponse {
external_id: string;
external_reference: string;
checkout_url: string;
}