From 4018031c26e904c246cdbfe19c256efd4cf403c8 Mon Sep 17 00:00:00 2001 From: is2ac2 <162246799+is2ac2@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:04:09 -0400 Subject: [PATCH 1/3] Email Verification Aesthetics (#103) * Email Vefication Aesthetics * formatting --- .../src/components/auth/EmailAuthComponent.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/auth/EmailAuthComponent.tsx b/frontend/src/components/auth/EmailAuthComponent.tsx index 42305bde..19243f37 100644 --- a/frontend/src/components/auth/EmailAuthComponent.tsx +++ b/frontend/src/components/auth/EmailAuthComponent.tsx @@ -13,6 +13,7 @@ import { Row, Tooltip, } from "react-bootstrap"; +import { CheckCircle } from "react-bootstrap-icons"; const isValidEmail = (email: string) => { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); @@ -52,12 +53,17 @@ const EmailAuthComponent = () => { - -

Success!

+ + +

+ Verification Email Sent! +

- - Check your email for a login link. + + + Check your email for a login link. + From b5993e50024de40ee1d2a86a40acc4faff07cb8e Mon Sep 17 00:00:00 2001 From: is2ac2 <162246799+is2ac2@users.noreply.github.com> Date: Tue, 11 Jun 2024 20:26:18 -0400 Subject: [PATCH 2/3] Homepage UI changes (#105) * ui changes * homepage ui styling * formatting --- frontend/src/App.css | 17 ++++++++++- frontend/src/hooks/theme.tsx | 11 ++++++- frontend/src/pages/Home.tsx | 47 ++++++++++++++++++++--------- frontend/src/pages/RobotDetails.tsx | 10 +++--- 4 files changed, 64 insertions(+), 21 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index d9641484..5576247f 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -11,5 +11,20 @@ div.content { html, body { - transition: color 0.4s ease; + transition: color 0.1s ease; } + +/* .custom-button { + color: var(--button-color); + background-color: var(--button-bg-color); + border-color: var(--button-border-color); + padding: 10px; + width: 100%; + transition: all 0.3s ease; +} + +.custom-button:hover { + color: var(--button-hover-color); + background-color: var(--button-hover-bg-color); + border-color: var(--button-hover-border-color); +} */ diff --git a/frontend/src/hooks/theme.tsx b/frontend/src/hooks/theme.tsx index 74f0521b..f18abd07 100644 --- a/frontend/src/hooks/theme.tsx +++ b/frontend/src/hooks/theme.tsx @@ -13,16 +13,25 @@ const THEME_KEY = "__THEME"; interface ThemeColors { backgroundColor: string; color: string; + buttonBorder: string; + buttonHover: string; + text_color: string; } const COLORS: { [key in Theme]: ThemeColors } = { light: { backgroundColor: "#ffffff", color: "#201a42", + buttonBorder: "#0D6EFD", + buttonHover: "#0D6EFD", + text_color: "#f5f2ef", }, dark: { backgroundColor: "#000000", color: "#f5f2ef", + buttonBorder: "#8AB9FE", + buttonHover: "#0D6EFD", + text_color: "#201a42", }, }; @@ -66,7 +75,7 @@ export const ThemeProvider = (props: ThemeProviderProps) => { document.body.classList.toggle("light-mode", theme === "light"); document.body.style.backgroundColor = COLORS[theme].backgroundColor; document.body.style.color = COLORS[theme].color; - }, [theme]); + }, [theme, COLORS]); return ( { +const Home: React.FC = () => { + const { theme, colors } = useTheme(); const navigate = useNavigate(); const { isAuthenticated } = useAuthentication(); + const [isHoveredR, setIsHoveredR] = useState(false); + + const handleMouseEnterR = () => setIsHoveredR(true); + const handleMouseLeaveR = () => setIsHoveredR(false); + + const [isHoveredL, setIsHoveredL] = useState(false); + + const handleMouseEnterL = () => setIsHoveredL(true); + const handleMouseLeaveL = () => setIsHoveredL(false); + return (
@@ -16,15 +29,15 @@ const Home = () => { navigate(`/robots`)}> - Robots - Buy and sell robot + Browse Robots + Buy and sell robots navigate(`/parts`)}> - Parts + Browse Parts Buy and sell robot parts @@ -34,11 +47,11 @@ const Home = () => { + + + ))} + + + + Submit: + + + + + + ); +}; + +export default EditPartForm; diff --git a/frontend/src/pages/PartDetails.tsx b/frontend/src/pages/PartDetails.tsx index 6edacb68..56f7216e 100644 --- a/frontend/src/pages/PartDetails.tsx +++ b/frontend/src/pages/PartDetails.tsx @@ -27,16 +27,21 @@ const PartDetails = () => { const { addAlert } = useAlertQueue(); const auth = useAuthentication(); const auth_api = new api(auth.api); + const [userId, setUserId] = useState(null); const { id } = useParams(); const [show, setShow] = useState(false); const [ownerEmail, setOwnerEmail] = useState(null); const [part, setPart] = useState(null); const [imageIndex, setImageIndex] = useState(0); const [error, setError] = useState(null); + const [showDelete, setShowDelete] = useState(false); const handleClose = () => setShow(false); const handleShow = () => setShow(true); + const handleShowDelete = () => setShowDelete(true); + const handleCloseDelete = () => setShowDelete(false); + useEffect(() => { const fetchPart = async () => { try { @@ -57,6 +62,24 @@ const PartDetails = () => { const navigate = useNavigate(); + useEffect(() => { + if (auth.isAuthenticated) { + try { + const fetchUserId = async () => { + const user_id = await auth_api.currentUser(); + setUserId(user_id); + }; + fetchUserId(); + } catch (err) { + if (err instanceof Error) { + setError(err.message); + } else { + setError("An unexpected error occurred"); + } + } + } + }, [auth.isAuthenticated]); + useEffect(() => { if (error) { addAlert(error, "error"); @@ -219,6 +242,81 @@ const PartDetails = () => { + <> + {part.owner === userId && ( + <> + + + + + + + + + + + + Are you sure you want to delete this part? + + + + + + + + + )} + ); }; diff --git a/frontend/src/pages/PartForm.tsx b/frontend/src/pages/PartForm.tsx index 4299054a..02d2bdf1 100644 --- a/frontend/src/pages/PartForm.tsx +++ b/frontend/src/pages/PartForm.tsx @@ -2,6 +2,7 @@ import { api, Image, Part } from "hooks/api"; import { useAuthentication } from "hooks/auth"; import React, { ChangeEvent, FormEvent, useState } from "react"; import { Button, Col, Form, Row } from "react-bootstrap"; +import { useNavigate } from "react-router-dom"; const PartForm: React.FC = () => { const auth = useAuthentication(); @@ -29,7 +30,7 @@ const PartForm: React.FC = () => { const newImages = part_images.filter((_, i) => i !== index); setImages(newImages); }; - + const navigate = useNavigate(); const handleSubmit = async (event: FormEvent) => { event.preventDefault(); if (part_images.length === 0) { @@ -46,6 +47,7 @@ const PartForm: React.FC = () => { try { await auth_api.addPart(newFormData); setMessage(`Part added successfully.`); + navigate("/parts/your/"); } catch (error) { setMessage("Error adding Part "); } diff --git a/frontend/src/pages/RobotDetails.tsx b/frontend/src/pages/RobotDetails.tsx index 81c4357f..45e1d868 100644 --- a/frontend/src/pages/RobotDetails.tsx +++ b/frontend/src/pages/RobotDetails.tsx @@ -80,7 +80,6 @@ const RobotDetails = () => { }; fetchRobot(); }, [id]); - useEffect(() => { if (auth.isAuthenticated) { try { @@ -343,7 +342,7 @@ const RobotDetails = () => { variant="danger" onClick={async () => { await auth_api.deleteRobot(id); - navigate(`/robots/`); + navigate(`/robots/your/`); }} > Delete Robot diff --git a/frontend/src/pages/YourParts.tsx b/frontend/src/pages/YourParts.tsx index 3871897c..ac71e22f 100644 --- a/frontend/src/pages/YourParts.tsx +++ b/frontend/src/pages/YourParts.tsx @@ -60,7 +60,7 @@ const YourParts = () => { <> navigate("/")}>Home - Parts + Your Parts diff --git a/store/app/crud/robots.py b/store/app/crud/robots.py index f01ff15c..d89ad875 100644 --- a/store/app/crud/robots.py +++ b/store/app/crud/robots.py @@ -56,6 +56,10 @@ async def delete_robot(self, robot_id: str) -> None: table = await self.db.Table("Robots") await table.delete_item(Key={"robot_id": robot_id}) + async def update_part(self, part_id: str, part: Part) -> None: + await self.delete_part(part_id) + await self.add_part(part) + async def update_robot(self, robot_id: str, robot: Robot) -> None: await self.delete_robot(robot_id) await self.add_robot(robot) diff --git a/store/app/routers/part.py b/store/app/routers/part.py index 4ea6c4e9..9f55a70e 100644 --- a/store/app/routers/part.py +++ b/store/app/routers/part.py @@ -12,7 +12,6 @@ parts_router = APIRouter() - logger = logging.getLogger(__name__) @@ -28,7 +27,7 @@ async def list_your_parts( try: user_id = await crud.get_user_id_from_api_key(data.api_key) if user_id is None: - raise HTTPException(status_code=401, detail="Must be logged in to view your robots") + raise HTTPException(status_code=401, detail="Must be logged in to view your parts") total = await crud.list_parts() user_parts = [part for part in total if str(part.owner) == str(user_id)] return user_parts @@ -41,6 +40,14 @@ async def get_part(part_id: str, crud: Annotated[Crud, Depends(Crud.get)]) -> Pa return await crud.get_part(part_id) +@parts_router.get("/user/") +async def current_user( + crud: Annotated[Crud, Depends(Crud.get)], data: Annotated[ApiKeyData, Depends(get_api_key)] +) -> str | None: + user_id = await crud.get_user_id_from_api_key(data.api_key) + return str(user_id) + + @parts_router.post("/add/") async def add_part( part: Part, @@ -66,7 +73,23 @@ async def delete_part( if part is None: raise HTTPException(status_code=404, detail="Part not found") user_id = await crud.get_user_id_from_api_key(data.api_key) - if part.owner != user_id: + if str(part.owner) != str(user_id): raise HTTPException(status_code=403, detail="You do not own this part") await crud.delete_part(part_id) return True + + +@parts_router.post("/edit-part/{part_id}/") +async def edit_part( + part_id: str, + part: Part, + data: Annotated[ApiKeyData, Depends(get_api_key)], + crud: Annotated[Crud, Depends(Crud.get)], +) -> bool: + user_id = await crud.get_user_id_from_api_key(data.api_key) + if user_id is None: + raise HTTPException(status_code=401, detail="Must be logged in to edit a part") + part.owner = str(user_id) + part.part_id = part_id + await crud.update_part(part_id, part) + return True