mirror of
https://github.com/hubHarmony/servii-frontend.git
synced 2024-11-17 21:40:30 +00:00
Merge pull request #53 from hubHarmony/Payement
handle subscription Payement on website
This commit is contained in:
commit
26302e5d88
@ -2,7 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Servii - Hebergement facile</title>
|
<title>Servii - Hebergement facile</title>
|
||||||
<!-- charger les polices -->
|
<!-- charger les polices -->
|
||||||
|
23
package-lock.json
generated
23
package-lock.json
generated
@ -10,6 +10,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.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/jest-dom": "^6.4.6",
|
||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
@ -3184,6 +3186,27 @@
|
|||||||
"@sinonjs/commons": "^3.0.0"
|
"@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": {
|
"node_modules/@testing-library/dom": {
|
||||||
"version": "10.3.0",
|
"version": "10.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz",
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.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/jest-dom": "^6.4.6",
|
||||||
"@testing-library/react": "^16.0.0",
|
"@testing-library/react": "^16.0.0",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
|
57
src/App.jsx
57
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 { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom';
|
||||||
import { ToastContainer } from 'react-toastify';
|
import { ToastContainer } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
import { auth } from './service/firebase';
|
import { auth, getUserSubscription } from './service/firebase';
|
||||||
import styles from './App.module.scss';
|
import styles from './App.module.scss';
|
||||||
import Loading from './pages/Loading/loading';
|
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 LoginPage = lazy(() => import('./pages/LoginPage/LoginPage'));
|
||||||
const ServerDetails = lazy(() => import('./pages/ServerDetails/ServerDetails'));
|
const ServerDetails = lazy(() => import('./pages/ServerDetails/ServerDetails'));
|
||||||
const CreatePage = lazy(() => import('./pages/CreateServer/CreateServer'));
|
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 Modpack = lazy(() => import('./pages/CreateServer/modpack/modpack'));
|
||||||
const Bedrock = lazy(() => import('./pages/CreateServer/bedrock/bedrock'));
|
const Bedrock = lazy(() => import('./pages/CreateServer/bedrock/bedrock'));
|
||||||
const DashboardPage = lazy(() => import('./pages/DashboardPage/DashboardPage'));
|
const DashboardPage = lazy(() => import('./pages/DashboardPage/DashboardPage'));
|
||||||
@ -18,6 +21,8 @@ const App = () => {
|
|||||||
const [user, setUser] = useState(() => JSON.parse(localStorage.getItem('user')) || null);
|
const [user, setUser] = useState(() => JSON.parse(localStorage.getItem('user')) || null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [showLoading, setShowLoading] = useState(false);
|
const [showLoading, setShowLoading] = useState(false);
|
||||||
|
const [subscription, setSubscription] = useState(0);
|
||||||
|
const [loadingSubscription, setLoadingSubscription] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeoutId = setTimeout(() => setShowLoading(true), 6000);
|
const timeoutId = setTimeout(() => setShowLoading(true), 6000);
|
||||||
@ -25,10 +30,11 @@ const App = () => {
|
|||||||
const unsubscribe = auth.onAuthStateChanged((user) => {
|
const unsubscribe = auth.onAuthStateChanged((user) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
localStorage.setItem('user', JSON.stringify(user));
|
localStorage.setItem('user', JSON.stringify(user));
|
||||||
|
setUser(user);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
|
setUser(null);
|
||||||
}
|
}
|
||||||
setUser(user);
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
clearTimeout(timeoutId);
|
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 <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,17 +74,34 @@ const App = () => {
|
|||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Suspense fallback={<Loading />}>
|
<Suspense fallback={<Loading />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
|
{/* Public Route */}
|
||||||
<Route path="/login" element={user ? <Navigate to="/dashboard" /> : <LoginPage />} />
|
<Route path="/login" element={user ? <Navigate to="/dashboard" /> : <LoginPage />} />
|
||||||
|
|
||||||
|
{/* Protected Routes (Requires Authentication) */}
|
||||||
<Route path="/dashboard" element={user ? <DashboardPage user={user} /> : <Navigate to="/login" />} />
|
<Route path="/dashboard" element={user ? <DashboardPage user={user} /> : <Navigate to="/login" />} />
|
||||||
<Route path="/createServer" element={user ? <CreatePage user={user} /> : <Navigate to="/login" />} />
|
<Route path="/createServer" element={user ? <CreatePage user={user} /> : <Navigate to="/login" />} />
|
||||||
<Route path="/createServer/java" element={user ? <Javapick user={user} /> : <Navigate to="/login" />} />
|
|
||||||
<Route path="/createServer/bedrock" element={user ? <Bedrock user={user} /> : <Navigate to="/login" />} />
|
{/* Routes with Subscription Levels */}
|
||||||
<Route path="/createServer/modpack" element={user ? <Modpack user={user} /> : <Navigate to="/login" />} />
|
<Route path="/createServer/java" element={user ? (subscription > 0 ? <Javapick user={user} /> : <Navigate to="/payement?package=Basique" />) : <Navigate to="/login" />} />
|
||||||
|
<Route path="/createServer/bedrock" element={user ? (subscription > 1 ? <Bedrock user={user} /> : <Navigate to="/payement?package=Standard" />) : <Navigate to="/login" />} />
|
||||||
|
<Route path="/createServer/modpack" element={user ? (subscription > 2 ? <Modpack user={user} /> : <Navigate to="/payement?package=Premium" />) : <Navigate to="/login" />} />
|
||||||
|
|
||||||
|
{/* Server Details Route */}
|
||||||
<Route path="/server/:serverName/*" element={user ? <ServerDetails user={user} /> : <Navigate to="/login" />} />
|
<Route path="/server/:serverName/*" element={user ? <ServerDetails user={user} /> : <Navigate to="/login" />} />
|
||||||
|
|
||||||
|
{/* Pricing and Payment */}
|
||||||
|
<Route path="/pricing" element={user ? <Pricing user={user} /> : <Navigate to="/login" />} />
|
||||||
|
<Route path="/payment/*" element={user ? <PaymentForm user={user} /> : <Navigate to="/login" />} />
|
||||||
|
<Route path="/return/*" element={user ? <Checkout user={user} /> : <Navigate to="/login" />} />
|
||||||
|
|
||||||
|
{/* Default Route */}
|
||||||
<Route path="/" element={<Navigate to={user ? "/dashboard" : "/login"} />} />
|
<Route path="/" element={<Navigate to={user ? "/dashboard" : "/login"} />} />
|
||||||
|
|
||||||
|
{/* Catch-all route */}
|
||||||
<Route path="*" element={user ? <DashboardPage user={user} /> : <Navigate to="/login" />} />
|
<Route path="*" element={user ? <DashboardPage user={user} /> : <Navigate to="/login" />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
position="top-right"
|
position="top-right"
|
||||||
autoClose={3500}
|
autoClose={3500}
|
||||||
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
src/assets/tier/basique.png
Normal file
BIN
src/assets/tier/basique.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
BIN
src/assets/tier/premium.png
Normal file
BIN
src/assets/tier/premium.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
BIN
src/assets/tier/standard.png
Normal file
BIN
src/assets/tier/standard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
@ -1,163 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
import styles from './CreateServer.module.scss';
|
|
||||||
import { getUserSubdomain } from "../../service/firebase";
|
|
||||||
import serviiApi from "../../service/api.tsx";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
import { GoTag, GoCheck } from "react-icons/go";
|
|
||||||
|
|
||||||
const versions = {
|
|
||||||
paper: [
|
|
||||||
"1.16.2",
|
|
||||||
"1.14.4",
|
|
||||||
"1.13.2",
|
|
||||||
"1.16.1",
|
|
||||||
"1.18.2",
|
|
||||||
"1.19",
|
|
||||||
"1.20.4",
|
|
||||||
"1.19.3",
|
|
||||||
"1.19.4",
|
|
||||||
"1.19.2",
|
|
||||||
"1.20.1",
|
|
||||||
"1.21.1",
|
|
||||||
"1.20.5",
|
|
||||||
"1.20.6",
|
|
||||||
"1.21",
|
|
||||||
"1.20.2",
|
|
||||||
"1.20",
|
|
||||||
"1.9.4",
|
|
||||||
"1.10.2",
|
|
||||||
"1.11.2",
|
|
||||||
"1.12.2",
|
|
||||||
"1.13.1",
|
|
||||||
"1.13.2",
|
|
||||||
"1.14.1",
|
|
||||||
"1.14.2",
|
|
||||||
"1.14.3",
|
|
||||||
"1.14.4",
|
|
||||||
"1.15.1",
|
|
||||||
"1.15.2",
|
|
||||||
"1.16.1",
|
|
||||||
"1.16.2",
|
|
||||||
"1.16.3",
|
|
||||||
"1.16.4",
|
|
||||||
"1.16.5",
|
|
||||||
"1.17.1",
|
|
||||||
"1.18.1"
|
|
||||||
].sort((a, b) => 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 (
|
|
||||||
<div className={styles.container}>
|
|
||||||
{noServers ? (
|
|
||||||
<div className={styles.mainCardNoserveur}>
|
|
||||||
<div className={styles.nsSubTitle}>Bonjour</div>
|
|
||||||
<div className={styles.nsTitle}>Aucun serveur</div>
|
|
||||||
<button className={styles.btnnoServCreate} onClick={onCreateServer}>Créer un nouveau serveur</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
subdomain === null ? (
|
|
||||||
<div>Loading...</div>
|
|
||||||
) : (
|
|
||||||
subdomain === '' ? (
|
|
||||||
<div className={styles.mainCardSubdomain}>
|
|
||||||
<div className={styles.title}>Ecrivez votre sous domaine</div>
|
|
||||||
<div className={styles.subtitle}>
|
|
||||||
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 !
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
className={styles.inputsubdomain}
|
|
||||||
type="text"
|
|
||||||
value={subdomainInput}
|
|
||||||
onChange={(e) => setSubdomainInput(validateInput(e.target.value))}
|
|
||||||
placeholder='Nom du sous domaine'
|
|
||||||
/>
|
|
||||||
<button className={styles.btnSubCreate} onClick={handleSaveSubdomain}>
|
|
||||||
Envoyer
|
|
||||||
</button>
|
|
||||||
<button className={styles.btnSubCreate} onClick={onCancel}>
|
|
||||||
Annuler
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={styles.GamesChoice}>
|
|
||||||
<div className={styles.title}>Création du serveur</div>
|
|
||||||
<div className={styles.subtitle}>Comment voulez vous jouer ?</div>
|
|
||||||
<div className={styles.GamesContainer}>dd</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,20 @@
|
|||||||
|
import styles from './DeleteConfirmationModal.module.scss';
|
||||||
|
|
||||||
|
const DeleteConfirmationModal = ({ isOpen, onClose, onDelete }) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.modalOverlay}>
|
||||||
|
<div className={styles.modal}>
|
||||||
|
<h2>Êtes-vous sûr de vouloir supprimer ?</h2>
|
||||||
|
<p>Cette action est irréversible.</p>
|
||||||
|
<div className={styles.modalButtons}>
|
||||||
|
<button className={styles.cancelButton} onClick={onClose}>Annuler</button>
|
||||||
|
<button className={styles.deleteButton} onClick={onDelete}>Supprimer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteConfirmationModal;
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -4,16 +4,19 @@ import styles from './Navbar.module.scss';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faUser, faCog, faSignOutAlt, faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
import { faUser, faCog, faSignOutAlt, faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
const Navbar = ({
|
const Navbar = ({
|
||||||
user,
|
user,
|
||||||
hasShadow = true,
|
hasShadow = true,
|
||||||
showBackButton = false,
|
showBackButton = false,
|
||||||
onBackClick,
|
onBackClick,
|
||||||
backButtonText = "Retour au dashboard"
|
backButtonText = "Retour au dashboard",
|
||||||
|
Subsribebtn = true
|
||||||
}) => {
|
}) => {
|
||||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||||
const dropdownRef = useRef(null);
|
const dropdownRef = useRef(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
auth.signOut();
|
auth.signOut();
|
||||||
@ -45,7 +48,15 @@ const Navbar = ({
|
|||||||
{showBackButton && (
|
{showBackButton && (
|
||||||
<div className={styles.backButton} onClick={onBackClick}>
|
<div className={styles.backButton} onClick={onBackClick}>
|
||||||
<FontAwesomeIcon icon={faArrowLeft} className={styles.backIcon} />
|
<FontAwesomeIcon icon={faArrowLeft} className={styles.backIcon} />
|
||||||
<span>{backButtonText}</span> {}
|
<span>{backButtonText}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{Subsribebtn && (
|
||||||
|
<div className={styles.subscribeSection}>
|
||||||
|
<div className={styles.subscribeBtn} onClick={() => navigate('/Pricing')}>
|
||||||
|
S'abonner
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -90,6 +101,7 @@ Navbar.propTypes = {
|
|||||||
showBackButton: PropTypes.bool,
|
showBackButton: PropTypes.bool,
|
||||||
onBackClick: PropTypes.func,
|
onBackClick: PropTypes.func,
|
||||||
backButtonText: PropTypes.string,
|
backButtonText: PropTypes.string,
|
||||||
|
Subsribebtn: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Navbar;
|
export default Navbar;
|
||||||
|
@ -89,6 +89,18 @@
|
|||||||
font-size: 1.5rem;
|
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) {
|
@media (max-width: 650px) {
|
||||||
.backButton{
|
.backButton{
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
|
@ -10,7 +10,7 @@ const ServerCard = ({ status, version, name, framework, onRunClick, onStopClick,
|
|||||||
|
|
||||||
const getFrameworkSource = () => {
|
const getFrameworkSource = () => {
|
||||||
switch (framework) {
|
switch (framework) {
|
||||||
case 'frabric':
|
case 'fabric':
|
||||||
return fabric;
|
return fabric;
|
||||||
case 'forge':
|
case 'forge':
|
||||||
return forge;
|
return forge;
|
||||||
|
@ -105,6 +105,7 @@
|
|||||||
.startStopButton {
|
.startStopButton {
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -42,19 +42,19 @@ body {
|
|||||||
|
|
||||||
@media (min-width: 1600px) {
|
@media (min-width: 1600px) {
|
||||||
html {
|
html {
|
||||||
font-size: 17px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1800px) {
|
@media (min-width: 1800px) {
|
||||||
html {
|
html {
|
||||||
font-size: 19px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 2000px) {
|
@media (min-width: 2000px) {
|
||||||
html {
|
html {
|
||||||
font-size: 15px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,60 +2,75 @@ import styles from './CreateServer.module.scss';
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import Navbar from '../../components/navbar/Navbar';
|
import Navbar from '../../components/navbar/Navbar';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 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 CreateServer = ({ user }) => {
|
||||||
const navigate = useNavigate();
|
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 (
|
return (
|
||||||
<div className={styles.Container}>
|
<div className={styles.Container}>
|
||||||
<Navbar
|
<Navbar
|
||||||
user={user}
|
user={user}
|
||||||
hasShadow={false}
|
hasShadow={false}
|
||||||
showBackButton={true}
|
showBackButton={true}
|
||||||
onBackClick={() => navigate('/dashboard')}
|
onBackClick={() => navigate('/dashboard')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.GamesChoice}>
|
<div className={styles.GamesChoice}>
|
||||||
<div className={styles.title}>Création du serveur</div>
|
<div className={styles.title}>Création du serveur</div>
|
||||||
<div className={styles.subtitle}>De quelle façon voulez-vous jouer ?</div>
|
<div className={styles.subtitle}>De quelle façon voulez-vous jouer ?</div>
|
||||||
<div className={styles.GamesContainer}>
|
<div className={styles.GamesContainer}>
|
||||||
<div className={styles.GameCard} onClick={() => navigate('/CreateServer/java')}>
|
<div className={styles.GameCard} onClick={() => (subscription > 0) ? navigate('/CreateServer/java') : navigate('/payement?package=Gratuit')}>
|
||||||
<img src={javaimg} className={styles.imgCard}/>
|
<img src={javaimg} className={styles.imgCard} alt="Java Edition" />
|
||||||
<div className={styles.Gamesubtitle}>Java Edition</div>
|
<div className={styles.Gamesubtitle}>Java Edition</div>
|
||||||
<div className={styles.Gamedescription}>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 !</div>
|
<div className={styles.Gamedescription}>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 !</div>
|
||||||
<button className={styles.GameButton}>Choisir ce modèle </button>
|
<button className={styles.GameButton}>Choisir ce modèle</button>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.GameCard} onClick={() => navigate('/CreateServer/bedrock')}>
|
|
||||||
<img src={bedrockimg} className={styles.imgCard}/>
|
<div className={styles.GameCard} onClick={() => (subscription > 1) ? navigate('/CreateServer/bedrock') : navigate('/payement?package=Standard')}>
|
||||||
<div className={styles.Gamesubtitle}>Mini-jeu</div>
|
<img src={bedrockimg} className={styles.imgCard} alt="Mini-jeu" />
|
||||||
<div className={styles.Gamedescription}>Plongez dans Minecraft avec des cartes personnalisées et des règles uniques, en solo ou avec vos amis.</div>
|
<div className={styles.Gamesubtitle}>Mini-jeu</div>
|
||||||
<button className={styles.GameButton}>Choisir ce modèle </button>
|
<div className={styles.Gamedescription}>Plongez dans Minecraft avec des cartes personnalisées et des règles uniques, en solo ou avec vos amis.</div>
|
||||||
</div>
|
<button className={styles.GameButton}>Choisir ce modèle</button>
|
||||||
<div className={styles.GameCard} onClick={() => navigate('/CreateServer/modpack')}>
|
</div>
|
||||||
<img src={modedimg} className={styles.imgCard}/>
|
|
||||||
<div className={styles.Gamesubtitle}>Minecraft Modé</div>
|
<div className={styles.GameCard} onClick={() => (subscription > 2) ? navigate('/CreateServer/modpack') : navigate('/payement?package=Premium')}>
|
||||||
<div className={styles.Gamedescription}>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.</div>
|
<img src={modedimg} className={styles.imgCard} alt="Minecraft Modé" />
|
||||||
<button className={styles.GameButton}>Choisir ce modèle </button>
|
<div className={styles.Gamesubtitle}>Minecraft Modé</div>
|
||||||
</div>
|
<div className={styles.Gamedescription}>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.</div>
|
||||||
</div>
|
<button className={styles.GameButton}>Choisir ce modèle</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
CreateServer.propTypes = {
|
CreateServer.propTypes = {
|
||||||
user: PropTypes.oneOfType([
|
user: PropTypes.shape({
|
||||||
PropTypes.shape({
|
uid: PropTypes.string.isRequired,
|
||||||
uid: PropTypes.string.isRequired,
|
displayName: PropTypes.string,
|
||||||
displayName: PropTypes.string,
|
email: PropTypes.string,
|
||||||
email: PropTypes.string,
|
photoURL: PropTypes.string,
|
||||||
photoURL: PropTypes.string,
|
}).isRequired,
|
||||||
}),
|
|
||||||
]),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CreateServer;
|
export default CreateServer;
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.GamesContainer {
|
.GamesContainer {
|
||||||
margin-top: 2.5rem;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -90,20 +89,23 @@ img {
|
|||||||
.GamesContainer {
|
.GamesContainer {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-wrap: row;
|
flex-wrap: row;
|
||||||
margin-top: 40rem;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin-top: 45rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.GameCard {
|
.GameCard {
|
||||||
margin: 1rem;
|
margin: 0rem 0rem .5rem 0rem;
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
padding: 2.5rem;
|
padding: 2.5rem;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 750px) {
|
.GamesChoice {
|
||||||
.GamesContainer {
|
margin-top: 35rem;
|
||||||
margin-top: 50rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { GoTag, GoCheck } from "react-icons/go";
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import styles from './java.module.scss';
|
import styles from './java.module.scss';
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import Navbar from '../../../components/navbar/Navbar';
|
import Navbar from '../../../components/navbar/Navbar.jsx';
|
||||||
import serviiApi from "../../../service/api.tsx";
|
import serviiApi from "../../../service/api.tsx";
|
||||||
|
|
||||||
const Javapick = ({ user }) => {
|
const Javapick = ({ user }) => {
|
@ -1,13 +1,57 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
import styles from './modpack.module.scss';
|
import styles from './modpack.module.scss';
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from 'prop-types';
|
||||||
import Navbar from '../../../components/navbar/Navbar';
|
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 Modpack = ({ user }) => {
|
||||||
const navigate = useNavigate();
|
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 (
|
return (
|
||||||
<div className={styles.Container}>
|
<div className={styles.Container}>
|
||||||
<Navbar
|
<Navbar
|
||||||
@ -17,9 +61,38 @@ const Modpack = ({ user }) => {
|
|||||||
backButtonText="Retour"
|
backButtonText="Retour"
|
||||||
onBackClick={() => navigate('/CreateServer')}
|
onBackClick={() => navigate('/CreateServer')}
|
||||||
/>
|
/>
|
||||||
<div className={styles.hey}>
|
<div className={styles.modpackGrid}>
|
||||||
<h1>Prochainement disponible</h1>
|
{error ? <h2>{error}</h2> : (
|
||||||
</div>
|
modpacks.map((modpack, index) => (
|
||||||
|
<div
|
||||||
|
key={`modpack-${index}`}
|
||||||
|
className={`${styles.modpackCard} ${selectedModpackIndex === index ? styles.selected : ''}`}
|
||||||
|
onClick={() => handleModpackClick(index)}
|
||||||
|
>
|
||||||
|
<img src={`https://www.servii.fr/api/modpacks/image/${modpack.image}`} alt={modpack.name} className={styles.modpackImage} />
|
||||||
|
<h3>{modpack.name}</h3>
|
||||||
|
<p>{modpack.description}</p>
|
||||||
|
<div className={styles.tags}>
|
||||||
|
<span className={`${styles.tag} ${styles.adventure}`}>Aventure</span>
|
||||||
|
<span className={`${styles.tag} ${styles.combat}`}>Combat</span>
|
||||||
|
<span className={`${styles.tag} ${styles.pve}`}>PvE</span>
|
||||||
|
<span className={`${styles.tag} ${styles.forge}`}>{modpack.framework}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.meta}>
|
||||||
|
<span>Version MC: {modpack.mcVersion}</span>
|
||||||
|
<span>Version Modpack: {modpack.version}</span>
|
||||||
|
</div>
|
||||||
|
{selectedModpackIndex === index && ( // Condition modifiée
|
||||||
|
<div className={styles.overlay}>
|
||||||
|
<div className={styles.shortNamtitle}>Créer le serveur {modpack.short_name} ?</div>
|
||||||
|
<button className={styles.createButton} onClick={handleCreate}>Créer</button>
|
||||||
|
<button className={styles.cancelButton} onClick={handleCancel}>Annuler</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,154 @@
|
|||||||
.hey{
|
.Container {
|
||||||
margin-top: 8em;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,10 +3,11 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import ServerCard from '../../components/serverCards/DefaultServerCard';
|
import ServerCard from '../../components/serverCards/DefaultServerCard';
|
||||||
import Navbar from '../../components/navbar/Navbar';
|
import Navbar from '../../components/navbar/Navbar';
|
||||||
import Loading from '../Loading/loading';
|
import Loading from '../Loading/loading';
|
||||||
import { getUserSubdomain } from "../../service/firebase";
|
import { getUserSubdomain, getUserSubscription } from "../../service/firebase";
|
||||||
import serviiApi from "../../service/api.tsx";
|
import serviiApi from "../../service/api.tsx";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import styles from './DashboardPage.module.scss';
|
import styles from './DashboardPage.module.scss';
|
||||||
|
import DeleteConfirmationModal from '../../components/DeleteConfirmationModal/DeleteConfirmationModal';
|
||||||
|
|
||||||
const CACHE_KEY_SERVERS = 'cachedServers';
|
const CACHE_KEY_SERVERS = 'cachedServers';
|
||||||
const CACHE_KEY_TIMESTAMP = 'cacheTimestamp';
|
const CACHE_KEY_TIMESTAMP = 'cacheTimestamp';
|
||||||
@ -20,15 +21,21 @@ const DashboardPage = ({ user }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [subdomain, setSubdomain] = useState(' ');
|
const [subdomain, setSubdomain] = useState(' ');
|
||||||
|
const [subscription, setSubscription] = useState(0);
|
||||||
const [loading, setLoading] = useState(servers.length === 0);
|
const [loading, setLoading] = useState(servers.length === 0);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [newSubdomain, setNewSubdomain] = useState(' ');
|
const [newSubdomain, setNewSubdomain] = useState(' ');
|
||||||
|
|
||||||
|
const [isModalOpen, setModalOpen] = useState(false);
|
||||||
|
const [serverToDelete, setServerToDelete] = useState(null);
|
||||||
|
|
||||||
const updateServersFromApi = useCallback(async () => {
|
const updateServersFromApi = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
if (user?.uid) {
|
if (user?.uid) {
|
||||||
const userSubdomain = await getUserSubdomain(user.uid);
|
const userSubdomain = await getUserSubdomain(user.uid);
|
||||||
setSubdomain(userSubdomain || null);
|
setSubdomain(userSubdomain || null);
|
||||||
|
const userSubscription = await getUserSubscription(user.uid);
|
||||||
|
setSubscription(userSubscription || 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ApiResponse = await serviiApi.fetchServers();
|
const ApiResponse = await serviiApi.fetchServers();
|
||||||
@ -51,14 +58,44 @@ const DashboardPage = ({ user }) => {
|
|||||||
|
|
||||||
const handleCreateServer = () => navigate('/CreateServer');
|
const handleCreateServer = () => navigate('/CreateServer');
|
||||||
|
|
||||||
const handleRunServer = async (serverName) => {
|
const handleRunServer = async (serverName, framework) => {
|
||||||
try {
|
try {
|
||||||
await serviiApi.serverRun(serverName);
|
if (subscription === 0) {
|
||||||
updateServersFromApi();
|
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) {
|
} catch (error) {
|
||||||
console.error('Error starting server:', error);
|
console.error('Error starting server:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleStopServer = async (serverName) => {
|
const handleStopServer = async (serverName) => {
|
||||||
try {
|
try {
|
||||||
@ -69,12 +106,16 @@ const DashboardPage = ({ user }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteServer = async (serverName) => {
|
const handleDeleteServer = async () => {
|
||||||
try {
|
if (serverToDelete) {
|
||||||
await serviiApi.serverDelete(serverName);
|
try {
|
||||||
updateServersFromApi();
|
await serviiApi.serverDelete(serverToDelete);
|
||||||
} catch (error) {
|
setModalOpen(false);
|
||||||
console.error('Error deleting server:', error);
|
setServerToDelete(null);
|
||||||
|
updateServersFromApi();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error deleting server:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -152,71 +193,83 @@ const DashboardPage = ({ user }) => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.dashboardContainer}>
|
<div className={styles.dashboardContainer}>
|
||||||
<Navbar user={user} />
|
<Navbar user={user} />
|
||||||
<div className={styles.mainContent}>
|
<div className={styles.mainContent}>
|
||||||
<div className={styles.iptitle}>
|
<div className={styles.iptitle}>
|
||||||
Adresse de connexion à vos serveurs :
|
Adresse de connexion à vos serveurs :
|
||||||
<span onClick={handleCopyAddress} className={styles.subdomain}>
|
<span onClick={handleCopyAddress} className={styles.subdomain}>
|
||||||
{" " + subdomain}.servii.fr
|
{" " + subdomain}.servii.fr
|
||||||
</span>
|
</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.headerContainer}>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Filtrer par nom"
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
|
||||||
className={styles.searchInput}
|
|
||||||
/>
|
|
||||||
<button className={styles.btnCreate} onClick={handleCreateServer}>
|
|
||||||
<div className={styles.plusIcon}>+</div>
|
|
||||||
<div>Créer un nouveau serveur</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.FavoriteServerContainer}>
|
|
||||||
{favoriteServer ? (
|
|
||||||
<div className={styles.favoriteServerCard}>
|
|
||||||
<ServerCard
|
|
||||||
key={favoriteServer.id}
|
|
||||||
status={favoriteServer.running}
|
|
||||||
version={favoriteServer.version}
|
|
||||||
name={favoriteServer.name}
|
|
||||||
framework={favoriteServer.framework}
|
|
||||||
maxPlayers={favoriteServer.maxPlayers}
|
|
||||||
countPlayers={favoriteServer.onlinePlayers}
|
|
||||||
onRunClick={() => handleRunServer(favoriteServer.name)}
|
|
||||||
onStopClick={() => handleStopServer(favoriteServer.name)}
|
|
||||||
onDeleteClick={() => handleDeleteServer(favoriteServer.name)}
|
|
||||||
subdomain={subdomain}
|
|
||||||
favoriteServer={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.cardsContainer}>
|
|
||||||
{serversWithoutFavorite.length > 0 ? (
|
|
||||||
serversWithoutFavorite.filter(server =>
|
|
||||||
server.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
||||||
).map((server) => (
|
|
||||||
<ServerCard
|
|
||||||
key={server.id}
|
|
||||||
status={server.running}
|
|
||||||
version={server.version}
|
|
||||||
name={server.name}
|
|
||||||
framework={server.framework}
|
|
||||||
maxPlayers={server.maxPlayers}
|
|
||||||
countPlayers={server.onlinePlayers}
|
|
||||||
onRunClick={() => handleRunServer(server.name)}
|
|
||||||
onStopClick={() => handleStopServer(server.name)}
|
|
||||||
onDeleteClick={() => handleDeleteServer(server.name)}
|
|
||||||
subdomain={subdomain}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.headerContainer}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Filtrer par nom"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className={styles.searchInput}
|
||||||
|
/>
|
||||||
|
<button className={styles.btnCreate} onClick={handleCreateServer}>
|
||||||
|
<div className={styles.plusIcon}>+</div>
|
||||||
|
<div>Créer un nouveau serveur</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.FavoriteServerContainer}>
|
||||||
|
{favoriteServer ? (
|
||||||
|
<div className={styles.favoriteServerCard}>
|
||||||
|
<ServerCard
|
||||||
|
key={favoriteServer.id}
|
||||||
|
status={favoriteServer.running}
|
||||||
|
version={favoriteServer.version}
|
||||||
|
name={favoriteServer.name}
|
||||||
|
framework={favoriteServer.framework}
|
||||||
|
maxPlayers={favoriteServer.maxPlayers}
|
||||||
|
countPlayers={favoriteServer.onlinePlayers}
|
||||||
|
onRunClick={() => handleRunServer(favoriteServer.name , favoriteServer.framework)}
|
||||||
|
onStopClick={() => handleStopServer(favoriteServer.name)}
|
||||||
|
onDeleteClick={() => {
|
||||||
|
setServerToDelete(favoriteServer.name);
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
subdomain={subdomain}
|
||||||
|
favoriteServer={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.cardsContainer}>
|
||||||
|
{serversWithoutFavorite.length > 0 ? (
|
||||||
|
serversWithoutFavorite.filter(server =>
|
||||||
|
server.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
).map((server) => (
|
||||||
|
<ServerCard
|
||||||
|
key={server.id}
|
||||||
|
status={server.running}
|
||||||
|
version={server.version}
|
||||||
|
name={server.name}
|
||||||
|
framework={server.framework}
|
||||||
|
maxPlayers={server.maxPlayers}
|
||||||
|
countPlayers={server.onlinePlayers}
|
||||||
|
onRunClick={() => handleRunServer(server.name , server.framework)}
|
||||||
|
onStopClick={() => handleStopServer(server.name)}
|
||||||
|
onDeleteClick={() => {
|
||||||
|
setServerToDelete(server.name);
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
subdomain={subdomain}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DeleteConfirmationModal
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={() => setModalOpen(false)}
|
||||||
|
onDelete={handleDeleteServer}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
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(`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 <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;
|
97
src/pages/Payement/PaymentForm/PaymentForm.jsx
Normal file
97
src/pages/Payement/PaymentForm/PaymentForm.jsx
Normal file
@ -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 (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
PaymentForm.propTypes = {
|
||||||
|
user: PropTypes.shape({
|
||||||
|
uid: PropTypes.string.isRequired,
|
||||||
|
email: PropTypes.string,
|
||||||
|
photoURL: PropTypes.string,
|
||||||
|
}).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PaymentForm;
|
7
src/pages/Payement/PaymentForm/PaymentForm.module.scss
Normal file
7
src/pages/Payement/PaymentForm/PaymentForm.module.scss
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.container{
|
||||||
|
margin-top: 5.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Footer-PoweredBy-Text{
|
||||||
|
display: none;
|
||||||
|
}
|
117
src/pages/Payement/Pricing/Pricing.jsx
Normal file
117
src/pages/Payement/Pricing/Pricing.jsx
Normal file
@ -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 (
|
||||||
|
<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}>
|
||||||
|
<div className={styles.packageCardheader}>
|
||||||
|
<h2 className={styles.title}>{pkg.title}</h2>
|
||||||
|
<img src={pkg.image} alt={pkg.title} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.priceContainer}>
|
||||||
|
<div className={styles.price}>{pkg.price}</div>
|
||||||
|
<span className={styles.mensuel}>/mois</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.description}>{pkg.description}</div>
|
||||||
|
<hr />
|
||||||
|
<ul className={styles.features}>
|
||||||
|
<div className={styles.inclut}>CE QUI EST INCLUS</div>
|
||||||
|
|
||||||
|
{pkg.features.map((feature, idx) => (
|
||||||
|
<li key={idx} className={feature.isAvailable ? styles.featureAvailable : styles.featureUnavailable}>
|
||||||
|
{feature.isAvailable ? (
|
||||||
|
<FaCheckCircle style={{ color: 'green', marginRight: '8px' }} />
|
||||||
|
) : (
|
||||||
|
<FaTimesCircle style={{ color: 'red', marginRight: '8px' }} />
|
||||||
|
)}
|
||||||
|
{feature.name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<button className={styles.button} onClick={() => handleSubscribe(pkg)}>
|
||||||
|
Démarrez maintenant
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Pricing.propTypes = {
|
||||||
|
user: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Pricing;
|
122
src/pages/Payement/Pricing/Pricing.module.scss
Normal file
122
src/pages/Payement/Pricing/Pricing.module.scss
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -88,7 +88,23 @@ function toast_status(status: number, message: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class serviiApi {
|
class serviiApi {
|
||||||
|
|
||||||
|
public static async fetchModpacks(): Promise<ApiResponse> {
|
||||||
|
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<T extends BaseRequest>(endpoint: serviiRequest, body: T, token: string): Promise<ApiResponse> {
|
private static async call<T extends BaseRequest>(endpoint: serviiRequest, body: T, token: string): Promise<ApiResponse> {
|
||||||
const unreachable: string = "Couldn't find an available API";
|
const unreachable: string = "Couldn't find an available API";
|
||||||
try {
|
try {
|
||||||
|
@ -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