diff --git a/.env b/.env index 2dd239f..cc2aa69 100644 --- a/.env +++ b/.env @@ -1,2 +1,6 @@ REACT_APP_AUTH0_DOMAIN=dev-lrsnvgepbp6l4a8d.us.auth0.com -REACT_APP_AUTH0_CLIENT_ID=w6GzmHDSuiU6cOu1NrDR4Av6P9aHXbty \ No newline at end of file +REACT_APP_AUTH0_CLIENT_ID=w6GzmHDSuiU6cOu1NrDR4Av6P9aHXbty + +# REACT_APP_API_URL= https://api.nodecraft.me +REACT_APP_API_URL= http://localhost:4000 +FRONTEND_URL= https://web.nodecraft.me \ No newline at end of file diff --git a/src/App.js b/src/App.jsx similarity index 57% rename from src/App.js rename to src/App.jsx index d890f35..6067899 100644 --- a/src/App.js +++ b/src/App.jsx @@ -2,7 +2,15 @@ import './App.css'; import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { useAuth0 } from '@auth0/auth0-react'; -import { generateInvoice } from './services/invoiceService.js'; +import {redirect, useNavigate} from 'react-router-dom'; +import { + fetchUser, + fetchFixtures, + signUpUser, + logInUser, + addMoneyToWallet as addMoneyService, + viewMyBonuses as viewMyBonusesService, +} from './services/apiService.jsx'; function App() { const { loginWithRedirect, logout, isAuthenticated, user, getAccessTokenSilently } = useAuth0(); @@ -19,146 +27,13 @@ function App() { const [result, setResult] = useState('home'); const [quantity, setQuantity] = useState(1); const [showAddMoneyModal, setShowAddMoneyModal] = useState(false); - const [authAction, setAuthAction] = useState(null); - const fixturesPerPage = 12; - - const removeDuplicateFixtures = (fixtures) => { - const uniqueFixtures = []; - const fixtureIds = new Set(); - - for (const fixture of fixtures) { - if (!fixtureIds.has(fixture.fixture_id)) { - fixtureIds.add(fixture.fixture_id); - uniqueFixtures.push(fixture); - } - } + const [authAction, setAuthAction] = useState(() => { + return localStorage.getItem('authAction') || null; + }); + const navigate = useNavigate(); + const apiUrl = process.env.REACT_APP_API_URL; - return uniqueFixtures; - }; -// eslint-disable-next-line - const fetchUser = async () => { - try { - const encodedUserId = localStorage.getItem('userId'); - const token = await getAccessTokenSilently(); - const response = await axios.get(`https://api.nodecraft.me/users/${encodedUserId}`, - { - headers: { - Authorization: `Bearer ${token}` - } - }); - setWalletBalance(response.data.wallet); - } catch (error) { - console.error('Error fetching wallet balance:', error); - } - }; -// eslint-disable-next-line - const fetchFixtures = async () => { - try { - const token = await getAccessTokenSilently(); - const response = await axios.get('https://api.nodecraft.me/fixtures', - { - headers: { - Authorization: `Bearer ${token}` - } - }); - const uniqueFixtures = removeDuplicateFixtures(response.data.data); - setFixtures(uniqueFixtures); - setFilteredFixtures(uniqueFixtures); - } catch (error) { - console.error('Error fetching fixtures:', error); - } - }; - - const generateLongUserId = () => { - const timestamp = Date.now(); - const highPrecision = Math.floor(performance.now() * 1000000); - const randomPart = Math.floor(Math.random() * 1000000000); - return `${timestamp}-${highPrecision}-${randomPart}`; - }; - // eslint-disable-next-line - const signUpUser = async () => { - try { - const token = await getAccessTokenSilently(); - const newUserId = generateLongUserId(); - - await axios.post('https://api.nodecraft.me/users', { - id: newUserId, - username: user.nickname, - email: user.email, - password: "Nohaypassword", - wallet: 0.0 - }, - { - headers: { - Authorization: `Bearer ${token}` - }, - } - ); - localStorage.setItem('userId', newUserId); - fetchUser(); - fetchFixtures(); - } catch (error) { - console.error('Error signing up user:', error); - } - }; - - const logInUser = async () => { - try { - let userId = localStorage.getItem('userId'); - if (!userId) { - const token = await getAccessTokenSilently(); - const response = await axios.get('https://api.nodecraft.me/users', { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - const users = response.data.users; - const existingUser = users.find((u) => u.email === user.email); - - if (existingUser) { - userId = existingUser.id; - localStorage.setItem('userId', userId); - fetchUser(); - fetchFixtures(); - } else { - console.error('User not found.'); - alert('User not found. Please sign up.'); - } - } - } catch (error) { - console.error('Error logging in user:', error); - } - }; - - const addMoneyToWallet = async () => { - const amount = prompt('Enter the amount to add to your wallet:'); - const parsedAmount = parseInt(amount, 10); - - if (!isNaN(parsedAmount) && parsedAmount > 0) { - try { - const userId = localStorage.getItem('userId'); - - const token = await getAccessTokenSilently(); - const response = await axios.patch( - `https://api.nodecraft.me/users/${userId}/wallet`, - { amount: parsedAmount }, - { - headers: { - Authorization: `Bearer ${token}` - } - } - ); - setWalletBalance(response.data.wallet); - alert(`Added $${parsedAmount} to your wallet.`); - } catch (error) { - console.error('Error adding money to wallet:', error); - alert('Failed to add money to wallet.'); - } - } else { - alert('Invalid amount entered. Please enter a valid integer.'); - } -}; + const fixturesPerPage = 12; const handleBuyBonusClick = (fixture) => { if (walletBalance >= 1000) { @@ -170,104 +45,90 @@ function App() { }; const buyBonus = async (fixture, result, quantity) => { - const cost = 1000 * quantity; const encodedUserId = localStorage.getItem('userId'); - - if (walletBalance >= cost && fixture.bonos >= quantity) { - setShowBuyModal(false); - setShowProcessingModal(true); - - try { - const token = await getAccessTokenSilently(); - const response = await axios.post( - `https://api.nodecraft.me/fixtures/${fixture.fixture_id}/compra`, - { - userId: encodedUserId, - result: result, - quantity: quantity - }, - { - headers: { - Authorization: `Bearer ${token}` - } - } - ); - - setShowProcessingModal(false); - - if (response.data.error) { - alert(response.data.error); - } else { - - // Agregado generación de boleta post compra - const userData = { - name: user.nickname, - email: user.email - }; - - const matchData = { - teams: fixture.teams, - date: fixture.date, - amount: cost // El valor depende de la cantidad - }; - - // Se genera el URL de la boleta y además se informa la ubicación - try { - const pdfUrl = await generateInvoice(userData, matchData); - alert(`Compra exitosa. Descarga tu boleta aquí: ${pdfUrl}. Ubicación: ${response.data.location.city}`); - fetchUser(); - fetchFixtures(); - } catch (error) { - alert('Error generando la boleta.'); - } - } - } catch (error) { - setShowProcessingModal(false); - alert('Hubo un error al procesar la compra.'); - } - } else { - alert('Fondos insuficientes o bonos no disponibles.'); - } - }; - const viewMyBonuses = async () => { - const userId = localStorage.getItem('userId'); - - if (!userId) { - alert('No se pudo encontrar el ID del usuario.'); + if (!encodedUserId) { + alert('Usuario no encontrado.'); return; } try { const token = await getAccessTokenSilently(); - const response = await axios.get(`https://api.nodecraft.me/requests/${userId}`, { - headers: { - Authorization: `Bearer ${token}` + console.log("mira tu") + const response = await axios.post( + `${apiUrl}/fixtures/${fixture.fixture_id}/compra`, + { + userId: encodedUserId, + result: result, + quantity: quantity + }, + { + headers: { + Authorization: `Bearer ${token}` + } + } + ) + + // del post me debería llegar un message: "Compra iniciada con éxito", un url y un token + console.log("obtuvimos el token") + console.log("url:", response.data.url) + console.log('token:', response.data.token) + + if (response.data.url && response.data.token) { + navigate('/confirm-purchase', { + state: { + url: response.data.url, + token: response.data.token, + amount: quantity, + fixture: fixture, + result: result, + locationInfo: response.data.location, + request_id: response.data.request_id + } + }); + + if (response.data.error) { + alert(response.data.error); } - }); - - if (response.data && response.data.requests) { - setBonuses(response.data.requests); - setShowModal(true); } else { - alert('No se encontraron bonuses para este usuario.'); + alert('Error al iniciar la transacción.'); } } catch (error) { - console.error('Error al obtener los bonuses:', error); - alert('Error al obtener los bonuses.'); + console.error('Error al realizar la compra:', error); + alert('Hubo un error al procesar la compra.'); } }; // eslint-disable-next-line useEffect(() => { const initializeUser = async () => { - if (isAuthenticated && authAction) { + if (isAuthenticated) { try { - if (authAction === 'signup') { - await signUpUser(); - } else if (authAction === 'login') { - await logInUser(); + let userId = localStorage.getItem('userId'); + if (!userId) { + // Si no hay userId en el localStorage, buscarlo del backend + const token = await getAccessTokenSilently(); + const apiUrl = process.env.REACT_APP_API_URL; + const response = await axios.get(`${apiUrl}/users`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + const users = response.data.users; + const existingUser = users.find(user => user.email === user.email); + if (existingUser) { + userId = existingUser.id; + localStorage.setItem('userId', userId); + } else { + // Si el usuario no existe en la base de datos, crearlo + userId = await signUpUser(user, getAccessTokenSilently); + } } + const wallet = await fetchUser(userId, getAccessTokenSilently); + setWalletBalance(wallet); + const uniqueFixtures = await fetchFixtures(getAccessTokenSilently); + setFixtures(uniqueFixtures); + setFilteredFixtures(uniqueFixtures); } catch (error) { console.error('Error initializing user:', error); } @@ -290,7 +151,7 @@ function App() { }); }; - + // UseEffect para aplicar los filtros useEffect(() => { const applyFilters = () => { let filtered = fixtures; @@ -324,22 +185,74 @@ function App() { const paginate = (pageNumber) => setCurrentPage(pageNumber); + // Función para añadir dinero a la wallet + const handleAddMoneyToWallet = async () => { + const amount = prompt('Enter the amount to add to your wallet:'); + const parsedAmount = parseInt(amount, 10); + + if (!isNaN(parsedAmount) && parsedAmount > 0) { + try { + const newBalance = await addMoneyService(parsedAmount, getAccessTokenSilently); + setWalletBalance(newBalance); + alert(`Added $${parsedAmount} to your wallet.`); + } catch (error) { + alert('Failed to add money to wallet.'); + } + } else { + alert('Invalid amount entered. Please enter a valid integer.'); + } + }; + + // Función para ver mis bonos + const handleViewBonuses = async () => { + const userId = localStorage.getItem('userId'); + + if (!userId) { + alert('No se pudo encontrar el ID del usuario.'); + return; + } + + try { + const fetchedBonuses = await viewMyBonusesService(userId, getAccessTokenSilently); + setBonuses(fetchedBonuses); + setShowModal(true); + } catch (error) { + alert('Error al obtener los bonuses.'); + } + }; + + const handleLogout = () => { + // Eliminar userId del localStorage + localStorage.removeItem('userId'); + + // Llamar a la función logout + logout({ returnTo: window.location.origin }); + // logout({ returnTo: 'http://localhost:3000/' }); // debugging + + }; + + const redirectToMyRequests = () => { + navigate('/my-requests'); + } + + console.log("isAuthenticated", isAuthenticated) + return (
{isAuthenticated ? ( <> {user.name} -

Welcome,

-

Wallet Balance: ${walletBalance}

- +
- +
@@ -388,16 +301,14 @@ function App() {
{fixture.odds && fixture.odds.map((odd, index) => ( - <> +

{odd.name}: Odd

-
{odd.values.map((valueObj, valueIndex) => (

{valueObj.value}: {valueObj.odd}

))}
Bonos: {fixture.bonos}
- - ))} + ))}
@@ -463,35 +374,35 @@ function App() {

Insufficient funds

Your current balance is not enough to buy the bonus. Please add money to your wallet.

- +
)} - {showModal && ( -
-
-

My Bonuses

- {bonuses.length > 0 ? ( - bonuses.map((bonus, index) => ( -
-

League: {bonus.league_name}

-

Round: {bonus.round}

-

Date: {new Date(bonus.date).toLocaleDateString()}

-

Result: {bonus.result}

-

Quantity: {bonus.quantity}

-

Processed: {bonus.processed ? 'Yes' : 'No'}

-

Odd: {bonus.odd}

-
- )) - ) : ( -

No bonuses found.

- )} - + {showModal && ( +
+
+

My Bonuses

+ {bonuses.length > 0 ? ( + bonuses.map((bonus, index) => ( +
+

League: {bonus.league_name}

+

Round: {bonus.round}

+

Date: {new Date(bonus.date).toLocaleDateString()}

+

Result: {bonus.result}

+

Quantity: {bonus.quantity}

+

Processed: {bonus.processed ? 'Yes' : 'No'}

+

Odd: {bonus.odd}

+
+ )) + ) : ( +

No bonuses found.

+ )} + +
-
- )} + )} ) : (
diff --git a/src/Routing.jsx b/src/Routing.jsx new file mode 100644 index 0000000..17e6ad1 --- /dev/null +++ b/src/Routing.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { Auth0Provider } from '@auth0/auth0-react'; +// import { QueryClient, QueryClientProvider } from '@tanstank/react-query'; + +import App from './App.jsx'; +import ConfirmPurchase from './components/ConfirmPurchase.jsx'; +import PurchaseCompleted from './components/PurchaseCompleted.jsx'; +import MyRequests from './components/MyRequests.jsx'; + +function Routing () { + return ( + + + + } /> + } /> + } /> + } /> + + + + ) +} + +export default Routing \ No newline at end of file diff --git a/src/api.js b/src/api.js index f952b92..8679696 100644 --- a/src/api.js +++ b/src/api.js @@ -7,12 +7,13 @@ const useApi = (endpoint) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(false); + const apiUrl = process.env.REACT_APP_API_URL useEffect(() => { const callApi = async () => { try { const token = await getAccessTokenSilently({ - audience: 'https://api.nodecraft.me', + audience: `${apiUrl}/`, }); const response = await fetch(endpoint, { diff --git a/src/components/ConfirmPurchase.css b/src/components/ConfirmPurchase.css new file mode 100644 index 0000000..0163f42 --- /dev/null +++ b/src/components/ConfirmPurchase.css @@ -0,0 +1,139 @@ +/* ConfirmPurchase.css */ +.ConfirmPurchase { + text-align: center; + background: linear-gradient(to right, #1e3c72, #2a5298); + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: white; + padding: 20px; +} + +.confirm-purchase-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: rgba(42, 82, 152, 0.9); /* Transparencia para mejor legibilidad */ + padding: 30px; + margin: 50px auto; + max-width: 600px; + border-radius: 12px; + box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.3); +} + +.confirm-purchase-container h2 { + color: #ffffff; /* Blanco para mejor contraste */ + margin-bottom: 20px; + font-size: 28px; +} + +.fixture-details { + width: 100%; + text-align: center; +} + +.teams { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 20px; + position: relative; /* Para posicionamiento relativo del "VS" */ +} + +.team { + display: flex; + flex-direction: column; + align-items: center; + margin: 0 20px; + width: 100px; /* Ancho fijo para simetría */ +} + +.team img { + width: 80px; + height: 80px; + margin-bottom: 10px; + border-radius: 50%; + border: 2px solid #ffffff; + object-fit: contain; /* Asegura que las imágenes se ajusten dentro del contenedor */ +} + +.team span { + font-size: 18px; + font-weight: bold; + color: #ffffff; /* Blanco para mejor contraste */ +} + +.vs { + position: absolute; + left: 50%; + transform: translateX(-50%); + font-size: 24px; + font-weight: bold; + color: #ffffff; /* Blanco para consistencia */ + background-color: rgba(255, 255, 255, 0); /* Fondo semitransparente para mejor legibilidad */ + padding: 5px 10px; + border-radius: 5px; +} + +.fixture-info p, +.purchase-info p { + font-size: 16px; + color: #ffffff; /* Blanco para mejor contraste */ + margin: 5px 0; +} + +.payment-form { + margin-top: 30px; + width: 100%; + display: flex; + justify-content: center; +} + +.pay-button { + background-color: #98b0d5; /* Azul claro para visibilidad */ + color: #1c2128; /* Texto en color azul oscuro para contraste */ + padding: 12px 30px; + font-weight: bold; + font-size: 18px; + border: none; + border-radius: 6px; + cursor: pointer; + transition: background-color 0.3s, transform 0.3s; +} + +.pay-button:hover { + background-color: #98b0d5; /* Azul más claro al pasar el cursor */ + transform: scale(1.05); /* Efecto de aumento al pasar el cursor */ +} + +/* Media Query para Pantallas Pequeñas */ +@media (max-width: 600px) { + .teams { + flex-direction: column; + } + + .vs { + position: static; + transform: none; + margin: 10px 0; + } + + .team { + margin: 10px 0; + width: auto; + } + + .confirm-purchase-container { + padding: 20px; + margin: 20px auto; + max-width: 90%; + } + + .pay-button { + width: 100%; + padding: 12px 0; + } +} diff --git a/src/components/ConfirmPurchase.jsx b/src/components/ConfirmPurchase.jsx new file mode 100644 index 0000000..1e15d3e --- /dev/null +++ b/src/components/ConfirmPurchase.jsx @@ -0,0 +1,54 @@ +// ConfirmPurchase.jsx +import React from 'react'; +import './ConfirmPurchase.css'; // Importamos el CSS específico para esta página +import { useLocation } from 'react-router-dom'; + +const ConfirmPurchase = () => { + const location = useLocation(); + const data = location.state; + + if (!data) { + return

No hay datos de compra disponibles.

; + } + + const { fixture, amount, result, url, token, locationInfo, request_id} = data; + + //Set fixture id in local storage + localStorage.setItem('request_id', request_id); + + return ( +
+
+

