ajout de la page a propos
@ -76,7 +76,9 @@ public class QueryObjects {
|
||||
.put("type", row.getString("type"))
|
||||
.put("location", row.getString("location"))
|
||||
.put("last_update", row.getLocalDateTime("last_update").format(formatter))
|
||||
.put("status", row.getString("status"));
|
||||
.put("status", row.getString("status"))
|
||||
.put("batterie",row.getInteger("batterie"))
|
||||
.put("type_batterie",row.getString("type_batterie"));
|
||||
objects.add(object);
|
||||
}
|
||||
return objects;
|
||||
|
||||
50
Front-end/package-lock.json
generated
@ -13,6 +13,7 @@
|
||||
"lucide-react": "^0.427.0",
|
||||
"react": "^18.3.1",
|
||||
"react-charts": "^3.0.0-beta.57",
|
||||
"react-circle-progress-bar": "^0.1.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.4.0",
|
||||
"recharts": "^2.15.1"
|
||||
@ -3584,6 +3585,55 @@
|
||||
"react-dom": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/react-circle-progress-bar": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-circle-progress-bar/-/react-circle-progress-bar-0.1.4.tgz",
|
||||
"integrity": "sha512-2a47TDthNyUHJf8p1hv0wcTwIWnJBbEUfj/7dZcO+7BYd1W1sRC2t5x+SEWX9/1QT7hhf4t8ppcLaG0XrdwwgQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/react-circle-progress-bar/node_modules/react": {
|
||||
"version": "16.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
||||
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-circle-progress-bar/node_modules/react-dom": {
|
||||
"version": "16.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
|
||||
"integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2",
|
||||
"scheduler": "^0.19.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-circle-progress-bar/node_modules/scheduler": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
|
||||
"integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
"lucide-react": "^0.427.0",
|
||||
"react": "^18.3.1",
|
||||
"react-charts": "^3.0.0-beta.57",
|
||||
"react-circle-progress-bar": "^0.1.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^7.4.0",
|
||||
"recharts": "^2.15.1"
|
||||
|
||||
27
Front-end/src/components/BatterieInfo.jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import { Battery } from "lucide-react";
|
||||
import Progress from "react-circle-progress-bar";
|
||||
|
||||
function BatterieInfo({ object }) {
|
||||
return (
|
||||
<div key={object.id} className="bg-white p-6 rounded-xl min-w-5xl">
|
||||
<div className="flex align-items gap-6">
|
||||
<div className="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center mb-4">
|
||||
<Battery className="text-indigo-600" size={24} />
|
||||
</div>
|
||||
<h1 className="text-black text-2xl font-bold mb-1 ">
|
||||
Etat de la batterie
|
||||
</h1>
|
||||
</div>
|
||||
<div className="flex flex-col items-center">
|
||||
<Progress progress={object.batterie} />
|
||||
<h1 className="font-bold">
|
||||
Type de batterie :{" "}
|
||||
<span className="capitalize font-normal">{object.type_batterie}</span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BatterieInfo;
|
||||
@ -1,17 +1,23 @@
|
||||
import React from "react";
|
||||
import React, {useRef} from "react";
|
||||
import { ChartLine } from "lucide-react";
|
||||
function BoutonGraphique({ TypeAff, setAffichage }) {
|
||||
function BoutonGraphique({ TypeAff, setAffichage,graphCible}) {
|
||||
const handleClick = (newAffichage) =>{
|
||||
setAffichage(newAffichage);
|
||||
if(graphCible.current){
|
||||
graphCible.current.scrollIntoView({ behavior: "smooth" });
|
||||
}
|
||||
};
|
||||
return !TypeAff ? (
|
||||
<button
|
||||
className="bg-blue-200 py-2 my-2 px-4 rounded-full mr-2"
|
||||
onClick={() => setAffichage(true)}
|
||||
onClick={() => handleClick(true)}
|
||||
>
|
||||
<ChartLine className="text-indigo-600" size={24} />
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
className="bg-blue-400 py-2 my-2 px-4 rounded-full mr-2"
|
||||
onClick={() => setAffichage(false)}
|
||||
onClick={() => handleClick(false)}
|
||||
>
|
||||
<ChartLine className="text-indigo-600" size={24} />
|
||||
</button>
|
||||
|
||||
@ -14,10 +14,12 @@ function MeteoInfos({
|
||||
AffPressionGraph,
|
||||
defAffHumiditeGraph,
|
||||
AffHumiditeGraph,
|
||||
graphCible
|
||||
}) {
|
||||
const [rawData, setRawData] = useState([]);
|
||||
const [AffAlert,setAffAlert] = useState(false);
|
||||
const identifiant = object.id;
|
||||
|
||||
useEffect(() => {
|
||||
axios.get(`${API_BASE_URL}/meteo?id=${identifiant}`).then((response) => {
|
||||
setRawData(response.data);
|
||||
@ -61,6 +63,8 @@ function MeteoInfos({
|
||||
<BoutonGraphique
|
||||
TypeAff={AffTempGraph}
|
||||
setAffichage={defAffTempGraph}
|
||||
graphCible={graphCible}
|
||||
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -84,6 +88,7 @@ function MeteoInfos({
|
||||
<BoutonGraphique
|
||||
TypeAff={AffPressionGraph}
|
||||
setAffichage={defAffPressionGraph}
|
||||
graphCible={graphCible}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -70,7 +70,10 @@ function ModifObject({ object, defafficherModif }) {
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<label htmlFor="type" className="block mb-2 text-sm font-medium text-gray-900">
|
||||
<label
|
||||
htmlFor="type"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Type :
|
||||
</label>
|
||||
<input
|
||||
@ -84,7 +87,10 @@ function ModifObject({ object, defafficherModif }) {
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<label htmlFor="location" className="block mb-2 text-sm font-medium text-gray-900">
|
||||
<label
|
||||
htmlFor="location"
|
||||
className="block mb-2 text-sm font-medium text-gray-900"
|
||||
>
|
||||
Localisation :
|
||||
</label>
|
||||
<input
|
||||
@ -98,7 +104,9 @@ function ModifObject({ object, defafficherModif }) {
|
||||
</div>
|
||||
|
||||
<div className="mb-5">
|
||||
<label className="block mb-2 text-sm font-medium text-gray-900">Status :</label>
|
||||
<label className="block mb-2 text-sm font-medium text-gray-900">
|
||||
Status :
|
||||
</label>
|
||||
<div className="inline-flex items-center gap-2">
|
||||
<label
|
||||
htmlFor="switch-component-on"
|
||||
|
||||
BIN
Front-end/src/img/NotreMission.png
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
Front-end/src/img/fr-alert.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
Front-end/src/img/gestioniot.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
Front-end/src/img/iotmeteo.jpg
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
Front-end/src/img/precisionfiable.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Front-end/src/img/surveillancemeteo.webp
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
Front-end/src/img/surveillancetempsreel.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
@ -1,11 +1,168 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<div>
|
||||
<h1>A propos</h1>
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="mb-5">
|
||||
{/* Grille principale */}
|
||||
<div className="grid md:grid-cols-2 gap-10 lg:gap-20 mb-5">
|
||||
{/* Section Notre mission */}
|
||||
<div className="order-1 md:order-1">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Notre mission
|
||||
</h1>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Notre mission est de fournir une solution complète et innovante
|
||||
pour la surveillance climatique et environnementale du
|
||||
territoire français. En combinant des prévisions météorologiques
|
||||
de haute qualité avec une gestion efficace des objets connectés,
|
||||
nous visons à offrir une plateforme centralisée permettant de
|
||||
surveiller en temps réel les conditions météorologiques locales,
|
||||
tout en facilitant l'analyse des données collectées par des
|
||||
objets connectés déployés à travers le pays.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
className="rounded-lg h-64 w-full object-cover order-2 md:order-2"
|
||||
src="./src/img/NotreMission.png"
|
||||
alt="Notre mission"
|
||||
/>
|
||||
|
||||
{/* Section Qui sommes-nous */}
|
||||
<img
|
||||
className="rounded-lg h-64 w-full object-cover order-4 md:order-3"
|
||||
src="./src/img/iotmeteo.jpg"
|
||||
alt="IoT et météo"
|
||||
/>
|
||||
<div className="order-3 md:order-4">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Qui sommes-nous ?
|
||||
</h1>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Nous sommes une équipe de passionnés de technologie,
|
||||
d’innovation et d’environnement. Nous croyons fermement que la
|
||||
combinaison de la donnée météorologique en temps réel et de
|
||||
l’Internet des Objets (IoT) peut avoir un impact majeur sur la
|
||||
gestion des territoires. Que ce soit pour les collectivités
|
||||
locales, les entreprises ou les acteurs publics, notre
|
||||
plateforme offre les outils nécessaires pour une gestion
|
||||
proactive et réactive de l’environnement.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Section Notre Vision */}
|
||||
<div className="order-5 md:order-5">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Notre Vision
|
||||
</h1>
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
Dans un monde où les conditions climatiques évoluent rapidement,
|
||||
il est essentiel de pouvoir anticiper et réagir efficacement
|
||||
face aux phénomènes météorologiques. Grâce à nos objets
|
||||
connectés et à notre interface intuitive, nous permettons aux
|
||||
utilisateurs de suivre les conditions en temps réel et d’agir en
|
||||
conséquence. De la gestion des risques climatiques à la
|
||||
planification urbaine, notre plateforme aide les décideurs à
|
||||
prendre des décisions éclairées basées sur des données fiables
|
||||
et locales.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
className="rounded-lg h-64 w-full object-cover order-6 md:order-6"
|
||||
src="./src/img/surveillancemeteo.webp"
|
||||
alt="Surveillance météo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Section Objectifs */}
|
||||
<div className="text-center col-span-2 order-7">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-10 mt-20">
|
||||
Les Objectifs de Notre Plateforme
|
||||
</h1>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-10">
|
||||
{/* Objectif 1 */}
|
||||
<div className="relative group w-full h-80 mb-7">
|
||||
<img
|
||||
src="./src/img/surveillancetempsreel.jpg"
|
||||
alt="Surveillance en temps réel"
|
||||
className="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
Grâce à nos objets connectés, nous collectons des données
|
||||
météorologiques locales, permettant une surveillance
|
||||
continue des conditions climatiques sur tout le territoire
|
||||
français.
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 ">
|
||||
Surveillance en temps réel
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Objectif 2 */}
|
||||
<div className="relative group w-full h-80 mb-7">
|
||||
<img
|
||||
src="./src/img/precisionfiable.jpg"
|
||||
alt="Précision fiable"
|
||||
className="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
En utilisant les meilleures technologies de prévision
|
||||
météorologique, nous vous fournissons des prévisions
|
||||
précises, qu’il s’agisse de la température, de la vitesse du
|
||||
vent ou de la qualité de l’air.
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">Prédiction fiable</h1>
|
||||
</div>
|
||||
|
||||
{/* Objectif 3 */}
|
||||
<div className="relative group w-full h-80 mb-7 border-2 rounded-xl">
|
||||
<img
|
||||
src="./src/img/gestioniot.png"
|
||||
alt="Gestion IoT"
|
||||
className="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
Nous permettons aux utilisateurs de gérer facilement leurs
|
||||
objets connectés (stations météo, capteurs, etc.) à travers
|
||||
une interface simple, tout en offrant un suivi en temps réel
|
||||
de leur statut et de leurs données.
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">
|
||||
Gestion des objets connectés
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Objectif 4 */}
|
||||
<div className="relative group w-full h-80 mb-7">
|
||||
<img
|
||||
src="./src/img/fr-alert.webp"
|
||||
alt="Réponse rapide"
|
||||
className="w-full h-full object-cover rounded-xl"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-blue-600 opacity-0 group-hover:opacity-100 transition-opacity duration-500 rounded-xl flex items-center justify-center">
|
||||
<p className="text-white text-lg font-bold px-4">
|
||||
Notre plateforme vous envoie des alertes instantanées
|
||||
concernant les phénomènes météorologiques extrêmes, vous
|
||||
permettant de prendre des décisions rapides et adaptées.
|
||||
</p>
|
||||
</div>
|
||||
<h1 className="text-xl font-bold mt-4 mb-6">
|
||||
Réponse rapide aux alertes climatiques
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default About;
|
||||
export default About;
|
||||
|
||||
@ -12,7 +12,6 @@ import {
|
||||
Settings,
|
||||
BadgePlus,
|
||||
} from "lucide-react";
|
||||
|
||||
function Gestion() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||
@ -61,7 +60,6 @@ function Gestion() {
|
||||
Ajouter un objet <ArrowRight size={16} className="ml-2" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { Thermometer, CircleGauge, Droplet } from "lucide-react";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useRef} from "react";
|
||||
import axios from "axios";
|
||||
import { API_BASE_URL } from "../../config";
|
||||
|
||||
@ -11,23 +11,32 @@ import WindGraph from "../../components/WindGraph";
|
||||
import WindInfo from "../../components/WindInfo";
|
||||
import MeteoInfos from "../../components/MeteoInfos";
|
||||
import MeteoGraph from "../../components/MeteoGraph";
|
||||
import BatterieInfo from "../../components/BatterieInfo";
|
||||
function Objet() {
|
||||
const identifiant = new URLSearchParams(window.location.search).get("id");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [activeFilter, setActiveFilter] = useState("all");
|
||||
const [object, setObject] = useState({});
|
||||
const [graphStates, setGraphStates] = useState({
|
||||
wind:false,
|
||||
temperature:false,
|
||||
pressure:false,
|
||||
humidity:false,
|
||||
})
|
||||
const [afficherModif, defafficherModif] = useState(false);
|
||||
const [AffWindGraph, defAffWindGraph] = useState(false);
|
||||
const [AffTempGraph, defAffTempGraph] = useState(false);
|
||||
const [AffPressionGraph, defAffPressionGraph] = useState(false);
|
||||
const [AffHumiditeGraph, defAffHumideGraph] = useState(false);
|
||||
const tempGraphRef = useRef(null);
|
||||
const pressureGraphRef = useRef(null);
|
||||
const humidityGraphRef = useRef(null);
|
||||
const windGraphRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
axios
|
||||
.get(`${API_BASE_URL}/objet?id=${identifiant}`)
|
||||
.then((response) => {
|
||||
setObject(response.data[0]);
|
||||
});
|
||||
axios.get(`${API_BASE_URL}/objet?id=${identifiant}`).then((response) => {
|
||||
setObject(response.data[0]);
|
||||
});
|
||||
}, [identifiant]);
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||
@ -37,10 +46,10 @@ function Objet() {
|
||||
Tableau de bord - {object.name}
|
||||
</h2>
|
||||
</div>
|
||||
<div className="grid md:grid-cols-3 gap-8 mb-5">
|
||||
{(!afficherModif) ? (
|
||||
<div className="grid md:grid-cols-1 lg:grid-cols-3 gap-8 mb-5">
|
||||
{!afficherModif ? (
|
||||
<InfoObjet object={object} defafficherModif={defafficherModif} />
|
||||
):(
|
||||
) : (
|
||||
<ModifObject object={object} defafficherModif={defafficherModif} />
|
||||
)}
|
||||
|
||||
@ -62,10 +71,14 @@ function Objet() {
|
||||
AffPressionGraph={AffPressionGraph}
|
||||
defAffHumiditeGraph={defAffHumideGraph}
|
||||
AffHumiditeGraph={AffHumiditeGraph}
|
||||
tempGraphRef={tempGraphRef}
|
||||
pressureGraphRef={pressureGraphRef}
|
||||
humidityGraphRef={humidityGraphRef}
|
||||
/>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
)}
|
||||
<BatterieInfo object={object} />
|
||||
</div>
|
||||
{AffWindGraph &&
|
||||
(object && object.id ? (
|
||||
@ -75,13 +88,21 @@ function Objet() {
|
||||
))}
|
||||
{AffTempGraph &&
|
||||
(object && object.id ? (
|
||||
<MeteoGraph object={object} categorie={"temperature"} Logo={Thermometer}/>
|
||||
<MeteoGraph
|
||||
object={object}
|
||||
categorie={"temperature"}
|
||||
Logo={Thermometer}
|
||||
/>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
))}
|
||||
{AffPressionGraph &&
|
||||
(object && object.id ? (
|
||||
<MeteoGraph object={object} categorie={"pressure"} Logo={CircleGauge}/>
|
||||
<MeteoGraph
|
||||
object={object}
|
||||
categorie={"pressure"}
|
||||
Logo={CircleGauge}
|
||||
/>
|
||||
) : (
|
||||
<p>Chargement des données...</p>
|
||||
))}
|
||||
|
||||
@ -7,15 +7,6 @@ function Home() {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [activeFilter, setActiveFilter] = useState('all');
|
||||
const [name, setName] = useState([]);
|
||||
useEffect(()=> {
|
||||
axios.get('http://localhost:8888?name=bob')
|
||||
.then(response => {
|
||||
setName(response.data.name);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There was an error!', error);
|
||||
});
|
||||
},[]);
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
|
||||