Changeset 79a0317 for imaps-frontend/src/pages
- Timestamp:
- 01/21/25 03:08:24 (5 months ago)
- Branches:
- main
- Parents:
- 0c6b92a
- Location:
- imaps-frontend/src/pages
- Files:
-
- 7 added
- 20 edited
Legend:
- Unmodified
- Added
- Removed
-
imaps-frontend/src/pages/AdminPage/AdminPage.jsx
r0c6b92a r79a0317 11 11 import Profile from "../../components/Profile/Profile.jsx"; 12 12 import Toast from "../../components/Toast/Toast.jsx"; 13 import ListReports from "../../components/ListReports/ListReports.jsx"; 13 14 14 15 const renderTile = ({data, isDragging}, handleApprove, handleDeny, openMapInfoModal, openPublishForm) => ( … … 82 83 gmaps_url: elem.gmaps_url, 83 84 image_url: card, 84 })).filter((tile) => tile.status === " INVALID");85 })).filter((tile) => tile.status === "PENDING"); 85 86 86 87 setPendingMaps(mapTiles); … … 145 146 146 147 try { 147 await httpService.post(url, formData); // Assuming formData contains all required fields148 await httpService.post(url, formData); 148 149 setPendingMaps((prev) => prev.filter((map) => map.mapName !== formData.mapName)); 149 150 alert(`Map "${formData.mapName}" published successfully.`); … … 187 188 <Profile></Profile> 188 189 <h1>Pending Maps for Approval</h1> 190 <hr/> 189 191 190 192 {publishFormMap && ( … … 210 212 /> 211 213 </div> 214 <hr/> 212 215 {isMapInfoModalOpen && ( 213 216 <MapInfoModal … … 223 226 )} 224 227 {toastMessage && <Toast message={toastMessage} type={toastType} onClose={() => setToastMessage(null)}/>} 225 228 <hr/> 229 <ListReports></ListReports> 226 230 </div> 227 231 ); -
imaps-frontend/src/pages/BrowseMaps/BrowseMaps.jsx
r0c6b92a r79a0317 1 1 import styles from "./Maps.module.css"; 2 2 import "react-tiles-dnd/esm/index.css"; 3 import { TilesContainer} from "react-tiles-dnd";4 import { Link} from "react-router-dom";3 import {TilesContainer} from "react-tiles-dnd"; 4 import {Link} from "react-router-dom"; 5 5 import card from "../../assets/card-map.png"; 6 6 import star_icon from "../../assets/star_icon.png"; // Unfilled star icon 7 7 import star_filled_icon from "../../assets/star_filled_icon.png"; // Filled star icon 8 import { useEffect, useState} from "react";8 import {useEffect, useState} from "react"; 9 9 import HttpService from "../../scripts/net/HttpService.js"; 10 10 import Logo from "../../components/Logo/Logo.jsx"; 11 11 import Profile from "../../components/Profile/Profile.jsx"; 12 12 import config from "../../scripts/net/netconfig.js"; 13 import { useAppContext } from "../../components/AppContext/AppContext.jsx"; 13 import {useAppContext} from "../../components/AppContext/AppContext.jsx"; 14 import FilterMaps from "../../components/FilterMaps/FilterMaps.jsx"; 14 15 15 16 let loadedTiles = []; 16 17 17 const renderTile = ({ data, isDragging, toggleFavorite}) => (18 <div style={{ padding: "1rem", width: "100%", position: "relative"}}>19 <Link to={`/Maps/ ${data.text}/View`} className={styles.linkStyle}>18 const renderTile = ({data, isDragging, toggleFavorite}) => ( 19 <div style={{padding: "1rem", width: "100%", position: "relative"}}> 20 <Link to={`/Maps/View/${data.text}`} className={styles.linkStyle}> 20 21 <div 21 22 className={`${styles.tile} ${isDragging ? styles.dragging : ""}`} 22 style={{ width: "100%", height: "100%"}}23 style={{width: "100%", height: "100%"}} 23 24 > 24 <img src={card} className={styles.imgStyle} alt="Map Thumbnail" 25 <div style={{ fontFamily: "exo"}}>25 <img src={card} className={styles.imgStyle} alt="Map Thumbnail"/> 26 <div style={{fontFamily: "exo"}}> 26 27 {data.text} {isDragging ? "DRAGGING" : null} 27 28 </div> … … 32 33 src={data.isFavorite ? star_filled_icon : star_icon} 33 34 alt="Favorite Icon" 34 style={{ width: "20px", height: "20px"}}35 style={{width: "20px", height: "20px"}} 35 36 /> 36 37 </div> … … 46 47 const [searchTerm, setSearchTerm] = useState(""); 47 48 const [tiles, setTiles] = useState([]); 48 const { username, isAuthenticated } = useAppContext(); 49 const {username, isAuthenticated} = useAppContext(); 50 const [filter, setFilter] = useState("all") 51 const [mapFilters, setMapFilters] = useState([]) 49 52 50 53 useEffect(() => { … … 52 55 const httpService = new HttpService(); 53 56 let mapTiles = []; 54 57 let mapTypes = ['Hospital', 'Faculty', 'House', 'Other']; 55 58 if (isAuthenticated) { 56 59 // :D … … 63 66 rows: 1, 64 67 isFavorite: true, 68 type: elem.mapType, 65 69 })); 66 70 71 console.log("TUKA") 67 72 // Load all maps 68 73 const allResp = await httpService.get(config.view_maps.display); 69 74 console.log("RESPONSE MAPS PUBLIC", allResp); 75 76 // mapTypes = allResp.filter(elem => elem.mapType != null && elem.mapType !== "").map(elem => elem.mapType); 77 70 78 71 79 const nonFavMapTiles = allResp … … 76 84 rows: 1, 77 85 isFavorite: false, 86 type: elem.mapType, 78 87 })); 88 79 89 80 90 mapTiles = [...favMapTiles, ...nonFavMapTiles]; … … 82 92 const allResp = await httpService.get(config.view_maps.display); 83 93 console.log("RESPONSE MAPS PUBLIC", allResp); 94 // mapTypes = allResp.filter(elem => elem.mapType != null && elem.mapType !== "").map(elem => elem.mapType); 84 95 85 96 mapTiles = allResp.map((elem) => ({ … … 88 99 rows: 1, 89 100 isFavorite: false, 101 type: elem.mapType, 90 102 })); 91 103 } 104 105 console.log("TYPES:", mapTypes); 106 setMapFilters(mapTypes); 92 107 93 108 loadedTiles = [...mapTiles]; … … 114 129 115 130 const updatedTiles = tiles.map((tile) => 116 tile.text === tileName ? { ...tile, isFavorite: !tile.isFavorite} : tile131 tile.text === tileName ? {...tile, isFavorite: !tile.isFavorite} : tile 117 132 ); 118 133 … … 140 155 }; 141 156 157 const onFilter = (selectedFilter) => { 158 setFilter(selectedFilter); 159 160 if (selectedFilter === "all") { 161 // Show all tiles 162 setTiles(loadedTiles); 163 } else { 164 // Filter tiles by selected type 165 const filteredTiles = loadedTiles.filter((tile) => tile.type === selectedFilter); 166 setTiles(filteredTiles); 167 } 168 }; 169 170 142 171 return ( 143 172 <div className={styles.container}> … … 152 181 onChange={handleSearchChange} 153 182 /> 183 154 184 </div> 155 185 <div className={styles.filterBar}> 186 <FilterMaps mapTypes={mapFilters} setFilter={onFilter}></FilterMaps> 187 </div> 188 {filter !== "all" && tiles.length === 0 && <p>No maps of type {filter} found</p>} 156 189 <TilesContainer 157 190 data={tiles} 158 renderTile={(props) => renderTile({ ...props, toggleFavorite})}191 renderTile={(props) => renderTile({...props, toggleFavorite})} 159 192 tileSize={tileSize} 160 forceTileWidth={1 50}161 forceTileHeight={ 170}193 forceTileWidth={170} 194 forceTileHeight={200} 162 195 /> 163 196 </div> -
imaps-frontend/src/pages/BrowseMaps/Maps.module.css
r0c6b92a r79a0317 19 19 margin-bottom: 30px; 20 20 text-align: center; 21 margin-top: 5em; 21 22 } 22 23 23 24 .searchBar input { 24 width: 300px; 25 /*width: 300px;*/ 26 width: 33vw; 25 27 padding: 10px; 26 28 font-size: 16px; … … 34 36 border-color: #1e90ff; 35 37 box-shadow: 0 0 8px rgba(30, 144, 255, 0.5); 38 } 39 40 .filterBar{ 41 margin-left: 6vw; 36 42 } 37 43 -
imaps-frontend/src/pages/Draw/Draw.jsx
r0c6b92a r79a0317 1 import { useContext, useEffect, useState} from "react";1 import {useContext, useEffect, useState} from "react"; 2 2 import styles from "./Draw.module.css"; 3 3 import RoomModal from "../../components/Modals/RoomModal/RoomModal.jsx"; … … 8 8 import SaveMap from "../../components/SaveMap/SaveMap.jsx"; 9 9 import Logo from "../../components/Logo/Logo.jsx"; 10 import { Link, useNavigate, useParams, useSearchParams} from "react-router-dom";10 import {Link, useNavigate, useParams, useSearchParams} from "react-router-dom"; 11 11 import Profile from "../../components/Profile/Profile.jsx"; 12 12 import HttpService from "../../scripts/net/HttpService.js"; … … 18 18 import config from "../../scripts/net/netconfig.js"; 19 19 import ShapeRegistry from "../../scripts/util/ShapeRegistry.js"; 20 import {TopPanel} from "./TopPanel/TopPanel.jsx"; 20 21 21 22 function Draw() { 22 const { mapName} = useParams();23 const { username} = useAppContext();23 const {mapName} = useParams(); 24 const {username} = useAppContext(); 24 25 25 const [isPopupVisible, setIsPopupVisible] = useState(false);26 const [errorMessage, setErrorMessage] = useState("Error");27 const [hasError, setHasError] = useState(false);28 const [searchParams, setSearchParams] = useSearchParams();26 const [isPopupVisible, setIsPopupVisible] = useState(false); 27 const [errorMessage, setErrorMessage] = useState("Error"); 28 const [hasError, setHasError] = useState(false); 29 const [searchParams, setSearchParams] = useSearchParams(); 29 30 30 const[roomTypes,setRoomTypes] = useState([]);31 const [roomTypes, setRoomTypes] = useState([]); 31 32 32 const {app,floors,saveFloor,setFloors} = useMapLoader(mapName,username,searchParams,setSearchParams)33 const {addRoomType} = useRoomTypesLoader(setRoomTypes,mapName,username);33 const {app, floors, saveFloor, setFloors} = useMapLoader(mapName, username, searchParams, setSearchParams) 34 const {addRoomType} = useRoomTypesLoader(setRoomTypes, mapName, username); 34 35 35 const addFloorHandler = async (newFloorNum) => {36 const httpService = new HttpService();37 httpService.setAuthenticated();36 const addFloorHandler = async (newFloorNum) => { 37 const httpService = new HttpService(); 38 httpService.setAuthenticated(); 38 39 39 const payload = { 40 num: newFloorNum, 41 mapName: mapName, 40 const payload = { 41 num: newFloorNum, 42 mapName: mapName, 43 }; 44 45 try { 46 await httpService.put(`${config.floors.add}`, payload); 47 console.log(`Added floor ${newFloorNum}`); 48 setFloors((prevFloors) => [...prevFloors, {num: newFloorNum}]); 49 } catch (error) { 50 console.error("Error adding floor:", error); 51 } 42 52 }; 43 53 44 try { 45 await httpService.put(`${config.floors.add}`, payload); 46 console.log(`Added floor ${newFloorNum}`); 47 setFloors((prevFloors) => [...prevFloors, { num: newFloorNum }]); 48 } catch (error) { 49 console.error("Error adding floor:", error); 50 } 51 }; 54 const deleteFloorHandler = async (floorNum) => { 55 if (floorNum === 0) return 52 56 53 const deleteFloorHandler = async (floorNum) => {54 if(floorNum === 0) return57 const httpService = new HttpService(); 58 httpService.setAuthenticated(); 55 59 56 const httpService = new HttpService(); 57 httpService.setAuthenticated(); 60 try { 61 await httpService.delete(`${config.floors.delete}?floorNum=${floorNum}&mapName=${mapName}`); 62 setFloors((prevFloors) => prevFloors.filter(f => f.num !== floorNum)) 58 63 59 try { 60 await httpService.delete(`${config.floors.delete}?floorNum=${floorNum}&mapName=${mapName}`); 61 setFloors((prevFloors) => prevFloors.filter(f => f.num !== floorNum)) 64 const currFloor = searchParams.get("floor"); 65 if (currFloor == floorNum) { 66 setSearchParams({floor: "0"}, {replace: true}) 67 } 68 console.log(`Deleted floor ${floorNum}`); 69 } catch (error) { 70 console.error("Error deleting floor:", error); 71 } 72 }; 62 73 63 const currFloor = searchParams.get("floor"); 64 if(currFloor == floorNum){ 65 setSearchParams({floor:"0"},{replace:true}) 66 } 67 console.log(`Deleted floor ${floorNum}`); 68 } catch (error) { 69 console.error("Error deleting floor:", error); 70 } 71 }; 72 73 useEffect(() => { 74 return () => { 75 ShapeRegistry.clear(); 76 } 77 }, []); 74 useEffect(() => { 75 return () => { 76 ShapeRegistry.clear(); 77 } 78 }, []); 78 79 79 80 80 const handleSaveClick = async () => { 81 saveFloor().then(r => { 82 floors.forEach(flr => { 83 setIsPopupVisible(true); 84 setTimeout(() => { 85 setIsPopupVisible(false);}, 86 3000); 87 console.log("floor after save: " + JSON.stringify(flr)) 88 }) 89 }); 81 const handleSaveClick = async () => { 82 saveFloor() 83 setIsPopupVisible(true); 84 setTimeout(() => { 85 setIsPopupVisible(false); 86 }, 87 3000); 88 }; 90 89 90 return ( 91 <div className={styles.wrapper} id="wrapper"> 92 {/* <SideBar></SideBar> */} 93 <Logo></Logo> 94 <div id="container" className={styles.cont}></div> 95 <div className={styles.panel}> 96 <div className={styles.topPanelH}> 97 <Profile position="inline"></Profile> 98 </div> 99 <Link to={`/myMaps/View/${mapName}`} className={styles.titleLink}> 100 <h1 className={styles.title}>{mapName}</h1> 101 </Link> 102 <div className={styles.guideWrapper}> 103 <DrawGuide/> 104 </div> 105 <hr/> 106 <br/> 107 {/* {<h2 className={styles.paragraph}>Objects:</h2>} */} 108 <ul className={styles.shapeOptions} id="shapeOptions"> 109 <li data-info="Entrance" className={`${styles.shapeOption} ${styles.entrance}`}></li> 110 <li data-info="Wall" className={`${styles.shapeOption} ${styles.wall}`} id="wall"></li> 111 <li data-info="Room" className={`${styles.shapeOption} ${styles.room}`} id="room"></li> 112 <li data-info="Stairs" className={`${styles.shapeOption} ${styles.stairs}`} id="stairs"></li> 113 </ul> 114 <RoomTypeModal map={app} roomTypes={roomTypes} addRoomTypeDB={addRoomType}></RoomTypeModal> 115 <br/> 116 <hr/> 117 <br/> 118 <FloorSelector floorConfig={{ 119 floors, searchParams, 120 setSearchParams, addFloorHandler, 121 deleteFloorHandler 122 }}></FloorSelector> 91 123 92 };124 <br/> 93 125 94 return ( 95 <div className={styles.wrapper} id="wrapper"> 96 {/* <SideBar></SideBar> */} 97 <Logo></Logo> 98 <div id="container" className={styles.cont}></div> 99 <div className={styles.panel}> 100 <div className={styles.topPanelH}> 101 <Profile position="inline"></Profile> 126 <hr/> 127 <br/> 128 {hasError && <p style={{color: "red", textAlign: "center"}}>{errorMessage}</p>} 129 <div className={styles.templateCont}> 130 <SaveMap submitHandler={handleSaveClick}></SaveMap> 131 </div> 132 133 <div className={styles.hide}> 134 <RoomModal map={app} roomTypes={roomTypes}></RoomModal> 135 <EntranceModal map={app}></EntranceModal> 136 <InfoPinModal map={app}></InfoPinModal> 137 <StairsModal map={app}></StairsModal> 138 </div> 139 </div> 140 141 {isPopupVisible && ( 142 <div className={styles.popup}> 143 <div className={styles.popupContent}> 144 <h2>Map Saved!</h2> 145 <p>Your map has been successfully saved.</p> 146 </div> 147 </div> 148 )} 102 149 </div> 103 <Link to={`/myMaps/${mapName}/View`} className={styles.titleLink}> 104 <h1 className={styles.title}>{mapName}</h1> 105 </Link> 106 <div className={styles.guideWrapper}> 107 <DrawGuide/> 108 </div> 109 <hr/> 110 <br/> 111 {/* {<h2 className={styles.paragraph}>Objects:</h2>} */} 112 <ul className={styles.shapeOptions} id="shapeOptions"> 113 <li data-info="Entrance" className={`${styles.shapeOption} ${styles.entrance}`}></li> 114 <li data-info="Wall" className={`${styles.shapeOption} ${styles.wall}`} id="wall"></li> 115 <li data-info="Room" className={`${styles.shapeOption} ${styles.room}`} id="room"></li> 116 <li data-info="Stairs" className={`${styles.shapeOption} ${styles.stairs}`} id="stairs"></li> 117 </ul> 118 <RoomTypeModal map={app} roomTypes={roomTypes} addRoomTypeDB={addRoomType}></RoomTypeModal> 119 <br/> 120 <hr/> 121 <br/> 122 <FloorSelector floorConfig={{ 123 floors,searchParams, 124 setSearchParams,addFloorHandler, 125 deleteFloorHandler 126 }}></FloorSelector> 127 128 <br/> 129 130 <hr/> 131 <br/> 132 {hasError && <p style={{color: "red", textAlign: "center"}}>{errorMessage}</p>} 133 <div className={styles.templateCont}> 134 <SaveMap submitHandler={handleSaveClick}></SaveMap> 135 </div> 136 137 <div className={styles.hide}> 138 <RoomModal map={app} roomTypes={roomTypes}></RoomModal> 139 <EntranceModal map={app}></EntranceModal> 140 <InfoPinModal map={app}></InfoPinModal> 141 <StairsModal map={app}></StairsModal> 142 </div> 143 </div> 144 145 {isPopupVisible && ( 146 <div className={styles.popup}> 147 <div className={styles.popupContent}> 148 <h2>Map Saved!</h2> 149 <p>Your map has been successfully saved.</p> 150 </div> 151 </div> 152 )} 153 </div> 154 ); 150 ); 155 151 } 156 152 -
imaps-frontend/src/pages/Draw/Draw.module.css
r0c6b92a r79a0317 25 25 font-weight: 700; 26 26 text-transform: uppercase; 27 color: # 333333;27 color: #c0c0c0; 28 28 font-family: "Oswald", sans-serif; 29 29 margin-bottom: 0; -
imaps-frontend/src/pages/IMaps/components/Button.css
r0c6b92a r79a0317 20 20 21 21 .btn--outline { 22 background-color: transparent;22 background-color: #ffffffc2; 23 23 24 color: # fff;24 color: #252627; 25 25 padding: 8px 20px; 26 border: 1px solid #252627 ; 26 27 /* border: 1px solid var(--primary); */ 27 28 transition: all 0.3s ease-out; … … 37 38 font-size: 24px; 38 39 } 40 .btn--outline:hover{ 41 transition: all 0.3s ease-out; 42 background: #000000; 43 color:#ffffffc2; 44 } 39 45 40 46 .btn--large:hover, … … 43 49 background: #ffffffc2; 44 50 color: #000000; 45 transition: 250ms;46 51 } -
imaps-frontend/src/pages/IMaps/components/Cards.jsx
r0c6b92a r79a0317 19 19 text="Create intricate floor plans for your building with precision and ease." 20 20 label="Create" 21 path=" /services"21 path="#" 22 22 /> 23 23 <CardItem … … 25 25 text="Explore and navigate through complex building layouts seamlessly." 26 26 label="Explore" 27 path=" /services"27 path="#" 28 28 /> 29 29 </ul> … … 33 33 text="Add custom icons, labels, and markers to personalize your indoor maps." 34 34 label="Customize" 35 path=" /services"35 path="#" 36 36 /> 37 37 <CardItem … … 39 39 text="Ensure accessibility by mapping out routes and facilities for all users." 40 40 label="Accessibility" 41 path=" /products"41 path="#" 42 42 /> 43 43 <CardItem … … 45 45 text="Share your maps with others and collaborate in real-time for efficient space planning." 46 46 label="Collaboration" 47 path=" /sign-up"47 path="#" 48 48 /> 49 49 </ul> -
imaps-frontend/src/pages/IMaps/components/Footer.css
r0c6b92a r79a0317 47 47 .footer-links { 48 48 width: 100%; 49 max-width: 1000px;50 49 display: flex; 51 justify-content: center; 50 justify-content: space-evenly; 51 } 52 .footer-link-wrapper a { 53 margin-top: 1em; 54 } 55 .footer-link-wrapper h2 { 56 width: 100%; 52 57 } 53 58 54 .footer-link-wrapper {55 display: flex;56 }57 59 58 60 .footer-link-items { 59 61 display: flex; 60 62 flex-direction: column; 61 align-items: flex-start;63 align-items: center; 62 64 margin: 16px; 63 65 text-align: left; … … 100 102 101 103 .social-media { 102 max-width: 1000px;103 width: 100%;104 104 } 105 105 106 106 .social-media-wrap { 107 107 display: flex; 108 justify-content: space-between;108 flex-direction: column; 109 109 align-items: center; 110 width: 90%;111 max-width: 1000px;112 margin: 40px auto 0 auto;113 110 } 114 111 … … 123 120 color: #fff; 124 121 justify-self: start; 125 margin-left: 20px;126 122 cursor: pointer; 127 123 text-decoration: none; … … 131 127 margin-bottom: 16px; 132 128 } 129 .social-media-wrap img { 130 width: 25%; 131 } 133 132 134 133 .website-rights { 135 134 color: #fff; 136 margin-bottom: 16px; 135 align-content: flex-end; 136 width: 50%; 137 margin-top: 2em; 137 138 } 138 139 -
imaps-frontend/src/pages/IMaps/components/Footer.jsx
r0c6b92a r79a0317 2 2 import "./Footer.css"; 3 3 import { Button } from "./Button"; 4 import logo from "../../../assets/logo_icon.png"; 4 import logo from "../../../assets/novo_logo_nobg_cropped.png"; 5 import {Link} from "react-router-dom"; 5 6 6 7 function Footer() { 7 8 return ( 8 9 <div className="footer-container"> 9 {/*<div className="footer-links">10 <div className="footer-links"> 10 11 <div className="footer-link-wrapper"> 11 12 <div className="footer-link-items"> … … 13 14 <a href="#">Contact</a> 14 15 <a href="#">Support</a> 15 <a href="#">Destinations</a>16 <a href="#">Sponsorships</a>17 16 </div> 18 17 </div> 19 <div className="footer-link-wrapper"> 20 <div className="footer-link-items"> 21 <h2>Social Media</h2> 22 <a href="#">Instagram</a> 23 <a href="#">Facebook</a> 24 <a href="#">Youtube</a> 25 <a href="#">Twitter</a> 18 <div className="social-media-wrap"> 19 20 <div className="footer-logo"> 21 <img src={logo} alt={"logo"}/> 22 </div> 23 <small className="website-rights">iMaps © {2024}</small> 24 </div> 25 <div> 26 <div className="footer-link-wrapper"> 27 <div className="footer-link-items"> 28 <h2>Legal & Privacy</h2> 29 <Link to={"/privacy-policy"}>Privacy Policy</Link> 30 <Link to={"/terms-of-service"}>Terms Of Service</Link> 31 32 </div> 26 33 </div> 27 34 </div> 28 </div> */} 29 <section className="social-media"> 30 <div className="social-media-wrap"> 31 <div className="footer-logo"> 32 <a href="#" className="social-logo"> 33 iMaps 34 </a> 35 </div> 36 <small className="website-rights">iMaps © {2024}</small> 37 <div className="social-icons"> 38 <a className="social-icon-link facebook" href="#" aria-label="Facebook"> 39 <i className="fab fa-facebook-f" /> 40 </a> 41 <a className="social-icon-link instagram" href="#" aria-label="Instagram"> 42 <i className="fab fa-instagram" /> 43 </a> 44 <a className="social-icon-link youtube" href="#" aria-label="Youtube"> 45 <i className="fab fa-youtube" /> 46 </a> 47 <a className="social-icon-link twitter" href="#" aria-label="Twitter"> 48 <i className="fab fa-twitter" /> 49 </a> 50 <a className="social-icon-link linkedin" href="#" aria-label="LinkedIn"> 51 <i className="fab fa-linkedin" /> 52 </a> 53 </div> 54 </div> 55 </section> 35 36 </div> 37 56 38 </div> 57 39 ); -
imaps-frontend/src/pages/IMaps/components/Navbar.css
r0c6b92a r79a0317 1 1 .modern-navbar { 2 /*background-color: #ffffff;*/ 3 /*background: linear-gradient(*/ 4 /* 90deg,*/ 5 /* #fafafa 0%,*/ 6 /* #c07e7e 25%,*/ 7 /* #af2525 50%,*/ 8 /* #443a3a 75%,*/ 9 /* #8d1010 100%*/ 10 /*);*/ 11 /*background: linear-gradient(to right, #ffffff, #c12c2c);*/ 12 /*background: linear-gradient(90deg, #3b3131, #ffffff);*/ 13 background: linear-gradient(90deg, #9b1818, #efefef); 2 background: linear-gradient(90deg, #ffffff, #fffefe); 14 3 color: #ffffff; 15 4 /*color: #333333;*/ … … 24 13 border-radius: 8px; 25 14 margin: 5px; 15 height: 70px; 16 overflow: hidden; 26 17 } 27 18 -
imaps-frontend/src/pages/IMaps/components/Navbar.jsx
r0c6b92a r79a0317 10 10 function Navbar() { 11 11 const [click, setClick] = useState(false); 12 const [button, setButton] = useState(true);13 14 const handleClick = () => setClick(!click);15 const closeMobileMenu = () => setClick(false);16 12 17 13 const { isAuthenticated } = useAppContext(); … … 23 19 <div className="navbar-left"> 24 20 <Logo position="relative"/> 25 <h1 className="navbar-title">iMaps</h1>26 21 </div> 27 22 -
imaps-frontend/src/pages/IMaps/components/pages/Home.jsx
r0c6b92a r79a0317 14 14 <h2 className="description">Create and explore detailed indoor maps.</h2> 15 15 <div className="hero-btns"> 16 <Link to="/ myMaps">17 <Button className="btns" buttonSize="btn--large">18 Create Maps16 <Link to="/Maps"> 17 <Button buttonSize="btn--large" > 18 Browse Maps <i className="far fa-play-circle" /> 19 19 </Button> 20 20 </Link> 21 <Link to="/ Maps">22 <Button className="btns" buttonSize="btn--large">23 Browse Maps <i className="far fa-play-circle" />21 <Link to="/myMaps"> 22 <Button buttonSize="btn--large" buttonStyle="btn--outline"> 23 Create Maps 24 24 </Button> 25 25 </Link> -
imaps-frontend/src/pages/IMaps/components/pages/Home.scss
r0c6b92a r79a0317 50 50 width: 100vw; 51 51 /* img size is 50x50 */ 52 background: url($bg-url) repeat 0 0; 52 /* Set a base background color */ 53 background: #87898d url($bg-url) repeat 0 0; 54 55 /* Apply hue rotation to the image */ 53 56 54 57 -webkit-animation: bg-scrolling-reverse .92s infinite; /* Safari 4+ */ … … 98 101 99 102 font-size: 6em; 100 101 102 103 103 104 -
imaps-frontend/src/pages/Login/Login.jsx
r0c6b92a r79a0317 6 6 import HttpService from "../../scripts/net/HttpService.js"; 7 7 import {useAppContext} from "../../components/AppContext/AppContext.jsx"; 8 import config from "../../scripts/net/netconfig.js"; 8 import config, {API_BASE_URL} from "../../scripts/net/netconfig.js"; 9 import google_icon from "../../assets/Logo-google-icon-PNG.png" 10 import github_icon from "../../assets/github-mark-white.png"; 11 import { v4 as uuidv4 } from 'uuid'; 9 12 10 13 const LoginPage = () => { … … 26 29 const handleLogin = async () => { 27 30 const httpService = new HttpService(); 28 return httpService.post(config.auth.login, payload) 29 31 return httpService.post(config.auth.login, payload); 30 32 }; 31 32 33 33 34 const login = async (e) => { … … 37 38 .then(resp => { 38 39 if (resp.token) { 39 navigate(targetPath) 40 navigate(targetPath); 40 41 localStorage.setItem("token", resp.token); 41 42 setUsername(resp.username); 42 43 setIsAuthenticated(true); 43 console.log("ROLES", resp.roles)44 console.log("ROLES", resp.roles); 44 45 } else { 45 46 setError("Invalid username or password."); … … 47 48 }).catch(reason => { 48 49 console.error("Login failed", reason); 49 setError("Login failed. Please try again.") 50 }) 50 setError("Login failed. Please try again."); 51 }); 52 }; 51 53 52 // fetch("http://localhost:8080/api/auth/login", { 53 // method: "POST", 54 // headers: { 55 // "Content-Type": "application/json", 56 // }, 57 // body: JSON.stringify(payload), 58 // }) 59 // .then((response) => { 60 // if (!response.ok) { 61 // throw new Error("Login failed: resp = " + response.statusText); 62 // } 63 // return response.json(); 64 // }) 65 // .then((data) => { 66 // if (data.token) { 67 // navigate(targetPath); 68 // handleLogin(data); 69 // } else { 70 // setError("Invalid username or password."); 71 // } 72 // }) 73 // .catch((error) => { 74 // console.error("Login failed", error); 75 // setError("Login failed. Please try again."); 76 // }); 54 const continueWithGitHub = async () => { 55 const httpService = new HttpService(); 56 httpService.setResponseType('text'); 57 const state = await httpService.get(config.auth.oauth.github.state) 58 const clientId = 'Iv23liqzhX5wMYNDHtnz'; 59 const redirectUri = encodeURI(`${API_BASE_URL}/oauth/callback/github`); 60 61 const githubAuthUrl = `https://github.com/login/oauth/authorize?client_id=${encodeURI(clientId)}&redirect_uri=${redirectUri}&state=${encodeURI(state)}&scope=user:email`; 62 63 window.location.href = githubAuthUrl; 64 65 }; 66 67 const continueWithGoogle = async () => { 68 console.log("Continue with Google"); 69 const httpService = new HttpService(); 70 httpService.setResponseType('text'); 71 const state = await httpService.get(config.auth.oauth.github.state) 72 const clientId = '1024418489231-ml40ukvqcg9ad1h5ejor5dm6ipt6p8fo.apps.googleusercontent.com'; 73 const redirectUri = encodeURI(`${API_BASE_URL}/oauth/callback/google`); 74 const googleAuthUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${encodeURIComponent(clientId)} 75 &redirect_uri=${encodeURIComponent(redirectUri)}&state=${encodeURIComponent(state)}&response_type=code&scope=${encodeURIComponent("openid profile email")}`; 76 window.location.href = googleAuthUrl 77 77 }; 78 78 … … 111 111 <button type="submit">Submit</button> 112 112 </form> 113 <div className={styles.or}>OR</div> 114 <div className={styles.socialButtons}> 115 <button className={styles.socialButton} onClick={continueWithGoogle}> 116 <img src={google_icon} alt="Facebook Icon" className={styles.socialIcon}/> 117 Sign In With Google 118 </button> 119 <button className={styles.socialButton} onClick={continueWithGitHub}> 120 <img src={github_icon} alt="GitHub Icon" className={styles.socialIcon}/> 121 Sign In With GitHub 122 </button> 123 </div> 113 124 <p> 114 125 Don't have an account? <Link to="/Signup"> Sign Up </Link> -
imaps-frontend/src/pages/Login/Login.module.css
r0c6b92a r79a0317 13 13 14 14 .wrapper { 15 display: -webkit-box;16 display: -ms-flexbox;17 15 display: flex; 16 justify-content: flex-end; /* Move content towards the right */ 17 align-items: center; /* Center content vertically */ 18 18 height: 100vh; 19 -webkit-box-align: center; 20 -ms-flex-align: center; 21 align-items: center; 22 } 23 24 .wrapper p { 25 font-size: 0.85rem; 26 margin-top: 1rem; 27 } 19 margin-left: 30%; 20 padding: 2rem; 21 } 22 23 28 24 29 25 .form { 30 padding: 1.5rem; 31 -ms-flex-preferred-size: 100vw; 32 flex-basis: 100vw; 26 padding: 2rem; 27 display: flex; 28 flex-direction: column; /* Stack form elements vertically */ 29 align-items: center; /* Center form elements horizontally */ 30 justify-content: center; /* Vertically center the form */ 31 background-color: #fff; 32 border-radius: 8px; /* Rounded corners for the form */ 33 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Add subtle shadow to form */ 34 width: 100%; 35 max-width: 400px; /* Restrict the form width for better appearance */ 33 36 } 34 37 35 38 .form .heading { 36 font-size: 1. 5rem;39 font-size: 1.8rem; 37 40 font-weight: bold; 38 41 text-align: center; 42 margin-bottom: 1.5rem; /* Spacing between heading and form */ 39 43 } 40 44 41 45 .or { 42 margin: 1rem 0; 46 margin-top: 1em; 47 text-align: center; 48 font-weight: bold; 49 color: #666; 43 50 } 44 51 … … 46 53 display: block; 47 54 margin: 1.25rem 0 1rem 0; 55 font-size: 0.95rem; 56 color: #333; 48 57 } 49 58 … … 51 60 height: 40px; 52 61 width: 100%; 62 max-width: 350px; /* Slightly larger max-width */ 53 63 padding: 15px; 54 64 background-color: #f1f9ff; 55 65 border: 2px solid #bce0fd; 56 66 border-radius: 8px; 67 box-sizing: border-box; 68 font-size: 1rem; /* Make input text a bit larger */ 57 69 } 58 70 … … 60 72 height: 40px; 61 73 width: 100%; 74 max-width: 350px; 75 margin-top: 1.5rem; 62 76 background-color: #258de6; 63 77 color: white; … … 65 79 letter-spacing: 1px; 66 80 border: none; 67 display: block; 68 margin: 0 auto; 69 font-weight: bold; 70 margin-top: 1.5rem; 81 font-weight: bold; 71 82 border-radius: 8px; 83 font-size: 1rem; /* Increase font size for better readability */ 84 } 85 86 .socialButtons { 87 display: flex; 88 flex-direction: column; /* Stack social buttons vertically */ 89 align-items: center; /* Center them horizontally */ 90 } 91 92 .socialButton { 93 display: inline-flex; /* Align text and image inline */ 94 align-items: center; /* Vertically center content */ 95 justify-content: flex-start; /* Align items to the left */ 96 padding: 10px 15px; 97 border: none; 98 border-radius: 8px; 99 cursor: pointer; 100 font-size: 0.8rem; 101 font-weight: bold; 102 transition: background-color 0.2s, transform 0.2s; 103 width: 100%; 104 max-width: 350px; /* Limit max width */ 105 } 106 107 .socialButton img { 108 width: 20px; 109 height: 20px; 110 margin-right: 10px; 111 vertical-align: middle; 112 } 113 114 .socialButton:hover { 115 filter: brightness(95%); 116 } 117 118 .socialButton:active { 119 transform: scale(0.98); 120 } 121 122 .socialButton:nth-child(2) { 123 background-color: #333; /* GitHub button */ 124 color: white; 125 } 126 127 .socialButton:nth-child(1) { 128 background-color: #ffffff; /* Google button */ 129 color: #000000; 130 box-shadow: 1px 2px #2f2525; 72 131 } 73 132 74 133 @media (min-width: 542px) { 75 134 body { 76 display: -webkit-box;77 display: -ms-flexbox;78 135 display: flex; 79 -webkit-box-pack: center;80 -ms-flex-pack: center;81 136 justify-content: center; 82 137 } 138 83 139 .wrapper { 84 display: -webkit-box;85 display: -ms-flexbox;86 140 display: flex; 87 141 height: 100vh; 88 -webkit-box-align: center;89 -ms-flex-align: center;90 142 align-items: center; 91 -ms-flex-pack: distribute; 92 justify-content: space-around; 93 padding: 1.5rem; 143 justify-content: flex-end; /* Keep the form to the right */ 144 padding: 2rem; 94 145 max-width: 1100px; 95 146 } 147 96 148 .form { 97 -ms-flex-preferred-size: auto;98 149 flex-basis: auto; 99 } 150 align-items: center; /* Center form in larger screens */ 151 width: 100%; 152 max-width: 400px; /* Keep form max width consistent */ 153 } 154 100 155 .form input { 101 width: 250px; 102 } 156 width: 100%; /* Ensures the input expands fully */ 157 max-width: 350px; 158 } 159 160 .illustration{ 161 margin-right: 40%; 162 } 163 103 164 .illustration img { 104 max-width: 80%;165 max-width: 100%; 105 166 height: auto; 106 167 } … … 108 169 109 170 @media (max-width: 680px) { 171 .wrapper { 172 flex-direction: column; /* Stack form and illustration vertically */ 173 align-items: center; /* Center content horizontally */ 174 padding: 1rem; 175 } 176 110 177 .illustration { 111 display: none; 112 } 113 } 114 115 .signUp .illustration { 116 order: 2; 117 justify-self: flex-end; 118 margin-left: 2rem; 178 display: none; /* Hide the illustration on mobile */ 179 } 180 181 .form { 182 width: 100%; /* Full width for mobile */ 183 align-items: center; /* Center form elements horizontally */ 184 } 185 186 .form input, 187 .form button, 188 .socialButton { 189 width: 100%; /* Full width for mobile */ 190 max-width: 300px; /* Optional: Limit max width */ 191 } 119 192 } 120 193 … … 126 199 transform: scale(0.98); 127 200 } 201 202 .error { 203 color: red; 204 font-size: 0.9rem; 205 margin-top: 0.5rem; 206 } -
imaps-frontend/src/pages/MapView/MapView.jsx
r0c6b92a r79a0317 14 14 import ShapeRegistry from "../../scripts/util/ShapeRegistry.js"; 15 15 import {useAppContext} from "../../components/AppContext/AppContext.jsx"; 16 import Report from "../../components/Report/Report.jsx"; 17 import {Button} from "../IMaps/components/Button.jsx"; 16 18 17 19 const MapView = ({isPrivate}) => { 18 20 const {mapName} = useParams(); 19 21 const {username} = useAppContext(); 22 const {isAuthenticated} = useAppContext(); 20 23 21 24 const [mapLoaded, setMapLoaded] = useState(false); … … 28 31 const [searchParams, setSearchParams] = useSearchParams(); 29 32 const [mainEntrance, setMainEntrance] = useState({}); 33 const [canDisplayNavDownload,setCanDisplayNavDownload] = useState(false); 34 const [from,setFrom] = useState(""); 35 const [to,setTo] = useState(""); 30 36 31 37 const defaultNavObj = { … … 115 121 setFloors(respFloors); 116 122 123 console.log("FLOOR DATA: " + tlFloor?.mapData) 124 117 125 appInstance.loadMapN(tlFloor?.mapData) 118 126 setApp(appInstance); … … 163 171 const toEncoded = encodeURIComponent(toSearch).trimEnd() 164 172 173 setFrom(fromSearch); 174 setTo(toSearch); 175 165 176 httpService.get(`${config.view_maps.navigate}?from=${fromEncoded}&to=${toEncoded}`).then(path => { 166 177 app.drawRouteNEW(path); 178 179 167 180 }).catch(reason => { 168 181 console.log("err", reason) … … 185 198 } 186 199 200 useEffect(() => { 201 const handleNavigateEnd = (event) => { 202 console.log("DETAIL END",event.detail) 203 setCanDisplayNavDownload(true); 204 } 205 206 window.addEventListener("navend",handleNavigateEnd) 207 return () => { 208 window.removeEventListener("navend",handleNavigateEnd) 209 } 210 }, [app]); 211 187 212 188 213 useEffect(() => { … … 216 241 app.loadMapN(chFloor.mapData) 217 242 app.floorNum = floorNum; 218 219 243 220 244 console.log(`Floor changed to: ${floorNum}`); … … 257 281 handleFloorChange={handleFloorChange} 258 282 /> 283 {canDisplayNavDownload && 284 (<div className={styles.downloadRouteButton}> 285 <button onClick={() => { 286 app.getRouteImages({ 287 mapName: mapName, 288 from: from, 289 to: to 290 }) 291 setCanDisplayNavDownload(false) 292 }}> Download Route</button> 293 </div>) } 259 294 <FilterBar map={app} roomTypes={roomTypes}/> 260 295 </div> 261 296 )} 297 298 {isAuthenticated && <Report mapName = {mapName}></Report>} 262 299 <div className={styles.profileContainer}> 263 300 <Profile position="relative"/> … … 265 302 </div> 266 303 </div> 304 <div id="temp"></div> 267 305 268 306 <div className={styles.floorSelectorContainer}> … … 283 321 </div> 284 322 323 285 324 </div> 286 325 ); -
imaps-frontend/src/pages/MyMaps/CreateMaps.module.css
r0c6b92a r79a0317 55 55 margin-bottom: 30px; 56 56 text-align: center; 57 margin-top: 5em; 58 57 59 } 58 60 59 61 .searchBar input { 60 width: 300px; 62 /*width: 300px;*/ 63 width: 33vw; 61 64 padding: 10px; 62 65 font-size: 16px; … … 83 86 display: flex; 84 87 flex-direction: column; 85 align-items: center; /* Center items horizontally */86 justify-content: flex-start; /* Ensure tiles stack from the top */88 align-items: center; 89 justify-content: flex-start; 87 90 padding: 10px; 91 /*padding-left: 2.5em;*/ 88 92 background-color: #f9f9f9; 89 93 border-radius: 8px; 90 94 box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 91 } 95 96 } 97 92 98 93 99 .tile { … … 127 133 } 128 134 135 .plusButton { 136 width: 50px; 137 height: 50px; 138 border: none; 139 cursor: pointer; 140 display: flex; 141 align-items: center; 142 justify-content: center; 143 transition: transform 0.3s ease-in-out; 144 background: #4ebc3b; 145 margin-right: 20px; 146 } 147 148 .plusButton img { 149 width: 50px; 150 height: auto; 151 margin-left: 0; 152 } 153 154 .plusButton:hover { 155 transform: scale(1.1); 156 background: #46be2f; 157 } 158 159 .header { 160 display: flex; 161 align-items: center; 162 justify-content: space-between; 163 background-color: #f8f9fa; /* Light grey background for a clean look */ 164 border-bottom: 1px solid #ddd; /* Subtle separation from the rest of the page */ 165 padding: 10px 20px; /* Add some space around the content */ 166 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* Light shadow for subtle elevation */ 167 } 168 169 .header .logo-wrapper, 170 .header .profile-wrapper { 171 display: flex; 172 align-items: center; 173 } 174 175 .header h1 { 176 font-size: 1.5rem; /* Slightly larger font size */ 177 color: #333; /* Dark grey for a professional tone */ 178 margin: 0 auto; /* Center-align the title */ 179 font-weight: 600; /* Semi-bold text for emphasis */ 180 text-align: center; 181 flex-grow: 1; /* Allow it to take space in the middle */ 182 } 183 184 .logo-wrapper { 185 margin-right: auto; /* Push logo to the far left */ 186 } 187 188 .profile-wrapper { 189 margin-left: auto; /* Push profile to the far right */ 190 } 191 192 193 194 129 195 @media (max-width: 768px) { 130 196 .mapsContainer { -
imaps-frontend/src/pages/MyMaps/MyMaps.jsx
r0c6b92a r79a0317 10 10 import {useAppContext} from "../../components/AppContext/AppContext.jsx"; 11 11 import config from "../../scripts/net/netconfig.js"; 12 import {element} from "prop-types";13 12 import Toast from "../../components/Toast/Toast.jsx"; 13 import plus_icon from "../../assets/plus_icon.png"; 14 14 15 15 const renderTile = ({data, isDragging}, openMapInfo) => ( … … 65 65 setToastMessage(message); 66 66 setToastType(type); 67 setTimeout(() => setToastMessage(null), 3000); // Automatically hide the toast after 3 seconds 68 }; 69 67 setTimeout(() => setToastMessage(null), 3000); 68 }; 70 69 71 70 const handleUpdate = async (updatedMap) => { 72 // Placeholder for map update logic 71 try { 72 let httpService = new HttpService(true); 73 const response = await httpService.post( 74 config.my_maps.edit_map_info, 75 updatedMap 76 ); 77 closeMapInfoModal() 78 window.location.reload(); 79 80 } catch (error) { 81 showToast("Map Name already taken", 0) 82 closeMapInfoModal() 83 } 73 84 }; 74 85 … … 83 94 setTiles((prevTiles) => prevTiles.filter((tile) => tile.mapName !== mapName)); 84 95 setAllTiles((prevTiles) => prevTiles.filter((tile) => tile.mapName !== mapName)); 85 showToast("Map deleted", 1) 96 showToast("Map deleted", 1); 86 97 }) 87 98 .catch((error) => { 88 99 const errorMessage = error.response?.data?.error || error.message || "Unknown error"; 89 // alert(`Error deleting the map: ${errorMessage}`); 90 showToast(`Error deleting the map: ${errorMessage}`, 0) 100 showToast(`Error deleting the map: ${errorMessage}`, 0); 91 101 }); 92 102 }; … … 97 107 98 108 httpService 99 .put(`${config.my_maps.add}?username=${ username}`, mapDetails)109 .put(`${config.my_maps.add}?username=${encodeURI(username)}`, mapDetails) 100 110 .then((respMap) => { 101 console.log("RESP NEW MAP: " + respMap)102 111 const mapTile = { 103 112 mapName: respMap.mapName, … … 108 117 modified_at: respMap.modifiedAt, 109 118 published_at: respMap.published_at, 110 gmaps_url: respMap.gmaps _url,119 gmaps_url: respMap.gmapsUrl, 111 120 image_url: card, 112 121 is_published: respMap.is_published, 122 mapType: respMap.mapType 113 123 }; 114 124 … … 116 126 setTiles((prevTiles) => [...prevTiles, mapTile]); 117 127 showToast("Map added successfully!"); 118 119 120 128 }) 121 129 .catch((error) => { 122 showToast("Map name already taken", 0) 130 showToast("Map name already taken", 0); 123 131 }); 124 132 }; … … 129 137 httpService.setAuthenticated(); 130 138 131 const respMaps = await httpService.get(`${config.my_maps.display}?username=${username}`); 139 const respMaps = await httpService.get( 140 `${config.my_maps.display}?username=${encodeURI(username)}` 141 ); 132 142 133 143 const mapTiles = respMaps.map((elem) => ({ … … 139 149 modified_at: elem.modifiedAt, 140 150 published_at: elem.published_at, 141 gmaps_url: elem.g MapsUrl,151 gmaps_url: elem.gmapsUrl, 142 152 image_url: card, 143 153 numFavourites: elem.numFavourites, 144 154 })); 145 146 155 147 156 setTiles(mapTiles); … … 154 163 setPublicMaps(tiles.filter((tile) => tile.status === "PUBLIC")); 155 164 setPrivateMaps(tiles.filter((tile) => tile.status === "PRIVATE")); 156 setPendingMaps(tiles.filter((tile) => tile.status === " INVALID"));165 setPendingMaps(tiles.filter((tile) => tile.status === "PENDING")); 157 166 }, [tiles]); 158 167 … … 164 173 return ( 165 174 <div className={styles.container}> 166 <Logo/> 167 <Profile/> 175 176 <div className={`${styles.logoWrapper}`}> 177 <Logo/> 178 </div> 168 179 <h1>Your Maps</h1> 169 170 <div className={styles.actionButtons}> 171 <button className={styles.createMapsButton} onClick={openCreateModal}> 172 Create Map 173 </button> 174 </div> 180 <div className={`${styles.profileWrapper}`}> 181 <Profile/> 182 </div> 183 175 184 176 185 <div className={styles.searchBar}> 177 <input type="text" placeholder="Search for maps..." onChange={handleSearch}/> 186 <input 187 type="text" 188 placeholder="Search for maps..." 189 onChange={handleSearch} 190 /> 178 191 </div> 179 192 … … 201 214 forceTileHeight={170} 202 215 /> 216 <button className={styles.plusButton} onClick={openCreateModal}> 217 <img src={plus_icon} alt="Add Map"/> 218 </button> 203 219 </div> 204 220 … … 222 238 onDelete={deleteMap} 223 239 onUpdate={handleUpdate} 224 onPublish={() => { 240 onPublish={(updatedMap = null) => { 241 242 const updatedTile = { 243 mapName: updatedMap.mapName, 244 cols: 1, 245 rows: 1, 246 status: updatedMap.mapStatus, 247 created_at: updatedMap.createdAt, 248 modified_at: updatedMap.modifiedAt, 249 published_at: updatedMap.published_at, 250 gmaps_url: updatedMap.gMapsUrl, 251 image_url: card, 252 numFavourites: updatedMap.numFavourites, 253 } 225 254 showToast(`Map ${selectedMap.mapName} published successfully!`); 226 255 setPrivateMaps((prevMaps) => prevMaps.filter(m => m.mapName !== selectedMap.mapName)) 227 setPendingMaps((prevMaps) => [...prevMaps, allTiles.find(m => m.mapName = selectedMap.mapName)])256 setPendingMaps((prevMaps) => [...prevMaps, updatedTile]) 228 257 closeMapInfoModal() 229 258 }} … … 236 265 /> 237 266 238 {toastMessage && <Toast message={toastMessage} type={toastType} onClose={() => setToastMessage(null)}/>} 267 {toastMessage && ( 268 <Toast 269 message={toastMessage} 270 type={toastType} 271 onClose={() => setToastMessage(null)} 272 /> 273 )} 239 274 </div> 240 275 ); -
imaps-frontend/src/pages/Signup/Signup.jsx
r0c6b92a r79a0317 4 4 import styles from "./Signup.module.css"; 5 5 import Logo from "../../components/Logo/Logo"; 6 import netconfig from "../../scripts/net/netconfig.js"; 6 7 7 8 export default function Signup() { … … 9 10 const [email, setEmail] = useState(""); 10 11 const [password, setPassword] = useState(""); 12 const [confirmPassword, setConfirmPassword] = useState(""); 11 13 const [message, setMessage] = useState(""); 12 const [messageType, setMessageType] = useState(""); // New state to manage message type14 const [messageType, setMessageType] = useState(""); 13 15 const navigate = useNavigate(); 14 16 15 17 const handleSubmit = async (e) => { 16 18 e.preventDefault(); 19 20 if (password !== confirmPassword) { 21 setMessageType("error"); 22 setMessage("Passwords do not match."); 23 return; 24 } 17 25 18 26 const payload = { … … 23 31 24 32 try { 25 const response = await fetch( "http://localhost:8080/api/auth/register", {33 const response = await fetch(netconfig.auth.register, { 26 34 method: "POST", 27 35 headers: { … … 35 43 setMessage("User registered successfully!"); 36 44 37 // Wait 3 seconds and then redirect to login page38 45 setTimeout(() => { 39 46 navigate("/login"); 40 }, 3000);47 }, 1000); 41 48 } else if (response.status === 409) { 42 49 setMessageType("error"); … … 54 61 55 62 return ( 56 <div className={styles.wrapper}> 57 <Logo></Logo> 58 <div className={styles.illustration}> 59 <img src={illustration} alt="illustration" /> 63 <div className={styles.wrapper}> 64 <Logo></Logo> 65 <div className={styles.illustration}> 66 <img src={illustration} alt="illustration" /> 67 </div> 68 <div className={styles.form}> 69 <div className={styles.heading}>CREATE AN ACCOUNT</div> 70 <form onSubmit={handleSubmit}> 71 <div> 72 <label htmlFor="name">Username</label> 73 <input 74 type="text" 75 id="name" 76 value={name} 77 onChange={(e) => setName(e.target.value)} 78 placeholder="Enter your username" 79 required 80 /> 81 </div> 82 <div> 83 <label htmlFor="email">E-Mail</label> 84 <input 85 type="email" 86 id="email" 87 value={email} 88 onChange={(e) => setEmail(e.target.value)} 89 placeholder="Enter your email" 90 required 91 /> 92 </div> 93 <div> 94 <label htmlFor="password">Password</label> 95 <input 96 type="password" 97 id="password" 98 value={password} 99 onChange={(e) => setPassword(e.target.value)} 100 placeholder="Enter your password" 101 required 102 /> 103 </div> 104 <div> 105 <label htmlFor="confirmPassword">Confirm Password</label> 106 <input 107 type="password" 108 id="confirmPassword" 109 value={confirmPassword} 110 onChange={(e) => setConfirmPassword(e.target.value)} 111 placeholder="Confirm your password" 112 required 113 /> 114 </div> 115 <button type="submit">Submit</button> 116 {message && ( 117 <p className={messageType === "success" ? styles.successMessage : styles.errorMessage}> 118 {message} 119 </p> 120 )} 121 </form> 122 <p> 123 Have an account? <Link to="/Login"> Login </Link> 124 </p> 125 </div> 60 126 </div> 61 <div className={styles.form}>62 <div className={styles.heading}>CREATE AN ACCOUNT</div>63 <form onSubmit={handleSubmit}>64 <div>65 <label htmlFor="name">Username</label>66 <input67 type="text"68 id="name"69 value={name}70 onChange={(e) => setName(e.target.value)}71 placeholder="Enter your username"72 required73 />74 </div>75 <div>76 <label htmlFor="email">E-Mail</label>77 <input78 type="email"79 id="email"80 value={email}81 onChange={(e) => setEmail(e.target.value)}82 placeholder="Enter your email"83 required84 />85 </div>86 <div>87 <label htmlFor="password">Password</label>88 <input89 type="password"90 id="password"91 value={password}92 onChange={(e) => setPassword(e.target.value)}93 placeholder="Enter your password"94 required95 />96 </div>97 <button type="submit">Submit</button>98 99 {/* Display message with appropriate styling */}100 {message && (101 <p className={messageType === "success" ? styles.successMessage : styles.errorMessage}>102 {message}103 </p>104 )}105 <h2 align="center" className={styles.or}>106 OR107 </h2>108 </form>109 <p>110 Have an account? <Link to="/Login"> Login </Link>111 </p>112 </div>113 </div>114 127 ); 115 128 } -
imaps-frontend/src/pages/Signup/Signup.module.css
r0c6b92a r79a0317 1 * {2 margin: 0;3 padding: 0;4 -webkit-box-sizing: border-box;5 box-sizing: border-box;6 font-family: sans-serif;7 }8 9 .illustration img {10 max-height: 500px;11 width: auto;12 }13 14 1 .wrapper { 15 display: -webkit-box;16 display: -ms-flexbox;17 2 display: flex; 18 height: 100vh; 19 -webkit-box-align: center; 20 -ms-flex-align: center; 21 align-items: center; 22 } 23 24 .wrapper p { 25 font-size: 0.85rem; 26 margin-top: 1rem; 3 align-items: center; /* Vertically center content */ 4 justify-content: center; /* Horizontally center content */ 5 height: 100vh; /* Full viewport height */ 6 background-color: #ffffff; /* Light background */ 7 padding: 1rem; 8 gap: 2rem; /* Space between form and image */ 27 9 } 28 10 29 11 .form { 30 padding: 1.5rem; 31 -ms-flex-preferred-size: 100vw; 32 flex-basis: 100vw; 12 padding: 2rem; 13 background-color: white; 14 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 15 border-radius: 10px; 16 flex-basis: 100%; 17 max-width: 400px; /* Restrict form width */ 18 text-align: center; /* Center text inside the form */ 33 19 } 34 20 35 21 .form .heading { 36 font-size: 1. 5rem;22 font-size: 1.8rem; 37 23 font-weight: bold; 38 text-align: center; 39 } 40 41 .or { 42 margin: 1rem 0; 24 color: #258de6; 25 margin-bottom: 1rem; 43 26 } 44 27 45 28 .form label { 46 29 display: block; 47 margin: 1.25rem 0 1rem 0; 30 margin: 1.25rem 0 0.5rem 0; 31 font-size: 0.9rem; 32 font-weight: bold; 33 color: #555; 48 34 } 49 35 … … 51 37 height: 40px; 52 38 width: 100%; 53 padding: 1 5px;39 padding: 10px 15px; 54 40 background-color: #f1f9ff; 55 41 border: 2px solid #bce0fd; 56 42 border-radius: 8px; 43 font-size: 1rem; 44 } 45 46 .form input:focus { 47 outline: none; 48 border-color: #258de6; 49 background-color: #e6f4ff; 57 50 } 58 51 59 52 .form button { 60 height: 4 0px;53 height: 45px; 61 54 width: 100%; 62 55 background-color: #258de6; … … 65 58 letter-spacing: 1px; 66 59 border: none; 60 margin-top: 1.5rem; 61 font-weight: bold; 62 border-radius: 8px; 63 cursor: pointer; 64 transition: all 0.2s ease; 65 } 66 67 .form button:hover { 68 filter: brightness(90%); 69 } 70 71 .form button:active { 72 transform: scale(0.96); 73 } 74 75 .form p { 76 text-align: center; 77 margin-top: 1rem; 78 font-size: 0.9rem; 79 } 80 81 .form p a { 82 color: #258de6; 83 text-decoration: none; 84 font-weight: bold; 85 } 86 87 .form p a:hover { 88 text-decoration: underline; 89 } 90 91 .illustration { 92 text-align: center; /* Center image container */ 93 } 94 95 .illustration img { 96 max-width: 100%; 97 height: auto; 98 border-radius: 10px; 67 99 display: block; 68 margin: 0 auto; 100 margin: 0 auto; /* Center the image */ 101 } 102 103 @media (min-width: 768px) { 104 .wrapper { 105 flex-direction: row; 106 gap: 4rem; /* Space between form and image */ 107 } 108 109 .form { 110 flex-basis: 50%; 111 } 112 113 .illustration { 114 flex-basis: 50%; 115 } 116 117 .illustration img { 118 max-height: 600px; 119 width: auto; 120 } 121 } 122 123 @media (max-width: 768px) { 124 .wrapper { 125 flex-direction: column; 126 text-align: center; /* Center all content in column layout */ 127 } 128 129 .illustration { 130 margin-bottom: 2rem; /* Space between image and form */ 131 } 132 } 133 134 .errorMessage, 135 .successMessage { 136 font-size: 0.9rem; 69 137 font-weight: bold; 70 margin-top: 1.5rem; 138 text-align: center; 139 margin-top: 1rem; 140 padding: 10px; 71 141 border-radius: 8px; 72 142 } 73 143 74 @media (min-width: 542px) { 75 body { 76 display: -webkit-box; 77 display: -ms-flexbox; 78 display: flex; 79 -webkit-box-pack: center; 80 -ms-flex-pack: center; 81 justify-content: center; 82 } 83 .wrapper { 84 display: -webkit-box; 85 display: -ms-flexbox; 86 display: flex; 87 height: 100vh; 88 -webkit-box-align: center; 89 -ms-flex-align: center; 90 align-items: center; 91 -ms-flex-pack: distribute; 92 justify-content: space-around; 93 padding: 1.5rem; 94 max-width: 1100px; 95 } 96 .form { 97 -ms-flex-preferred-size: auto; 98 flex-basis: auto; 99 } 100 .form input { 101 width: 250px; 102 } 103 .illustration img { 104 max-width: 80%; 105 height: auto; 106 } 144 .errorMessage { 145 color: #e74c3c; 146 background-color: #fbeaea; 147 border: 1px solid #e74c3c; 107 148 } 108 149 109 @media (max-width: 680px){110 .illustration {111 display: none;112 }150 .successMessage { 151 color: #2ecc71; 152 background-color: #ebf9f1; 153 border: 1px solid #2ecc71; 113 154 } 114 115 .signUp .illustration {116 order: 2;117 justify-self: flex-end;118 margin-left: 2rem;119 }120 121 button:hover {122 filter: brightness(95%);123 }124 125 button:active {126 transform: scale(0.98);127 }128 129 /* Error message style */130 .errorMessage {131 color: #e74c3c; /* Red color for error */132 background-color: #fbeaea;133 padding: 10px;134 border: 1px solid #e74c3c;135 border-radius: 5px;136 margin-top: 1rem;137 text-align: center;138 font-size: 0.9rem;139 font-weight: bold;140 }141 142 /* Success message style */143 .successMessage {144 color: #2ecc71; /* Green color for success */145 background-color: #ebf9f1;146 padding: 10px;147 border: 1px solid #2ecc71;148 border-radius: 5px;149 margin-top: 1rem;150 text-align: center;151 font-size: 0.9rem;152 font-weight: bold;153 }
Note:
See TracChangeset
for help on using the changeset viewer.