diff --git a/package-lock.json b/package-lock.json index 6921446..237c5c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@stripe/react-stripe-js": "^2.8.0", + "@stripe/stripe-js": "^4.5.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", "@types/jest": "^29.5.12", @@ -3184,6 +3186,27 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@stripe/react-stripe-js": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.8.0.tgz", + "integrity": "sha512-Vf1gNEuBxA9EtxiLghm2ZWmgbADNMJw4HW6eolUu0DON/6mZvWZgk0KHolN0sozNJwYp0i/8hBsDBcBUWcvnbw==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "@stripe/stripe-js": "^1.44.1 || ^2.0.0 || ^3.0.0 || ^4.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@stripe/stripe-js": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-4.5.0.tgz", + "integrity": "sha512-dMOzc58AOlsF20nYM/avzV8RFhO/vgYTY7ajLMH6mjlnZysnOHZxsECQvjEmL8Q/ukPwHkOnxSPW/QGCCnp7XA==", + "engines": { + "node": ">=12.16" + } + }, "node_modules/@testing-library/dom": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.3.0.tgz", diff --git a/package.json b/package.json index 70d12b1..2ed23ab 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "dependencies": { "@fortawesome/free-solid-svg-icons": "^6.5.2", "@fortawesome/react-fontawesome": "^0.2.2", + "@stripe/react-stripe-js": "^2.8.0", + "@stripe/stripe-js": "^4.5.0", "@testing-library/jest-dom": "^6.4.6", "@testing-library/react": "^16.0.0", "@types/jest": "^29.5.12", diff --git a/src/App.jsx b/src/App.jsx index 57d30ee..824d4fb 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,6 +5,7 @@ import 'react-toastify/dist/ReactToastify.css'; import { auth } from './service/firebase'; import styles from './App.module.scss'; import Loading from './pages/Loading/loading'; +import Pricing from './pages/Payement/Pricing'; const LoginPage = lazy(() => import('./pages/LoginPage/LoginPage')); const ServerDetails = lazy(() => import('./pages/ServerDetails/ServerDetails')); @@ -55,6 +56,8 @@ const App = () => { : } /> : } /> : } /> + : } /> + } /> : } /> diff --git a/src/pages/Payement/PaymentForm/PaymentForm.jsx b/src/pages/Payement/PaymentForm/PaymentForm.jsx new file mode 100644 index 0000000..59231c4 --- /dev/null +++ b/src/pages/Payement/PaymentForm/PaymentForm.jsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'; +import styles from './PaymentForm.module.scss'; + +const PaymentForm = ({ groups, closeModal }) => { + const stripe = useStripe(); + const elements = useElements(); + const [errorMessage, setErrorMessage] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); + + const handleSubmit = async (event) => { + event.preventDefault(); + + if (!stripe || !elements) return; + + const cardElement = elements.getElement(CardElement); + const { error, paymentMethod } = await stripe.createPaymentMethod({ + type: 'card', + card: cardElement, + }); + + if (error) { + setErrorMessage(error.message); + setSuccessMessage(null); + } else { + setSuccessMessage(`Paiement réussi! Méthode de paiement ID: ${paymentMethod.id}`); + setErrorMessage(null); + // Ici, vous devriez également envoyer paymentMethod.id à votre serveur + // Fermer la modal après un court délai ou après l'envoi à votre serveur + setTimeout(() => closeModal(), 2000); + } + }; + + return ( +
+ + {errorMessage &&

{errorMessage}

} + {successMessage &&

{successMessage}

} + + + + ); +}; + +export default PaymentForm; diff --git a/src/pages/Payement/PaymentForm/PaymentForm.module.scss b/src/pages/Payement/PaymentForm/PaymentForm.module.scss new file mode 100644 index 0000000..0bbf541 --- /dev/null +++ b/src/pages/Payement/PaymentForm/PaymentForm.module.scss @@ -0,0 +1,19 @@ +.modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + } + + .modalContent { + background: #fff; + padding: 20px; + border-radius: 8px; + width: 300px; + } + \ No newline at end of file diff --git a/src/pages/Payement/Pricing.jsx b/src/pages/Payement/Pricing.jsx new file mode 100644 index 0000000..f82b4df --- /dev/null +++ b/src/pages/Payement/Pricing.jsx @@ -0,0 +1,68 @@ +import React, { useState } from 'react'; +import styles from './Pricing.module.scss'; +import Navbar from '../../components/navbar/Navbar'; +import { useNavigate } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import { loadStripe } from '@stripe/stripe-js'; +import { Elements } from '@stripe/react-stripe-js'; +import PaymentForm from './PaymentForm/PaymentForm'; + +const stripePromise = loadStripe('pk_test_51PyIYTP3VLLeb9GlXCKgD4ylbemZPx72I3HkEAu0bRtcsfK31nqb3WtUbXKXUcKmyfrxKLfuJzZCPyp7Ymtlq9zy00c7VmkL6G'); + +const Pricing = ({ user }) => { + const navigate = useNavigate(); + const [selectedPackage, setSelectedPackage] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + + const groups = [ + { title: 'Gratuit', price: '0 €', features: ['Accès limité', 'Support par e-mail'] }, + { title: 'Standard', price: '2 €', features: ['Accès complet', 'Support prioritaire'] }, + { title: 'Premium', price: '9 €', features: ['Accès illimité', 'Support 24/7'] }, + ]; + + const handleSubscribe = (pkg) => { + setSelectedPackage(pkg); + setIsModalOpen(true); + }; + + return ( +
+ navigate('/Dashboard')} + /> + + {groups.map((pkg, index) => ( +
+

{pkg.title}

+

{pkg.price}

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

Paiement pour {selectedPackage.title}

+ + setIsModalOpen(false)} /> + +
+
+ )} +
+ ); +}; + +Pricing.propTypes = { + user: PropTypes.object.isRequired, +}; + +export default Pricing; diff --git a/src/pages/Payement/Pricing.module.scss b/src/pages/Payement/Pricing.module.scss new file mode 100644 index 0000000..b9d81a9 --- /dev/null +++ b/src/pages/Payement/Pricing.module.scss @@ -0,0 +1,60 @@ +$primary-color: #000; +$secondary-color: #fff; + +.pricingContainer { + display: flex; + justify-content: center; + gap: 20px; + padding: 40px; + background-color: $secondary-color; +} + +.packageCard { + background-color: $primary-color; + color: $secondary-color; + border-radius: 8px; + padding: 20px; + text-align: center; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + transition: transform 0.3s; + + &:hover { + transform: scale(1.05); + } +} + +.title { + font-size: 24px; + margin-bottom: 10px; +} + +.price { + font-size: 32px; + margin: 10px 0; +} + +.features { + list-style-type: none; + padding: 0; + margin: 20px 0; + + li { + margin: 5px 0; + } +} + +.button { + background-color: $secondary-color; + color: $primary-color; + border: none; + border-radius: 4px; + padding: 10px 20px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s; + + &:hover { + background-color: $primary-color; + color: $secondary-color; + } +}