2 Commits f34fb21590 ... 49e9cc4942

Autor SHA1 Mensagem Data
  wpfat23-5 49e9cc4942 Connected some endpoints from server-side 2 anos atrás
  wpfat23-5 1d668a4994 Config file update. Credentials in LoginPage are now sent in JSON 2 anos atrás

+ 5 - 0
package-lock.json

@@ -8291,6 +8291,11 @@
         "object.assign": "^4.1.3"
       }
     },
+    "jwt-decode": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
+      "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
+    },
     "kind-of": {
       "version": "6.0.3",
       "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",

+ 1 - 0
package.json

@@ -6,6 +6,7 @@
     "@testing-library/jest-dom": "^5.16.5",
     "@testing-library/react": "^13.4.0",
     "@testing-library/user-event": "^13.5.0",
+    "jwt-decode": "^3.1.2",
     "leaflet": "^1.9.3",
     "primeflex": "^3.3.0",
     "primeicons": "^6.0.1",

+ 43 - 4
src/components/NewParcelComponent.js

@@ -1,20 +1,59 @@
-import React from "react";
+import React, {useEffect, useState} from "react";
 import {InputText} from "primereact/inputtext";
 import {Button} from "primereact/button";
+import {SPRING_SERVER} from "../config";
 
 export default function (props) {
+    const userDataUrl = SPRING_SERVER + '/api/users/' + props.username;
+    const [name,setName] = useState("");
+    const [address, setAddress] = useState("");
+
+    function addNewParcel() {
+        fetch(userDataUrl, {
+            method: 'PUT',
+            headers: {
+                "Authorization": "Bearer " + localStorage.getItem("access_token"),
+                'Content-Type': 'application/json',
+            },
+            body: JSON.stringify({
+                deliveryAddress: address,
+                recipientName: "Adam Nowak",
+                deliveryTime: [
+                    2024,
+                    1,
+                    25,
+                    21,
+                    34,
+                    55
+                ],
+                packets: [
+                    {
+                        description: "Packet description",
+                        name: name
+                    }
+                ]
+            })
+        })
+            .then(response => response.json())
+            .then(data => {
+                props.callback()
+            })
+    }
 
     return (
         <div className="w-11 my-2 p-2 surface-200 border-round shadow-3 flex flex-column">
             <div>
                 <div>Parcel name:</div>
-                <InputText type="text" className="w-full p-inputtext-sm surface-300 "/>
+                <InputText type="text" className="w-full p-inputtext-sm surface-300 " onChange={(e) => setName(e.target.value)}/>
             </div>
             <div>
                 <div>Address:</div>
-                <InputText type="text" className="w-full p-inputtext-sm surface-300 "/>
+                <InputText type="text" className="w-full p-inputtext-sm surface-300 " onChange={(e) => setAddress(e.target.value)}/>
             </div>
-            <Button className="align-self-end mt-2"><i className="pi pi-check text-green-500 text-xl font-bold"></i> Add</Button>
+            <Button onClick={() => {
+                addNewParcel()
+            }} className="align-self-end mt-2"><i
+                className="pi pi-check text-green-500 text-xl font-bold"></i> Add</Button>
         </div>
     )
 }

+ 43 - 4
src/components/ParcelsListComponent.js

@@ -1,22 +1,61 @@
 import {ScrollPanel} from "primereact/scrollpanel";
-import React from "react";
+import React, {Component, useEffect, useState} from "react";
 import {Button} from "primereact/button";
+import {SPRING_SERVER} from "../config";
 
 export default function (props) {
+    const userDataUrl = SPRING_SERVER + '/api/users/' + props.username;
+    const [userData, setUserData] = useState()
+
+    useEffect(() => {
+        fetch(userDataUrl, {
+            headers: {
+                "Authorization": "Bearer " + localStorage.getItem("access_token"),
+            }
+        })
+            .then(response => response.json())
+            .then(data => {
+                setUserData(data)
+            })
+
+    },[])
+
+    function removeParcel(item){
+        const deleteUrl = SPRING_SERVER + '/deliveries/' + item.id ;
+        fetch(deleteUrl, {
+            method: 'DELETE',
+            headers: {
+                "Authorization": "Bearer " + localStorage.getItem("access_token"),
+            }
+        }).then(() => {
+            fetch(userDataUrl, {
+                headers: {
+                    "Authorization": "Bearer " + localStorage.getItem("access_token"),
+                }
+            })
+                .then(response => response.json())
+                .then(data => {
+                    console.log(data)
+                    setUserData(data)
+                })
+        })
+
+    }
 
     return (
         <ScrollPanel className="w-11 my-2 surface-200 max-h-10rem border-round shadow-3 ">
-            {props.parcels.map((parcel, index) =>
+            {userData?.deliveries?.map((parcel, index) =>
                 <div key={index} className="flex flex-row">
                     <div className="m-1 p-1 w-full surface-300 border-round flex flex-column">
                         <div className="font-bold">
-                            {parcel.name}
+                            {parcel.packets[0].name}
                         </div>
                         <div className="flex flex-row">
-                            <div>{parcel.address}</div>
+                            <div>{parcel.deliveryAddress}</div>
                         </div>
                     </div>
                     <Button
+                        onClick={() => removeParcel(parcel)}
                         className="text-900 bg-red-200 border-1 border-black-alpha-10 border-round p-1 m-1 flex flex-column align-items-center justify-content-center">
                         <i className="pi pi-trash"></i>
                     </Button>

+ 1 - 0
src/config.js

@@ -1 +1,2 @@
 export const SPRING_SERVER = "http://localhost:8080";
+export const KEYCLOAK_SERVER = "http://localhost:8181"

+ 24 - 22
src/pages/HomePage.js

@@ -12,18 +12,18 @@ import ParcelsListComponent from "../components/ParcelsListComponent";
 import NewParcelComponent from "../components/NewParcelComponent";
 import MarkersComponent from "../components/MarkersComponent";
 import {Navigate, useNavigate} from "react-router-dom";
+import jwt_decode from 'jwt-decode';
 
 export default function () {
     const navigate = useNavigate();
 
     const [positions, setPositions] = useState([[51.746732, 19.450587], [51.744347, 19.451015]]);
-    /*
-        const [authenticated, setAuthenticated] = useState(false)
-    */
+    const [username, setUsername] = useState("...");
+    const [userData, setUserData] = useState()
+    const [newParcelComponentSeed, setNewParcelComponentSeed] = useState(1);
 
     if (!localStorage.getItem("access_token") || !localStorage.getItem("expires_at"))
     {
-
         localStorage.removeItem("access_token")
         localStorage.removeItem("expires_at")
         return <Navigate to="/" replace/>
@@ -36,24 +36,16 @@ export default function () {
         }
     }
 
-    /*    useEffect(() => {
-            if (localStorage.getItem("access_token") && localStorage.getItem("expires_at") &&
-                localStorage.getItem("expires_at") < Date.now()) {
-                setAuthenticated(true)
-            } else {
-                setAuthenticated(false)
-            }
-        })*/
 
+    const decoded = jwt_decode(localStorage.getItem("access_token"));
     useEffect(() => {
-
+        setUsername(decoded.preferred_username)
         const params = new URLSearchParams({
             lon1: 19.3037,
             lat1: 51.8194,
             lon2: 19.46211,
             lat2: 51.74275
         });
-
         const url = SPRING_SERVER + '/route/nodes?' + params;
 
         fetch(url, {
@@ -69,13 +61,27 @@ export default function () {
             .catch(error => console.error(error));
     }, []);
 
+    useEffect(() => {
+        const url = SPRING_SERVER + '/api/users/' + decoded.preferred_username;
+        fetch(url, {
+            headers: {
+                "Authorization": "Bearer " + localStorage.getItem("access_token"),
+            }
+        })
+            .then(response => response.json())
+            .then(data => {
+                console.log(data)
+                setUserData(data)
+            })
+            .catch(error => console.error(error));
+    }, [])
+
     const onButtonClick = () => {
         localStorage.removeItem("access_token")
         return navigate("/")
     };
 
 
-    /*if (authenticated === true)*/
     return (
         < >
             <div className="flex flex-row h-screen text-900 ">
@@ -84,17 +90,13 @@ export default function () {
                     <div className="flex flex-row align-self-center mt-3 surface-200 w-11 border-round p-2 shadow-5">
                         <Avatar className="align-self-center text-700" icon="pi pi-user" size="xlarge" shape="circle"
                                 style={{fontSize: '2.5rem'}}/>
-                        <div className="font-bold text-2xl pl-3 align-self-center">John Doe</div>
+                        <div className="font-bold text-2xl pl-3 align-self-center">{username}</div>
                     </div>
 
                     <Button className="col-11 mt-2" onClick={onButtonClick}><i className="pi pi-sign-out"
                                                                                style={{fontSize: '1rem'}}></i></Button>
-                    <ParcelsListComponent parcels={[
-                        {'id': 1, 'name': 'package2241', 'address': 'New York 232-421'},
-                        {'id': 2, 'name': 'package12437', 'address': 'New York 232-421'},
-                        {'id': 2, 'name': 'package12437', 'address': 'New York 232-421'},
-                        {'id': 2, 'name': 'package12437', 'address': 'New York 232-421'}]}></ParcelsListComponent>
-                    <NewParcelComponent></NewParcelComponent>
+                    {userData ? <ParcelsListComponent key={newParcelComponentSeed} username={username}></ParcelsListComponent> : <div></div>}
+                    <NewParcelComponent username={username} callback={() => {setNewParcelComponentSeed(Math.random())}}></NewParcelComponent>
                 </div>
                 <div className="w-full bg-orange-500">
 

+ 60 - 4
src/pages/LoginPage.js

@@ -1,20 +1,54 @@
 import {Button} from "primereact/button";
 import {InputText} from "primereact/inputtext";
 import {Navigate, useNavigate} from "react-router-dom";
-import React, {useState} from "react";
+import React, {useRef, useState} from "react";
+import {KEYCLOAK_SERVER, SPRING_SERVER} from "../config";
+import {Toast} from "primereact/toast";
+
 
 export default function () {
     const navigate = useNavigate();
     const [login, setLogin] = useState("");
     const [password, setPassword] = useState("");
+    const [login2, setLogin2] = useState("");
+    const [password2, setPassword2] = useState("");
+    const my_toast = useRef(null)
 
     if (localStorage.getItem("access_token")) {
         return <Navigate to="/home" replace/>
     }
 
+    function onRegisterClick() {
+
+        const url = SPRING_SERVER + '/keycloak/createUser'
+        fetch(url, {
+            method: 'POST',
+            headers: {
+                'Accept': 'application/json',
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify({
+                "username": login2,
+                "enabled": true,
+                "credentials": [
+                    {
+                        "type": "password",
+                        "value": password2,
+                        "temporary": false
+                    }
+                ]
+            })
+        }).then((res) => {
+            if (res.ok) {
+                my_toast.current.show({severity: 'success', summary: 'Success Message', detail: 'Account created'});
+            } else {
+                my_toast.current.show({severity: 'error', summary: 'Error Message', detail: 'Validation failed'});
+            }
+        })
+    }
 
     function onLoginClick() {
-        fetch('http://localhost:8181/auth/realms/SpringBootKeycloak/protocol/openid-connect/token', {
+        fetch(KEYCLOAK_SERVER + '/auth/realms/SpringBootKeycloak/protocol/openid-connect/token', {
             method: 'POST',
             headers: {
                 'Content-Type': 'application/x-www-form-urlencoded'
@@ -27,6 +61,7 @@ export default function () {
             })
         }).then(response => {
             if (!response.ok) {
+                my_toast.current.show({severity: 'error', summary: 'Error Message', detail: 'Validation failed'});
                 throw new Error(response.status)
             } else {
                 return response.json()
@@ -46,8 +81,10 @@ export default function () {
 
     return (
         <div style={{display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh'}}
-             className="bg-orange-500">
-            <div className="surface-card p-4 shadow-2 border-round flex flex-column w-3 ">
+             className="bg-orange-500 flex flex-row">
+            <Toast ref={my_toast}></Toast>
+
+            <div className="surface-card p-4 shadow-2 border-round flex flex-column w-3 mx-2">
                 <div className="text-3xl font-medium text-900 mb-2">Login Panel</div>
                 <div className="font-medium text-500 mb-3">Login using your credentials</div>
 
@@ -66,6 +103,25 @@ export default function () {
                 </div>
 
             </div>
+            <div className="surface-card p-4 shadow-2 border-round flex flex-column w-3 mx-2">
+                <div className="text-3xl font-medium text-900 mb-2">Register Panel</div>
+                <div className="font-medium text-500 mb-3">Register your new account</div>
+
+                <span className="p-input-icon-left py-1 ">
+                        <i className="pi pi-user"/>
+                        <InputText placeholder="Username" onChange={(e) => setLogin2(e.target.value)}
+                                   className="min-w-full max-w-full"/>
+                </span>
+                <span className="p-input-icon-left py-1">
+                        <i className="pi pi-key"/>
+                        <InputText type="password" placeholder="Password" onChange={(e) => setPassword2(e.target.value)}
+                                   className="min-w-full max-w-full"/>
+                </span>
+                <div className="flex justify-content-end pt-2">
+                    <Button onClick={onRegisterClick}>Register</Button>
+                </div>
+
+            </div>
         </div>
     )
 }