diff --git a/tracknow/web/src/Types.ts b/tracknow/web/src/Types.ts index 1f87bbb..d20b218 100644 --- a/tracknow/web/src/Types.ts +++ b/tracknow/web/src/Types.ts @@ -143,3 +143,16 @@ export interface YoutubeSearchResult { publishTime: string; }; } + +// helper functions +export const mapToLaptime = (response: GetUserLaptimesResponse): Laptime => ({ + title: response.title, + car: response.car, + track: response.track, + time: response.time, + simracing: response.simracing, + platform: response.platform, + youtube_link: response.youtube_link, + comment: response.comment, + image: response.image, +}); diff --git a/tracknow/web/src/components/Post/MediaCarousel.tsx b/tracknow/web/src/components/Post/MediaCarousel.tsx index 084322a..4b80f0b 100644 --- a/tracknow/web/src/components/Post/MediaCarousel.tsx +++ b/tracknow/web/src/components/Post/MediaCarousel.tsx @@ -1,7 +1,7 @@ import { ChevronLeftIcon, ChevronRightIcon } from "@chakra-ui/icons"; import * as React from "react"; import miscFunctions from "../../misc/miscFunctions"; -import { Box, Button, Spinner, Text } from "@chakra-ui/react"; +import { Box, Button, Text } from "@chakra-ui/react"; import { LoadingSpinner } from "../Loading/LoadingSpinner"; type MediaItem = { @@ -59,11 +59,7 @@ const MediaCarousel: React.FC = ({ }, [currentIndex, mediaItems]); if (mediaItems.length === 0) { - return ( - - No media items available. - - ); + return ; } const currentItem = mediaItems[currentIndex]; diff --git a/tracknow/web/src/components/Post/Post.tsx b/tracknow/web/src/components/Post/Post.tsx index 49e0cbe..6730e9e 100644 --- a/tracknow/web/src/components/Post/Post.tsx +++ b/tracknow/web/src/components/Post/Post.tsx @@ -10,16 +10,36 @@ import { Center, Divider, Button, + useToast, + AlertDialog, + AlertDialogBody, + AlertDialogFooter, + AlertDialogContent, + AlertDialogOverlay, + Modal, + ModalOverlay, + ModalContent, + ModalHeader, + ModalFooter, + ModalBody, + ModalCloseButton, + Input, + Textarea, + FormControl, + FormLabel, + useDisclosure, } from "@chakra-ui/react"; import { ArrowBackIcon } from "@chakra-ui/icons"; -import { GetUserLaptimesResponse } from "../../Types"; +import { GetUserLaptimesResponse, mapToLaptime, Laptime } from "../../Types"; import { RiComputerLine, RiMapPinLine, RiTimerFlashLine } from "react-icons/ri"; import { FaCar } from "react-icons/fa"; +import { MdEdit, MdDelete } from "react-icons/md"; import { LoadingSpinner } from "../Loading/LoadingSpinner"; import { BeatLoader } from "react-spinners"; import miscFunctions from "../../misc/miscFunctions"; import { useLaptimes } from "../../hooks/useLaptimes"; +import { useUsers } from "../../hooks/useUsers"; import { useParams, useNavigate } from "react-router-dom"; import { Link as ReactRouterLink } from "react-router-dom"; import MediaCarousel from "./MediaCarousel"; @@ -318,15 +338,36 @@ export const HomePost: React.FC = ({ }; export const SelectedPost: React.FC = () => { - const { fetchAUserLaptime } = useLaptimes(); + const { fetchAUserLaptime, editLaptime, deleteLaptime } = useLaptimes(); + const { userId } = useUsers(); const { id, user_id } = useParams<{ id: string; user_id: string }>(); const [laptime, setLaptime] = React.useState( null, ); + const isOwner = userId === Number(user_id); // check if user is the owner of post const navigate = useNavigate(); + const Toast = useToast(); const { formatTimeAgo } = miscFunctions(); + const { + isOpen: isDeleteOpen, + onOpen: onDeleteOpen, + onClose: onDeleteClose, + } = useDisclosure(); + const { + isOpen: isEditOpen, + onOpen: onEditOpen, + onClose: onEditClose, + } = useDisclosure(); + const [editData, setEditData] = React.useState({ + title: "", + simracing: true, + comment: "", + }); + const [isLoading, setIsLoading] = React.useState(false); + const cancelRef = React.useRef(null); + React.useEffect(() => { const fetchData = async () => { try { @@ -341,7 +382,47 @@ export const SelectedPost: React.FC = () => { }; fetchData(); - }, [id, user_id, fetchAUserLaptime]); + }); + + const handleEdit = () => { + if (laptime) { + setEditData(mapToLaptime(laptime)); + onEditOpen(); + } + }; + + const handleDelete = async () => { + try { + await deleteLaptime(Number(id)); + Toast({ title: "Post deleted successfully.", status: "success" }); + navigate(-1); // Navigate back after deletion + } catch (error) { + Toast({ title: "Failed to delete post.", status: "error" }); + } + }; + + const handleEditChange = ( + e: React.ChangeEvent, + ) => { + setEditData((prev) => ({ ...prev, [e.target.name]: e.target.value })); + }; + + const saveChanges = async () => { + setIsLoading(true); + try { + await editLaptime(Number(id), editData); + Toast({ title: "Post edited successfully.", status: "success" }); + + // Wait 500 milliseconds before reloading + await new Promise((resolve) => setTimeout(resolve, 500)); + + navigate(0); + } catch (error) { + Toast({ title: "Failed to edit post.", status: "error" }); + } finally { + setIsLoading(false); + } + }; if (!laptime) { return ; @@ -495,37 +576,106 @@ export const SelectedPost: React.FC = () => { )} + {/* user can edit/delete his own posts*/} + {isOwner && ( + + + + + + {/* Edit Modal */} + + + + Edit Laptime + + + + Title + + + + Body + {/*Fix: Max length doesnt work */} +