diff --git a/package-lock.json b/package-lock.json index ffbe500..0230f52 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "react": "^18.2.0", "react-calendar": "^4.6.0", "react-dom": "^18.2.0", + "react-ga": "^3.3.1", "react-icons": "^4.10.1", "react-leaflet": "^4.2.1", "react-resizable": "^3.0.5", @@ -16509,6 +16510,15 @@ } } }, + "node_modules/react-ga": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/react-ga/-/react-ga-3.3.1.tgz", + "integrity": "sha512-4Vc0W5EvXAXUN/wWyxvsAKDLLgtJ3oLmhYYssx+YzphJpejtOst6cbIHCIyF50Fdxuf5DDKqRYny24yJ2y7GFQ==", + "peerDependencies": { + "prop-types": "^15.6.0", + "react": "^15.6.2 || ^16.0 || ^17 || ^18" + } + }, "node_modules/react-icons": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.10.1.tgz", diff --git a/package.json b/package.json index c95238f..b3d5626 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react": "^18.2.0", "react-calendar": "^4.6.0", "react-dom": "^18.2.0", + "react-ga": "^3.3.1", "react-icons": "^4.10.1", "react-leaflet": "^4.2.1", "react-resizable": "^3.0.5", diff --git a/src/App.js b/src/App.js index b627ec6..77fee29 100644 --- a/src/App.js +++ b/src/App.js @@ -10,7 +10,9 @@ import { ChakraProvider } from "@chakra-ui/react"; import "leaflet/dist/leaflet.css"; import { AuthContextProvider } from "./context/AuthContext"; import AboutPage from "./components/AboutPage/AboutPage"; -// +import ReactGA from "react-ga"; + +ReactGA.initialize("UA-283774176-1"); function App() { return ( diff --git a/src/components/AboutPage/AboutPage.jsx b/src/components/AboutPage/AboutPage.jsx index f10cfec..5fa1f44 100644 --- a/src/components/AboutPage/AboutPage.jsx +++ b/src/components/AboutPage/AboutPage.jsx @@ -10,14 +10,17 @@ import "swiper/css/navigation"; import logo from "../../assets/images/small_logo.png"; import login_page from "../../assets/images/login_page.jpg"; import axios from "axios"; +import ReactGA from "react-ga"; export default function AboutPage() { const navigate = useNavigate(); const [screenWidth, setScreenWidth] = useState(window.screen.width); const [data, setData] = useState([]); const [leaderboard, setLeaderboard] = useState([]); - console.log("leaderboard", leaderboard); - console.log("data", data); + + useEffect(() => { + ReactGA.pageview(window.location.pathname); + }, []); window.onresize = () => { setScreenWidth(window.screen.width); diff --git a/src/components/FeedbackModal/FeedbackModal.jsx b/src/components/FeedbackModal/FeedbackModal.jsx index 0e64dc7..db51cd5 100644 --- a/src/components/FeedbackModal/FeedbackModal.jsx +++ b/src/components/FeedbackModal/FeedbackModal.jsx @@ -23,15 +23,26 @@ export default function FeedbackModal({ email, }) { const [feedbackHelped, setFeedbackHelped] = useState(null); - const { setLoading } = useContext(DataContext); + const { setLoading, token } = useContext(DataContext); async function handleFeedback() { + if (!token) { + return; + } setLoading(false); axios - .put(`${process.env.REACT_APP_AWS_BACKEND_URL}/items/${props.id}`, { - ...props, - isresolved: true, - ishelped: feedbackHelped, - }) + .put( + `${process.env.REACT_APP_AWS_BACKEND_URL}/items/${props.id}`, + { + ...props, + isresolved: true, + ishelped: feedbackHelped, + }, + { + headers: { + Authorization: `Bearer ${token}`, // verify auth + }, + } + ) .then(() => console.log("Success")) .catch((err) => console.log(err)); @@ -51,10 +62,18 @@ export default function FeedbackModal({ // Update the leaderboard const pointsToAdd = props.islost ? 2 : 5; - axios.put(`${process.env.REACT_APP_AWS_BACKEND_URL}/leaderboard`, { - email: email, - pointsToAdd: pointsToAdd, - }); + axios.put( + `${process.env.REACT_APP_AWS_BACKEND_URL}/leaderboard`, + { + email: email, + pointsToAdd: pointsToAdd, + }, + { + headers: { + Authorization: `Bearer ${token}`, // verify auth + }, + } + ); setLeaderboard((prev) => prev.map((u) => diff --git a/src/components/Filter/Filter.jsx b/src/components/Filter/Filter.jsx index 9001308..dc22fa0 100644 --- a/src/components/Filter/Filter.jsx +++ b/src/components/Filter/Filter.jsx @@ -18,9 +18,11 @@ import { Button, } from "@chakra-ui/react"; import "./Filter.css"; +import { UserAuth } from "../../context/AuthContext"; export default function Filter({ findFilter, setFindFilter, onClose, isOpen }) { const [value, setValue] = useState("everything"); + const { user } = UserAuth(); useEffect(() => { /* eslint-disable react-hooks/exhaustive-deps */ @@ -70,12 +72,29 @@ export default function Filter({ findFilter, setFindFilter, onClose, isOpen }) { Found + + { + setFindFilter((prev) => ({ + ...prev, + isShowReturned: !prev.isShowReturned, + })); + }} + defaultChecked={findFilter.isShowReturned} + /> + + Returned + + { setFindFilter((prev) => ({ ...prev, @@ -83,7 +102,12 @@ export default function Filter({ findFilter, setFindFilter, onClose, isOpen }) { })); }} /> - + Your Posts @@ -124,6 +148,9 @@ export default function Filter({ findFilter, setFindFilter, onClose, isOpen }) { + + Found/Lost Date: + { setFindFilter((prev) => ({ @@ -149,6 +176,8 @@ export default function Filter({ findFilter, setFindFilter, onClose, isOpen }) { isFound: true, islost: true, uploadDate: "", + isYourPosts: false, + isShowReturned: true, }); onClose(); }} diff --git a/src/components/Home/Home.jsx b/src/components/Home/Home.jsx index e00e8f5..d0e2872 100644 --- a/src/components/Home/Home.jsx +++ b/src/components/Home/Home.jsx @@ -53,6 +53,7 @@ export default function Home() { const [data, setData] = useState([]); const [leaderboard, setLeaderboard] = useState([]); const { user, logOut } = UserAuth(); + const [token, setToken] = useState(""); const btnRef = useRef(); const { isOpen, onOpen, onClose } = useDisclosure(); @@ -74,6 +75,7 @@ export default function Home() { islost: true, uploadDate: "", isYourPosts: false, + isShowReturned: true, }); function isFilterOff() { @@ -83,10 +85,13 @@ export default function Home() { findFilter.islost === true && findFilter.uploadDate === "" && search === "" && - !findFilter.isYourPosts + !findFilter.isYourPosts && + findFilter.isShowReturned === true ); } + console.log(token); + const [loading, setLoading] = useState(false); const [newAddedItem, setNewAddedItem] = useState({ @@ -157,12 +162,10 @@ export default function Home() { setLeaderboard( leaderboardData.map((item) => ({ ...item, id: item.id })) ); - // Check if the current user's email exists in the leaderboard const userEmailExists = leaderboardData.some( (entry) => entry.email === user?.email ); - // If it does not exist, add the user to the leaderboard if (!userEmailExists) { await axios.post( @@ -170,9 +173,13 @@ export default function Home() { { email: user.email, points: 5, // You can modify this as per your requirements + }, + { + headers: { + Authorization: `Bearer ${token}`, // verify auth + }, } ); - // Fetch the leaderboard again after insertion const { data: updatedLeaderboardData } = await axios.get( `${process.env.REACT_APP_AWS_BACKEND_URL}/leaderboard/` @@ -189,6 +196,13 @@ export default function Home() { }; getLeaderboard(); + }, [user, token]); + + // set token to auth + useEffect(() => { + if (user) { + setToken(user.accessToken); + } }, [user]); window.onresize = () => { @@ -199,6 +213,7 @@ export default function Home() { - + {user ? leaderboard.find((u) => u.email === user.email)?.points : 0} diff --git a/src/components/Home/Leaderboard.jsx b/src/components/Home/Leaderboard.jsx index 36994d0..15f307f 100644 --- a/src/components/Home/Leaderboard.jsx +++ b/src/components/Home/Leaderboard.jsx @@ -69,7 +69,7 @@ export default function Leaderboard({ 1 - + {leaderboard[0]?.email} - - + + {leaderboard[0]?.points} gold medal 2 - - {leaderboard[0]?.email} + + {leaderboard[1]?.email} - - {leaderboard[0]?.points} 🍪 + + {leaderboard[1]?.points} 🍪 3 - - {leaderboard[0]?.email} + + {leaderboard[2]?.email} - - {leaderboard[0]?.points} 🍪 + + {leaderboard[2]?.points} 🍪 diff --git a/src/components/ImageContainer/ImageContainer.jsx b/src/components/ImageContainer/ImageContainer.jsx index 39b03fe..e65226a 100644 --- a/src/components/ImageContainer/ImageContainer.jsx +++ b/src/components/ImageContainer/ImageContainer.jsx @@ -16,7 +16,7 @@ export default function ImageContainer({ image, isresolved }) { alignItems={"center"} marginTop={30} flexDir={"column"} - w={450} + w={{ base: "100vw", md: 450 }} > RETURNED diff --git a/src/components/InfoModal/InfoModal.jsx b/src/components/InfoModal/InfoModal.jsx index 762e880..57da1bb 100644 --- a/src/components/InfoModal/InfoModal.jsx +++ b/src/components/InfoModal/InfoModal.jsx @@ -18,6 +18,7 @@ import DataContext from "../../context/DataContext"; import ImageContainer from "../ImageContainer/ImageContainer"; import FeedbackModal from "../FeedbackModal/FeedbackModal"; import { LinkIcon, CheckIcon, EmailIcon } from "@chakra-ui/icons"; +import axios from "axios"; export default function InfoModal({ setData, @@ -27,22 +28,40 @@ export default function InfoModal({ setLeaderboard, }) { const [showEmail, setShowEmail] = useState(false); - const { onLoginModalOpen } = useContext(DataContext); + const [isShared, setIsShared] = useState(false); + const { onLoginModalOpen, token, setLoading } = useContext(DataContext); const { user } = UserAuth(); const navigate = useNavigate(); const feedbackModalDisclosure = useDisclosure(); const currentEmail = user?.email; - // function viewEmail() { - // if (user) { - // setShowEmail(true); - // } - // } - async function handleResolve() { feedbackModalDisclosure.onOpen(); } + async function handleDelete() { + onClose(); + setLoading(false); + if (!currentEmail) { + return; + } + axios + .delete(`${process.env.REACT_APP_AWS_BACKEND_URL}/items/${props.id}`, { + headers: { + Authorization: `Bearer ${token}`, // verify auth + }, + }) + .then(() => console.log("Success")) + .catch((err) => console.log(err)); + setData((prevItems) => { + if (prevItems && prevItems.length > 0) { + return prevItems.filter((item) => item.id !== props.id); + } + return prevItems; + }); + setLoading(true); + } + const formattedDate = formatDate(new Date(props.date)); return ( <> @@ -56,7 +75,11 @@ export default function InfoModal({ > - + {props.islost ? ( - Lost on {props.itemDate} + Lost on {props.itemdate} ) : ( - Found on {props.itemDate} + Found on {props.itemdate} )} Resolve )} + + {[ + "dangnn1@uci.edu", + "stevenz9@uci.edu", + "katyh1@uci.edu", + ].includes(currentEmail) && ( + + )} diff --git a/src/components/Map/Map.jsx b/src/components/Map/Map.jsx index a8ed65b..d7101dc 100644 --- a/src/components/Map/Map.jsx +++ b/src/components/Map/Map.jsx @@ -22,6 +22,7 @@ import InfoModal from "../InfoModal/InfoModal"; import DataContext from "../../context/DataContext"; import { UserAuth } from "../../context/AuthContext"; +import ReactGA from "react-ga"; import axios from "axios"; @@ -44,10 +45,11 @@ export default function Map({ setLeaderboard, }) { const { user } = UserAuth(); - const { data, setLoading } = useContext(DataContext); + const { data, setLoading, token } = useContext(DataContext); const { isOpen, onOpen, onClose } = useDisclosure(); const [itemData, setItemData] = useState({}); const [showDonut, setShowDonut] = useState(false); + console.log(data); const allowedBounds = [ [33.656487295651, -117.85412222020983], @@ -64,6 +66,10 @@ export default function Map({ setShowDonut(false); }; + useEffect(() => { + ReactGA.pageview(window.location.pathname); + }, []); + useEffect(() => { const handleFocus = async () => { await handleMarkerSelect(); @@ -86,7 +92,8 @@ export default function Map({ (findFilter.uploadDate === "" || (item.itemdate && item.itemdate.includes(findFilter.uploadDate))) && (!findFilter.isYourPosts || - (findFilter.isYourPosts && item.email === user.email)) + (findFilter.isYourPosts && item.email === user.email)) && + (findFilter.isShowReturned || !item.isresolved) ); }) .map((item) => { @@ -99,6 +106,10 @@ export default function Map({ onOpen(); setItemData(item); setFocusLocation(item.location); + ReactGA.event({ + category: item.name, + action: "click on item", + }); }, }} icon={ @@ -134,24 +145,34 @@ export default function Map({ }), [setPosition] ); - async function handleSubmit() { const date = new Date(); + if (!token) { + return; + } axios - .post(`${process.env.REACT_APP_AWS_BACKEND_URL}/items`, { - image: newAddedItem.image, - type: newAddedItem.type, - islost: newAddedItem.islost, - name: newAddedItem.name, - description: newAddedItem.description, - email: user.email, - location: [position.lat, position.lng], - itemdate: newAddedItem.itemdate, - date: date.toISOString(), - isresolved: newAddedItem.isresolved, - ishelped: newAddedItem.ishelped, - }) + .post( + `${process.env.REACT_APP_AWS_BACKEND_URL}/items`, + { + image: newAddedItem.image, + type: newAddedItem.type, + islost: newAddedItem.islost, + name: newAddedItem.name, + description: newAddedItem.description, + email: user.email, + location: [position.lat, position.lng], + itemdate: newAddedItem.itemdate, + date: date.toISOString(), + isresolved: newAddedItem.isresolved, + ishelped: newAddedItem.ishelped, + }, + { + headers: { + Authorization: `Bearer ${token}`, // verify auth + }, + } + ) .then((item) => { const newItem = { image: newAddedItem.image, @@ -186,10 +207,18 @@ export default function Map({ // Update the leaderboard const pointsToAdd = newAddedItem.islost ? 1 : 3; - axios.put(`${process.env.REACT_APP_AWS_BACKEND_URL}/leaderboard`, { - email: user.email, - pointsToAdd: pointsToAdd, - }); + axios.put( + `${process.env.REACT_APP_AWS_BACKEND_URL}/leaderboard`, + { + email: user.email, + pointsToAdd: pointsToAdd, + }, + { + headers: { + Authorization: `Bearer ${token}`, // verify auth + }, + } + ); setLeaderboard((prev) => prev.map((u) => diff --git a/src/components/Map/MapIcons.js b/src/components/Map/MapIcons.js index 582980a..5a35815 100644 --- a/src/components/Map/MapIcons.js +++ b/src/components/Map/MapIcons.js @@ -20,74 +20,74 @@ import fly_img from "../../assets/images/fly_img.png"; const resolvedIcon = L.icon({ iconUrl: resolved, - iconSize: [50, 50], - iconAnchor: [25, 40], + iconSize: [40, 40], + iconAnchor: [20, 30], }); const headphoneLost = L.icon({ iconUrl: headphone_lost, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); const headphoneFound = L.icon({ iconUrl: headphone_found, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); const phoneLost = L.icon({ iconUrl: phone_lost, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); const phoneFound = L.icon({ iconUrl: phone_found, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); const keyLost = L.icon({ iconUrl: key_lost, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); const keyFound = L.icon({ iconUrl: key_found, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); const walletLost = L.icon({ iconUrl: wallet_lost, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); const walletFound = L.icon({ iconUrl: wallet_found, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); const othersLost = L.icon({ iconUrl: others_lost, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); const othersFound = L.icon({ iconUrl: others_found, - iconSize: [60, 60], - iconAnchor: [30, 50], + iconSize: [50, 50], + iconAnchor: [25, 40], }); export const othersDrag = L.icon({ iconUrl: others_black, - iconSize: [50, 50], - iconAnchor: [25, 30], + iconSize: [40, 40], + iconAnchor: [25, 25], }); export const flyImg = L.icon({ diff --git a/src/components/ResultsBar/ResultsBar.jsx b/src/components/ResultsBar/ResultsBar.jsx index b2eee54..6507e22 100644 --- a/src/components/ResultsBar/ResultsBar.jsx +++ b/src/components/ResultsBar/ResultsBar.jsx @@ -24,10 +24,10 @@ export default function ResultsBar({ findFilter.isFound === !item.islost) && (findFilter.type === "everything" || findFilter.type === item.type) && (findFilter.uploadDate === "" || - !item.itemdate || (item.itemdate && item.itemdate.includes(findFilter.uploadDate))) && (!findFilter.isYourPosts || - (findFilter.isYourPosts && item.email === user.email)) + (findFilter.isYourPosts && item.email === user.email)) && + (findFilter.isShowReturned || !item.isresolved) ); }) .map((item) => { diff --git a/src/components/Type/TypeCard.jsx b/src/components/Type/TypeCard.jsx index 977a017..ec08fd6 100644 --- a/src/components/Type/TypeCard.jsx +++ b/src/components/Type/TypeCard.jsx @@ -19,15 +19,20 @@ export default function TypeCard({ backgroundColor={newAddedItem.type === type ? "#787092" : "white"} variant="outline" border="5px rgb(166, 152, 216) solid" - w={{ md: "7vw", base: "13vh" }} - h={{ md: "7vw", base: "13vh" }} + minW={{ md: "7vw", base: "13vh" }} + minH={{ md: "7vw", base: "13vh" }} borderRadius="20px" alignItems={"center"} justifyContent={"center"} flexDir={"column"} onClick={handleOnClick} > - + {type.toUpperCase()} { + ReactGA.pageview(window.location.pathname); + }, []); + window.onresize = () => { setScreenWidth(window.screen.width); }; diff --git a/src/context/DataContext.js b/src/context/DataContext.js index 0d090b2..6918ee0 100644 --- a/src/context/DataContext.js +++ b/src/context/DataContext.js @@ -2,6 +2,7 @@ import { createContext } from "react"; const DataContext = createContext({ data: [], + token: "", isLoginModalOpen: false, onLoginModalOpen: () => {}, onLoginModalClose: () => {},