diff --git a/package-lock.json b/package-lock.json
index b64737bfa9..1add5b0728 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"dependencies": {
"@headlessui/react": "^2.2.4",
"@heroicons/react": "^2.2.0",
+ "@stripe/stripe-js": "^7.8.0",
"@vis.gl/react-google-maps": "^1.5.5",
"bcrypt": "^6.0.0",
"lucide-react": "^0.525.0",
@@ -1250,6 +1251,15 @@
"node": ">=14.0.0"
}
},
+ "node_modules/@stripe/stripe-js": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.8.0.tgz",
+ "integrity": "sha512-DNXRfYUgkZlrniQORbA/wH8CdFRhiBSE0R56gYU0V5vvpJ9WZwvGrz9tBAZmfq2aTgw6SK7mNpmTizGzLWVezw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.16"
+ }
+ },
"node_modules/@swc/helpers": {
"version": "0.5.17",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
@@ -8944,6 +8954,11 @@
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz",
"integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw=="
},
+ "@stripe/stripe-js": {
+ "version": "7.8.0",
+ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.8.0.tgz",
+ "integrity": "sha512-DNXRfYUgkZlrniQORbA/wH8CdFRhiBSE0R56gYU0V5vvpJ9WZwvGrz9tBAZmfq2aTgw6SK7mNpmTizGzLWVezw=="
+ },
"@swc/helpers": {
"version": "0.5.17",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
diff --git a/package.json b/package.json
index d02a41631c..44b15e03f8 100755
--- a/package.json
+++ b/package.json
@@ -54,6 +54,7 @@
"dependencies": {
"@headlessui/react": "^2.2.4",
"@heroicons/react": "^2.2.0",
+ "@stripe/stripe-js": "^7.8.0",
"@vis.gl/react-google-maps": "^1.5.5",
"bcrypt": "^6.0.0",
"lucide-react": "^0.525.0",
diff --git a/src/api/routes/games.py b/src/api/routes/games.py
index 9c5d4507e3..8b620fb732 100644
--- a/src/api/routes/games.py
+++ b/src/api/routes/games.py
@@ -130,19 +130,4 @@ def get_games_by_platform(platform):
games = [g.serialize() for g in q.all()] #devuelve todos los juegos encontrados
return jsonify({"games": games}), 200
- # BUSCAR JUEGOS NAVBAR
-#@api.route('/SearchGames/', methods=["GET"])
-#def get_games_by_search():
- #query = request.args.get("query")
-
- #if not query:
- #return jsonify({"error": "Falta el parámetro 'query'"}), 400
-
- #try:
- #results = Games.query.filter(Games.name.ilike(f"%{query}%")).all()
- #games_list = [{"id": game.id, "name": game.name} for game in results]
- #return jsonify(games_list)
- #except Exception as e:
- #print("Error en la búsqueda:", e)
- #return jsonify({"error": "Juego no encontrado"}), 500
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/api/routes/payment.py b/src/api/routes/payment.py
index ee6e5d99ab..35789e4cf9 100644
--- a/src/api/routes/payment.py
+++ b/src/api/routes/payment.py
@@ -1,43 +1,108 @@
-# #! /usr/bin/env python3.6
-
-# """
-# server.py
-# Stripe Sample.
-# Python 3.6 or newer required.
-# """
import os
-from flask import Flask, redirect, request
+from flask import Flask, redirect, request, jsonify, Blueprint
from api.models.Games import Games
import stripe
from api.database.db import db
-# This is your test secret API key.
-os.getenv('VITE_FRONT_URL')
-stripe.api_key = os.getenv('SECRET_KEY_STRIPE')
-
-app = Flask(__name__,
- static_url_path='',
- static_folder='public')
+from flask_cors import CORS
+# Blueprint para las rutas de pago
+payment_api = Blueprint("api/payment", __name__)
+# Configuración de Stripe
+stripe.api_key = os.getenv('SECRET_KEY_STRIPE')
YOUR_DOMAIN = os.getenv('VITE_FRONT_URL')
-@app.route("/checkout", methods=['POST'])
+@payment_api.route("/create-checkout-session", methods=['POST'])
def create_checkout_session():
try:
+ # Obtener los datos del carrito desde el frontend
+ data = request.get_json()
+ cart_items = data.get('cart_items', [])
+
+ if not cart_items:
+ return jsonify({"error": "El carrito está vacío"}), 400
+
+ # Crear line_items para Stripe
+ line_items = []
+
+ for item in cart_items:
+ # Verificar que el juego existe en la base de datos
+ game = Games.query.get(item['id'])
+ if not game:
+ return jsonify({"error": f"Juego con ID {item['id']} no encontrado"}), 400
+
+ line_item = {
+ 'price_data': {
+ 'currency': 'eur', # Cambié a euros ya que veo precios en €
+ 'product_data': {
+ 'name': game.name,
+ 'description': f"Plataforma: {game.platform}",
+ 'images': [game.img] if game.img else [],
+ },
+ 'unit_amount': int(game.price * 100), # Stripe usa centavos
+ },
+ 'quantity': item.get('quantity', 1),
+ }
+ line_items.append(line_item)
+
+ # Crear la sesión de checkout
checkout_session = stripe.checkout.Session.create(
- line_items=[
- #aqui va la logica del carrito de compra
- ],
+ payment_method_types=['card'],
+ line_items=line_items,
mode='payment',
- success_url=YOUR_DOMAIN + '?success=true',
- cancel_url=YOUR_DOMAIN + '?canceled=true',
+ success_url=YOUR_DOMAIN + 'success?session_id={CHECKOUT_SESSION_ID}',
+ cancel_url=YOUR_DOMAIN + 'cancel',
+ automatic_tax={'enabled': True}, # Para manejar IVA automáticamente
+ # Opcional: agregar metadata del usuario
+ metadata={
+ 'user_id': data.get('user_id', ''),
+ 'total_items': len(cart_items)
+ }
)
+ return jsonify({
+ 'checkout_url': checkout_session.url,
+ 'session_id': checkout_session.id
+ }), 200
+
+ except stripe.error.StripeError as e:
+ return jsonify({"error": f"Error de Stripe: {str(e)}"}), 400
except Exception as e:
- return str(e)
-
- return redirect(checkout_session.url, code=303)
+ return jsonify({"error": f"Error interno: {str(e)}"}), 500
-if __name__ == '__main__':
- app.run(port=4242)
+@payment_api.route("/verify-session/", methods=['GET'])
+def verify_session(session_id):
+ """Verificar el estado de una sesión de pago"""
+ try:
+ session = stripe.checkout.Session.retrieve(session_id)
+ return jsonify({
+ 'payment_status': session.payment_status,
+ 'amount_total': session.amount_total,
+ 'currency': session.currency
+ }), 200
+ except stripe.error.StripeError as e:
+ return jsonify({"error": f"Error de Stripe: {str(e)}"}), 400
+# Webhook para manejar eventos de Stripe (opcional pero recomendado)
+@payment_api.route("/webhook", methods=['POST'])
+def stripe_webhook():
+ payload = request.get_data(as_text=True)
+ sig_header = request.headers.get('Stripe-Signature')
+ endpoint_secret = os.getenv('STRIPE_WEBHOOK_SECRET')
+
+ try:
+ event = stripe.Webhook.construct_event(
+ payload, sig_header, endpoint_secret
+ )
+ except ValueError:
+ return "Invalid payload", 400
+ except stripe.error.SignatureVerificationError:
+ return "Invalid signature", 400
+
+ # Manejar el evento
+ if event['type'] == 'checkout.session.completed':
+ session = event['data']['object']
+ # Aquí puedes guardar la compra en tu base de datos
+ print(f"Pago completado para sesión: {session['id']}")
+
+ return jsonify(success=True), 200
\ No newline at end of file
diff --git a/src/app.py b/src/app.py
index 6e598954cd..c6d8eec3ae 100644
--- a/src/app.py
+++ b/src/app.py
@@ -12,6 +12,8 @@
import api.routes.user as api_user
import api.routes.games as api_games
+from api.routes.payment import payment_api as payment_api
+
from api.admin import setup_admin
from api.commands import setup_commands
from flask_cors import CORS
@@ -64,6 +66,8 @@
# Add all endpoints form the API with a "api" prefix
app.register_blueprint(api_user.api, url_prefix='/api/user')
app.register_blueprint(api_games.api, url_prefix='/api/games')
+app.register_blueprint(payment_api, url_prefix="/api/payment")
+
# Handle/serialize errors like a JSON object
diff --git a/src/front/components/Navbar.jsx b/src/front/components/Navbar.jsx
index eab6a395a7..659f7b74bf 100644
--- a/src/front/components/Navbar.jsx
+++ b/src/front/components/Navbar.jsx
@@ -143,7 +143,7 @@ export default function Navbar({ showDrowpdown, setShowDrowpdown }) {
gamesFilter.map((g) => {
return (
- {g.name}
+ {g.name},{g.price}
)
})
diff --git a/src/front/components/maps/Maps.jsx b/src/front/components/maps/Maps.jsx
index 8742d3ecfd..f3ef3941d6 100644
--- a/src/front/components/maps/Maps.jsx
+++ b/src/front/components/maps/Maps.jsx
@@ -9,12 +9,11 @@ function MapInner() {
return (
+ />
);
}
diff --git a/src/front/components/searchgames/SearchGames.jsx b/src/front/components/searchgames/SearchGames.jsx
deleted file mode 100644
index dcd2d33a32..0000000000
--- a/src/front/components/searchgames/SearchGames.jsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import { useLocation } from "react-router-dom";
-import { useEffect, useState } from "react";
-
-const backendUrl = "https://refactored-couscous-x5p76ppwgq5v3xxr-3001.app.github.dev/"; // Cada uno tiene que poner su url de su backend hasta tener BD común
-
-export default function SearchResults() {
- const location = useLocation();
- const query = new URLSearchParams(location.search).get("query");
- const [results, setResults] = useState([]);
-
- useEffect(() => {
- if (query) {
- fetch(`${backendUrl}api/games/search?query=${encodeURIComponent(query)}`)
- .then(res => res.json())
- .then(data => setResults(data))
- .catch(err => console.error("Error al buscar:", err));
- }
- }, [query]);
-
- return (
-
-
Resultados para: {query}
- {results.length > 0 ? (
-
- {results.map(game => (
- - {game.name}
- ))}
-
- ) : (
-
No se encontraron resultados
- )}
-
- );
-}
\ No newline at end of file
diff --git a/src/front/components/stripe/StripeIntegration.jsx b/src/front/components/stripe/StripeIntegration.jsx
new file mode 100644
index 0000000000..7f06f0498b
--- /dev/null
+++ b/src/front/components/stripe/StripeIntegration.jsx
@@ -0,0 +1,100 @@
+import { loadStripe } from '@stripe/stripe-js';
+
+// Cargar Stripe con tu clave pública
+const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY);
+
+export const StripeService = {
+ // Crear sesión de checkout
+ async createCheckoutSession(cartItems, userId = null) {
+ const backendUrl = import.meta.env.VITE_BACKEND_URL;
+
+ try {
+ const response = await fetch(`${backendUrl}/api/payment/create-checkout-session`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ cart_items: cartItems,
+ user_id: userId
+ }),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.error || 'Error al crear la sesión de pago');
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ console.error('Error al crear sesión de checkout:', error);
+ throw error;
+ }
+ },
+
+ // Redirigir a Stripe Checkout
+ async redirectToCheckout(sessionData) {
+ const stripe = await stripePromise;
+
+ if (!stripe) {
+ throw new Error('Stripe no se ha cargado correctamente');
+ }
+
+ // Opción 1: Usar la URL directa (más simple)
+ window.location.href = sessionData.checkout_url;
+
+ // Opción 2: Usar el método de Stripe (alternativa)
+ // const result = await stripe.redirectToCheckout({
+ // sessionId: sessionData.session_id,
+ // });
+
+ // if (result.error) {
+ // throw new Error(result.error.message);
+ // }
+ },
+
+ // Verificar estado de una sesión
+ async verifySession(sessionId) {
+ const backendUrl = import.meta.env.VITE_BACKEND_URL;
+
+ try {
+ const response = await fetch(`${backendUrl}/api/payment/verify-session/${sessionId}`);
+
+ if (!response.ok) {
+ throw new Error('Error al verificar la sesión');
+ }
+
+ return await response.json();
+ } catch (error) {
+ console.error('Error al verificar sesión:', error);
+ throw error;
+ }
+ }
+};
+
+// Hook personalizado para manejar pagos con Stripe
+export const useStripePayment = () => {
+ const handlePayment = async (cartItems, userId = null) => {
+ try {
+ // Validar que el carrito no esté vacío
+ if (!cartItems || cartItems.length === 0) {
+ throw new Error('El carrito está vacío');
+ }
+
+ // Crear sesión de checkout
+ const sessionData = await StripeService.createCheckoutSession(cartItems, userId);
+
+ // Redirigir a Stripe Checkout
+ await StripeService.redirectToCheckout(sessionData);
+
+ } catch (error) {
+ console.error('Error en el proceso de pago:', error);
+ // Aquí puedes mostrar un mensaje de error al usuario
+ alert(`Error en el pago: ${error.message}`);
+ throw error;
+ }
+ };
+
+ return { handlePayment };
+};
\ No newline at end of file
diff --git a/src/front/pages/Success.jsx b/src/front/pages/Success.jsx
new file mode 100644
index 0000000000..0a9954cb8c
--- /dev/null
+++ b/src/front/pages/Success.jsx
@@ -0,0 +1,179 @@
+import React, { useEffect, useState } from 'react';
+import { useSearchParams, useNavigate, Link } from 'react-router-dom';
+import { StripeService } from '../components/stripe/StripeIntegration';
+import useGlobalReducer from '../hooks/useGlobalReducer';
+
+export const Success = () => {
+ const [searchParams] = useSearchParams();
+ const navigate = useNavigate();
+ const { dispatch } = useGlobalReducer();
+ const [verificationStatus, setVerificationStatus] = useState('loading');
+ const [paymentDetails, setPaymentDetails] = useState(null);
+
+ useEffect(() => {
+ const sessionId = searchParams.get('session_id');
+
+ if (!sessionId) {
+ setVerificationStatus('error');
+ return;
+ }
+
+ // Verificar la sesión con Stripe
+ const verifyPayment = async () => {
+ try {
+ const sessionData = await StripeService.verifySession(sessionId);
+
+ if (sessionData.payment_status === 'paid') {
+ setVerificationStatus('success');
+ setPaymentDetails(sessionData);
+
+ // Limpiar el carrito tras pago exitoso
+ dispatch({ type: "clearCarro" });
+ localStorage.removeItem("carro");
+
+ // Opcional: Guardar en historial
+ const nuevaCompra = {
+ fecha: new Date().toLocaleString(),
+ sessionId: sessionId,
+ total: sessionData.amount_total / 100, // Convertir de centavos
+ currency: sessionData.currency,
+ status: 'completed'
+ };
+
+ const historial = JSON.parse(localStorage.getItem("historialCompras")) || [];
+ historial.push(nuevaCompra);
+ localStorage.setItem("historialCompras", JSON.stringify(historial));
+
+ } else {
+ setVerificationStatus('pending');
+ }
+ } catch (error) {
+ console.error('Error al verificar pago:', error);
+ setVerificationStatus('error');
+ }
+ };
+
+ verifyPayment();
+ }, [searchParams, dispatch]);
+
+ const renderContent = () => {
+ switch (verificationStatus) {
+ case 'loading':
+ return (
+
+
+
+ Verificando tu pago...
+
+
+ Por favor espera mientras confirmamos tu transacción.
+
+
+ );
+
+ case 'success':
+ return (
+
+
+
+ ¡Pago Exitoso!
+
+
+ Tu pago ha sido procesado correctamente.
+
+ {paymentDetails && (
+
+
+ Monto: €{(paymentDetails.amount_total / 100).toFixed(2)}
+
+
+ Estado: Pagado
+
+
+ )}
+
+
+ Ver Historial de Compras
+
+
+ Volver al Inicio
+
+
+
+ );
+
+ case 'pending':
+ return (
+
+
+
+ Pago Pendiente
+
+
+ Tu pago está siendo procesado. Te notificaremos cuando se complete.
+
+
+ Volver al Inicio
+
+
+ );
+
+ case 'error':
+ default:
+ return (
+
+
+
+ Error al Verificar Pago
+
+
+ Hubo un problema al verificar tu pago. Por favor contacta a soporte.
+
+
+
+ Volver al Carrito
+
+
+ Volver al Inicio
+
+
+
+ );
+ }
+ };
+
+ return (
+
+
+ {renderContent()}
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/pages/aboutus/AboutUs.jsx b/src/front/pages/aboutus/AboutUs.jsx
index ceceb222da..73116d3432 100644
--- a/src/front/pages/aboutus/AboutUs.jsx
+++ b/src/front/pages/aboutus/AboutUs.jsx
@@ -1,4 +1,3 @@
-// AboutUs.jsx
import "./aboutus.css";
import SilviaImg from "./img/Silvia.png";
import AlbertoImg from "./img/Alberto.jpg";
@@ -8,8 +7,7 @@ import { Instagram, Facebook, Mail, Twitter } from "lucide-react";
export const AboutUs = () => {
return (
-
);
-};
+};
\ No newline at end of file
diff --git a/src/front/pages/aboutus/aboutus.css b/src/front/pages/aboutus/aboutus.css
index 56ee96d926..54372549a5 100644
--- a/src/front/pages/aboutus/aboutus.css
+++ b/src/front/pages/aboutus/aboutus.css
@@ -3,9 +3,23 @@
--gap: 24px;
--brand: #7c3aed;
--brand-2: #2563eb;
- --ink: #0b1020;
+ --ink: #0b1020;
+ --nav-h: 72px;
+}
+
+.container{
+ width:min(100% - 32px, var(--container));
+ margin-inline:auto;
+}
+
+.navbar, .site-nav {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ z-index: 2147483647;
+ pointer-events: auto;
}
-.container{ width:min(100% - 32px, var(--container)); margin-inline:auto; }
.about{
color:#fff;
@@ -13,8 +27,18 @@
radial-gradient(900px 400px at 80% 10%, #1b2150 0%, transparent 60%),
linear-gradient(180deg, #0b1020 0%, #0b0f1a 100%);
overflow: visible;
+
+ padding-top: var(--nav-h);
+ position: relative;
+ z-index: 0;
}
+
+[id] {
+ scroll-margin-top: calc(var(--nav-h) + 8px);
+}
+
+
.about__hero{
position:relative;
min-height: 60vh;
@@ -25,12 +49,14 @@
}
.about__hero-bg{
- position:absolute; inset:-20% -5% 0 -5%;
+ position:absolute;
+ inset:-20% -5% 0 -5%;
background:
radial-gradient(800px 200px at 20% 0%, rgba(124,58,237,.35), transparent 65%),
radial-gradient(700px 220px at 80% 5%, rgba(37,99,235,.35), transparent 65%);
filter: blur(30px) saturate(120%);
z-index:-1;
+ pointer-events: none;
animation: pulse 6s ease-in-out infinite alternate;
}
@keyframes pulse{
@@ -146,23 +172,29 @@
line-height: 1.6;
}
+
.about__team{
- padding-block: clamp(48px, 6vw, 96px) clamp(32px, 5vw, 72px);
+ position: relative;
+ z-index: 0;
}
.team__title{
- text-align:center;
- font-size: clamp(1.4rem, 3.5vw, 2.2rem);
- font-weight: 900;
+ display: inline-block;
+ line-height: 1.2;
+ padding-block: 12px;
margin: clamp(32px, 4vw, 56px) 0;
+ font-size: 50px;
+ font-weight: 900;
letter-spacing: .5px;
+
background: linear-gradient(135deg, #93c5fd, #a78bfa);
- -webkit-background-clip:text;
- background-clip:text;
- color:transparent;
+ -webkit-background-clip: text;
+ background-clip: text;
+ -webkit-text-fill-color: transparent;
+ color: transparent;
+
filter: drop-shadow(0 10px 30px rgba(147,197,253,.25));
}
-
.team__grid{
display:grid;
gap: clamp(16px, 2.4vw, 28px);
@@ -170,7 +202,6 @@
align-items: stretch;
}
-
.team-card{
position: relative;
background: rgba(15, 17, 35, .8);
@@ -182,10 +213,9 @@
transform-style: preserve-3d;
transition: transform .25s ease, box-shadow .25s ease;
-
display:flex;
flex-direction:column;
- min-height: 480px;
+ min-height: 480px;
}
.team-card:focus-visible{ outline: 2px solid #7c3aed; outline-offset: 3px; }
.team-card:hover{
@@ -200,17 +230,23 @@
pointer-events:none;
}
-.team-card__img-wrap{
- aspect-ratio: 4 / 3;
- border-radius: 12px;
- overflow: hidden;
- border: 1px solid rgba(255,255,255,.12);
- background:#0b0f1a;
- box-shadow: inset 0 0 0 1px rgba(255,255,255,.04);
-}
-.team-card__img{
- width:100%; height:100%; object-fit: cover; display:block;
- transition: transform .4s ease, filter .4s ease;
+.team-card__img-wrap {
+ width: 100%;
+ aspect-ratio: 1 / 1;
+ border-radius: 50%;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: #0b0f1a;
+ border: 2px solid rgba(255,255,255,.12);
+}
+.team-card__img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ object-position: center;
+ border-radius: 50%;
}
.team-card:hover .team-card__img{ transform: scale(1.04); filter:saturate(1.05); }
diff --git a/src/front/pages/cancelPayment/Cancel.jsx b/src/front/pages/cancelPayment/Cancel.jsx
new file mode 100644
index 0000000000..efa4524863
--- /dev/null
+++ b/src/front/pages/cancelPayment/Cancel.jsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+
+export const Cancel = () => {
+ return (
+
+
+
+ {/* Icono de cancelación */}
+
+
+ {/* Título */}
+
+ Pago Cancelado
+
+
+ {/* Descripción */}
+
+ Has cancelado el proceso de pago. Tus productos siguen en el carrito por si quieres intentarlo de nuevo.
+
+
+ {/* Botones */}
+
+
+ Volver al Carrito
+
+
+ Seguir Comprando
+
+
+
+ {/* Información adicional */}
+
+
+ ¿Tuviste algún problema?
+
+
+ Si experimentaste algún error durante el proceso de pago, no dudes en contactar a nuestro soporte.
+
+
+ Contactar Soporte →
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/src/front/pages/carro/Carro.css b/src/front/pages/carro/Carro.css
index 7830c6adfa..11fb816353 100644
--- a/src/front/pages/carro/Carro.css
+++ b/src/front/pages/carro/Carro.css
@@ -13,7 +13,6 @@
padding-bottom: 70px;
}
-
.carro-bg{
position: fixed;
inset: 0;
@@ -56,10 +55,11 @@
display: grid;
grid-template-columns: 1fr 350px;
gap: 2rem;
+ align-items: start;
}
.carro-box {
- background: #1f2937; /* gris oscuro */
+ background: #1f2937;
border-radius: 10px;
padding: 1.25rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
@@ -142,9 +142,23 @@
}
.carro-summary {
+ align-self: start;
background: #1f2937;
border-radius: 10px;
padding: 1.5rem;
color: #f3f4f6;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
+
+ position: sticky;
+ top: 90px;
+}
+
+@media (max-width: 900px) {
+ .carro-grid {
+ grid-template-columns: 1fr;
+ }
+ .carro-summary {
+ position: static;
+ top: auto;
+ }
}
diff --git a/src/front/pages/carro/Carro.jsx b/src/front/pages/carro/Carro.jsx
index cc715c6d77..8a50768aed 100644
--- a/src/front/pages/carro/Carro.jsx
+++ b/src/front/pages/carro/Carro.jsx
@@ -1,16 +1,22 @@
import React, { useEffect, useState, useRef } from "react";
import useGlobalReducer from "../../hooks/useGlobalReducer";
+import { useStripePayment } from "../../components/stripe/StripeIntegration";
import mario from "../aboutus/img/mario.jpg";
import "./Carro.css";
-
-
export const Carro = () => {
const { store, dispatch } = useGlobalReducer();
- const [showModal, setShowModal] = useState(false);
+ const { handlePayment } = useStripePayment();
+ const [paymentLoading, setPaymentLoading] = useState(false);
const [paymentSuccess, setPaymentSuccess] = useState(false);
const didInit = useRef(false);
+ // Obtener usuario actual si está logueado
+ const getCurrentUser = () => {
+ const user = localStorage.getItem('user');
+ return user ? JSON.parse(user) : null;
+ };
+
const handleRemove = (id) => dispatch({ type: "removeFromCarro", payload: id });
const handleClear = () => dispatch({ type: "clearCarro" });
const handleQuantityChange = (id, quantity) =>
@@ -35,15 +41,49 @@ export const Carro = () => {
}
}, [store.carro]);
- const handlePayment = () => {
- setShowModal(false);
- setPaymentSuccess(true);
+ // Función para procesar pago con Stripe
+ const handleStripePayment = async () => {
+ if (store.carro.length === 0) {
+ alert('El carrito está vacío');
+ return;
+ }
+
+ setPaymentLoading(true);
+
+ try {
+ const currentUser = getCurrentUser();
+ const userId = currentUser ? currentUser.id : null;
+
+ // Preparar items del carrito para Stripe
+ const cartItems = store.carro.map(item => ({
+ id: item.id,
+ name: item.name,
+ price: item.price,
+ quantity: item.quantity,
+ platform: item.platform
+ }));
+
+ // Procesar pago con Stripe
+ await handlePayment(cartItems, userId);
+
+ // Si llegamos aquí, el usuario fue redirigido a Stripe
+ // La limpieza del carrito se hará en la página de éxito
+
+ } catch (error) {
+ console.error('Error al procesar pago:', error);
+ setPaymentLoading(false);
+ // El error ya se muestra en useStripePayment
+ }
+ };
+ // Función de pago simulado (mantener como backup o para testing)
+ const handleSimulatedPayment = () => {
const nuevaCompra = {
fecha: new Date().toLocaleString(),
total: getTotal(),
productos: [...store.carro],
};
+
const historial = JSON.parse(localStorage.getItem("historialCompras")) || [];
historial.push(nuevaCompra);
localStorage.setItem("historialCompras", JSON.stringify(historial));
@@ -51,13 +91,14 @@ export const Carro = () => {
dispatch({ type: "clearCarro" });
localStorage.removeItem("carro");
+ setPaymentSuccess(true);
setTimeout(() => setPaymentSuccess(false), 3000);
};
const getTitle = (it) => it.title || it.name || "Juego";
const getImg = (it) => it.img || it.image || "https://via.placeholder.com/100";
-return (
+ return (
{/* Fondo a pantalla completa */}
Carro de compras
- {store.carro.length === 0 ? (
-
-
Tu carro está vacío
-
Empieza a añadir juegos para verlos aquí.
-
- ) : (
-
-
- {store.carro.map((item) => (
-
-
-
-
})
-
-
{getTitle(item)}
-
- ${item.price}
- {item.platform ? ` · ${item.platform}` : ""}
-
-
-
- handleQuantityChange(item.id, e.target.value)
- }
- className="carro-input"
- />
-
+ {store.carro.length === 0 ? (
+
+
Tu carro está vacío
+
Empieza a añadir juegos para verlos aquí.
+
+ ) : (
+
+
+ {store.carro.map((item) => (
+
+
+
+
})
+
+
{getTitle(item)}
+
+ €{item.price}
+ {item.platform ? ` · ${item.platform}` : ""}
+
+
+
+ handleQuantityChange(item.id, e.target.value)
+ }
+ className="carro-input"
+ />
+
+
+
+ €{(item.price * item.quantity).toFixed(2)}
+
-
- ${(item.price * item.quantity).toFixed(2)}
-
-
-
- ))}
-
-
-
+
+ ))}
-
-
- )}
-
- {showModal && (
-
-
-
¿Confirmar pago?
-
Total a pagar: ${getTotal().toFixed(2)}
-
-
-
+
+
+
+ )}
+
+ {/* Mensaje de éxito */}
+ {paymentSuccess && (
+
+ ¡Pago realizado con éxito!
-
+ )}
- )}
- {paymentSuccess && (
-
- ¡Pago realizado con éxito!
-
- )}
-
);
-};
+};
\ No newline at end of file
diff --git a/src/front/pages/detailsGames/DetailsGames.jsx b/src/front/pages/detailsGames/DetailsGames.jsx
index fa8a5331b3..9699824373 100644
--- a/src/front/pages/detailsGames/DetailsGames.jsx
+++ b/src/front/pages/detailsGames/DetailsGames.jsx
@@ -57,7 +57,7 @@ export const DetailsGames = () => {
@@ -86,11 +86,7 @@ export const DetailsGames = () => {
>
Añadir al carrito
-
+
)}
@@ -161,15 +157,7 @@ export const DetailsGames = () => {
{/* Age Rating */}
-
- {/* AQUI PONDREMOS LA EDAD COMO PARAMETRO DE JUEGO */}
-
18
-
- {/* AQUI PONDREMOS LAS OPCIONES DEL PEGY */}
-
Lenguaje fuerte, Violencia fuerte,
-
Compras dentro del juego, Los usuarios interactúan
-
-
+
diff --git a/src/front/pages/home/Home.jsx b/src/front/pages/home/Home.jsx
index ad0ca9388c..c8365995c4 100644
--- a/src/front/pages/home/Home.jsx
+++ b/src/front/pages/home/Home.jsx
@@ -8,7 +8,7 @@ import { Games } from "../../components/games/Games";
export const Home = () => {
const images = [
{
- src: "https://www.xtrafondos.com/wallpapers/god-of-war-ragnarok-11256.jpg",
+ src: "https://cdn1.epicgames.com/offer/24b9b5e323bc40eea252a10cdd3b2f10/EGS_LeagueofLegends_RiotGames_S1_2560x1440-47eb328eac5ddd63ebd096ded7d0d5ab",
},
{
src: "https://4kwallpapers.com/images/wallpapers/the-super-mario-2880x1800-10955.jpg",
diff --git a/src/front/pages/soporte/Soporte.jsx b/src/front/pages/soporte/Soporte.jsx
index 5ef7e1456f..e2edb0570f 100644
--- a/src/front/pages/soporte/Soporte.jsx
+++ b/src/front/pages/soporte/Soporte.jsx
@@ -1,7 +1,12 @@
+import { useState } from "react";
import "./soporte.css";
+
export const Soporte = () => {
+ const [enviado, setEnviado] = useState(false);
+
const handleSubmit = (e) => {
e.preventDefault();
+ setEnviado(true);
};
return (
@@ -14,56 +19,66 @@ export const Soporte = () => {
Envíanos tu consulta y te responderemos lo antes posible.
-