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">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="src/assets/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Servii - Hebergement facile</title>
|
||||
<!-- charger les polices -->
|
||||
|
23
package-lock.json
generated
23
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
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 { 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 <Loading />;
|
||||
}
|
||||
|
||||
@ -48,17 +74,34 @@ const App = () => {
|
||||
<div className={styles.container}>
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Routes>
|
||||
{/* Public Route */}
|
||||
<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="/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" />} />
|
||||
<Route path="/createServer/modpack" element={user ? <Modpack user={user} /> : <Navigate to="/login" />} />
|
||||
|
||||
{/* Routes with Subscription Levels */}
|
||||
<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" />} />
|
||||
|
||||
{/* 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"} />} />
|
||||
|
||||
{/* Catch-all route */}
|
||||
<Route path="*" element={user ? <DashboardPage user={user} /> : <Navigate to="/login" />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
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 { 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 && (
|
||||
<div className={styles.backButton} onClick={onBackClick}>
|
||||
<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>
|
||||
)}
|
||||
|
||||
@ -90,6 +101,7 @@ Navbar.propTypes = {
|
||||
showBackButton: PropTypes.bool,
|
||||
onBackClick: PropTypes.func,
|
||||
backButtonText: PropTypes.string,
|
||||
Subsribebtn: PropTypes.bool
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -105,6 +105,7 @@
|
||||
.startStopButton {
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,26 @@ import Navbar from '../../components/navbar/Navbar';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
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 (
|
||||
<div className={styles.Container}>
|
||||
@ -23,20 +38,22 @@ const CreateServer = ({ user }) => {
|
||||
<div className={styles.title}>Création du serveur</div>
|
||||
<div className={styles.subtitle}>De quelle façon voulez-vous jouer ?</div>
|
||||
<div className={styles.GamesContainer}>
|
||||
<div className={styles.GameCard} onClick={() => navigate('/CreateServer/java')}>
|
||||
<img src={javaimg} className={styles.imgCard}/>
|
||||
<div className={styles.GameCard} onClick={() => (subscription > 0) ? navigate('/CreateServer/java') : navigate('/payement?package=Gratuit')}>
|
||||
<img src={javaimg} className={styles.imgCard} alt="Java Edition" />
|
||||
<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>
|
||||
<button className={styles.GameButton}>Choisir ce modèle</button>
|
||||
</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')}>
|
||||
<img src={bedrockimg} className={styles.imgCard} alt="Mini-jeu" />
|
||||
<div className={styles.Gamesubtitle}>Mini-jeu</div>
|
||||
<div className={styles.Gamedescription}>Plongez dans Minecraft avec des cartes personnalisées et des règles uniques, en solo ou avec vos amis.</div>
|
||||
<button className={styles.GameButton}>Choisir ce modèle</button>
|
||||
</div>
|
||||
<div className={styles.GameCard} onClick={() => navigate('/CreateServer/modpack')}>
|
||||
<img src={modedimg} className={styles.imgCard}/>
|
||||
|
||||
<div className={styles.GameCard} onClick={() => (subscription > 2) ? navigate('/CreateServer/modpack') : navigate('/payement?package=Premium')}>
|
||||
<img src={modedimg} className={styles.imgCard} alt="Minecraft Modé" />
|
||||
<div className={styles.Gamesubtitle}>Minecraft Modé</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>
|
||||
<button className={styles.GameButton}>Choisir ce modèle</button>
|
||||
@ -48,14 +65,12 @@ const CreateServer = ({ user }) => {
|
||||
};
|
||||
|
||||
CreateServer.propTypes = {
|
||||
user: PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
user: PropTypes.shape({
|
||||
uid: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string,
|
||||
email: PropTypes.string,
|
||||
photoURL: PropTypes.string,
|
||||
}),
|
||||
]),
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default CreateServer;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
.GamesChoice {
|
||||
margin-top: 35rem;
|
||||
}
|
||||
|
||||
@media (max-width: 750px) {
|
||||
.GamesContainer {
|
||||
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 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 }) => {
|
@ -1,12 +1,56 @@
|
||||
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 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 (
|
||||
<div className={styles.Container}>
|
||||
@ -17,8 +61,37 @@ const Modpack = ({ user }) => {
|
||||
backButtonText="Retour"
|
||||
onBackClick={() => navigate('/CreateServer')}
|
||||
/>
|
||||
<div className={styles.hey}>
|
||||
<h1>Prochainement disponible</h1>
|
||||
<div className={styles.modpackGrid}>
|
||||
{error ? <h2>{error}</h2> : (
|
||||
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>
|
||||
);
|
||||
|
@ -1,3 +1,154 @@
|
||||
.hey{
|
||||
margin-top: 8em;
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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,15 +58,45 @@ const DashboardPage = ({ user }) => {
|
||||
|
||||
const handleCreateServer = () => navigate('/CreateServer');
|
||||
|
||||
const handleRunServer = async (serverName) => {
|
||||
const handleRunServer = async (serverName, framework) => {
|
||||
try {
|
||||
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 {
|
||||
await serviiApi.serverStop(serverName);
|
||||
@ -69,13 +106,17 @@ const DashboardPage = ({ user }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteServer = async (serverName) => {
|
||||
const handleDeleteServer = async () => {
|
||||
if (serverToDelete) {
|
||||
try {
|
||||
await serviiApi.serverDelete(serverName);
|
||||
await serviiApi.serverDelete(serverToDelete);
|
||||
setModalOpen(false);
|
||||
setServerToDelete(null);
|
||||
updateServersFromApi();
|
||||
} catch (error) {
|
||||
console.error('Error deleting server:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyAddress = () => {
|
||||
@ -185,9 +226,12 @@ const DashboardPage = ({ user }) => {
|
||||
framework={favoriteServer.framework}
|
||||
maxPlayers={favoriteServer.maxPlayers}
|
||||
countPlayers={favoriteServer.onlinePlayers}
|
||||
onRunClick={() => handleRunServer(favoriteServer.name)}
|
||||
onRunClick={() => handleRunServer(favoriteServer.name , favoriteServer.framework)}
|
||||
onStopClick={() => handleStopServer(favoriteServer.name)}
|
||||
onDeleteClick={() => handleDeleteServer(favoriteServer.name)}
|
||||
onDeleteClick={() => {
|
||||
setServerToDelete(favoriteServer.name);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
subdomain={subdomain}
|
||||
favoriteServer={true}
|
||||
/>
|
||||
@ -208,15 +252,24 @@ const DashboardPage = ({ user }) => {
|
||||
framework={server.framework}
|
||||
maxPlayers={server.maxPlayers}
|
||||
countPlayers={server.onlinePlayers}
|
||||
onRunClick={() => handleRunServer(server.name)}
|
||||
onRunClick={() => handleRunServer(server.name , server.framework)}
|
||||
onStopClick={() => handleStopServer(server.name)}
|
||||
onDeleteClick={() => handleDeleteServer(server.name)}
|
||||
onDeleteClick={() => {
|
||||
setServerToDelete(server.name);
|
||||
setModalOpen(true);
|
||||
}}
|
||||
subdomain={subdomain}
|
||||
/>
|
||||
))
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DeleteConfirmationModal
|
||||
isOpen={isModalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
onDelete={handleDeleteServer}
|
||||
/>
|
||||
</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 {
|
||||
|
||||
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> {
|
||||
const unreachable: string = "Couldn't find an available API";
|
||||
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