add hosting plans via fossbilling
Deploy Nexus Portal to HestiaCP (FTP) / build-and-deploy (push) Successful in 33s
Deploy Nexus Portal to HestiaCP (FTP) / build-and-deploy (push) Successful in 33s
This commit is contained in:
Generated
+406
@@ -8,19 +8,24 @@
|
|||||||
"name": "portail-gise",
|
"name": "portail-gise",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"lucide-react": "^1.3.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
"react-router-dom": "^7.16.0"
|
"react-router-dom": "^7.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@tailwindcss/vite": "^4.3.1",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"autoprefixer": "^10.5.0",
|
||||||
"eslint": "^10.3.0",
|
"eslint": "^10.3.0",
|
||||||
"eslint-plugin-react-hooks": "^7.1.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^17.6.0",
|
"globals": "^17.6.0",
|
||||||
|
"postcss": "^8.5.15",
|
||||||
|
"tailwindcss": "^4.3.1",
|
||||||
"vite": "^8.0.12"
|
"vite": "^8.0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -835,6 +840,278 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@tailwindcss/node": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/remapping": "^2.3.5",
|
||||||
|
"enhanced-resolve": "5.21.6",
|
||||||
|
"jiti": "^2.7.0",
|
||||||
|
"lightningcss": "1.32.0",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"source-map-js": "^1.2.1",
|
||||||
|
"tailwindcss": "4.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@tailwindcss/oxide-android-arm64": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-darwin-arm64": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-darwin-x64": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-freebsd-x64": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-linux-arm64-gnu": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-linux-arm64-musl": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-linux-x64-gnu": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-linux-x64-musl": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-win32-arm64-msvc": "4.3.1",
|
||||||
|
"@tailwindcss/oxide-win32-x64-msvc": "4.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==",
|
||||||
|
"bundleDependencies": [
|
||||||
|
"@napi-rs/wasm-runtime",
|
||||||
|
"@emnapi/core",
|
||||||
|
"@emnapi/runtime",
|
||||||
|
"@tybys/wasm-util",
|
||||||
|
"@emnapi/wasi-threads",
|
||||||
|
"tslib"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"wasm32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/core": "^1.10.0",
|
||||||
|
"@emnapi/runtime": "^1.10.0",
|
||||||
|
"@emnapi/wasi-threads": "^1.2.1",
|
||||||
|
"@napi-rs/wasm-runtime": "^1.1.4",
|
||||||
|
"@tybys/wasm-util": "^0.10.2",
|
||||||
|
"tslib": "^2.8.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tailwindcss/vite": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@tailwindcss/node": "4.3.1",
|
||||||
|
"@tailwindcss/oxide": "4.3.1",
|
||||||
|
"tailwindcss": "4.3.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tybys/wasm-util": {
|
"node_modules/@tybys/wasm-util": {
|
||||||
"version": "0.10.2",
|
"version": "0.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
|
||||||
@@ -953,6 +1230,43 @@
|
|||||||
"url": "https://github.com/sponsors/epoberezkin"
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/autoprefixer": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"browserslist": "^4.28.2",
|
||||||
|
"caniuse-lite": "^1.0.30001787",
|
||||||
|
"fraction.js": "^5.3.4",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"postcss-value-parser": "^4.2.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"autoprefixer": "bin/autoprefixer"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "4.0.4",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||||
@@ -1128,6 +1442,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/enhanced-resolve": {
|
||||||
|
"version": "5.21.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz",
|
||||||
|
"integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.4",
|
||||||
|
"tapable": "^2.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
@@ -1423,6 +1751,20 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/fraction.js": {
|
||||||
|
"version": "5.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
|
||||||
|
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/rawify"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -1474,6 +1816,13 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/graceful-fs": {
|
||||||
|
"version": "4.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/hermes-estree": {
|
"node_modules/hermes-estree": {
|
||||||
"version": "0.25.1",
|
"version": "0.25.1",
|
||||||
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
|
||||||
@@ -1541,6 +1890,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/jiti": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"jiti": "lib/jiti-cli.mjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@@ -1906,6 +2265,25 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-aKUU4jKpXe26Y3xmF3DGg+NXHteRCVT96r/+Jp7wziqvmFr0/x8ReRsES0wHgTvW7OLxiHvFLN+YeyDnK6h0dQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/magic-string": {
|
||||||
|
"version": "0.30.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
|
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "10.2.5",
|
"version": "10.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||||
@@ -2084,6 +2462,13 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||||
@@ -2252,6 +2637,27 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tailwindcss": {
|
||||||
|
"version": "4.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz",
|
||||||
|
"integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/tapable": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.17",
|
"version": "0.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||||
|
|||||||
@@ -10,19 +10,24 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"lucide-react": "^1.3.0",
|
||||||
"react": "^19.2.6",
|
"react": "^19.2.6",
|
||||||
"react-dom": "^19.2.6",
|
"react-dom": "^19.2.6",
|
||||||
"react-router-dom": "^7.16.0"
|
"react-router-dom": "^7.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@tailwindcss/vite": "^4.3.1",
|
||||||
"@types/react": "^19.2.14",
|
"@types/react": "^19.2.14",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^6.0.1",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
|
"autoprefixer": "^10.5.0",
|
||||||
"eslint": "^10.3.0",
|
"eslint": "^10.3.0",
|
||||||
"eslint-plugin-react-hooks": "^7.1.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^17.6.0",
|
"globals": "^17.6.0",
|
||||||
|
"postcss": "^8.5.15",
|
||||||
|
"tailwindcss": "^4.3.1",
|
||||||
"vite": "^8.0.12"
|
"vite": "^8.0.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import Home from './pages/public/Home';
|
|||||||
|
|
||||||
// Import des Pages Privées (Espace Client)
|
// Import des Pages Privées (Espace Client)
|
||||||
import Dashboard from './pages/app/Dashboard';
|
import Dashboard from './pages/app/Dashboard';
|
||||||
|
import Store from './pages/app/Store';
|
||||||
//import Services from './pages/app/Services';
|
//import Services from './pages/app/Services';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@@ -42,6 +43,7 @@ export default function App() {
|
|||||||
{/* Si autorisé, on charge l'interface avec la Sidebar */}
|
{/* Si autorisé, on charge l'interface avec la Sidebar */}
|
||||||
<Route element={<AppLayout />}>
|
<Route element={<AppLayout />}>
|
||||||
<Route path="/dashboard" element={<Dashboard />} />
|
<Route path="/dashboard" element={<Dashboard />} />
|
||||||
|
<Route path="/store" element={<Store />} />
|
||||||
{/* <Route path="/services" element={<Services />} /> */}
|
{/* <Route path="/services" element={<Services />} /> */}
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display:block;
|
display:block;
|
||||||
|
|||||||
+119
-154
@@ -1,163 +1,128 @@
|
|||||||
// src/pages/app/Dashboard.jsx
|
import { useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { getClientOrders } from '../../services/api';
|
||||||
|
import { Server, Database, Cloud, Globe, Plus, AlertCircle, Loader } from 'lucide-react';
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
// --- DESIGN SYSTEM DU DASHBOARD ---
|
const navigate = useNavigate();
|
||||||
const gridStyle = {
|
const [orders, setOrders] = useState([]);
|
||||||
display: 'grid',
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))',
|
const [error, setError] = useState(null);
|
||||||
gap: '25px',
|
|
||||||
marginTop: '30px'
|
|
||||||
};
|
|
||||||
|
|
||||||
const cardStyle = {
|
// Chargement des données à l'ouverture du Sas
|
||||||
backgroundColor: '#121212',
|
useEffect(() => {
|
||||||
border: '1px solid #222',
|
const fetchInventory = async () => {
|
||||||
borderTop: '4px solid #00E5FF',
|
try {
|
||||||
padding: '25px',
|
// Appel à FOSSBilling via notre api.js
|
||||||
position: 'relative',
|
const data = await getClientOrders();
|
||||||
overflow: 'hidden'
|
|
||||||
};
|
|
||||||
|
|
||||||
const statusBadgeStyle = {
|
// FOSSBilling renvoie souvent la liste dans data.list
|
||||||
position: 'absolute',
|
setOrders(data.list || []);
|
||||||
top: '20px',
|
} catch (err) {
|
||||||
right: '20px',
|
setError(err.message || "Impossible de récupérer la télémétrie des services.");
|
||||||
backgroundColor: 'rgba(0, 229, 255, 0.1)',
|
} finally {
|
||||||
color: '#00E5FF',
|
setIsLoading(false);
|
||||||
padding: '4px 8px',
|
}
|
||||||
fontSize: '0.7rem',
|
};
|
||||||
fontWeight: 'bold',
|
|
||||||
letterSpacing: '1px'
|
|
||||||
};
|
|
||||||
|
|
||||||
const btnStyle = {
|
fetchInventory();
|
||||||
display: 'inline-block',
|
}, []);
|
||||||
width: '100%',
|
|
||||||
padding: '10px',
|
|
||||||
marginTop: '20px',
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
color: '#00E5FF',
|
|
||||||
border: '1px solid #00E5FF',
|
|
||||||
textAlign: 'center',
|
|
||||||
textDecoration: 'none',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
fontSize: '0.8rem',
|
|
||||||
cursor: 'pointer',
|
|
||||||
transition: 'all 0.2s',
|
|
||||||
boxSizing: 'border-box'
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
// Fonction Radar : Détecte le type de service selon son nom pour afficher la bonne icône
|
||||||
<div style={{ fontFamily: 'monospace' }}>
|
const getServiceIcon = (title) => {
|
||||||
|
const t = title.toLowerCase();
|
||||||
|
if (t.includes('vps') || t.includes('serveur')) return <Server className="w-10 h-10 text-cyan-400" />;
|
||||||
|
if (t.includes('cloud') || t.includes('nextcloud')) return <Cloud className="w-10 h-10 text-blue-400" />;
|
||||||
|
if (t.includes('db') || t.includes('base') || t.includes('sql')) return <Database className="w-10 h-10 text-purple-400" />;
|
||||||
|
return <Globe className="w-10 h-10 text-emerald-400" />; // Par défaut : Web / Hestia
|
||||||
|
};
|
||||||
|
|
||||||
{/* --- EN-TÊTE DU TABLEAU DE BORD --- */}
|
// Fonction d'état : Formate le statut du service
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', borderBottom: '1px solid #222', paddingBottom: '20px' }}>
|
const getStatusBadge = (status) => {
|
||||||
<div>
|
switch (status) {
|
||||||
<h2 style={{ color: '#FFF', margin: '0 0 5px 0', fontSize: '2rem', textTransform: 'uppercase' }}>
|
case 'active': return <span className="px-2 py-1 text-xs text-green-400 bg-green-400/10 border border-green-400/20 rounded">ACTIF</span>;
|
||||||
Console Opérationnelle
|
case 'pending_setup': return <span className="px-2 py-1 text-xs text-orange-400 bg-orange-400/10 border border-orange-400/20 rounded">EN PRÉPARATION</span>;
|
||||||
</h2>
|
case 'suspended': return <span className="px-2 py-1 text-xs text-red-400 bg-red-400/10 border border-red-400/20 rounded">SUSPENDU</span>;
|
||||||
<span style={{ color: '#888' }}>ID Réseau: <span style={{ color: '#E0E0E0' }}>usr_8472_alpha</span></span>
|
default: return <span className="px-2 py-1 text-xs text-gray-400 bg-gray-400/10 border border-gray-400/20 rounded">{status.toUpperCase()}</span>;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-6xl p-6 mx-auto">
|
||||||
|
|
||||||
|
<header className="mb-8">
|
||||||
|
<h1 className="text-3xl font-bold text-white tracking-widest">
|
||||||
|
TERMINAL <span className="text-cyan-400">NEXUS</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-400 mt-2">Aperçu de vos accréditations réseau et infrastructures.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* GESTION DES ERREURS & CHARGEMENT */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="flex items-center space-x-3 text-cyan-400">
|
||||||
|
<Loader className="w-6 h-6 animate-spin" />
|
||||||
|
<span>Synchronisation avec l'orchestrateur en cours...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="flex items-center space-x-3 text-red-400 bg-red-400/10 border border-red-400 p-4 rounded-lg">
|
||||||
|
<AlertCircle className="w-6 h-6" />
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* GRILLE DES SERVICES */}
|
||||||
|
{!isLoading && !error && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
|
|
||||||
|
{/* Boucle sur les services FOSSBilling */}
|
||||||
|
{orders.map((order) => (
|
||||||
|
<div
|
||||||
|
key={order.id}
|
||||||
|
className="bg-gray-900 border border-gray-800 p-6 rounded-xl hover:border-cyan-400/50 transition-colors group cursor-pointer flex flex-col justify-between"
|
||||||
|
onClick={() => navigate(`/services/${order.id}`)} // Redirection future vers les détails
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between items-start mb-4">
|
||||||
|
<div className="p-3 bg-black/50 rounded-lg group-hover:scale-110 transition-transform">
|
||||||
|
{getServiceIcon(order.title)}
|
||||||
|
</div>
|
||||||
|
{getStatusBadge(order.status)}
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold text-white truncate" title={order.title}>
|
||||||
|
{order.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">
|
||||||
|
Facturation : {order.period}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 pt-4 border-t border-gray-800 flex justify-between items-center text-sm">
|
||||||
|
<span className="text-gray-400">ID Réseau: #{order.id}</span>
|
||||||
|
<span className="text-cyan-400 group-hover:underline">Gérer ></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* LA CARTE : OBTENIR UN NOUVEAU PRODUIT */}
|
||||||
|
<div
|
||||||
|
onClick={() => navigate('/store')} // Remplace /store par l'URL de ton catalogue
|
||||||
|
className="bg-transparent border-2 border-dashed border-gray-700 hover:border-cyan-400 p-6 rounded-xl transition-colors cursor-pointer flex flex-col items-center justify-center text-center group min-h-[200px]"
|
||||||
|
>
|
||||||
|
<div className="p-3 bg-gray-800/50 rounded-full group-hover:bg-cyan-400/20 transition-colors mb-4">
|
||||||
|
<Plus className="w-8 h-8 text-gray-400 group-hover:text-cyan-400" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-lg font-semibold text-white group-hover:text-cyan-400">
|
||||||
|
Demander une accréditation
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-500 mt-2">
|
||||||
|
Déployer un nouveau serveur Web, VPS ou Cloud.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ textAlign: 'right' }}>
|
);
|
||||||
<div style={{ color: '#00FF66', fontSize: '0.85rem', marginBottom: '5px' }}>● SYSTÈME NOMINAL</div>
|
|
||||||
<div style={{ color: '#555', fontSize: '0.75rem' }}>Dernière connexion : Aujourd'hui</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* --- GRILLE DES MODULES DE L'INFRASTRUCTURE --- */}
|
|
||||||
<div style={gridStyle}>
|
|
||||||
|
|
||||||
{/* MODULE 1 : BARE-METAL (HESTIACP) */}
|
|
||||||
<div style={cardStyle}>
|
|
||||||
<div style={statusBadgeStyle}>ACTIF</div>
|
|
||||||
<h3 style={{ color: '#FFF', marginTop: 0, fontSize: '1.2rem' }}>// Serveur Web</h3>
|
|
||||||
<p style={{ color: '#888', fontSize: '0.85rem', lineHeight: '1.5' }}>
|
|
||||||
Nœud HestiaCP (panel.gise.be). Supervision des partitions web, DNS et bases de données.
|
|
||||||
</p>
|
|
||||||
<div style={{ margin: '20px 0', fontSize: '0.85rem', color: '#A0A0A0' }}>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
|
||||||
<span>Domaines Actifs:</span> <span style={{ color: '#FFF' }}>1 / 5</span>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<span>Bases SQL:</span> <span style={{ color: '#FFF' }}>2 / 10</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a href="https://panel.gise.be" target="_blank" rel="noopener noreferrer" style={btnStyle}
|
|
||||||
onMouseOver={(e) => { e.target.style.backgroundColor = '#00E5FF'; e.target.style.color = '#000'; }}
|
|
||||||
onMouseOut={(e) => { e.target.style.backgroundColor = 'transparent'; e.target.style.color = '#00E5FF'; }}>
|
|
||||||
Accéder au Panel
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* MODULE 2 : CLOUD (NEXTCLOUD) */}
|
|
||||||
<div style={cardStyle}>
|
|
||||||
<div style={statusBadgeStyle}>ACTIF</div>
|
|
||||||
<h3 style={{ color: '#FFF', marginTop: 0, fontSize: '1.2rem' }}>// Sanctuaire Cloud</h3>
|
|
||||||
<p style={{ color: '#888', fontSize: '0.85rem', lineHeight: '1.5' }}>
|
|
||||||
Stockage chiffré Nextcloud (cloud.gise.be). Synchronisation des terminaux et partage sécurisé.
|
|
||||||
</p>
|
|
||||||
<div style={{ margin: '20px 0', fontSize: '0.85rem', color: '#A0A0A0' }}>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
|
||||||
<span>Espace Alloué:</span> <span style={{ color: '#FFF' }}>50 Go</span>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<span>Espace Utilisé:</span> <span style={{ color: '#FFF' }}>1.2 Go</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<a href="https://cloud.gise.be" target="_blank" rel="noopener noreferrer" style={btnStyle}
|
|
||||||
onMouseOver={(e) => { e.target.style.backgroundColor = '#00E5FF'; e.target.style.color = '#000'; }}
|
|
||||||
onMouseOut={(e) => { e.target.style.backgroundColor = 'transparent'; e.target.style.color = '#00E5FF'; }}>
|
|
||||||
Ouvrir le Cloud
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* MODULE 3 : FACTURATION (FOSSBILLING) */}
|
|
||||||
<div style={cardStyle}>
|
|
||||||
<div style={statusBadgeStyle} style={{ ...statusBadgeStyle, color: '#FFB800', backgroundColor: 'rgba(255, 184, 0, 0.1)' }}>EN ATTENTE</div>
|
|
||||||
<h3 style={{ color: '#FFF', marginTop: 0, fontSize: '1.2rem' }}>// Facturation</h3>
|
|
||||||
<p style={{ color: '#888', fontSize: '0.85rem', lineHeight: '1.5' }}>
|
|
||||||
Centre de gestion FOSSBilling. Historique des paiements et renouvellement des baux réseau.
|
|
||||||
</p>
|
|
||||||
<div style={{ margin: '20px 0', fontSize: '0.85rem', color: '#A0A0A0' }}>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
|
|
||||||
<span>Solde Compte:</span> <span style={{ color: '#FFF' }}>0.00 €</span>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<span>Prochaine Échéance:</span> <span style={{ color: '#FFF' }}>12/07/2026</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Link to="/billing" style={{ ...btnStyle, borderColor: '#FFB800', color: '#FFB800' }}
|
|
||||||
onMouseOver={(e) => { e.target.style.backgroundColor = '#FFB800'; e.target.style.color = '#000'; }}
|
|
||||||
onMouseOut={(e) => { e.target.style.backgroundColor = 'transparent'; e.target.style.color = '#FFB800'; }}>
|
|
||||||
Régler la facture
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* --- TERMINAL DE LOGS (ESTHÉTIQUE SYSADMIN) --- */}
|
|
||||||
<div style={{ marginTop: '40px', backgroundColor: '#0A0A0A', border: '1px solid #1A1A1A', padding: '20px' }}>
|
|
||||||
<h4 style={{ color: '#555', marginTop: 0, fontSize: '0.8rem', letterSpacing: '1px' }}>>_ SYSTEM_LOGS</h4>
|
|
||||||
<div style={{ color: '#444', fontSize: '0.75rem', lineHeight: '1.8' }}>
|
|
||||||
<div>[ OK ] Connexion chiffrée établie via TLS v1.3</div>
|
|
||||||
<div>[ OK ] Jetons d'authentification synchronisés avec FOSSBilling API</div>
|
|
||||||
<div>[ INFO ] Vérification des quotas de stockage Nextcloud... Terminée.</div>
|
|
||||||
<div>[ INFO ] 0 ticket(s) de support en attente.</div>
|
|
||||||
<div><span style={{ color: '#00E5FF' }}>></span> En attente d'instructions...<span style={{ animation: 'blink 1s step-end infinite' }}>_</span></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Animation CSS pour le curseur clignotant du terminal */}
|
|
||||||
<style>
|
|
||||||
{`
|
|
||||||
@keyframes blink {
|
|
||||||
0%, 100% { opacity: 1; }
|
|
||||||
50% { opacity: 0; }
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { getProductList } from '../../services/api';
|
||||||
|
import { Server, Database, Cloud, Globe, ShoppingCart, Loader, AlertCircle, Check } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function Store() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [products, setProducts] = useState([]);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
// Récupération du catalogue
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCatalog = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getProductList();
|
||||||
|
// FOSSBilling renvoie les produits dans data.list
|
||||||
|
setProducts(data.list || []);
|
||||||
|
} catch (err) {
|
||||||
|
setError("Impossible de contacter le serveur d'approvisionnement.");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchCatalog();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Radar visuel : Associe une icône selon le nom de la catégorie ou du produit
|
||||||
|
const getCategoryIcon = (text) => {
|
||||||
|
const t = text.toLowerCase();
|
||||||
|
if (t.includes('vps') || t.includes('serveur')) return <Server className="w-8 h-8 text-cyan-400" />;
|
||||||
|
if (t.includes('cloud') || t.includes('nextcloud')) return <Cloud className="w-8 h-8 text-blue-400" />;
|
||||||
|
if (t.includes('db') || t.includes('base')) return <Database className="w-8 h-8 text-purple-400" />;
|
||||||
|
return <Globe className="w-8 h-8 text-emerald-400" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extracteur de prix : L'API FOSSBilling a une structure de prix assez profonde
|
||||||
|
const extractMonthlyPrice = (pricing) => {
|
||||||
|
if (pricing?.type === 'free') return 'Gratuit';
|
||||||
|
if (pricing?.type === 'once') return `${pricing.once.price} € (Une fois)`;
|
||||||
|
|
||||||
|
// CORRECTION ICI : Utilisation des crochets pour la clé numérique '1'
|
||||||
|
if (pricing?.type === 'recurrent' && pricing.recurrent['1']) {
|
||||||
|
return `${pricing.recurrent['1'].price} € / mois`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Prix sur demande';
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-7xl p-6 mx-auto">
|
||||||
|
|
||||||
|
<header className="mb-12 text-center">
|
||||||
|
<h1 className="text-4xl font-bold text-white tracking-widest mb-4">
|
||||||
|
CATALOGUE <span className="text-cyan-400">NEXUS</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-400 max-w-2xl mx-auto">
|
||||||
|
Déployez de nouvelles instances et étendez votre infrastructure en quelques secondes. Provisionnement automatisé 24/7.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* GESTION DES ERREURS & CHARGEMENT */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="flex justify-center items-center space-x-3 text-cyan-400 my-20">
|
||||||
|
<Loader className="w-8 h-8 animate-spin" />
|
||||||
|
<span className="text-lg tracking-widest">TÉLÉCHARGEMENT DU CATALOGUE...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="flex justify-center my-20">
|
||||||
|
<div className="flex items-center space-x-3 text-red-400 bg-red-400/10 border border-red-400 p-6 rounded-lg max-w-lg">
|
||||||
|
<AlertCircle className="w-8 h-8 flex-shrink-0" />
|
||||||
|
<span>{error}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* GRILLE DU CATALOGUE */}
|
||||||
|
{!isLoading && !error && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
|
||||||
|
{products.map((product) => (
|
||||||
|
<div
|
||||||
|
key={product.id}
|
||||||
|
className="bg-gray-900 border border-gray-800 rounded-2xl overflow-hidden hover:border-cyan-400/50 transition-all duration-300 group flex flex-col relative"
|
||||||
|
>
|
||||||
|
{/* En-tête de la carte */}
|
||||||
|
<div className="p-8 border-b border-gray-800 relative overflow-hidden">
|
||||||
|
<div className="absolute top-0 right-0 p-8 opacity-10 group-hover:scale-110 group-hover:opacity-20 transition-all duration-500">
|
||||||
|
{getCategoryIcon(product.category || product.title)}
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold text-white mb-2 relative z-10">
|
||||||
|
{product.title}
|
||||||
|
</h3>
|
||||||
|
<div className="text-cyan-400 font-mono text-xl relative z-10">
|
||||||
|
{extractMonthlyPrice(product.pricing)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Description du produit */}
|
||||||
|
<div className="p-8 flex-grow flex flex-col justify-between">
|
||||||
|
{/* FOSSBilling permet de mettre du HTML dans la description.
|
||||||
|
Ici, on triche un peu en simulant des puces pour le design,
|
||||||
|
mais tu pourras utiliser dangerouslySetInnerHTML si tu as mis du vrai HTML. */}
|
||||||
|
<div className="text-gray-400 text-sm mb-8 space-y-3">
|
||||||
|
{product.description ? (
|
||||||
|
<p>{product.description}</p>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center space-x-2"><Check className="w-4 h-4 text-cyan-400"/> <span>Provisionnement instantané</span></div>
|
||||||
|
<div className="flex items-center space-x-2"><Check className="w-4 h-4 text-cyan-400"/> <span>Support technique 24/7</span></div>
|
||||||
|
<div className="flex items-center space-x-2"><Check className="w-4 h-4 text-cyan-400"/> <span>SLA 99.9%</span></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bouton d'action */}
|
||||||
|
<button
|
||||||
|
onClick={() => navigate(`/checkout/${product.id}`)}
|
||||||
|
className="w-full bg-cyan-400/10 hover:bg-cyan-400 text-cyan-400 hover:text-gray-900 border border-cyan-400 py-3 rounded-lg font-bold tracking-widest transition-all flex justify-center items-center space-x-2 group-hover:shadow-[0_0_20px_rgba(34,211,238,0.2)]"
|
||||||
|
>
|
||||||
|
<ShoppingCart className="w-5 h-5" />
|
||||||
|
<span>COMMANDER</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -42,6 +42,10 @@ export const getClientOrders = () =>
|
|||||||
export const getOrderService = (order_id) =>
|
export const getOrderService = (order_id) =>
|
||||||
apiCall(`${BASE_URL}/api/client/order/service`, { id: order_id });
|
apiCall(`${BASE_URL}/api/client/order/service`, { id: order_id });
|
||||||
|
|
||||||
|
// Récupère le catalogue public des produits FOSSBilling
|
||||||
|
export const getProductList = () =>
|
||||||
|
apiCall(`${BASE_URL}/api/guest/product/get_list`);
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// ROUTES PERSONNALISÉES (CUSTOM API)
|
// ROUTES PERSONNALISÉES (CUSTOM API)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
+2
-1
@@ -1,8 +1,9 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react(), tailwindcss()],
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
// Toutes les requêtes qui commencent par /api iront vers ton FOSSBilling
|
// Toutes les requêtes qui commencent par /api iront vers ton FOSSBilling
|
||||||
|
|||||||
Reference in New Issue
Block a user