mirror of
https://github.com/hubHarmony/servii-frontend.git
synced 2024-11-17 21:40:30 +00:00
+stripe integration
This commit is contained in:
parent
f13473c2f8
commit
a7044a9dc7
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="src/assets/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Servii - Hebergement facile</title>
|
||||
<!-- charger les polices -->
|
||||
|
@ -5,7 +5,9 @@ import 'react-toastify/dist/ReactToastify.css';
|
||||
import { auth } from './service/firebase';
|
||||
import styles from './App.module.scss';
|
||||
import Loading from './pages/Loading/loading';
|
||||
import Pricing from './pages/Payement/Pricing';
|
||||
import Pricing from './pages/Payement/Pricing/Pricing';
|
||||
import PaymentForm from './pages/Payement/PaymentForm/PaymentForm';
|
||||
import Checkout from './pages/Payement/Checkout';
|
||||
|
||||
const LoginPage = lazy(() => import('./pages/LoginPage/LoginPage'));
|
||||
const ServerDetails = lazy(() => import('./pages/ServerDetails/ServerDetails'));
|
||||
@ -57,7 +59,8 @@ const App = () => {
|
||||
<Route path="/createServer/modpack" element={user ? <Modpack user={user} /> : <Navigate to="/login" />} />
|
||||
<Route path="/server/:serverName/*" element={user ? <ServerDetails user={user} /> : <Navigate to="/login" />} />
|
||||
<Route path="/pricing" element={user ? <Pricing user={user} /> : <Navigate to="/login" />} />
|
||||
|
||||
<Route path="/payement/*" element={user ? <PaymentForm user={user} /> : <Navigate to="/login" />} />
|
||||
<Route path="/return/*" element={user ? <Checkout user={user} /> : <Navigate to="/login" />} />
|
||||
<Route path="/" element={<Navigate to={user ? "/dashboard" : "/login"} />} />
|
||||
<Route path="*" element={user ? <DashboardPage user={user} /> : <Navigate to="/login" />} />
|
||||
</Routes>
|
||||
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
@ -52,7 +52,7 @@ const Navbar = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{Subsribebtn && ( // Updated: Conditional rendering for the subscribe button
|
||||
{Subsribebtn && (
|
||||
<div className={styles.subscribeSection}>
|
||||
<div className={styles.subscribeBtn} onClick={() => navigate('/Pricing')}>
|
||||
S'abonner
|
||||
@ -101,7 +101,7 @@ Navbar.propTypes = {
|
||||
showBackButton: PropTypes.bool,
|
||||
onBackClick: PropTypes.func,
|
||||
backButtonText: PropTypes.string,
|
||||
Subsribebtn: PropTypes.bool // Updated: Prop type corrected
|
||||
Subsribebtn: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
|
@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import ServerCard from '../../components/serverCards/DefaultServerCard';
|
||||
import Navbar from '../../components/navbar/Navbar';
|
||||
import Loading from '../Loading/loading';
|
||||
import { getUserSubdomain } from "../../service/firebase";
|
||||
import { getUserSubdomain, getUserSubscription } from "../../service/firebase";
|
||||
import serviiApi from "../../service/api.tsx";
|
||||
import PropTypes from "prop-types";
|
||||
import styles from './DashboardPage.module.scss';
|
||||
@ -21,6 +21,7 @@ const DashboardPage = ({ user }) => {
|
||||
});
|
||||
|
||||
const [subdomain, setSubdomain] = useState(' ');
|
||||
const [subscription, setSubscription] = useState(0);
|
||||
const [loading, setLoading] = useState(servers.length === 0);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [newSubdomain, setNewSubdomain] = useState(' ');
|
||||
@ -33,6 +34,8 @@ const DashboardPage = ({ user }) => {
|
||||
if (user?.uid) {
|
||||
const userSubdomain = await getUserSubdomain(user.uid);
|
||||
setSubdomain(userSubdomain || null);
|
||||
const userSubscription = await getUserSubscription(user.uid);
|
||||
setSubscription(userSubscription || 0);
|
||||
}
|
||||
|
||||
const ApiResponse = await serviiApi.fetchServers();
|
||||
@ -55,14 +58,44 @@ const DashboardPage = ({ user }) => {
|
||||
|
||||
const handleCreateServer = () => navigate('/CreateServer');
|
||||
|
||||
const handleRunServer = async (serverName) => {
|
||||
const handleRunServer = async (serverName, framework) => {
|
||||
try {
|
||||
await serviiApi.serverRun(serverName);
|
||||
updateServersFromApi();
|
||||
if (subscription === 0) {
|
||||
if (framework === "paper") {
|
||||
navigate('/payement?package=Gratuit');
|
||||
} if (framework === "Bedrock") {
|
||||
navigate('/payement?package=Standard');
|
||||
}
|
||||
else {
|
||||
navigate('/payement?package=Standard');
|
||||
}
|
||||
}
|
||||
|
||||
else if (subscription === 1) {
|
||||
if (framework === "paper") {
|
||||
await serviiApi.serverRun(serverName);
|
||||
updateServersFromApi();
|
||||
} else {
|
||||
navigate('/payement?package=Standard');
|
||||
}
|
||||
}
|
||||
else if (subscription === 2) {
|
||||
if (framework === "paper" || framework === "Bedrock") {
|
||||
await serviiApi.serverRun(serverName);
|
||||
updateServersFromApi();
|
||||
} else {
|
||||
navigate('/payement?package=Premium');
|
||||
}
|
||||
}
|
||||
else if (subscription === 3) {
|
||||
await serviiApi.serverRun(serverName);
|
||||
updateServersFromApi();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error starting server:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const handleStopServer = async (serverName) => {
|
||||
try {
|
||||
@ -193,7 +226,7 @@ const DashboardPage = ({ user }) => {
|
||||
framework={favoriteServer.framework}
|
||||
maxPlayers={favoriteServer.maxPlayers}
|
||||
countPlayers={favoriteServer.onlinePlayers}
|
||||
onRunClick={() => handleRunServer(favoriteServer.name)}
|
||||
onRunClick={() => handleRunServer(favoriteServer.name , favoriteServer.framework)}
|
||||
onStopClick={() => handleStopServer(favoriteServer.name)}
|
||||
onDeleteClick={() => {
|
||||
setServerToDelete(favoriteServer.name);
|
||||
@ -219,7 +252,7 @@ const DashboardPage = ({ user }) => {
|
||||
framework={server.framework}
|
||||
maxPlayers={server.maxPlayers}
|
||||
countPlayers={server.onlinePlayers}
|
||||
onRunClick={() => handleRunServer(server.name)}
|
||||
onRunClick={() => handleRunServer(server.name , server.framework)}
|
||||
onStopClick={() => handleStopServer(server.name)}
|
||||
onDeleteClick={() => {
|
||||
setServerToDelete(server.name);
|
||||
|
51
src/pages/Payement/Checkout.jsx
Normal file
51
src/pages/Payement/Checkout.jsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
const Checkout = () => {
|
||||
const [sessionStatus, setSessionStatus] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const session_id = params.get('session_id');
|
||||
|
||||
console.log('session_id:', session_id);
|
||||
|
||||
if (session_id) {
|
||||
const fetchSessionStatus = async () => {
|
||||
try {
|
||||
const response = await fetch(`http://192.168.68.114:3000/get-session-status?session_id=${session_id}`);
|
||||
const data = await response.json();
|
||||
setSessionStatus(data);
|
||||
} catch (error) {
|
||||
console.error('Error fetching session status:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSessionStatus();
|
||||
} else {
|
||||
console.error('No session_id found in URL');
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!sessionStatus) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{sessionStatus.status === 'open' && (
|
||||
<div>
|
||||
<h1>Checkout is still open</h1>
|
||||
</div>
|
||||
)}
|
||||
{sessionStatus.status === 'complete' && (
|
||||
<div>
|
||||
<h1>Success!</h1>
|
||||
<p>Payment Status: {sessionStatus.payment_status}</p>
|
||||
<p>Customer Email: {sessionStatus.customer_email}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Checkout;
|
@ -1,49 +1,97 @@
|
||||
import React, { useState } from 'react';
|
||||
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
|
||||
import { useCallback, useState, useEffect } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import Navbar from '../../../components/navbar/Navbar';
|
||||
import PropTypes from 'prop-types';
|
||||
import { loadStripe } from '@stripe/stripe-js';
|
||||
import { EmbeddedCheckoutProvider, EmbeddedCheckout } from '@stripe/react-stripe-js';
|
||||
import styles from './PaymentForm.module.scss';
|
||||
|
||||
const PaymentForm = ({ groups, closeModal }) => {
|
||||
const stripe = useStripe();
|
||||
const elements = useElements();
|
||||
const [errorMessage, setErrorMessage] = useState(null);
|
||||
const [successMessage, setSuccessMessage] = useState(null);
|
||||
const stripePromise = loadStripe("pk_test_51PyIYTP3VLLeb9GlXCKgD4ylbemZPx72I3HkEAu0bRtcsfK31nqb3WtUbXKXUcKmyfrxKLfuJzZCPyp7Ymtlq9zy00c7VmkL6G");
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
const PackageNumber = (selectedPackage) => {
|
||||
switch (selectedPackage) {
|
||||
case 'Gratuit':
|
||||
return 1;
|
||||
case 'Standard':
|
||||
return 2;
|
||||
case 'Premium':
|
||||
return 3;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
if (!stripe || !elements) return;
|
||||
const CheckoutForm = ({ email }) => {
|
||||
const location = useLocation();
|
||||
const [clientSecret, setClientSecret] = useState('');
|
||||
const queryParams = new URLSearchParams(location.search);
|
||||
const selectedPackage = queryParams.get('package');
|
||||
|
||||
const fetchClientSecret = useCallback(() => {
|
||||
return fetch('http://192.168.68.114:3000/get-checkout-session', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'permission': PackageNumber(selectedPackage),
|
||||
'email': email,
|
||||
},
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data && data.clientSecret) {
|
||||
setClientSecret(data.clientSecret);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error fetching client secret:', error);
|
||||
});
|
||||
}, [selectedPackage, email]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchClientSecret();
|
||||
|
||||
|
||||
}, [fetchClientSecret]);
|
||||
|
||||
|
||||
|
||||
return { clientSecret };
|
||||
};
|
||||
|
||||
|
||||
|
||||
const PaymentForm = ({ user }) => {
|
||||
const navigate = useNavigate();
|
||||
const { clientSecret } = CheckoutForm({ email: user.email });
|
||||
|
||||
const options = { clientSecret };
|
||||
|
||||
const cardElement = elements.getElement(CardElement);
|
||||
const { error, paymentMethod } = await stripe.createPaymentMethod({
|
||||
type: 'card',
|
||||
card: cardElement,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
setErrorMessage(error.message);
|
||||
setSuccessMessage(null);
|
||||
} else {
|
||||
setSuccessMessage(`Paiement réussi! Méthode de paiement ID: ${paymentMethod.id}`);
|
||||
setErrorMessage(null);
|
||||
// Ici, vous devriez également envoyer paymentMethod.id à votre serveur
|
||||
// Fermer la modal après un court délai ou après l'envoi à votre serveur
|
||||
setTimeout(() => closeModal(), 2000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={styles.paymentForm}>
|
||||
<CardElement />
|
||||
{errorMessage && <p className={styles.error}>{errorMessage}</p>}
|
||||
{successMessage && <p className={styles.success}>{successMessage}</p>}
|
||||
<button type="submit" disabled={!stripe} className={styles.submitButton}>
|
||||
Payer {groups.price}
|
||||
</button>
|
||||
<button type="button" onClick={closeModal} className={styles.closeButton}>
|
||||
Fermer
|
||||
</button>
|
||||
</form>
|
||||
<div className={styles.container}>
|
||||
<Navbar
|
||||
user={user}
|
||||
hasShadow={true}
|
||||
showBackButton={true}
|
||||
onBackClick={() => navigate('/Pricing')}
|
||||
backButtonText="Retour"
|
||||
/>
|
||||
<div id="checkout">
|
||||
<EmbeddedCheckoutProvider stripe={stripePromise} options={options} >
|
||||
<EmbeddedCheckout />
|
||||
</EmbeddedCheckoutProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentForm;
|
||||
PaymentForm.propTypes = {
|
||||
user: PropTypes.shape({
|
||||
uid: PropTypes.string.isRequired,
|
||||
email: PropTypes.string,
|
||||
photoURL: PropTypes.string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default PaymentForm;
|
@ -1,19 +1,7 @@
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modalContent {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.container{
|
||||
margin-top: 5.5rem;
|
||||
}
|
||||
|
||||
.Footer-PoweredBy-Text{
|
||||
display: none;
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import styles from './Pricing.module.scss';
|
||||
import Navbar from '../../components/navbar/Navbar';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { loadStripe } from '@stripe/stripe-js';
|
||||
import { Elements } from '@stripe/react-stripe-js';
|
||||
import PaymentForm from './PaymentForm/PaymentForm';
|
||||
|
||||
const stripePromise = loadStripe('pk_test_51PyIYTP3VLLeb9GlXCKgD4ylbemZPx72I3HkEAu0bRtcsfK31nqb3WtUbXKXUcKmyfrxKLfuJzZCPyp7Ymtlq9zy00c7VmkL6G');
|
||||
|
||||
const Pricing = ({ user }) => {
|
||||
const navigate = useNavigate();
|
||||
const [selectedPackage, setSelectedPackage] = useState(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const groups = [
|
||||
{ title: 'Gratuit', price: '0 €', features: ['Accès limité', 'Support par e-mail'] },
|
||||
{ title: 'Standard', price: '2 €', features: ['Accès complet', 'Support prioritaire'] },
|
||||
{ title: 'Premium', price: '9 €', features: ['Accès illimité', 'Support 24/7'] },
|
||||
];
|
||||
|
||||
const handleSubscribe = (pkg) => {
|
||||
setSelectedPackage(pkg);
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.pricingContainer}>
|
||||
<Navbar
|
||||
user={user}
|
||||
hasShadow={true}
|
||||
showBackButton={true}
|
||||
onBackClick={() => navigate('/Dashboard')}
|
||||
/>
|
||||
|
||||
{groups.map((pkg, index) => (
|
||||
<div key={index} className={styles.packageCard}>
|
||||
<h2 className={styles.title}>{pkg.title}</h2>
|
||||
<p className={styles.price}>{pkg.price}</p>
|
||||
<ul className={styles.features}>
|
||||
{pkg.features.map((feature, idx) => (
|
||||
<li key={idx}>{feature}</li>
|
||||
))}
|
||||
</ul>
|
||||
<button className={styles.button} onClick={() => handleSubscribe(pkg)}>Sinscrire</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{isModalOpen && (
|
||||
<div className={styles.modal}>
|
||||
<div className={styles.modalContent}>
|
||||
<h2>Paiement pour {selectedPackage.title}</h2>
|
||||
<Elements stripe={stripePromise}>
|
||||
<PaymentForm groups={selectedPackage} closeModal={() => setIsModalOpen(false)} />
|
||||
</Elements>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Pricing.propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default Pricing;
|
52
src/pages/Payement/Pricing/Pricing.jsx
Normal file
52
src/pages/Payement/Pricing/Pricing.jsx
Normal file
@ -0,0 +1,52 @@
|
||||
import styles from './Pricing.module.scss';
|
||||
import Navbar from '../../../components/navbar/Navbar';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Pricing = ({ user }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const groups = [
|
||||
{ title: 'Gratuit', price: '0 €', features: ['Accès limité', 'Support par e-mail'] },
|
||||
{ title: 'Standard', price: '2 €', features: ['Accès complet', 'Support prioritaire'] },
|
||||
{ title: 'Premium', price: '9 €', features: ['Accès illimité', 'Support 24/7'] },
|
||||
];
|
||||
|
||||
const handleSubscribe = (pkg) => {
|
||||
navigate(`/payement?package=${pkg.title}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.pricingContainer}>
|
||||
<Navbar
|
||||
user={user}
|
||||
hasShadow={true}
|
||||
showBackButton={true}
|
||||
onBackClick={() => navigate('/Dashboard')}
|
||||
/>
|
||||
|
||||
<div className={styles.packageList}>
|
||||
{groups.map((pkg, index) => (
|
||||
<div key={index} className={styles.packageCard}>
|
||||
<h2 className={styles.title}>{pkg.title}</h2>
|
||||
<p className={styles.price}>{pkg.price}</p>
|
||||
<ul className={styles.features}>
|
||||
{pkg.features.map((feature, idx) => (
|
||||
<li key={idx}>{feature}</li>
|
||||
))}
|
||||
</ul>
|
||||
<button className={styles.button} onClick={() => handleSubscribe(pkg)}>
|
||||
S'inscrire
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Pricing.propTypes = {
|
||||
user: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default Pricing;
|
@ -2,35 +2,43 @@ $primary-color: #000;
|
||||
$secondary-color: #fff;
|
||||
|
||||
.pricingContainer {
|
||||
margin-top: 5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
padding: 40px;
|
||||
flex-direction: row;
|
||||
gap: 2rem;
|
||||
padding: 4rem;
|
||||
background-color: $secondary-color;
|
||||
}
|
||||
|
||||
.packageList{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.packageCard {
|
||||
background-color: $primary-color;
|
||||
color: $secondary-color;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
border-radius: .8rem;
|
||||
padding: 5rem;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
transform: scale(1.01);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 2rem;
|
||||
margin-bottom: .6rem;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 32px;
|
||||
margin: 10px 0;
|
||||
font-size: 2.5rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.features {
|
||||
@ -52,9 +60,4 @@ $secondary-color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
transition: background-color 0.3s;
|
||||
|
||||
&:hover {
|
||||
background-color: $primary-color;
|
||||
color: $secondary-color;
|
||||
}
|
||||
}
|
||||
}
|
@ -37,4 +37,16 @@ const getUserSubdomain = async (userId) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { auth, googleProvider, signInWithPopup, getUserSubdomain, app };
|
||||
|
||||
const getUserSubscription = async (userId) => {
|
||||
const userDocRef = doc(db, 'users', userId);
|
||||
const userDocSnap = await getDoc(userDocRef);
|
||||
if (userDocSnap.exists()) {
|
||||
const userData = userDocSnap.data();
|
||||
return userData.subscription;
|
||||
} else {
|
||||
throw new Error("No such document!");
|
||||
}
|
||||
};
|
||||
|
||||
export { auth, googleProvider, signInWithPopup, getUserSubdomain, getUserSubscription, app };
|
||||
|
Loading…
Reference in New Issue
Block a user