diff --git a/index.html b/index.html index e1f3365..6878b75 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + Servii - Hebergement facile diff --git a/src/App.jsx b/src/App.jsx index 824d4fb..a6212b3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -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 = () => { : } /> : } /> : } /> - + : } /> + : } /> } /> : } /> diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..b6ffd49 Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components/navbar/Navbar.jsx b/src/components/navbar/Navbar.jsx index a301ae5..e5f27de 100644 --- a/src/components/navbar/Navbar.jsx +++ b/src/components/navbar/Navbar.jsx @@ -52,7 +52,7 @@ const Navbar = ({ )} - {Subsribebtn && ( // Updated: Conditional rendering for the subscribe button + {Subsribebtn && (
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; diff --git a/src/pages/DashboardPage/DashboardPage.jsx b/src/pages/DashboardPage/DashboardPage.jsx index bec1a02..2b030cc 100644 --- a/src/pages/DashboardPage/DashboardPage.jsx +++ b/src/pages/DashboardPage/DashboardPage.jsx @@ -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); diff --git a/src/pages/Payement/Checkout.jsx b/src/pages/Payement/Checkout.jsx new file mode 100644 index 0000000..120c0a6 --- /dev/null +++ b/src/pages/Payement/Checkout.jsx @@ -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
Loading...
; + } + + return ( +
+ {sessionStatus.status === 'open' && ( +
+

Checkout is still open

+
+ )} + {sessionStatus.status === 'complete' && ( +
+

Success!

+

Payment Status: {sessionStatus.payment_status}

+

Customer Email: {sessionStatus.customer_email}

+
+ )} +
+ ); +}; + +export default Checkout; diff --git a/src/pages/Payement/PaymentForm/PaymentForm.jsx b/src/pages/Payement/PaymentForm/PaymentForm.jsx index 59231c4..628ba22 100644 --- a/src/pages/Payement/PaymentForm/PaymentForm.jsx +++ b/src/pages/Payement/PaymentForm/PaymentForm.jsx @@ -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 ( -
- - {errorMessage &&

{errorMessage}

} - {successMessage &&

{successMessage}

} - - - +
+ navigate('/Pricing')} + backButtonText="Retour" + /> +
+ + + +
+
); }; -export default PaymentForm; +PaymentForm.propTypes = { + user: PropTypes.shape({ + uid: PropTypes.string.isRequired, + email: PropTypes.string, + photoURL: PropTypes.string, + }).isRequired, +}; + +export default PaymentForm; \ No newline at end of file diff --git a/src/pages/Payement/PaymentForm/PaymentForm.module.scss b/src/pages/Payement/PaymentForm/PaymentForm.module.scss index 0bbf541..95748db 100644 --- a/src/pages/Payement/PaymentForm/PaymentForm.module.scss +++ b/src/pages/Payement/PaymentForm/PaymentForm.module.scss @@ -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; - } - \ No newline at end of file +.container{ + margin-top: 5.5rem; +} + +.Footer-PoweredBy-Text{ + display: none; +} \ No newline at end of file diff --git a/src/pages/Payement/Pricing.jsx b/src/pages/Payement/Pricing.jsx deleted file mode 100644 index f82b4df..0000000 --- a/src/pages/Payement/Pricing.jsx +++ /dev/null @@ -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 ( -
- navigate('/Dashboard')} - /> - - {groups.map((pkg, index) => ( -
-

{pkg.title}

-

{pkg.price}

-
    - {pkg.features.map((feature, idx) => ( -
  • {feature}
  • - ))} -
- -
- ))} - - {isModalOpen && ( -
-
-

Paiement pour {selectedPackage.title}

- - setIsModalOpen(false)} /> - -
-
- )} -
- ); -}; - -Pricing.propTypes = { - user: PropTypes.object.isRequired, -}; - -export default Pricing; diff --git a/src/pages/Payement/Pricing/Pricing.jsx b/src/pages/Payement/Pricing/Pricing.jsx new file mode 100644 index 0000000..7f025c3 --- /dev/null +++ b/src/pages/Payement/Pricing/Pricing.jsx @@ -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 ( +
+ navigate('/Dashboard')} + /> + +
+ {groups.map((pkg, index) => ( +
+

{pkg.title}

+

{pkg.price}

+
    + {pkg.features.map((feature, idx) => ( +
  • {feature}
  • + ))} +
+ +
+ ))} +
+
+ ); +}; + +Pricing.propTypes = { + user: PropTypes.object.isRequired, +}; + +export default Pricing; diff --git a/src/pages/Payement/Pricing.module.scss b/src/pages/Payement/Pricing/Pricing.module.scss similarity index 70% rename from src/pages/Payement/Pricing.module.scss rename to src/pages/Payement/Pricing/Pricing.module.scss index b9d81a9..259d960 100644 --- a/src/pages/Payement/Pricing.module.scss +++ b/src/pages/Payement/Pricing/Pricing.module.scss @@ -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; - } -} +} \ No newline at end of file diff --git a/src/service/firebase.jsx b/src/service/firebase.jsx index 5f84bdd..d8245a1 100644 --- a/src/service/firebase.jsx +++ b/src/service/firebase.jsx @@ -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 };