Confirmar Compra

+
+
+
+ {fixture.home_team_name} + {fixture.home_team_name} +
+ VS +
+ {fixture.away_team_name} + {fixture.away_team_name} +
+
+
+

Liga: {fixture.league_name}

+

Fecha: {new Date(fixture.date).toLocaleDateString()}

+

Hora: {new Date(fixture.date).toLocaleTimeString()}

+
+
+

Cantidad de Bonos: {amount}

+

Resultado Apostado: {result}

+
+
+
+ + +
+
+
+ ); +}; + +export default ConfirmPurchase; diff --git a/src/components/MyRequests.css b/src/components/MyRequests.css new file mode 100644 index 0000000..130ddbf --- /dev/null +++ b/src/components/MyRequests.css @@ -0,0 +1,110 @@ +/* MyRequests.css */ + +/* Aplicar el fondo al contenedor principal */ +.MyRequests { + background: linear-gradient(to right, #1e3c72, #2a5298); + min-height: 100vh; + padding: 20px; + } + + /* Contenedor interno para el contenido */ + .my-requests { + max-width: 1200px; + margin: 0 auto; + color: #fff; /* Color de texto blanco para mejor contraste */ + } + + .my-requests h2 { + text-align: center; + margin-bottom: 20px; + color: #ffffff; + } + + .filter-options { + display: flex; + justify-content: flex-end; + margin-bottom: 20px; + } + + .filter-options label { + margin-right: 10px; + font-weight: bold; + color: #ffffff; + } + + .filter-options select { + padding: 5px; + font-size: 16px; + } + + .requests-table { + width: 100%; + border-collapse: collapse; + } + + .requests-table th, + .requests-table td { + border: 1px solid #ddd; + padding: 8px; + } + + .requests-table th { + background-color: #2a5298; /* Color de fondo para los encabezados */ + text-align: left; + color: #ffffff; + } + + .requests-table tr:nth-child(even) { + background-color: rgba(255, 255, 255, 0.1); + } + + .requests-table tr:hover { + background-color: rgba(255, 255, 255, 0.2); + } + + .requests-table td { + color: #ffffff; + } + + .requests-table td:last-child { + font-weight: bold; + } + + .requests-table p { + text-align: center; + font-size: 18px; + color: #ffffff; + } + + /* Estilos para el botón de volver al inicio */ + .back-button { + margin-top: 20px; + background-color: #007bff; + color: white; + padding: 12px 30px; + font-size: 18px; + border: none; + border-radius: 6px; + cursor: pointer; + transition: background-color 0.3s ease-in-out, transform 0.3s ease-in-out; + } + + .back-button:hover { + background-color: #0056b3; + transform: scale(1.05); + } + + + .invoice-button { + background-color: #28a745; /* Verde */ + color: white; + padding: 8px 12px; + border: none; + border-radius: 4px; + cursor: pointer; + margin: 0; + } + + .invoice-button:hover { + background-color: #218838; + } diff --git a/src/components/MyRequests.jsx b/src/components/MyRequests.jsx new file mode 100644 index 0000000..4a5d2de --- /dev/null +++ b/src/components/MyRequests.jsx @@ -0,0 +1,167 @@ +// MyRequests.jsx +import React, { useEffect, useState } from 'react'; +import './MyRequests.css'; // Archivo CSS para este componente +import axios from 'axios'; +import { useAuth0 } from '@auth0/auth0-react'; +import { useNavigate } from 'react-router-dom'; +import { generateInvoice } from '../services/invoiceService'; + +const MyRequests = () => { + const [requests, setRequests] = useState([]); + const [filteredRequests, setFilteredRequests] = useState([]); + const [filterOption, setFilterOption] = useState('valid'); // Opciones: valid, invalid, pending, all + const { getAccessTokenSilently } = useAuth0(); + const navigate = useNavigate(); + + useEffect(() => { + const fetchRequests = async () => { + try { + const apiUrl = process.env.REACT_APP_API_URL; + const userId = localStorage.getItem('userId'); + + if (!userId) { + alert('Usuario no autenticado.'); + return; + } + + const token = await getAccessTokenSilently(); + + const response = await axios.get(`${apiUrl}/requests/${userId}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + setRequests(response.data.requests); + setFilteredRequests(response.data.requests.filter(req => req.valid === true)); + } catch (error) { + console.error('Error al obtener las solicitudes:', error); + alert('Hubo un error al obtener tus solicitudes.'); + } + }; + + fetchRequests(); + }, [getAccessTokenSilently]); + + // Función para manejar el cambio de filtro + const handleFilterChange = (event) => { + const option = event.target.value; + setFilterOption(option); + + let filtered = []; + + if (option === 'valid') { + filtered = requests.filter(req => req.valid === true); + } else if (option === 'invalid') { + filtered = requests.filter(req => req.valid === false); + } else if (option === 'pending') { + filtered = requests.filter(req => req.valid === null); + } else { + filtered = requests; // Mostrar todas + } + + setFilteredRequests(filtered); + }; + + // Función para manejar la descarga de la boleta + const handleDownloadInvoice = async (requestId) => { + try { + const apiUrl = process.env.REACT_APP_API_URL; + const token = await getAccessTokenSilently(); + + // Obtener datos necesarios para generar la boleta + const response = await axios.get(`${apiUrl}/requestsbyid/${requestId}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + const userData = response.data.user; + const matchData = response.data.match; + + // Generar la boleta + const pdfUrl = await generateInvoice(userData, matchData); + + // Descargar la boleta + window.open(pdfUrl, '_blank'); + } catch (error) { + console.error('Error al descargar la boleta:', error); + alert('Hubo un error al descargar la boleta.'); + } + }; + + // Función para manejar el botón de volver al inicio + const handleGoBack = () => { + navigate('/'); // Navega a la página de inicio + }; + + return ( +
+
+

Mis Compras Realizadas

+ +
+ + +
+ + {filteredRequests.length > 0 ? ( + + + + + + + + + + + + + + + {filteredRequests.map((req, index) => ( + + + + + + + + + + + + ))} + +
LigaJornadaFechaResultadoCantidad¿Has acertado?OddEstado
{req.league_name}{req.round}{new Date(req.date).toLocaleDateString()}{req.result}{req.quantity}{req.processed ? 'Sí' : 'No'}{req.odd} + {req.valid === true && 'Válida'} + {req.valid === false && 'No Válida'} + {req.valid === null && 'Pendiente'} + + {req.valid === true && ( + + )} +
+ ) : ( +

No se encontraron solicitudes para mostrar.

+ )} + + {/* Botón para regresar a la página principal */} + +
+
+ ); +}; + +export default MyRequests; diff --git a/src/components/PurchaseCompleted.css b/src/components/PurchaseCompleted.css new file mode 100644 index 0000000..261db05 --- /dev/null +++ b/src/components/PurchaseCompleted.css @@ -0,0 +1,55 @@ +/* PurchaseCompleted.css */ +.PurchaseCompleted { + text-align: center; + background: linear-gradient(to right, #1e3c72, #2a5298); + min-height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: white; + padding: 20px; +} + +.purchase-completed-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: rgba(42, 82, 152, 0.9); /* Transparencia para mejor legibilidad */ + padding: 30px; + margin: 50px auto; + max-width: 600px; + border-radius: 12px; + box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.3); +} + +.purchase-completed-container h2 { + color: #ffffff; /* Blanco para mejor contraste */ + margin-bottom: 20px; + font-size: 28px; +} + +.purchase-completed-container p { + font-size: 18px; + color: #ffffff; /* Blanco para mejor contraste */ + margin: 10px 0; +} + +/* Estilos para el botón "Volver al inicio" */ +.go-back-button { + margin-top: 20px; + background-color: #007bff; /* Azul brillante para el botón */ + color: white; + padding: 12px 30px; + font-size: 18px; + border: none; + border-radius: 6px; + cursor: pointer; + transition: background-color 0.3s ease-in-out, transform 0.3s ease-in-out; +} + +.go-back-button:hover { + background-color: #0056b3; /* Azul más oscuro cuando se pasa el mouse */ + transform: scale(1.05); /* Efecto de aumento al pasar el mouse */ +} diff --git a/src/components/PurchaseCompleted.jsx b/src/components/PurchaseCompleted.jsx new file mode 100644 index 0000000..799547b --- /dev/null +++ b/src/components/PurchaseCompleted.jsx @@ -0,0 +1,115 @@ +// PurchaseCompleted.jsx +import React, { useEffect, useState, useRef } from 'react'; +import './PurchaseCompleted.css'; // Puedes crear un CSS específico para esta página +import { useLocation, useNavigate } from 'react-router-dom'; +import axios from 'axios'; +import { generateInvoice } from '../services/invoiceService'; +import { useAuth0 } from '@auth0/auth0-react'; + +const PurchaseCompleted = () => { + const [statusMessage, setStatusMessage] = useState('Validando tu compra...'); + const [isSuccess, setIsSuccess] = useState(null); + const [isAborted, setIsAborted] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const hasConfirmed = useRef(false); + + const location = useLocation(); + const navigate = useNavigate(); + const { user } = useAuth0(); // Obtén la información del usuario autenticado + + useEffect(() => { + const confirmTransaction = async () => { + if (hasConfirmed.current) return; + hasConfirmed.current = true; + + const queryParams = new URLSearchParams(location.search); + const token_ws = queryParams.get('token_ws'); + + if (!token_ws) { + setStatusMessage('Compra anulada.'); + setIsAborted(true); + setIsSuccess(false); + setIsLoading(false); + return; + } + + // Elimina el token de la URL después de obtenerlo + window.history.replaceState({}, document.title, location.pathname); + + try { + const apiUrl = process.env.REACT_APP_API_URL; + + // get request_id from local storage + const request_id = localStorage.getItem('request_id'); + localStorage.removeItem('request_id'); + console.log('request_id:', request_id); + + // data to send to the server + const data = { + token_ws, + request_id, + }; + + const response = await axios.post(`${apiUrl}/transactions/commit`, data); + + if (response.data.status === 'AUTHORIZED') { + setStatusMessage('¡Compra realizada con éxito!'); + setIsSuccess(true); + + // Datos para la boleta desde la respuesta del backend + const userData = response.data.user; + const matchData = response.data.match; + + console.log('userData:', userData); + console.log('matchData:', matchData); + + try { + const pdfUrl = await generateInvoice(userData, matchData); + alert(`Compra exitosa. Descarga tu boleta aquí: ${pdfUrl}.`); + } catch (error) { + console.error('Error generando la boleta:', error); + alert('Error generando la boleta.'); + } + } else { + setStatusMessage('La compra no pudo ser completada.'); + setIsSuccess(false); + } + } catch (error) { + console.error('Error al confirmar la transacción:', error); + if (error.response && error.response.data && error.response.data.error) { + setStatusMessage(`Error: ${error.response.data.error}`); + } else { + setStatusMessage('Pago rechazado, la compra no pudo completarse.'); + } + setIsSuccess(false); + } finally { + setIsLoading(false); + } + }; + + confirmTransaction(); + }, [location]); + + const handleGoBack = () => { + navigate('/'); // Navega a la página de inicio + }; + + return ( +
+
+

{statusMessage}

+ {isSuccess === null &&

Por favor, espera...

} + {isSuccess &&

Gracias por tu compra. ¡Disfruta de tus bonos!

} + {(isSuccess === false)&&(isAborted === false) && ( +

+ Hubo un problema con tu compra. Por favor, intenta de nuevo o contacta al soporte. +

+ )} + {isAborted &&

Decidiste anular tu compra, ¡Quizás te convezca otro partido!

} + +
+
+ ); +}; + +export default PurchaseCompleted; diff --git a/src/index.js b/src/index.jsx similarity index 93% rename from src/index.js rename to src/index.jsx index bc4304c..31e7986 100644 --- a/src/index.js +++ b/src/index.jsx @@ -1,11 +1,12 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; -import App from './App.js'; +import Routing from './Routing.jsx'; import reportWebVitals from './reportWebVitals.js'; import { Auth0Provider } from '@auth0/auth0-react'; + const root = ReactDOM.createRoot(document.getElementById('root')); root.render( @@ -15,7 +16,7 @@ root.render( redirectUri={window.location.origin} audience="https://api.nodecraft.me" > - + ); diff --git a/src/services/apiService.jsx b/src/services/apiService.jsx new file mode 100644 index 0000000..a8bc89c --- /dev/null +++ b/src/services/apiService.jsx @@ -0,0 +1,227 @@ +// src/services/apiService.jsx +import axios from 'axios'; + +/** + * Elimina fixtures duplicados basados en fixture_id. + * @param {Array} fixtures - Lista de fixtures. + * @returns {Array} Lista de fixtures únicos. + */ + +export const removeDuplicateFixtures = (fixtures) => { + const uniqueFixtures = []; + const fixtureIds = new Set(); + + for (const fixture of fixtures) { + if (!fixtureIds.has(fixture.fixture_id)) { + fixtureIds.add(fixture.fixture_id); + uniqueFixtures.push(fixture); + } + } + + return uniqueFixtures; +}; + +/** + * Obtiene el balance de la wallet del usuario. + * @param {string} userId - ID del usuario. + * @param {Function} getAccessTokenSilently - Función para obtener el token de acceso. + * @returns {Promise} Balance de la wallet. + */ + +export const fetchUser = async (userId, getAccessTokenSilently) => { + try { + const token = await getAccessTokenSilently(); + const apiUrl = process.env.REACT_APP_API_URL; + const response = await axios.get(`${apiUrl}/users/${userId}`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + return response.data.wallet; + } catch (error) { + console.error('Error fetching wallet balance:', error); + throw error; + } +}; + +/** + * Obtiene la lista de fixtures. + * @param {Function} getAccessTokenSilently - Función para obtener el token de acceso. + * @returns {Promise} Lista de fixtures únicos. + */ +export const fetchFixtures = async (getAccessTokenSilently) => { + try { + const token = await getAccessTokenSilently(); + const apiUrl = process.env.REACT_APP_API_URL; + const response = await axios.get(`${apiUrl}/fixtures`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + const uniqueFixtures = removeDuplicateFixtures(response.data.data); + return uniqueFixtures; + } catch (error) { + console.error('Error fetching fixtures:', error); + throw error; + } +}; + +/** + * Genera un ID de usuario único. + * @returns {string} ID único. + */ +const generateLongUserId = () => { + const timestamp = Date.now(); + const highPrecision = Math.floor(performance.now() * 1000000); + const randomPart = Math.floor(Math.random() * 1000000000); + return `${timestamp}-${highPrecision}-${randomPart}`; +}; + +/** + * Registra un nuevo usuario. + * @param {Object} user - Objeto de usuario de Auth0. + * @param {Function} getAccessTokenSilently - Función para obtener el token de acceso. + * @returns {Promise} + */ +export const signUpUser = async (user, getAccessTokenSilently) => { + try { + const token = await getAccessTokenSilently(); + const newUserId = generateLongUserId(); + const apiUrl = process.env.REACT_APP_API_URL; + + const response = await axios.post(`${apiUrl}/users`, { + id: newUserId, + username: user.nickname, + email: user.email, + password: 'Nohaypassword', + wallet: 0.0, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + if (response.status === 201) { + localStorage.setItem('userId', newUserId); + return newUserId; + } else { + console.log('User must be already registered'); + // get the user id from the email + const response = await axios.get(`${apiUrl}/users`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + const users = response.data.users; + const existingUser = users.find(u => u.email === user.email); + if (existingUser) { + localStorage.setItem('userId', existingUser.id); + return existingUser.id; + } else { + throw new Error('No user found with this email.'); + } + } + // await fetchUser(newUserId, getAccessTokenSilently); + // await fetchFixtures(getAccessTokenSilently); + // esto se hace al llamar a signUpUser en el componente + } catch (error) { + console.error('Error signing up user:', error); + throw error; + } +}; + +/** + * Inicia sesión al usuario existente. + * @param {Object} user - Objeto de usuario de Auth0. + * @param {Function} getAccessTokenSilently - Función para obtener el token de acceso. + * @param {Function} fetchUser - Función para obtener el balance de la wallet. + * @returns {Promise} + */ + +export const logInUser = async (email, getAccessTokenSilently, fetchUser) => { + try { + // debo obtener el id del usuario a partir del email + let userId = localStorage.getItem('userId'); + const apiUrl = process.env.REACT_APP_API_URL; + if (!userId) { + const token = await getAccessTokenSilently(); + const response = await axios.get(`${apiUrl}/users`, { + headers: { + Authorization: `Bearer ${token}` + } + }); + const users = response.data.users; + const existingUser = users.find(user => user.email === email); + if (existingUser) { + userId = existingUser.id; + localStorage.setItem('userId', userId); + if (localStorage.getItem('authAction') === 'login') { + localStorage.removeItem('authAction'); + const wallet = await fetchUser(userId, getAccessTokenSilently); + return wallet; + } + } else { + throw new Error('No user found with this email.'); + } + } + } catch (error) { + console.error('Error logging in user:', error); + throw error; + } +}; + +/** + * Añade dinero a la wallet del usuario. + * @param {number} amount - Cantidad a añadir. + * @param {Function} getAccessTokenSilently - Función para obtener el token de acceso. + * @returns {Promise} Nuevo balance de la wallet. + */ + +export const addMoneyToWallet = async (amount, getAccessTokenSilently) => { + try { + const userId = localStorage.getItem('userId'); + const token = await getAccessTokenSilently(); + const apiUrl = process.env.REACT_APP_API_URL; + const response = await axios.patch( + `${apiUrl}/users/${userId}/wallet`, + { amount: amount }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + return response.data.wallet; + } catch (error) { + console.error('Error adding money to wallet:', error); + throw error; + } +}; + +/** + * Obtiene los bonuses del usuario. + * @param {string} userId - ID del usuario. + * @param {Function} getAccessTokenSilently - Función para obtener el token de acceso. + * @returns {Promise} Lista de bonuses. + */ + +export const viewMyBonuses = async (userId, getAccessTokenSilently) => { + try { + const token = await getAccessTokenSilently(); + const apiUrl = process.env.REACT_APP_API_URL; + const response = await axios.get(`${apiUrl}/requests/${userId}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + if (response.data && response.data.bonuses) { + return response.data.bonuses; + } else { + throw new Error('No bonuses found for this user.'); + } + } catch (error) { + console.error('Error viewing bonuses:', error); + throw error; + } +}; \ No newline at end of file diff --git a/src/services/invoiceService.js b/src/services/invoiceService.js index 36f6af1..d00a766 100644 --- a/src/services/invoiceService.js +++ b/src/services/invoiceService.js @@ -2,8 +2,11 @@ import axios from 'axios'; export const generateInvoice = async (userData, matchData) => { try { + console.log(userData) + console.log(matchData) - const response = await axios.post('https://axp8mrrbk1.execute-api.us-east-1.amazonaws.com/generate', { + // const response = await axios.post('https://axp8mrrbk1.execute-api.us-east-1.amazonaws.com/generate', { + const response = await axios.post('https://cy9d01jjhi.execute-api.us-east-1.amazonaws.com/generate', { userData: { name: userData.name, email: userData.email