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/package-lock.json b/package-lock.json index 8148d7d..ce0d535 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@stripe/react-stripe-js": "^2.8.0", + "@stripe/stripe-js": "^4.5.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", "@types/jest": "^29.5.12", @@ -3184,6 +3186,27 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@stripe/react-stripe-js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.8.0.tgz", + "integrity": "sha512-Vf1gNEuBxA9EtxiLghm2ZWmgbADNMJw4HW6eolUu0DON/6mZvWZgk0KHolN0sozNJwYp0i/8hBsDBcBUWcvnbw==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "@stripe/stripe-js": "^1.44.1 || ^2.0.0 || ^3.0.0 || ^4.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@stripe/stripe-js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-4.5.0.tgz", + "integrity": "sha512-dMOzc58AOlsF20nYM/avzV8RFhO/vgYTY7ajLMH6mjlnZysnOHZxsECQvjEmL8Q/ukPwHkOnxSPW/QGCCnp7XA==", + "engines": { + "node": ">=12.16" + } + }, "node_modules/@testing-library/dom": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz", diff --git a/package.json b/package.json index 70d12b1..2ed23ab 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "dependencies": { "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@stripe/react-stripe-js": "^2.8.0", + "@stripe/stripe-js": "^4.5.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", "@types/jest": "^29.5.12", diff --git a/src/App.jsx b/src/App.jsx index 5d92b87..433a668 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,14 +2,17 @@ import { useEffect, useState, Suspense, lazy } from 'react'; import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom'; import { ToastContainer } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; -import { auth } from './service/firebase'; +import { auth, getUserSubscription } from './service/firebase'; import styles from './App.module.scss'; import Loading from './pages/Loading/loading'; +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')); const CreatePage = lazy(() => import('./pages/CreateServer/CreateServer')); -const Javapick = lazy(() => import('./pages/CreateServer/Javapick/java')); +const Javapick = lazy(() => import('./pages/CreateServer/java/java')); const Modpack = lazy(() => import('./pages/CreateServer/modpack/modpack')); const Bedrock = lazy(() => import('./pages/CreateServer/bedrock/bedrock')); const DashboardPage = lazy(() => import('./pages/DashboardPage/DashboardPage')); @@ -18,6 +21,8 @@ const App = () => { const [user, setUser] = useState(() => JSON.parse(localStorage.getItem('user')) || null); const [loading, setLoading] = useState(true); const [showLoading, setShowLoading] = useState(false); + const [subscription, setSubscription] = useState(0); + const [loadingSubscription, setLoadingSubscription] = useState(true); useEffect(() => { const timeoutId = setTimeout(() => setShowLoading(true), 6000); @@ -25,10 +30,11 @@ const App = () => { const unsubscribe = auth.onAuthStateChanged((user) => { if (user) { localStorage.setItem('user', JSON.stringify(user)); + setUser(user); } else { localStorage.removeItem('user'); + setUser(null); } - setUser(user); setLoading(false); clearTimeout(timeoutId); }); @@ -39,7 +45,27 @@ const App = () => { }; }, []); - if (loading && showLoading) { + useEffect(() => { + if (user) { + const fetchSubscription = async () => { + try { + setLoadingSubscription(true); + const userSubscription = await getUserSubscription(user.uid); + console.log('User Subscription:', userSubscription); + setSubscription(userSubscription || 0); + } catch (error) { + console.error('Error fetching subscription:', error); + } finally { + setLoadingSubscription(false); + } + }; + fetchSubscription(); + } else { + setLoadingSubscription(false); + } + }, [user]); + + if (loading || loadingSubscription) { return ; } @@ -48,17 +74,34 @@ const App = () => {
}> + {/* Public Route */} : } /> + + {/* Protected Routes (Requires Authentication) */} : } /> : } /> - : } /> - : } /> - : } /> + + {/* Routes with Subscription Levels */} + 0 ? : ) : } /> + 1 ? : ) : } /> + 2 ? : ) : } /> + + {/* Server Details Route */} : } /> + + {/* Pricing and Payment */} + : } /> + : } /> + : } /> + + {/* Default Route */} } /> + + {/* Catch-all route */} : } /> + b.localeCompare(a, undefined, { numeric: true })) -}; - -const CreateServer = ({ user, onCreateServer, onSubdomainUpdate, onCancel, noServers }) => { - const [subdomain, setSubdomain] = useState(null); - const [subdomainInput, setSubdomainInput] = useState(''); - const [serverName, setServerName] = useState(''); - const [serverVersion, setServerVersion] = useState('1.21.1'); - - const loadSubdomain = async () => { - try { - if (user && user.uid) { - const userSubdomain = await getUserSubdomain(user.uid); - setSubdomain(userSubdomain || ''); - onSubdomainUpdate(userSubdomain || ''); - } else { - console.error('User or user.uid is undefined'); - } - } catch (error) { - console.error('Error fetching subdomain:', error); - } - }; - - useEffect(() => { - loadSubdomain().then(r => r); - }, - [user]); - - const handleSaveSubdomain = async () => { - try { - await serviiApi.setSubdomain(subdomainInput); - setSubdomain(subdomainInput); - onSubdomainUpdate(subdomainInput); - } catch (error) { - console.error('Error setting subdomain:', error); - } - }; - - const handleCreateServer = async () => { - try { - const frameworkToSend = 'paper'; - await onCreateServer(serverName, serverVersion, frameworkToSend); - } catch (error) { - console.error('Error creating server:', error); - } - }; - - const VersionChoice = (version) => { - return () => { - setServerVersion(version); - }; - }; - - const validateInput = (input) => { - return input.replace(/[^a-zA-Z]/g, ''); - }; - - return ( -
- {noServers ? ( -
-
Bonjour
-
Aucun serveur
- -
- ) : ( - subdomain === null ? ( -
Loading...
- ) : ( - subdomain === '' ? ( -
-
Ecrivez votre sous domaine
-
- Le sous-domaine est le nom sous lequel vos amis et vous rejoignez le serveur, un peu comme une adresse. Choisissez-le bien, car il nest pas facilement modifiable ! -
- setSubdomainInput(validateInput(e.target.value))} - placeholder='Nom du sous domaine' - /> - - -
- ) : ( -
-
Création du serveur
-
Comment voulez vous jouer ?
-
dd
-
- ) - ) - )} -
- ); -}; - -CreateServer.propTypes = { - user: PropTypes.oneOfType([ - PropTypes.shape({ - uid: PropTypes.string.isRequired, - displayName: PropTypes.string, - email: PropTypes.string, - photoURL: PropTypes.string, - }), - ]), - onCreateServer: PropTypes.func.isRequired, - onSubdomainUpdate: PropTypes.func.isRequired, - onCancel: PropTypes.func, - noServers: PropTypes.any -}; - -export default CreateServer; diff --git a/src/components/CreateServer/CreateServer.module.scss b/src/components/CreateServer/CreateServer.module.scss deleted file mode 100644 index 067dce3..0000000 --- a/src/components/CreateServer/CreateServer.module.scss +++ /dev/null @@ -1,296 +0,0 @@ -.mainCardCommon { - display: flex; - justify-content: start; - align-items: start; - flex-direction: column; - background-color: #1D1836; - border-radius: 1.5rem; -} - -.title { - font-size: 2.5rem; - font-weight: 700; - margin-bottom: 1.5rem; -} - -.subtitle { - font-size: 1.2rem; - font-weight: 300; - text-align: center; - margin-bottom: 2rem; -} - -.input, .select { - width: 60rem; - padding: 1rem; - background-color: #090325; - border: none; - border-radius: 0.5rem; - color: white; - font-size: 1rem; -} - -.btnSubCreate, .btnServCreate { - margin-top: 2rem; - padding: 1rem 2rem; - background-color: #090325; - color: white; - border: none; - border-radius: 0.5rem; - cursor: pointer; - font-size: 1rem; - transition: background-color 0.3s, transform 0.3s; - margin-right: 1rem; -} - -.containerNoserveur { - display: flex; - justify-content: center; - align-items: center; -} - -.mainCardNoserveur { - background-color: #1D1836; - padding: 3rem; - margin-top: 5rem; - border-radius: 1rem; -} - -.nsTitle { - font-size: 3rem; - color: #F2F2F2; - font-weight: 900; -} - -.nsSubTitle { - font-size: 1.8rem; - font-weight: 300; -} - -.btnnoServCreate { - width: 40rem; - height: 5rem; - margin-top: 5rem; - font-size: 2rem; - font-weight: 900; - background-color: #090325; - border-radius: 1rem; - color: white; - border: none; - cursor: pointer; -} - -.btnServCreate { - width: 15rem; - height: 3.5rem; - margin-top: 2.5rem; - font-size: 1.5rem; - font-weight: 900; - border-radius: 1rem; - background-color: #090325; -} - -.GamesChoice { - margin-top: var(--navbar-height); - width: 50rem; - height: 10rem; - background-color: red; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; -} - -.mainCardSubdomain { - background-color: #1D1836; - padding: 3rem; - margin-top: 5rem; - border-radius: 1rem; - width: 55rem; -} - -.subtitle { - font-size: 1.2rem; - font-weight: 600; - text-align: start; - margin-bottom: 1.5rem; - margin-top: 2rem; - -} - -.inputsubdomain { - width: 50rem; - padding: 1rem; - margin-top: 1rem; - background-color: #090325; - border: none; - border-radius: 0.5rem; - color: white; - font-size: 1rem; -} - - -.carreContainer { - display: flex; - justify-content: space-around; - align-items: center; - width: 100%; - height: 6rem; -} - -.carreWrapper { - display: flex; - flex-direction: column; - align-items: center; -} - -.carre { - width: 5rem; - height: 5rem; - object-fit: cover; - display: block; - cursor: pointer; - transition: transform 0.2s; -} - -.carre:hover { - transform: scale(1.1); -} - -.selected { - border: 2px solid #fff; - transform: scale(1.2); -} - -.adviced { - border: 2px solid violet; -} - -.recommendationLabel { - margin-top: 0.5rem; - font-size: 1rem; - color: violet; - font-weight: 700; - text-align: center; -} - -.VersionContainer { - display: grid; - grid-template-columns: repeat(6, 1fr); - justify-items: center; - align-items: center; - width: 100%; -} - -.VersionCard{ - height: 4rem; - width: 8rem; - background-color: #090325; - display: flex; - align-items: center; - justify-content: center ; - font-size: 1.5rem; - margin-bottom: 1rem; - border-radius: .3rem; -} - -.VersionCard svg { - margin-right: 0.5rem; -} - -.VersionCard:hover{ - background-color: #09032579; - cursor: pointer; -} - -.selectedVersion { - border: .15rem solid #fff; - color: #fff; -} - - - - -@media (max-width: 800px) { - - .VersionContainer { - grid-template-columns: repeat(3, 1fr); - } - - .buttonContainer{ - display: flex; - justify-content: center; - align-items: center; - flex-direction: row; - } - - .title { - font-size: 1.8rem; - margin-bottom: 1rem; - } - - .subtitle { - font-size: 1rem; - margin-bottom: 1.5rem; - } - - .input, .select { - width: 100%; - padding: 0.75rem; - font-size: 0.875rem; - } - - .btnSubCreate, .btnServCreate { - margin-top: 2rem; - padding: 1rem 2.5rem; - font-size: 1rem; - margin-right: 2rem; - display: flex; - justify-content: center; - align-items: center; - } - - .mainCardNoserveur { - padding: 2rem; - margin-top: 3rem; - } - - .nsTitle { - font-size: 2rem; - } - - .nsSubTitle { - font-size: 1.2rem; - } - - .btnnoServCreate { - width: 100%; - height: 3.5rem; - font-size: 1.5rem; - margin-top: 3rem; - } - - .btnServCreate { - width: 100%; - height: 2.5rem; - font-size: 1.25rem; - } - - .mainCardCreateServ { - margin-top: 3rem; - padding: 1.5rem; - width: 35rem; - } - - .mainCardSubdomain { - padding: 2rem; - margin-top: 3rem; - width: 100%; - } - - .inputsubdomain { - width: 100%; - padding: 0.75rem; - font-size: 0.875rem; - } -} diff --git a/src/components/DeleteConfirmationModal/DeleteConfirmationModal.jsx b/src/components/DeleteConfirmationModal/DeleteConfirmationModal.jsx new file mode 100644 index 0000000..1d02f39 --- /dev/null +++ b/src/components/DeleteConfirmationModal/DeleteConfirmationModal.jsx @@ -0,0 +1,20 @@ +import styles from './DeleteConfirmationModal.module.scss'; + +const DeleteConfirmationModal = ({ isOpen, onClose, onDelete }) => { + if (!isOpen) return null; + + return ( +
+
+

Êtes-vous sûr de vouloir supprimer ?

+

Cette action est irréversible.

+
+ + +
+
+
+ ); +}; + +export default DeleteConfirmationModal; diff --git a/src/components/DeleteConfirmationModal/DeleteConfirmationModal.module.scss b/src/components/DeleteConfirmationModal/DeleteConfirmationModal.module.scss new file mode 100644 index 0000000..70ed127 --- /dev/null +++ b/src/components/DeleteConfirmationModal/DeleteConfirmationModal.module.scss @@ -0,0 +1,62 @@ +.modalOverlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; +} + +.modal { + background-color: white; + border-radius: 0.67rem; + padding: 1.67rem; + box-shadow: 0 0.33rem 2.5rem rgba(0, 0, 0, 0.1); + max-width: 33.33rem; + width: 100%; + text-align: center; +} + +h2 { + margin: 0 0 0.83rem; +} + +p { + margin: 0 0 1.67rem; +} + +.modalButtons { + display: flex; + justify-content: space-between; +} + +.cancelButton { + background-color: #f0f0f0; + color: #333; + border: none; + padding: 0.83rem 1.25rem; + border-radius: 0.42rem; + font-size: 1rem; + cursor: pointer; + + &:hover { + background-color: #e0e0e0; + } +} + +.deleteButton { + background-color: #be3939; + color: white; + border: none; + padding: 0.83rem 1.25rem; + border-radius: 0.42rem; + font-size: 1rem; + cursor: pointer; + + &:hover { + background-color: rgba(255, 59, 59, 0.76); + } +} diff --git a/src/components/navbar/Navbar.jsx b/src/components/navbar/Navbar.jsx index f7f9ee6..e5f27de 100644 --- a/src/components/navbar/Navbar.jsx +++ b/src/components/navbar/Navbar.jsx @@ -4,16 +4,19 @@ import styles from './Navbar.module.scss'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faUser, faCog, faSignOutAlt, faArrowLeft } from '@fortawesome/free-solid-svg-icons'; import PropTypes from "prop-types"; +import { useNavigate } from 'react-router-dom'; const Navbar = ({ user, hasShadow = true, showBackButton = false, onBackClick, - backButtonText = "Retour au dashboard" + backButtonText = "Retour au dashboard", + Subsribebtn = true }) => { const [dropdownOpen, setDropdownOpen] = useState(false); const dropdownRef = useRef(null); + const navigate = useNavigate(); const handleLogout = () => { auth.signOut(); @@ -45,7 +48,15 @@ const Navbar = ({ {showBackButton && (
- {backButtonText} {} + {backButtonText} +
+ )} + + {Subsribebtn && ( +
+
navigate('/Pricing')}> + S'abonner +
)} @@ -90,6 +101,7 @@ Navbar.propTypes = { showBackButton: PropTypes.bool, onBackClick: PropTypes.func, backButtonText: PropTypes.string, + Subsribebtn: PropTypes.bool }; export default Navbar; diff --git a/src/components/navbar/Navbar.module.scss b/src/components/navbar/Navbar.module.scss index 8c1347f..46492f6 100644 --- a/src/components/navbar/Navbar.module.scss +++ b/src/components/navbar/Navbar.module.scss @@ -89,6 +89,18 @@ font-size: 1.5rem; } + +.subscribeBtn{ + padding: .5rem 1rem; + border-radius: .4rem; + background-color: #2f2f2f; + color: white; + cursor: pointer; + border: none; + margin-right: 2rem; +} + + @media (max-width: 650px) { .backButton{ font-size: 1.25rem; diff --git a/src/components/serverCards/DefaultServerCard.jsx b/src/components/serverCards/DefaultServerCard.jsx index efb00e4..224d5f5 100644 --- a/src/components/serverCards/DefaultServerCard.jsx +++ b/src/components/serverCards/DefaultServerCard.jsx @@ -10,7 +10,7 @@ const ServerCard = ({ status, version, name, framework, onRunClick, onStopClick, const getFrameworkSource = () => { switch (framework) { - case 'frabric': + case 'fabric': return fabric; case 'forge': return forge; diff --git a/src/components/serverCards/DefaultServerCard.module.scss b/src/components/serverCards/DefaultServerCard.module.scss index 8f6ef1b..1f45f2e 100644 --- a/src/components/serverCards/DefaultServerCard.module.scss +++ b/src/components/serverCards/DefaultServerCard.module.scss @@ -105,6 +105,7 @@ .startStopButton { padding: 1rem 1.5rem; border-radius: 0.5rem; + font-size: 1rem; border: none; color: white; cursor: pointer; diff --git a/src/main.css b/src/main.css index d1b4cd9..886f248 100644 --- a/src/main.css +++ b/src/main.css @@ -42,19 +42,19 @@ body { @media (min-width: 1600px) { html { - font-size: 17px; + font-size: 14px; } } @media (min-width: 1800px) { html { - font-size: 19px; + font-size: 15px; } } @media (min-width: 2000px) { html { - font-size: 15px; + font-size: 17px; } } diff --git a/src/pages/CreateServer/CreateServer.jsx b/src/pages/CreateServer/CreateServer.jsx index ecbf540..4b4ef32 100644 --- a/src/pages/CreateServer/CreateServer.jsx +++ b/src/pages/CreateServer/CreateServer.jsx @@ -2,60 +2,75 @@ import styles from './CreateServer.module.scss'; import PropTypes from "prop-types"; import Navbar from '../../components/navbar/Navbar'; import { useNavigate } from 'react-router-dom'; -import bedrockimg from '../../assets/bedrock.png'; +import bedrockimg from '../../assets/bedrock.png'; import modedimg from '../../assets/moded.png'; -import javaimg from '../../assets/java.png' +import javaimg from '../../assets/java.png'; +import { getUserSubscription } from "../../service/firebase"; +import { useState, useEffect } from 'react'; const CreateServer = ({ user }) => { const navigate = useNavigate(); + const [subscription, setSubscription] = useState(0); + + useEffect(() => { + const fetchSubscription = async () => { + try { + const userSubscription = await getUserSubscription(user.uid); + setSubscription(userSubscription || 0); + } catch (error) { + console.error('Error fetching subscription:', error); + } + }; + + fetchSubscription(); + }, [user.uid]); - return (
navigate('/dashboard')} - /> + user={user} + hasShadow={false} + showBackButton={true} + onBackClick={() => navigate('/dashboard')} + /> -
-
Création du serveur
-
De quelle façon voulez-vous jouer ?
-
-
navigate('/CreateServer/java')}> - -
Java Edition
-
Découvrez la version classique de Minecraft sur PC, avec un large éventail de mises à jour et de fonctionnalités, couvrant plus de vingt versions !
- -
-
navigate('/CreateServer/bedrock')}> - -
Mini-jeu
-
Plongez dans Minecraft avec des cartes personnalisées et des règles uniques, en solo ou avec vos amis.
- -
-
navigate('/CreateServer/modpack')}> - -
Minecraft Modé
-
Explorez la version modifiée de Minecraft sur PC, avec des modpacks riches et variés, contenant plus de 200 mods pour une expérience de jeu personnalisée.
- -
-
+
+
Création du serveur
+
De quelle façon voulez-vous jouer ?
+
+
(subscription > 0) ? navigate('/CreateServer/java') : navigate('/payement?package=Gratuit')}> + Java Edition +
Java Edition
+
Découvrez la version classique de Minecraft sur PC, avec un large éventail de mises à jour et de fonctionnalités, couvrant plus de vingt versions !
+ +
+ +
(subscription > 1) ? navigate('/CreateServer/bedrock') : navigate('/payement?package=Standard')}> + Mini-jeu +
Mini-jeu
+
Plongez dans Minecraft avec des cartes personnalisées et des règles uniques, en solo ou avec vos amis.
+ +
+ +
(subscription > 2) ? navigate('/CreateServer/modpack') : navigate('/payement?package=Premium')}> + Minecraft Modé +
Minecraft Modé
+
Explorez la version modifiée de Minecraft sur PC, avec des modpacks riches et variés, contenant plus de 200 mods pour une expérience de jeu personnalisée.
+ +
+
); }; CreateServer.propTypes = { - user: PropTypes.oneOfType([ - PropTypes.shape({ - uid: PropTypes.string.isRequired, - displayName: PropTypes.string, - email: PropTypes.string, - photoURL: PropTypes.string, - }), - ]), + user: PropTypes.shape({ + uid: PropTypes.string.isRequired, + displayName: PropTypes.string, + email: PropTypes.string, + photoURL: PropTypes.string, + }).isRequired, }; export default CreateServer; diff --git a/src/pages/CreateServer/CreateServer.module.scss b/src/pages/CreateServer/CreateServer.module.scss index 915ca10..bc72c7b 100644 --- a/src/pages/CreateServer/CreateServer.module.scss +++ b/src/pages/CreateServer/CreateServer.module.scss @@ -26,7 +26,6 @@ } .GamesContainer { - margin-top: 2.5rem; display: flex; justify-content: center; align-items: center; @@ -90,20 +89,23 @@ img { .GamesContainer { flex-direction: column; flex-wrap: row; - margin-top: 40rem; height: 100%; - + margin-top: 45rem; } .GameCard { - margin: 1rem; + margin: 0rem 0rem .5rem 0rem; width: 20rem; padding: 2.5rem; } -} -@media (max-width: 750px) { - .GamesContainer { - margin-top: 50rem; + .GamesChoice { + margin-top: 35rem; } + + .title { + margin-top: 2rem; + } + + } diff --git a/src/pages/CreateServer/Javapick/java.jsx b/src/pages/CreateServer/java/java.jsx similarity index 98% rename from src/pages/CreateServer/Javapick/java.jsx rename to src/pages/CreateServer/java/java.jsx index 52dbc07..4b8e1fe 100644 --- a/src/pages/CreateServer/Javapick/java.jsx +++ b/src/pages/CreateServer/java/java.jsx @@ -3,7 +3,7 @@ import { GoTag, GoCheck } from "react-icons/go"; import { useNavigate } from 'react-router-dom'; import styles from './java.module.scss'; import PropTypes from "prop-types"; -import Navbar from '../../../components/navbar/Navbar'; +import Navbar from '../../../components/navbar/Navbar.jsx'; import serviiApi from "../../../service/api.tsx"; const Javapick = ({ user }) => { diff --git a/src/pages/CreateServer/Javapick/java.module.scss b/src/pages/CreateServer/java/java.module.scss similarity index 100% rename from src/pages/CreateServer/Javapick/java.module.scss rename to src/pages/CreateServer/java/java.module.scss diff --git a/src/pages/CreateServer/modpack/modpack.jsx b/src/pages/CreateServer/modpack/modpack.jsx index 57e8345..81b7afd 100644 --- a/src/pages/CreateServer/modpack/modpack.jsx +++ b/src/pages/CreateServer/modpack/modpack.jsx @@ -1,13 +1,57 @@ +import { useEffect, useState } from 'react'; import styles from './modpack.module.scss'; -import PropTypes from "prop-types"; +import PropTypes from 'prop-types'; import Navbar from '../../../components/navbar/Navbar'; -import { useNavigate } from 'react-router-dom'; - +import { useNavigate } from 'react-router-dom'; +import serviiApi from '../../../service/api'; const Modpack = ({ user }) => { const navigate = useNavigate(); + const [modpacks, setModpacks] = useState([]); + const [selectedModpackIndex, setSelectedModpackIndex] = useState(null); + const [error, setError] = useState(''); + + useEffect(() => { + const fetchModpacks = async () => { + try { + const response = await serviiApi.fetchModpacks(); + if (response.return_code === 200) { + setModpacks(response.message); + } else { + setError(response.message); + } + } catch (error) { + console.error(error); + setError("Erreur lors du chargement des modpacks."); + } + }; + + fetchModpacks(); + }, [user]); + + const handleModpackClick = (index) => { + setSelectedModpackIndex(index); + }; + + const handleCancel = () => { + console.log("Annuler cliqué, index sélectionné avant :", selectedModpackIndex); + setSelectedModpackIndex(null); + }; + + const handleCreate = async () => { + if (selectedModpackIndex !== null) { + const selectedModpack = modpacks[selectedModpackIndex]; + const { short_name, framework } = selectedModpack; + + try { + await serviiApi.serverCreate(short_name, short_name, framework); + navigate('/Dashboard'); + } catch (error) { + console.error('Error creating server:', error); + } + } + }; - return (
{ backButtonText="Retour" onBackClick={() => navigate('/CreateServer')} /> -
-

Prochainement disponible

-
+
+ {error ?

{error}

: ( + modpacks.map((modpack, index) => ( +
handleModpackClick(index)} + > + {modpack.name} +

{modpack.name}

+

{modpack.description}

+
+ Aventure + Combat + PvE + {modpack.framework} +
+
+ Version MC: {modpack.mcVersion} + Version Modpack: {modpack.version} +
+ {selectedModpackIndex === index && ( // Condition modifiée +
+
Créer le serveur {modpack.short_name} ?
+ + +
+ )} +
+ )) + )} +
); }; diff --git a/src/pages/CreateServer/modpack/modpack.module.scss b/src/pages/CreateServer/modpack/modpack.module.scss index 5ec5e9c..792658d 100644 --- a/src/pages/CreateServer/modpack/modpack.module.scss +++ b/src/pages/CreateServer/modpack/modpack.module.scss @@ -1,3 +1,154 @@ -.hey{ - margin-top: 8em; -} \ No newline at end of file +.Container { + display: flex; + flex-direction: column; + align-items: center; + padding: 6rem; +} + +.modpackGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(30rem, 1fr)); + gap: 2rem; + width: 100%; + padding: 1.5rem; +} + +.modpackCard { + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: .7rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 2rem; + width: 30rem; + text-align: center; + transition: all 0.3s ease; + cursor: pointer; + position: relative; + + &:hover { + transform: translateY(-.2rem); + } + + .content { + transition: filter 0.3s ease; /* Transition pour le flou */ + filter: none; /* Par défaut, pas de flou */ + } + + &.selected .content { + filter: blur(0.8rem); /* Appliquer le flou si sélectionné */ + } + + h3 { + margin: .8rem 0; + font-size: 1.5em; + color: #333; + } + + p { + font-size: 1em; + color: #666; + } + + .modpackImage { + width: 100%; + height: auto; + margin-bottom: 1rem; + } + + .tags { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: .5rem; + margin: 2.5rem 0rem; + + .tag { + display: flex; + align-items: center; + padding: .4rem .8rem; + border-radius: 1.5rem; + font-size: 0.9em; + font-weight: 500; + color: #fff; + + &.adventure { + background-color: #4CAF50; + } + + &.combat { + background-color: #E91E63; + } + + &.pve { + background-color: #2196F3; + } + + &.forge { + background-color: #FFC107; + } + } + } + + .meta { + display: flex; + justify-content: space-between; + font-size: .9rem; + color: #999; + } + + .overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.9); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 1rem; + z-index: 10; + + h4 { + font-size: 1.8rem; + color: #333; + margin-bottom: 1rem; + } + + .createButton { + background-color: black; + color: white; + padding: 0.5rem 1.5rem; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 1.25rem; + } + + .createButton:hover { + background-color: #252525bb; + } + + .cancelButton { + background-color: gray; + color: white; + padding: 0.5rem 1.5rem; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 1.25rem; + } + + .cancelButton:hover { + background-color: rgba(95, 95, 95, 0.685); + } + + .shortNamtitle { + font-weight: 400; + font-size: 1.5rem; + color: #333; + margin-bottom: 1rem; + } + } +} diff --git a/src/pages/DashboardPage/DashboardPage.jsx b/src/pages/DashboardPage/DashboardPage.jsx index 459ae41..2b030cc 100644 --- a/src/pages/DashboardPage/DashboardPage.jsx +++ b/src/pages/DashboardPage/DashboardPage.jsx @@ -3,10 +3,11 @@ 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'; +import DeleteConfirmationModal from '../../components/DeleteConfirmationModal/DeleteConfirmationModal'; const CACHE_KEY_SERVERS = 'cachedServers'; const CACHE_KEY_TIMESTAMP = 'cacheTimestamp'; @@ -20,15 +21,21 @@ 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(' '); + + const [isModalOpen, setModalOpen] = useState(false); + const [serverToDelete, setServerToDelete] = useState(null); const updateServersFromApi = useCallback(async () => { try { 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(); @@ -51,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 { @@ -69,12 +106,16 @@ const DashboardPage = ({ user }) => { } }; - const handleDeleteServer = async (serverName) => { - try { - await serviiApi.serverDelete(serverName); - updateServersFromApi(); - } catch (error) { - console.error('Error deleting server:', error); + const handleDeleteServer = async () => { + if (serverToDelete) { + try { + await serviiApi.serverDelete(serverToDelete); + setModalOpen(false); + setServerToDelete(null); + updateServersFromApi(); + } catch (error) { + console.error('Error deleting server:', error); + } } }; @@ -152,71 +193,83 @@ const DashboardPage = ({ user }) => { return (
-
-
- Adresse de connexion à vos serveurs : - - {" " + subdomain}.servii.fr - -
- -
- setSearchTerm(e.target.value)} - className={styles.searchInput} - /> - -
- -
- {favoriteServer ? ( -
- handleRunServer(favoriteServer.name)} - onStopClick={() => handleStopServer(favoriteServer.name)} - onDeleteClick={() => handleDeleteServer(favoriteServer.name)} - subdomain={subdomain} - favoriteServer={true} - /> -
- ) : null} -
- -
- {serversWithoutFavorite.length > 0 ? ( - serversWithoutFavorite.filter(server => - server.name.toLowerCase().includes(searchTerm.toLowerCase()) - ).map((server) => ( - handleRunServer(server.name)} - onStopClick={() => handleStopServer(server.name)} - onDeleteClick={() => handleDeleteServer(server.name)} - subdomain={subdomain} - /> - )) - ) : null} -
+
+
+ Adresse de connexion à vos serveurs : + + {" " + subdomain}.servii.fr +
+ +
+ setSearchTerm(e.target.value)} + className={styles.searchInput} + /> + +
+ +
+ {favoriteServer ? ( +
+ handleRunServer(favoriteServer.name , favoriteServer.framework)} + onStopClick={() => handleStopServer(favoriteServer.name)} + onDeleteClick={() => { + setServerToDelete(favoriteServer.name); + setModalOpen(true); + }} + subdomain={subdomain} + favoriteServer={true} + /> +
+ ) : null} +
+ +
+ {serversWithoutFavorite.length > 0 ? ( + serversWithoutFavorite.filter(server => + server.name.toLowerCase().includes(searchTerm.toLowerCase()) + ).map((server) => ( + handleRunServer(server.name , server.framework)} + onStopClick={() => handleStopServer(server.name)} + onDeleteClick={() => { + setServerToDelete(server.name); + setModalOpen(true); + }} + subdomain={subdomain} + /> + )) + ) : null} +
+
+ + setModalOpen(false)} + onDelete={handleDeleteServer} + />
); }; diff --git a/src/pages/Payement/Checkout.jsx b/src/pages/Payement/Checkout.jsx new file mode 100644 index 0000000..625c9e3 --- /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(`https://www.servii.fr/api/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 new file mode 100644 index 0000000..1ef9164 --- /dev/null +++ b/src/pages/Payement/PaymentForm/PaymentForm.jsx @@ -0,0 +1,97 @@ +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 stripePromise = loadStripe("pk_live_51PyIYTP3VLLeb9GlHpiK8p5lVC3kGPvAAb0Nn8m5qVAGzespGfGlDoP8Wmw4HbZJJgqxxHEIG7MJwP4IAWCpRBi100dYMpV1gv"); + +const PackageNumber = (selectedPackage) => { + switch (selectedPackage) { + case 'Basique': + return 1; + case 'Standard': + return 2; + case 'Premium': + return 3; + default: + return 1; + } +}; + +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('https://www.servii.fr/api/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 }; + + + + return ( +
+ navigate('/Pricing')} + backButtonText="Retour" + /> +
+ + + +
+
+ ); +}; + +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 new file mode 100644 index 0000000..95748db --- /dev/null +++ b/src/pages/Payement/PaymentForm/PaymentForm.module.scss @@ -0,0 +1,7 @@ +.container{ + margin-top: 5.5rem; +} + +.Footer-PoweredBy-Text{ + display: none; +} \ No newline at end of file diff --git a/src/pages/Payement/Pricing/Pricing.jsx b/src/pages/Payement/Pricing/Pricing.jsx new file mode 100644 index 0000000..4963637 --- /dev/null +++ b/src/pages/Payement/Pricing/Pricing.jsx @@ -0,0 +1,117 @@ +import styles from './Pricing.module.scss'; +import Navbar from '../../../components/navbar/Navbar'; +import { useNavigate } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import basique from '../../../assets/tier/basique.png'; +import standard from '../../../assets/tier/standard.png'; +import premium from '../../../assets/tier/premium.png'; + +// Import des icônes +import { FaCheckCircle, FaTimesCircle } from 'react-icons/fa'; + +const Pricing = ({ user }) => { + const navigate = useNavigate(); + + + const groups = [ + { + title: 'Basique', + price: '2.99€', + description: 'Fait pour les joueurs vanilla sur un petit serveur.', + features: [ + { name: '2gb de Ram', isAvailable: true }, + { name: 'Accès au serveurs vanilla', isAvailable: true }, + { name: 'Personalisation complète', isAvailable: true }, + { name: 'Support par e-mail', isAvailable: true }, + { name: 'Accès au serveurs Mini-jeux', isAvailable: false }, + { name: 'Accès au serveurs de modpacks', isAvailable: false }, + ], + image: basique + }, + { + title: 'Standard', + price: '4.99€', + description: 'Fait pour les joueurs de mini-jeux et vanilla !', + features: [ + { name: '5gb de Ram', isAvailable: true }, + { name: 'Accès au serveurs vanilla', isAvailable: true }, + { name: 'Personalisation complète', isAvailable: true }, + { name: 'Support par e-mail', isAvailable: true }, + { name: 'Accès au serveurs Mini-jeux', isAvailable: true }, + { name: 'Accès au serveurs de modpacks', isAvailable: false } + ], + image: standard + }, + { + title: 'Premium', + price: '9.99€', + description: 'Conçu pour les joueurs de modpacks robustes.', + features: [ + { name: '10gb de Ram', isAvailable: true }, + { name: 'Accès au serveurs vanilla', isAvailable: true }, + { name: 'Personalisation complète', isAvailable: true }, + { name: 'Support par e-mail', isAvailable: true }, + { name: 'Accès au serveurs Mini-jeux', isAvailable: true }, + { name: 'Accès au serveurs de modpacks', isAvailable: true } + ], + image: premium + }, + ]; + + const handleSubscribe = (pkg) => { + navigate(`/payment?package=${pkg.title}`); + }; + + return ( +
+ navigate('/Dashboard')} + /> + +
+ {groups.map((pkg, index) => ( +
+
+

{pkg.title}

+ {pkg.title} +
+ +
+
{pkg.price}
+ /mois +
+ +
{pkg.description}
+
+
    +
    CE QUI EST INCLUS
    + + {pkg.features.map((feature, idx) => ( +
  • + {feature.isAvailable ? ( + + ) : ( + + )} + {feature.name} +
  • + ))} +
+ +
+ ))} +
+
+ ); +}; + +Pricing.propTypes = { + user: PropTypes.object.isRequired, +}; + +export default Pricing; diff --git a/src/pages/Payement/Pricing/Pricing.module.scss b/src/pages/Payement/Pricing/Pricing.module.scss new file mode 100644 index 0000000..cee635f --- /dev/null +++ b/src/pages/Payement/Pricing/Pricing.module.scss @@ -0,0 +1,122 @@ +$primary-color: #000; +$secondary-color: #fff; + +.pricingContainer { + margin-top: 4rem; + display: flex; + justify-content: center; + flex-direction: row; + gap: 2rem; + padding: 4rem; +} + +.packageList { + display: flex; + flex-direction: row; + gap: 3rem; +} + +.packageCard { + background-color: $secondary-color; + color: $primary-color; + border-radius: .5rem; + padding: .5rem 2rem 2rem 2rem; + text-align: start; + transition: transform 0.3s, box-shadow 0.3s; + border: 0.15rem solid #dfdcd5; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + + .packageCardheader { + display: flex; + justify-content: space-between; + align-items: center; + + img { + width: 3rem; + height: auto; + margin-left: 0.5rem; + } + } + + .title { + font-size: 1.8rem; + font-weight: 600; + } + + .priceContainer { + display: flex; + justify-content: start; + align-items: baseline; + margin-bottom: 1.5rem; + + .price { + font-size: 2.5rem; + font-weight: bold; + color: #2F2F2F; + } + + .mensuel { + font-size: 1rem; + margin-left: 0.25rem; + color: #7c7c7c; + } + } + + .description { + width: 16.5rem; + height: 4rem; + font-size: 1rem; + margin-top: 0.25rem; + color: #3c3c3c; + } + + .features { + list-style-type: none; + padding: 0; + margin: 1rem 0; + + li { + margin: 0.5rem 0; + font-size: 1rem; + color: #555; + } + } + + .inclut { + font-size: 1.25rem; + margin-bottom: 1rem; + font-weight: 500; + } + + .button { + color: $primary-color; + border: none; + border-radius: 2px; + padding: 12px 24px; + cursor: pointer; + font-size: 1rem; + margin-top: 1rem; + background-color: #2F2F2F; + color: $secondary-color; + transition: background-color 0.3s, transform 0.2s; + width: 100%; + + &:hover { + background-color: darken(#2F2F2F, 10%); + transform: translateY(-2px); + } + } + + hr { + border: 0.1rem solid #dfdcd5; + margin: 1.5rem -2rem; + } +} + + + +@media (max-width: 700px) { + .packageList { + flex-direction: column; + } +} diff --git a/src/service/api.test.js b/src/service/api.test.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/service/api.tsx b/src/service/api.tsx index 810def7..2464f96 100644 --- a/src/service/api.tsx +++ b/src/service/api.tsx @@ -88,7 +88,23 @@ function toast_status(status: number, message: string) { }); } + + class serviiApi { + + public static async fetchModpacks(): Promise { + try { + const response = await fetch(`https://www.servii.fr/api/modpacks/image/a-metadata.txt`, { + method: 'GET', + }); + const json = await response.json(); + return { return_code: response.status, message: json }; + } catch (error) { + toast_status(666, "Couldn't fetch modpacks"); + console.error(error); + return { return_code: 503, message: "Couldn't fetch modpacks" }; + } + } private static async call(endpoint: serviiRequest, body: T, token: string): Promise { const unreachable: string = "Couldn't find an available API"; try { 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 };