From a9e41ba5eaaa25a1a1c68acc798ee33f3db9a8e0 Mon Sep 17 00:00:00 2001 From: Abhik Ray Date: Tue, 3 Dec 2024 20:06:47 -0500 Subject: [PATCH 1/3] review page styling --- frontend/app/(tabs)/search.tsx | 187 ++- frontend/app/ReviewPage.tsx | 1485 ++++++++--------- .../components/search/AlbumSearchCard.tsx | 195 +-- frontend/components/search/SearchResults.tsx | 310 ++-- 4 files changed, 1047 insertions(+), 1130 deletions(-) diff --git a/frontend/app/(tabs)/search.tsx b/frontend/app/(tabs)/search.tsx index 83038687..62b90372 100644 --- a/frontend/app/(tabs)/search.tsx +++ b/frontend/app/(tabs)/search.tsx @@ -1,111 +1,110 @@ -import React, { useState, useEffect } from "react"; -import { StyleSheet, View, ScrollView } from "react-native"; -import SearchBar from "@/components/search/SearchBar"; -import SearchResults from "@/components/search/SearchResults"; -import TopAlbums from "@/components/search/TopAlbums"; -import TopSongs from "@/components/search/TopSongs"; -import TopReviews from "@/components/search/TopReviews"; -import Profiles from "@/components/search/Profiles"; -import axios from "axios"; +import React, { useState, useEffect } from 'react'; +import { StyleSheet, View, ScrollView } from 'react-native'; +import SearchBar from '@/components/search/SearchBar'; +import SearchResults from '@/components/search/SearchResults'; +import TopAlbums from '@/components/search/TopAlbums'; +import TopSongs from '@/components/search/TopSongs'; +import TopReviews from '@/components/search/TopReviews'; +import Profiles from '@/components/search/Profiles'; +import axios from 'axios'; const SearchPage: React.FC = () => { - const [searchResults, setSearchResults] = useState<{ - songs: Media[]; - albums: Media[]; - profiles: UserProfile[]; - }>({ - songs: [], - albums: [], - profiles: [], - }); - const [isSearchActive, setIsSearchActive] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [initialSongs, setInitialSongs] = useState([]); - const [initialAlbums, setInitialAlbums] = useState([]); - const [initialReviews, setInitialReviews] = useState([]); + const [searchResults, setSearchResults] = useState<{ + songs: Media[]; + albums: Media[]; + profiles: UserProfile[]; + }>({ + songs: [], + albums: [], + profiles: [], + }); + const [isSearchActive, setIsSearchActive] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [initialSongs, setInitialSongs] = useState([]); + const [initialAlbums, setInitialAlbums] = useState([]); + const [initialReviews, setInitialReviews] = useState([]); - const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; + const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; - // Fetch initial top songs and albums - useEffect(() => { - axios - .get(`${BASE_URL}/media?sort=review&type=album`) - .then((response) => setInitialAlbums(response.data)) - .catch((error) => console.error(error)); + // Fetch initial top songs and albums + useEffect(() => { + axios + .get(`${BASE_URL}/media?sort=review&type=album`) + .then((response) => setInitialAlbums(response.data)) + .catch((error) => console.error(error)); - axios - .get(`${BASE_URL}/media?sort=review&type=track`) - .then((response) => setInitialSongs(response.data)) - .catch((error) => console.error(error)); + axios + .get(`${BASE_URL}/media?sort=review&type=track&limit=13`) + .then((response) => setInitialSongs(response.data)) + .catch((error) => console.error(error)); - axios - .get(`${BASE_URL}/reviews/popular`) - .then((response) => setInitialReviews(response.data)) - .catch((error) => console.error(error)); - }, []); + axios + .get(`${BASE_URL}/reviews/popular`) + .then((response) => setInitialReviews(response.data)) + .catch((error) => console.error(error)); + }, []); - const handleSearch = async (query: string) => { - if (!query.trim()) { - setSearchResults({ songs: [], albums: [], profiles: [] }); - setIsSearchActive(false); - return; - } + const handleSearch = async (query: string) => { + if (!query.trim()) { + setSearchResults({ songs: [], albums: [], profiles: [] }); + setIsSearchActive(false); + return; + } - setIsLoading(true); - try { - const [songsResponse, albumsResponse, profilesResponse] = - await Promise.all([ - axios.get(`${BASE_URL}/media/${query}?media_type=track`), - axios.get(`${BASE_URL}/media/${query}?media_type=album`), - axios.get(`${BASE_URL}/users/profile/name/${query}`), - ]); + setIsLoading(true); + try { + const [songsResponse, albumsResponse, profilesResponse] = await Promise.all([ + axios.get(`${BASE_URL}/media/${query}?media_type=track`), + axios.get(`${BASE_URL}/media/${query}?media_type=album`), + axios.get(`${BASE_URL}/users/profile/name/${query}`), + ]); - setSearchResults({ - songs: songsResponse.data ?? [], - albums: albumsResponse.data ?? [], - profiles: profilesResponse.data ?? [], - }); - setIsSearchActive(true); - } catch (error) { - console.error("Search error:", error); - setSearchResults({ songs: [], albums: [], profiles: [] }); - } finally { - setIsLoading(false); - } - }; + setSearchResults({ + songs: songsResponse.data ?? [], + albums: albumsResponse.data ?? [], + profiles: profilesResponse.data ?? [], + }); + setIsSearchActive(true); + } catch (error) { + console.error('Search error:', error); + setSearchResults({ songs: [], albums: [], profiles: [] }); + } finally { + setIsLoading(false); + } + }; - return ( - - + return ( + + - - {isSearchActive ? ( - - ) : ( - - - - - - )} - - - ); + + {isSearchActive ? ( + + ) : ( + + + + + + )} + + + ); }; const styles = StyleSheet.create({ - container: { - fontFamily: "NeueHaasUnicaPro-Regular", - flex: 1, - paddingTop: 80, - backgroundColor: "#fff", - }, + container: { + fontFamily: 'NeueHaasUnicaPro-Regular', + flex: 1, + paddingTop: 80, + backgroundColor: '#fff', + }, }); export default SearchPage; diff --git a/frontend/app/ReviewPage.tsx b/frontend/app/ReviewPage.tsx index 83aabf62..5c6ba301 100644 --- a/frontend/app/ReviewPage.tsx +++ b/frontend/app/ReviewPage.tsx @@ -1,775 +1,734 @@ -import HeaderComponent from "@/components/HeaderComponent"; -import CommentComponent from "@/components/CommentComponent"; -import axios from "axios"; -import { router, useLocalSearchParams } from "expo-router"; -import React, { useEffect, useState } from "react"; +import HeaderComponent from '@/components/HeaderComponent'; +import CommentComponent from '@/components/CommentComponent'; +import axios from 'axios'; +import { router, useLocalSearchParams } from 'expo-router'; +import React, { useEffect, useState } from 'react'; import { - View, - Text, - StyleSheet, - Image, - ScrollView, - TouchableOpacity, - TextInput, - Modal, - KeyboardAvoidingView, -} from "react-native"; -import Rating0 from "@/assets/images/Ratings/Radial-0.svg"; -import Rating1 from "@/assets/images/Ratings/Radial-1.svg"; -import Rating2 from "@/assets/images/Ratings/Radial-2.svg"; -import Rating3 from "@/assets/images/Ratings/Radial-3.svg"; -import Rating4 from "@/assets/images/Ratings/Radial-4.svg"; -import Rating5 from "@/assets/images/Ratings/Radial-5.svg"; -import Rating6 from "@/assets/images/Ratings/Radial-6.svg"; -import Rating7 from "@/assets/images/Ratings/Radial-7.svg"; -import Rating8 from "@/assets/images/Ratings/Radial-8.svg"; -import Rating9 from "@/assets/images/Ratings/Radial-9.svg"; -import Rating10 from "@/assets/images/Ratings/Radial-10.svg"; -import Downvote from "@/assets/images/ReviewPreview/downvote.svg"; -import Upvote from "@/assets/images/ReviewPreview/upvote.svg"; -import Comment from "@/assets/images/ReviewPreview/comment.svg"; -import Share from "@/assets/images/ReviewPreview/share.svg"; -import Upload from "@/assets/images/Icons/Upload.svg"; -import { useAuthContext } from "@/components/AuthProvider"; + View, + Text, + StyleSheet, + Image, + ScrollView, + TouchableOpacity, + TextInput, + Modal, + KeyboardAvoidingView, +} from 'react-native'; +import Rating0 from '@/assets/images/Ratings/Radial-0.svg'; +import Rating1 from '@/assets/images/Ratings/Radial-1.svg'; +import Rating2 from '@/assets/images/Ratings/Radial-2.svg'; +import Rating3 from '@/assets/images/Ratings/Radial-3.svg'; +import Rating4 from '@/assets/images/Ratings/Radial-4.svg'; +import Rating5 from '@/assets/images/Ratings/Radial-5.svg'; +import Rating6 from '@/assets/images/Ratings/Radial-6.svg'; +import Rating7 from '@/assets/images/Ratings/Radial-7.svg'; +import Rating8 from '@/assets/images/Ratings/Radial-8.svg'; +import Rating9 from '@/assets/images/Ratings/Radial-9.svg'; +import Rating10 from '@/assets/images/Ratings/Radial-10.svg'; +import Downvote from '@/assets/images/ReviewPreview/downvote.svg'; +import Upvote from '@/assets/images/ReviewPreview/upvote.svg'; +import Comment from '@/assets/images/ReviewPreview/comment.svg'; +import Share from '@/assets/images/ReviewPreview/share.svg'; +import Upload from '@/assets/images/Icons/Upload.svg'; +import { useAuthContext } from '@/components/AuthProvider'; interface ReviewPageProps { - route: { - params: { - review_id: string; - user_id: string; - }; - }; + route: { + params: { + review_id: string; + user_id: string; + }; + }; } const ReviewPage: React.FC = ({ route }) => { - const { review_id, user_id } = useLocalSearchParams<{ - review_id: string; - user_id: string; - }>(); - const [review, setReview] = useState(); - const [comments, setComments] = useState(); - const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; - const { userId } = useAuthContext(); - const MusicDisk = require("../assets/images/music-disk.png"); - - const [currentVote, setCurrentVote] = useState(false); // does a vote currently exist? - const [currentVoteValue, setCurrentVoteValue] = useState(false); // what is the current vote's value? - - const [upvoteCount, setUpvoteCount] = useState(0); - const [downvoteCount, setDownvoteCount] = useState(0); - const [commentCount, setCommentCount] = useState(0); - const [newComment, setNewComment] = useState(""); - - const [isEditable, setIsEditable] = useState(false); - const [editedComment, setEditedComment] = useState(""); - const [showPopup, setShowPopup] = useState(false); - - const ratingImages = { - 0: Rating0, - 1: Rating1, - 2: Rating2, - 3: Rating3, - 4: Rating4, - 5: Rating5, - 6: Rating6, - 7: Rating7, - 8: Rating8, - 9: Rating9, - 10: Rating10, - }; - - const [sharePopupVisible, setSharePopupVisible] = useState(false); - - const handleSharePress = () => { - setSharePopupVisible(true); // Show the share popup - }; - - const closeSharePopup = () => { - setSharePopupVisible(false); // Close the share popup - }; - - const getRatingImage = (rating: keyof typeof ratingImages) => { - return ratingImages[rating]; // Access the image from the preloaded images object - }; - - const handleVotePress = async (newVoteValue: boolean) => { - if (currentVote) { - // if there is already a vote value, we have to delete or swap it - if (currentVoteValue && newVoteValue) { - // if there is an upvote and the user clicks upvote again - setCurrentVote(false); // cancel out the vote - setUpvoteCount(upvoteCount - 1); - } else if (!currentVoteValue && !newVoteValue) { - // if there is a downvote and the user clicks downvote again - setCurrentVote(false); // cancel out the vote - setDownvoteCount(downvoteCount - 1); - } else if (currentVoteValue && !newVoteValue) { - // if there is an upvote and the user clicks downvote - setCurrentVoteValue(false); - setUpvoteCount(upvoteCount - 1); - setDownvoteCount(downvoteCount + 1); - } else if (!currentVoteValue && newVoteValue) { - // if there is a downvote and the user clicks upvote - setCurrentVoteValue(true); - setUpvoteCount(upvoteCount + 1); - setDownvoteCount(downvoteCount - 1); - } - } else { - setCurrentVote(true); - setCurrentVoteValue(newVoteValue); - if (newVoteValue) { - setUpvoteCount(upvoteCount + 1); - } else { - setDownvoteCount(downvoteCount + 1); - } - } - - try { - await axios.post(`${BASE_URL}/reviews/vote`, { - user_id: userId, - post_id: review_id, - upvote: newVoteValue, - }); - } catch (error) { - console.error("Error downvoting comment:", error); - } - }; - - const handleCommentPress = () => { - console.log("comment icon pressed"); - }; - - const handleCommentSubmit = async () => { - if (!newComment.trim()) return; // Do not submit if the comment is empty - setCommentCount(commentCount + 1); - - try { - await axios.post( - `${BASE_URL}/reviews/comment`, - { - user_id: userId, - review_id: parseInt(review_id, 10), - text: newComment, - }, - { - headers: { - "Content-Type": "application/json", - }, - }, - ); - setNewComment(""); // Clear the input after submitting - // Fetch updated comments after submitting - fetchComments(); - } catch (error) { - console.error("Error submitting comment:", error); - } - }; - - const handleEditSave = async () => { - try { - const requestBody = { - user_id: userId, // User ID to validate ownership - comment: editedComment, // The updated comment - }; - - await axios.patch(`${BASE_URL}/reviews/${review_id}`, requestBody); - setIsEditable(false); - setReview((prev) => (prev ? { ...prev, comment: editedComment } : prev)); - } catch (error) { - console.error("Error saving edited review:", error); - } - }; - - const handleMenuOption = (option: string) => { - setShowPopup(false); - if (option === "edit") { - setIsEditable(true); - setEditedComment(review?.comment || ""); - } else if (option === "delete") { - // Add delete functionality - } else if (option === "manageComments") { - // Add manage comments functionality - } else if (option === "share") { - // Add share functionality - } - }; - - // Fetch the review data using the review_id - useEffect(() => { - const fetchReview = async () => { - try { - const response = await axios.get(`${BASE_URL}/reviews/${review_id}`); - const review = response.data; - setReview(review); - if (review) { - setUpvoteCount(review.review_stat.upvotes); - setDownvoteCount(review.review_stat.downvotes); - setCommentCount(review.review_stat.comment_count); - } - } catch (error) { - console.error("Error fetching review:", error); - } - }; - - fetchReview(); - fetchComments(); - }, [review_id, userId, user_id, newComment]); - - const fetchComments = async () => { - try { - const response = await axios.get( - `${BASE_URL}/reviews/comments/${review_id}`, - ); - setComments(response.data); - } catch (error) { - console.error("Error fetching comments:", error); - } - }; - - useEffect(() => { - const fetchVote = async () => { - try { - if (review) { - setUpvoteCount(review.review_stat.upvotes); - setDownvoteCount(review.review_stat.downvotes); - setCommentCount(review.review_stat.comment_count); - } - const response = await axios.get( - `${BASE_URL}/reviews/vote/${userId}/${review_id}`, - ); - if (response.data) { - setCurrentVote(true); - const { upvote } = response.data; // Assuming the API returns { user_id, post_id, upvote } - setCurrentVoteValue(upvote); - } else { - setCurrentVote(false); - } - } catch (error) { - console.error("Error fetching vote:", error); - } - }; - fetchVote(); - }, [review_id, userId, newComment]); - - const handleUserPress = () => { - // Navigate to the UserPage when the user is clicked - const pathName = - review?.user_id === userId ? "/(tabs)/profile" : "/(tabs)/user"; - router.push({ - pathname: pathName, - params: { - userId: review?.user_id, - }, - }); - }; - - const handleMediaPress = () => { - // Navigate to the MediaPage - console.log("Media pressed"); - router.push({ - pathname: "/MediaPage", - params: { mediaId: review?.media_id, mediaType: review?.media_type }, - }); - }; - - return review ? ( - - - - - - - - - - {review.display_name} - @{review.username} - - - - - - - {review.media_cover && ( - - )} - - - - - - - - {review.media_title} - - {review.media_artist} - - - - - {React.createElement( - getRatingImage(review.rating as keyof typeof ratingImages), - { - width: 150, // Adjust size as needed - height: 150, - }, - { - style: styles.ratingImage, - } as any, - )} - - - setShowPopup(false)} - activeOpacity={1} // Prevent the modal itself from closing on tap - > - - handleMenuOption("edit")} - > - Edit Review - - handleMenuOption("manageComments")} - > - Manage Comments - - handleMenuOption("share")} - > - Share - - handleMenuOption("delete")} - > - Delete - - - - - - - - Share This Review - console.log("Share to Friends Pressed")} - > - Share to Friends - - - - - {isEditable ? ( - - - - Save - - - ) : ( - {review.comment} - )} - {/* Tags Section */} - {review.tags && review.tags.length > 0 && ( - - {review.tags.map((tag, index) => ( - - {tag} - - ))} - - )} - {/* Action Buttons */} - - - handleVotePress(true)} - style={styles.voteButton} - > - - {upvoteCount} - - handleVotePress(false)} - style={styles.voteButton} - > - - {downvoteCount} - - - - - {review.review_stat.comment_count} - - - - - - - {review.user_id === userId && ( - setShowPopup(true)} - > - - - )} - - - {comments && comments.length > 0 ? ( - comments.map((comment, index) => { - return ; - }) - ) : ( - No comments found. - )} - - - - - - {/* Fixed TextBox for Comment */} - - - - - - - - - ) : ( - - Loading... - - ); + const { review_id, user_id } = useLocalSearchParams<{ + review_id: string; + user_id: string; + }>(); + const [review, setReview] = useState(); + const [comments, setComments] = useState(); + const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; + const { userId } = useAuthContext(); + const MusicDisk = require('../assets/images/music-disk.png'); + + const [currentVote, setCurrentVote] = useState(false); // does a vote currently exist? + const [currentVoteValue, setCurrentVoteValue] = useState(false); // what is the current vote's value? + + const [upvoteCount, setUpvoteCount] = useState(0); + const [downvoteCount, setDownvoteCount] = useState(0); + const [commentCount, setCommentCount] = useState(0); + const [newComment, setNewComment] = useState(''); + + const [isEditable, setIsEditable] = useState(false); + const [editedComment, setEditedComment] = useState(''); + const [showPopup, setShowPopup] = useState(false); + + const ratingImages = { + 0: Rating0, + 1: Rating1, + 2: Rating2, + 3: Rating3, + 4: Rating4, + 5: Rating5, + 6: Rating6, + 7: Rating7, + 8: Rating8, + 9: Rating9, + 10: Rating10, + }; + + const [sharePopupVisible, setSharePopupVisible] = useState(false); + + const handleSharePress = () => { + setSharePopupVisible(true); // Show the share popup + }; + + const closeSharePopup = () => { + setSharePopupVisible(false); // Close the share popup + }; + + const getRatingImage = (rating: keyof typeof ratingImages) => { + return ratingImages[rating]; // Access the image from the preloaded images object + }; + + const handleVotePress = async (newVoteValue: boolean) => { + if (currentVote) { + // if there is already a vote value, we have to delete or swap it + if (currentVoteValue && newVoteValue) { + // if there is an upvote and the user clicks upvote again + setCurrentVote(false); // cancel out the vote + setUpvoteCount(upvoteCount - 1); + } else if (!currentVoteValue && !newVoteValue) { + // if there is a downvote and the user clicks downvote again + setCurrentVote(false); // cancel out the vote + setDownvoteCount(downvoteCount - 1); + } else if (currentVoteValue && !newVoteValue) { + // if there is an upvote and the user clicks downvote + setCurrentVoteValue(false); + setUpvoteCount(upvoteCount - 1); + setDownvoteCount(downvoteCount + 1); + } else if (!currentVoteValue && newVoteValue) { + // if there is a downvote and the user clicks upvote + setCurrentVoteValue(true); + setUpvoteCount(upvoteCount + 1); + setDownvoteCount(downvoteCount - 1); + } + } else { + setCurrentVote(true); + setCurrentVoteValue(newVoteValue); + if (newVoteValue) { + setUpvoteCount(upvoteCount + 1); + } else { + setDownvoteCount(downvoteCount + 1); + } + } + + try { + await axios.post(`${BASE_URL}/reviews/vote`, { + user_id: userId, + post_id: review_id, + upvote: newVoteValue, + }); + } catch (error) { + console.error('Error downvoting comment:', error); + } + }; + + const handleCommentPress = () => { + console.log('comment icon pressed'); + }; + + const handleCommentSubmit = async () => { + if (!newComment.trim()) return; // Do not submit if the comment is empty + setCommentCount(commentCount + 1); + + try { + await axios.post( + `${BASE_URL}/reviews/comment`, + { + user_id: userId, + review_id: parseInt(review_id, 10), + text: newComment, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + setNewComment(''); // Clear the input after submitting + // Fetch updated comments after submitting + fetchComments(); + } catch (error) { + console.error('Error submitting comment:', error); + } + }; + + const handleEditSave = async () => { + try { + const requestBody = { + user_id: userId, // User ID to validate ownership + comment: editedComment, // The updated comment + }; + + await axios.patch(`${BASE_URL}/reviews/${review_id}`, requestBody); + setIsEditable(false); + setReview((prev) => (prev ? { ...prev, comment: editedComment } : prev)); + } catch (error) { + console.error('Error saving edited review:', error); + } + }; + + const handleMenuOption = (option: string) => { + setShowPopup(false); + if (option === 'edit') { + setIsEditable(true); + setEditedComment(review?.comment || ''); + } else if (option === 'delete') { + // Add delete functionality + } else if (option === 'manageComments') { + // Add manage comments functionality + } else if (option === 'share') { + // Add share functionality + } + }; + + // Fetch the review data using the review_id + useEffect(() => { + const fetchReview = async () => { + try { + const response = await axios.get(`${BASE_URL}/reviews/${review_id}`); + const review = response.data; + setReview(review); + if (review) { + setUpvoteCount(review.review_stat.upvotes); + setDownvoteCount(review.review_stat.downvotes); + setCommentCount(review.review_stat.comment_count); + } + } catch (error) { + console.error('Error fetching review:', error); + } + }; + + fetchReview(); + fetchComments(); + }, [review_id, userId, user_id, newComment]); + + const fetchComments = async () => { + try { + const response = await axios.get(`${BASE_URL}/reviews/comments/${review_id}`); + setComments(response.data); + } catch (error) { + console.error('Error fetching comments:', error); + } + }; + + useEffect(() => { + const fetchVote = async () => { + try { + if (review) { + setUpvoteCount(review.review_stat.upvotes); + setDownvoteCount(review.review_stat.downvotes); + setCommentCount(review.review_stat.comment_count); + } + const response = await axios.get(`${BASE_URL}/reviews/vote/${userId}/${review_id}`); + if (response.data) { + setCurrentVote(true); + const { upvote } = response.data; // Assuming the API returns { user_id, post_id, upvote } + setCurrentVoteValue(upvote); + } else { + setCurrentVote(false); + } + } catch (error) { + console.error('Error fetching vote:', error); + } + }; + fetchVote(); + }, [review_id, userId, newComment]); + + const handleUserPress = () => { + // Navigate to the UserPage when the user is clicked + const pathName = review?.user_id === userId ? '/(tabs)/profile' : '/(tabs)/user'; + router.push({ + pathname: pathName, + params: { + userId: review?.user_id, + }, + }); + }; + + const handleMediaPress = () => { + // Navigate to the MediaPage + console.log('Media pressed'); + router.push({ + pathname: '/MediaPage', + params: { mediaId: review?.media_id, mediaType: review?.media_type }, + }); + }; + + return review ? ( + + + + + + + + + + {review.display_name} + @{review.username} + + + + + + + {review.media_cover && ( + + )} + + + + + + + + {review.media_title} + + {review.media_artist} + + + + + {React.createElement( + getRatingImage(review.rating as keyof typeof ratingImages), + { + width: 150, // Adjust size as needed + height: 150, + }, + { + style: styles.ratingImage, + } as any, + )} + + + setShowPopup(false)} + activeOpacity={1} // Prevent the modal itself from closing on tap + > + + handleMenuOption('edit')}> + Edit Review + + handleMenuOption('manageComments')}> + Manage Comments + + handleMenuOption('share')}> + Share + + handleMenuOption('delete')}> + Delete + + + + + + + + Share This Review + console.log('Share to Friends Pressed')}> + Share to Friends + + + + + {isEditable ? ( + + + + Save + + + ) : ( + {review.comment} + )} + {/* Tags Section */} + {review.tags && review.tags.length > 0 && ( + + {review.tags.map((tag, index) => ( + + {tag} + + ))} + + )} + {/* Action Buttons */} + + + handleVotePress(true)} style={styles.voteButton}> + + {upvoteCount} + + handleVotePress(false)} style={styles.voteButton}> + + {downvoteCount} + + + + + {review.review_stat.comment_count} + + + + + + + {review.user_id === userId && ( + setShowPopup(true)}> + + + )} + + + {comments && comments.length > 0 ? ( + comments.map((comment, index) => { + return ; + }) + ) : ( + No comments found. + )} + + + + + + {/* Fixed TextBox for Comment */} + + + + + + + + + ) : ( + + Loading... + + ); }; const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: "#fff", - marginTop: 20, - paddingBottom: 80, // Add padding at the bottom equal to the height of the bottom tab bar - }, - reviewContainer: { - alignItems: "flex-start", - paddingLeft: 20, - paddingBottom: 30, - }, - coverImage: { - width: 200, - height: 200, - borderRadius: 10, - marginBottom: 20, - overflow: "scroll", - }, - voteButton: { - flexDirection: "row", - alignItems: "center", - marginHorizontal: 5, - }, - ratingImageWrapper: { - width: "100%", - height: 100, - overflow: "hidden", // This ensures cropping of the image - alignItems: "center", - }, - ratingContainer: { - justifyContent: "center", - alignItems: "flex-start", - }, - mediaContainer: { - flexDirection: "row", - alignItems: "flex-start", - width: "95%", - height: "15%", - marginTop: 20, - }, - songName: { - fontSize: 20, // Adjust size as needed - fontWeight: "bold", - color: "#000", // Adjust color as needed - flexWrap: "wrap", - width: "95%", - }, - artistName: { - fontSize: 16, - color: "#888", - }, - comment: { - fontSize: 16, - }, - - noReviewsText: { - textAlign: "center", - color: "#888", - marginVertical: 20, - }, - comments: { - width: "100%", - marginTop: 20, - marginBottom: 50, - marginLeft: -20, - }, - rating: { - width: "100%", - justifyContent: "center", - alignItems: "center", - marginVertical: -20, - paddingRight: 20, // Add this to account for the left padding of reviewContainer - }, - - ratingImage: { - alignSelf: "center", // Add this - }, - - tagsContainer: { - flexDirection: "row", - marginBottom: 10, - paddingVertical: 15, - minHeight: 60, // Change from fixed height to minHeight - flexWrap: "wrap", // Allows wrapping to a new line - gap: 8, // Space between tags - }, - - tag: { - backgroundColor: "rgba(242, 128, 55, 0.65)", - paddingVertical: 5, - paddingHorizontal: 12, - borderRadius: 20, - marginHorizontal: 5, - borderWidth: 1, - borderColor: "#C0C0C0", - minHeight: 25, // Change from height to minHeight - justifyContent: "center", // Add this - }, - - tagText: { - color: "#333", - fontSize: 12, - lineHeight: 16, // Add this to ensure proper text spacing - textAlignVertical: "center", // Add this - }, - actionsContainer: { - flexDirection: "row", - justifyContent: "space-between", - width: "100%", - marginTop: 15, - }, - voteContainer: { - flexDirection: "row", - alignItems: "center", - }, - vote: { - marginHorizontal: 5, - }, - voteIcon: { - marginHorizontal: 10, - }, - vinyl: { - position: "absolute", - top: 0, - right: 0, - alignItems: "center", - }, - musicDisk: { - position: "absolute", - top: 0, - right: 0, - width: 100, - height: 100, - }, - mediaCover: { - position: "absolute", - width: 80, - height: 80, - top: -12, - right: -5, - borderRadius: 40, - overflow: "hidden", - }, - topSection: { - position: "relative", - width: "100%", - }, - topContainer: { - flexDirection: "row", - justifyContent: "space-between", // Align left and right sections - width: "100%", - marginBottom: 10, - }, - leftSection: { - flexDirection: "row", - alignItems: "center", - }, - profilePicture: { - width: 45, - height: 45, - borderRadius: 22.5, - marginRight: 10, - backgroundColor: "grey", - }, - textContainer: { - flexDirection: "column", - }, - displayName: { - fontWeight: "bold", - fontSize: 16, - color: "#333", - }, - username: { - fontSize: 13, - color: "#888", - }, - commentBoxContainer: { - position: "absolute", - bottom: 50, - left: 0, - right: 0, - backgroundColor: "#fff", - padding: 10, - borderTopWidth: 1, - borderTopColor: "#ddd", - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - }, - commentInput: { - flex: 1, - flexDirection: "row", - height: 50, - borderRadius: 5, - paddingHorizontal: 16, - paddingVertical: 16, - backgroundColor: "#EFF1F5", - }, - submitButtonText: { - color: "#333", - fontWeight: "bold", - }, - menuButton: { padding: 10, marginRight: 10 }, - menuText: { fontSize: 24, marginLeft: 10 }, - editInput: { - borderColor: "#ddd", - borderWidth: 1, - margin: 10, - padding: 10, - marginRight: 25, - }, - saveButton: { - backgroundColor: "#ddd", - padding: 10, - borderRadius: 10, - margin: 10, - width: 60, - }, - modalOverlay: { - flex: 1, - backgroundColor: "rgba(0, 0, 0, 0.5)", // Semi-transparent background - justifyContent: "center", // Center the modal vertically - alignItems: "center", // Center the modal horizontally - }, - popupContainer: { - backgroundColor: "#fff", // Modal background - borderRadius: 10, - padding: 20, - width: "80%", // Adjust width as needed - alignItems: "center", - zIndex: 1000, // Ensure the modal is on top - }, - popupOption: { - padding: 10, - borderBottomWidth: 1, - borderBottomColor: "#ddd", - width: "100%", - alignItems: "center", - }, - sharePopupContainer: { - width: "80%", - backgroundColor: "#fff", - borderRadius: 10, - padding: 20, - alignItems: "center", - }, - sharePopupTitle: { - fontSize: 18, - fontWeight: "bold", - marginBottom: 20, - }, - shareButton: { - backgroundColor: "#6200ee", - padding: 10, - borderRadius: 5, - width: "100%", - alignItems: "center", - }, - shareButtonText: { - color: "#fff", - fontSize: 16, - fontWeight: "600", - }, + container: { + flex: 1, + backgroundColor: '#fff', + marginTop: 20, + paddingBottom: 80, // Add padding at the bottom equal to the height of the bottom tab bar + }, + reviewContainer: { + alignItems: 'flex-start', + padding: 20, + paddingBottom: 30, + }, + coverImage: { + width: 200, + height: 200, + borderRadius: 10, + marginBottom: 20, + overflow: 'scroll', + }, + voteButton: { + flexDirection: 'row', + alignItems: 'center', + marginHorizontal: 5, + }, + ratingImageWrapper: { + width: '100%', + height: 100, + overflow: 'hidden', // This ensures cropping of the image + alignItems: 'center', + }, + ratingContainer: { + justifyContent: 'center', + alignItems: 'flex-start', + }, + mediaContainer: { + flexDirection: 'row', + alignItems: 'flex-start', + width: '95%', + marginTop: 20, + }, + songName: { + fontSize: 20, // Adjust size as needed + fontWeight: 'bold', + color: '#000', // Adjust color as needed + flexWrap: 'wrap', + marginTop: 12, + textAlign: 'left', + }, + artistName: { + fontSize: 16, + color: '#888', + }, + comment: { + fontSize: 16, + }, + + noReviewsText: { + textAlign: 'center', + color: '#888', + marginVertical: 20, + }, + comments: { + width: '100%', + marginTop: 20, + marginBottom: 50, + marginLeft: -20, + }, + rating: { + width: '100%', + justifyContent: 'center', + alignItems: 'center', + marginTop: 32, + paddingRight: 20, // Add this to account for the left padding of reviewContainer + }, + + ratingImage: { + alignSelf: 'center', // Add this + }, + + tagsContainer: { + flexDirection: 'row', + marginBottom: 10, + paddingVertical: 15, + minHeight: 60, // Change from fixed height to minHeight + flexWrap: 'wrap', // Allows wrapping to a new line + gap: 8, // Space between tags + }, + + tag: { + backgroundColor: 'rgba(242, 128, 55, 0.65)', + paddingVertical: 5, + paddingHorizontal: 12, + borderRadius: 20, + marginHorizontal: 5, + borderWidth: 1, + borderColor: '#C0C0C0', + minHeight: 25, // Change from height to minHeight + justifyContent: 'center', // Add this + }, + + tagText: { + color: '#333', + fontSize: 12, + lineHeight: 16, // Add this to ensure proper text spacing + textAlignVertical: 'center', // Add this + }, + actionsContainer: { + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + marginTop: 15, + }, + voteContainer: { + flexDirection: 'row', + alignItems: 'center', + }, + vote: { + marginHorizontal: 5, + }, + voteIcon: { + marginHorizontal: 10, + }, + vinyl: { + position: 'absolute', + top: 0, + right: 0, + alignItems: 'center', + }, + musicDisk: { + position: 'absolute', + top: 0, + right: 0, + width: 100, + height: 100, + }, + mediaCover: { + position: 'absolute', + width: 80, + height: 80, + top: -12, + right: -5, + borderRadius: 40, + overflow: 'hidden', + }, + topSection: { + position: 'relative', + width: '100%', + }, + topContainer: { + flexDirection: 'row', + justifyContent: 'space-between', // Align left and right sections + width: '100%', + marginBottom: 10, + }, + leftSection: { + flexDirection: 'row', + alignItems: 'center', + }, + profilePicture: { + width: 45, + height: 45, + borderRadius: 22.5, + marginRight: 10, + backgroundColor: 'grey', + }, + textContainer: { + flexDirection: 'column', + }, + displayName: { + fontWeight: 'bold', + fontSize: 16, + color: '#333', + }, + username: { + fontSize: 13, + color: '#888', + }, + commentBoxContainer: { + position: 'absolute', + bottom: 50, + left: 0, + right: 0, + backgroundColor: '#fff', + padding: 10, + borderTopWidth: 1, + borderTopColor: '#ddd', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, + commentInput: { + flex: 1, + flexDirection: 'row', + height: 50, + borderRadius: 5, + paddingHorizontal: 16, + paddingVertical: 16, + backgroundColor: '#EFF1F5', + }, + submitButtonText: { + color: '#333', + fontWeight: 'bold', + }, + menuButton: { padding: 10, marginRight: 10 }, + menuText: { fontSize: 24, marginLeft: 10 }, + editInput: { + borderColor: '#ddd', + borderWidth: 1, + margin: 10, + padding: 10, + marginRight: 25, + }, + saveButton: { + backgroundColor: '#ddd', + padding: 10, + borderRadius: 10, + margin: 10, + width: 60, + }, + modalOverlay: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', // Semi-transparent background + justifyContent: 'center', // Center the modal vertically + alignItems: 'center', // Center the modal horizontally + }, + popupContainer: { + backgroundColor: '#fff', // Modal background + borderRadius: 10, + padding: 20, + width: '80%', // Adjust width as needed + alignItems: 'center', + zIndex: 1000, // Ensure the modal is on top + }, + popupOption: { + padding: 10, + borderBottomWidth: 1, + borderBottomColor: '#ddd', + width: '100%', + alignItems: 'center', + }, + sharePopupContainer: { + width: '80%', + backgroundColor: '#fff', + borderRadius: 10, + padding: 20, + alignItems: 'center', + }, + sharePopupTitle: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 20, + }, + shareButton: { + backgroundColor: '#6200ee', + padding: 10, + borderRadius: 5, + width: '100%', + alignItems: 'center', + }, + shareButtonText: { + color: '#fff', + fontSize: 16, + fontWeight: '600', + }, }); export default ReviewPage; diff --git a/frontend/components/search/AlbumSearchCard.tsx b/frontend/components/search/AlbumSearchCard.tsx index 867a1cd8..80ea51a0 100644 --- a/frontend/components/search/AlbumSearchCard.tsx +++ b/frontend/components/search/AlbumSearchCard.tsx @@ -1,119 +1,104 @@ -import React from "react"; -import { router } from "expo-router"; -import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native"; +import React from 'react'; +import { router } from 'expo-router'; +import { View, Text, StyleSheet, Image, TouchableOpacity } from 'react-native'; interface AlbumSearchCardProps { - id: number; - rank: number; - artist_name: string; - album_name: string; - cover: string; + id: number; + rank: number; + artist_name: string; + album_name: string; + cover: string; } -const AlbumSearchCard: React.FC = ({ - id, - rank, - artist_name, - album_name, - cover, -}) => { - const placeholderImage = - "https://upload.wikimedia.org/wikipedia/en/thumb/d/d5/Taylor_Swift_-_1989_%28Taylor%27s_Version%29.png/220px-Taylor_Swift_-_1989_%28Taylor%27s_Version%29.png"; +const AlbumSearchCard: React.FC = ({ id, rank, artist_name, album_name, cover }) => { + const placeholderImage = + 'https://upload.wikimedia.org/wikipedia/en/thumb/d/d5/Taylor_Swift_-_1989_%28Taylor%27s_Version%29.png/220px-Taylor_Swift_-_1989_%28Taylor%27s_Version%29.png'; - return ( - - router.push({ - pathname: "/MediaPage", - params: { - mediaType: "album", - mediaId: id, - }, - }) - } - > - - {/* Rank */} - {rank}. + return ( + + router.push({ + pathname: '/MediaPage', + params: { + mediaType: 'album', + mediaId: id, + }, + }) + }> + + {/* Rank */} + {rank}. - {/* Album Cover */} - - - + {/* Album Cover */} + + + - {/* Record Image */} - - - - + {/* Record Image */} + + + + - {/* Album and Artist Name */} - {album_name} - {artist_name} - - ); + {/* Album and Artist Name */} + {album_name} + {artist_name} + + ); }; const styles = StyleSheet.create({ - cardContainer: { - alignItems: "flex-start", - marginRight: 25, - marginBottom: 16, - width: 140, - }, - albumContainer: { - flexDirection: "row", // Set horizontal layout to align rank and cover side-by-side - alignItems: "center", // Align items vertically centered - position: "relative", - width: 140, - }, - rank: { - color: "#000", - fontSize: 18, - fontWeight: "600", - lineHeight: 20, - marginRight: 6, // Spacing between rank and cover image - marginTop: -85, - }, - coverContainer: { - zIndex: 2, // Ensure cover is on top - }, - recordContainer: { - position: "absolute", // Position record on top of cover - bottom: 5, - left: "50%", - transform: [{ translateX: 0 }], - }, - recordImage: { - width: 100, - height: 100, - }, - albumCover: { - width: 110, - height: 110, - borderRadius: 8, - }, - albumName: { - fontSize: 16, - fontWeight: "bold", - color: "#434343", - marginTop: 4, - textAlign: "left", - marginLeft: 24, - }, - artistName: { - fontSize: 14, - color: "#434343", - textAlign: "left", - marginLeft: 24, - }, + cardContainer: { + marginBottom: 16, + width: 160, + }, + albumContainer: { + flexDirection: 'row', // Set horizontal layout to align rank and cover side-by-side + alignItems: 'center', // Align items vertically centered + position: 'relative', + width: 140, + }, + rank: { + color: '#000', + fontSize: 18, + fontWeight: '600', + lineHeight: 20, + marginRight: 6, // Spacing between rank and cover image + marginTop: -85, + }, + coverContainer: { + zIndex: 2, // Ensure cover is on top + }, + recordContainer: { + position: 'absolute', // Position record on top of cover + bottom: 5, + left: '50%', + transform: [{ translateX: 0 }], + }, + recordImage: { + width: 100, + height: 100, + }, + albumCover: { + width: 110, + height: 110, + borderRadius: 8, + }, + albumName: { + fontSize: 16, + fontWeight: 'bold', + color: '#434343', + marginTop: 4, + textAlign: 'left', + marginLeft: 24, + }, + artistName: { + fontSize: 14, + color: '#434343', + textAlign: 'left', + marginLeft: 24, + }, }); export default AlbumSearchCard; diff --git a/frontend/components/search/SearchResults.tsx b/frontend/components/search/SearchResults.tsx index 641f09e5..7e1c1b9e 100644 --- a/frontend/components/search/SearchResults.tsx +++ b/frontend/components/search/SearchResults.tsx @@ -1,184 +1,158 @@ -import React, { useState } from "react"; -import { StyleSheet, View, Text, ScrollView, Dimensions } from "react-native"; -import SongChip from "@/components/search/SongChip"; -import AlbumSearchCard from "@/components/search/AlbumSearchCard"; -import ProfileChip from "@/components/search/ProfileChip"; -import Filter from "@/components/search/Filter"; -import { takeWhile } from "lodash"; +import React, { useState } from 'react'; +import { StyleSheet, View, Text, ScrollView, Dimensions } from 'react-native'; +import SongChip from '@/components/search/SongChip'; +import AlbumSearchCard from '@/components/search/AlbumSearchCard'; +import ProfileChip from '@/components/search/ProfileChip'; +import Filter from '@/components/search/Filter'; +import { takeWhile } from 'lodash'; interface SearchResultsProps { - songs: Media[]; - albums: Media[]; - profiles: UserProfile[]; - isLoading: boolean; - filter: "all" | "songs" | "albums" | "profile"; + songs: Media[]; + albums: Media[]; + profiles: UserProfile[]; + isLoading: boolean; + filter: 'all' | 'songs' | 'albums' | 'profile'; } -const SearchResults: React.FC = ({ - songs, - albums, - profiles, - isLoading, -}) => { - if (isLoading) { - return Searching...; - } +const SearchResults: React.FC = ({ songs, albums, profiles, isLoading }) => { + if (isLoading) { + return Searching...; + } - if (songs?.length === 0 && albums?.length === 0 && profiles?.length == 0) { - return No results found; - } + if (songs?.length === 0 && albums?.length === 0 && profiles?.length == 0) { + return No results found; + } - const filterOptions = ["all", "songs", "albums", "profile"]; + const filterOptions = ['all', 'songs', 'albums', 'profile']; - const [selectedFilter, setSelectedFilter] = useState("all"); + const [selectedFilter, setSelectedFilter] = useState('all'); - const handleFilterChange = (filter: FilterOption) => { - setSelectedFilter(filter); - }; + const handleFilterChange = (filter: FilterOption) => { + setSelectedFilter(filter); + }; - return ( - - - - - {(selectedFilter === "all" || selectedFilter === "profile") && - profiles?.length > 0 && ( - - Profiles - {profiles?.map((profile, idx) => ( - - ))} - - )} + return ( + + + + + {(selectedFilter === 'all' || selectedFilter === 'profile') && profiles?.length > 0 && ( + + Profiles + {profiles?.map((profile, idx) => ( + + ))} + + )} - {(selectedFilter === "all" || selectedFilter === "songs") && ( - - Albums - - {albums.map((album, index) => ( - - ))} - - - )} - {(selectedFilter === "all" || selectedFilter === "songs") && ( - - Songs - - {songs?.map((song, index) => ( - - ))} - - - )} - {(selectedFilter === "all" || selectedFilter === "songs") && ( - - Songs - - {songs?.map((song, index) => ( - - ))} - - - )} - - - - ); + {(selectedFilter === 'all' || selectedFilter === 'albums') && ( + + Albums + + {albums.map((album, index) => ( + + + + ))} + + + )} + {(selectedFilter === 'all' || selectedFilter === 'songs') && ( + + Songs + + {songs?.map((song, index) => ( + + ))} + + + )} + + + + ); }; const styles = StyleSheet.create({ - title: { - fontSize: 24, - fontWeight: "bold", - padding: 16, - }, - container: { - flex: 1, - paddingHorizontal: 16, - }, - headerContainer: { - marginTop: 10, - marginBottom: 20, - fontSize: 16, - textAlign: "center", - fontWeight: "600", - color: "#000000", - }, - section: { - marginTop: 20, - }, - sectionTitle: { - fontSize: 20, - fontWeight: "600", - marginBottom: 10, - color: "#434343", - }, - loadingText: { - textAlign: "center", - marginTop: 20, - color: "#666666", - }, - twoColumnList: { - flex: 1, - flexDirection: "row", - flexWrap: "wrap", - justifyContent: "space-between", - }, - resultGrid: { - flexDirection: "row", - flexWrap: "wrap", - gap: 10, - }, - songsList: { - marginRight: 20, - width: "100%", - }, - albumsList: { - width: "100%", - marginBottom: 16, - paddingHorizontal: 4, - }, - albumsList: { - width: "48%", // Slightly less than 50% to allow for spacing - marginBottom: 16, - paddingHorizontal: 4, - }, - noResults: { - textAlign: "center", - marginTop: 20, - color: "#666666", - }, + title: { + fontSize: 24, + fontWeight: 'bold', + padding: 16, + }, + container: { + flex: 1, + paddingHorizontal: 16, + flexWrap: 'wrap', + flexDirection: 'row', + }, + headerContainer: { + marginTop: 10, + marginBottom: 20, + fontSize: 16, + textAlign: 'center', + fontWeight: '600', + color: '#000000', + }, + section: { + marginTop: 20, + }, + sectionTitle: { + fontSize: 20, + fontWeight: '600', + marginBottom: 10, + color: '#434343', + }, + loadingText: { + textAlign: 'center', + marginTop: 20, + color: '#666666', + }, + twoColumnList: { + flex: 1, + flexWrap: 'wrap', + flexDirection: 'row', + width: '100%', + }, + resultGrid: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 10, + width: '100%', + }, + songsList: { + marginRight: 20, + width: '100%', + }, + albumsList: { + marginBottom: 16, + paddingHorizontal: 4, + flex: 1, + flexWrap: 'wrap', + }, + noResults: { + textAlign: 'center', + marginTop: 20, + color: '#666666', + }, }); export default SearchResults; From 7ba972e9c15458464181de63f69c19a4e6632c38 Mon Sep 17 00:00:00 2001 From: Abhik Ray Date: Tue, 3 Dec 2024 20:07:27 -0500 Subject: [PATCH 2/3] lint --- frontend/app/(tabs)/search.tsx | 187 +-- frontend/app/(tabs)/user.tsx | 20 +- frontend/app/ReviewPage.tsx | 1485 +++++++++-------- .../components/search/AlbumSearchCard.tsx | 193 ++- frontend/components/search/SearchResults.tsx | 294 ++-- 5 files changed, 1122 insertions(+), 1057 deletions(-) diff --git a/frontend/app/(tabs)/search.tsx b/frontend/app/(tabs)/search.tsx index 62b90372..b1e3580e 100644 --- a/frontend/app/(tabs)/search.tsx +++ b/frontend/app/(tabs)/search.tsx @@ -1,110 +1,111 @@ -import React, { useState, useEffect } from 'react'; -import { StyleSheet, View, ScrollView } from 'react-native'; -import SearchBar from '@/components/search/SearchBar'; -import SearchResults from '@/components/search/SearchResults'; -import TopAlbums from '@/components/search/TopAlbums'; -import TopSongs from '@/components/search/TopSongs'; -import TopReviews from '@/components/search/TopReviews'; -import Profiles from '@/components/search/Profiles'; -import axios from 'axios'; +import React, { useState, useEffect } from "react"; +import { StyleSheet, View, ScrollView } from "react-native"; +import SearchBar from "@/components/search/SearchBar"; +import SearchResults from "@/components/search/SearchResults"; +import TopAlbums from "@/components/search/TopAlbums"; +import TopSongs from "@/components/search/TopSongs"; +import TopReviews from "@/components/search/TopReviews"; +import Profiles from "@/components/search/Profiles"; +import axios from "axios"; const SearchPage: React.FC = () => { - const [searchResults, setSearchResults] = useState<{ - songs: Media[]; - albums: Media[]; - profiles: UserProfile[]; - }>({ - songs: [], - albums: [], - profiles: [], - }); - const [isSearchActive, setIsSearchActive] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [initialSongs, setInitialSongs] = useState([]); - const [initialAlbums, setInitialAlbums] = useState([]); - const [initialReviews, setInitialReviews] = useState([]); + const [searchResults, setSearchResults] = useState<{ + songs: Media[]; + albums: Media[]; + profiles: UserProfile[]; + }>({ + songs: [], + albums: [], + profiles: [], + }); + const [isSearchActive, setIsSearchActive] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [initialSongs, setInitialSongs] = useState([]); + const [initialAlbums, setInitialAlbums] = useState([]); + const [initialReviews, setInitialReviews] = useState([]); - const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; + const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; - // Fetch initial top songs and albums - useEffect(() => { - axios - .get(`${BASE_URL}/media?sort=review&type=album`) - .then((response) => setInitialAlbums(response.data)) - .catch((error) => console.error(error)); + // Fetch initial top songs and albums + useEffect(() => { + axios + .get(`${BASE_URL}/media?sort=review&type=album`) + .then((response) => setInitialAlbums(response.data)) + .catch((error) => console.error(error)); - axios - .get(`${BASE_URL}/media?sort=review&type=track&limit=13`) - .then((response) => setInitialSongs(response.data)) - .catch((error) => console.error(error)); + axios + .get(`${BASE_URL}/media?sort=review&type=track&limit=13`) + .then((response) => setInitialSongs(response.data)) + .catch((error) => console.error(error)); - axios - .get(`${BASE_URL}/reviews/popular`) - .then((response) => setInitialReviews(response.data)) - .catch((error) => console.error(error)); - }, []); + axios + .get(`${BASE_URL}/reviews/popular`) + .then((response) => setInitialReviews(response.data)) + .catch((error) => console.error(error)); + }, []); - const handleSearch = async (query: string) => { - if (!query.trim()) { - setSearchResults({ songs: [], albums: [], profiles: [] }); - setIsSearchActive(false); - return; - } + const handleSearch = async (query: string) => { + if (!query.trim()) { + setSearchResults({ songs: [], albums: [], profiles: [] }); + setIsSearchActive(false); + return; + } - setIsLoading(true); - try { - const [songsResponse, albumsResponse, profilesResponse] = await Promise.all([ - axios.get(`${BASE_URL}/media/${query}?media_type=track`), - axios.get(`${BASE_URL}/media/${query}?media_type=album`), - axios.get(`${BASE_URL}/users/profile/name/${query}`), - ]); + setIsLoading(true); + try { + const [songsResponse, albumsResponse, profilesResponse] = + await Promise.all([ + axios.get(`${BASE_URL}/media/${query}?media_type=track`), + axios.get(`${BASE_URL}/media/${query}?media_type=album`), + axios.get(`${BASE_URL}/users/profile/name/${query}`), + ]); - setSearchResults({ - songs: songsResponse.data ?? [], - albums: albumsResponse.data ?? [], - profiles: profilesResponse.data ?? [], - }); - setIsSearchActive(true); - } catch (error) { - console.error('Search error:', error); - setSearchResults({ songs: [], albums: [], profiles: [] }); - } finally { - setIsLoading(false); - } - }; + setSearchResults({ + songs: songsResponse.data ?? [], + albums: albumsResponse.data ?? [], + profiles: profilesResponse.data ?? [], + }); + setIsSearchActive(true); + } catch (error) { + console.error("Search error:", error); + setSearchResults({ songs: [], albums: [], profiles: [] }); + } finally { + setIsLoading(false); + } + }; - return ( - - + return ( + + - - {isSearchActive ? ( - - ) : ( - - - - - - )} - - - ); + + {isSearchActive ? ( + + ) : ( + + + + + + )} + + + ); }; const styles = StyleSheet.create({ - container: { - fontFamily: 'NeueHaasUnicaPro-Regular', - flex: 1, - paddingTop: 80, - backgroundColor: '#fff', - }, + container: { + fontFamily: "NeueHaasUnicaPro-Regular", + flex: 1, + paddingTop: 80, + backgroundColor: "#fff", + }, }); export default SearchPage; diff --git a/frontend/app/(tabs)/user.tsx b/frontend/app/(tabs)/user.tsx index 0530ce55..1757124a 100644 --- a/frontend/app/(tabs)/user.tsx +++ b/frontend/app/(tabs)/user.tsx @@ -32,11 +32,11 @@ export default function ProfilePage() { setFollowing(!following); // Optionally, trigger API call to update follow state in the backend - fetch('/users/follow', { method: 'POST', body: JSON.stringify({ userId }) }) - .then(response => response.json()) - .catch(error => console.error('Error updating follow state:', error)); + fetch("/users/follow", { method: "POST", body: JSON.stringify({ userId }) }) + .then((response) => response.json()) + .catch((error) => console.error("Error updating follow state:", error)); }; - + return ( userProfile && ( @@ -82,7 +82,7 @@ export default function ProfilePage() { - {following ? 'Following' : 'Follow'} + {following ? "Following" : "Follow"} @@ -195,15 +195,15 @@ const styles = StyleSheet.create({ color: "#666", }, followButton: { - backgroundColor: '#d3d3d3', // Grey background + backgroundColor: "#d3d3d3", // Grey background borderRadius: 20, // Rounded corners paddingVertical: 10, paddingHorizontal: 20, - alignItems: 'center', - justifyContent: 'center', + alignItems: "center", + justifyContent: "center", }, followButtonText: { - color: '#000', // Black text - fontWeight: 'bold', + color: "#000", // Black text + fontWeight: "bold", }, }); diff --git a/frontend/app/ReviewPage.tsx b/frontend/app/ReviewPage.tsx index 5c6ba301..08f93eb3 100644 --- a/frontend/app/ReviewPage.tsx +++ b/frontend/app/ReviewPage.tsx @@ -1,734 +1,775 @@ -import HeaderComponent from '@/components/HeaderComponent'; -import CommentComponent from '@/components/CommentComponent'; -import axios from 'axios'; -import { router, useLocalSearchParams } from 'expo-router'; -import React, { useEffect, useState } from 'react'; +import HeaderComponent from "@/components/HeaderComponent"; +import CommentComponent from "@/components/CommentComponent"; +import axios from "axios"; +import { router, useLocalSearchParams } from "expo-router"; +import React, { useEffect, useState } from "react"; import { - View, - Text, - StyleSheet, - Image, - ScrollView, - TouchableOpacity, - TextInput, - Modal, - KeyboardAvoidingView, -} from 'react-native'; -import Rating0 from '@/assets/images/Ratings/Radial-0.svg'; -import Rating1 from '@/assets/images/Ratings/Radial-1.svg'; -import Rating2 from '@/assets/images/Ratings/Radial-2.svg'; -import Rating3 from '@/assets/images/Ratings/Radial-3.svg'; -import Rating4 from '@/assets/images/Ratings/Radial-4.svg'; -import Rating5 from '@/assets/images/Ratings/Radial-5.svg'; -import Rating6 from '@/assets/images/Ratings/Radial-6.svg'; -import Rating7 from '@/assets/images/Ratings/Radial-7.svg'; -import Rating8 from '@/assets/images/Ratings/Radial-8.svg'; -import Rating9 from '@/assets/images/Ratings/Radial-9.svg'; -import Rating10 from '@/assets/images/Ratings/Radial-10.svg'; -import Downvote from '@/assets/images/ReviewPreview/downvote.svg'; -import Upvote from '@/assets/images/ReviewPreview/upvote.svg'; -import Comment from '@/assets/images/ReviewPreview/comment.svg'; -import Share from '@/assets/images/ReviewPreview/share.svg'; -import Upload from '@/assets/images/Icons/Upload.svg'; -import { useAuthContext } from '@/components/AuthProvider'; + View, + Text, + StyleSheet, + Image, + ScrollView, + TouchableOpacity, + TextInput, + Modal, + KeyboardAvoidingView, +} from "react-native"; +import Rating0 from "@/assets/images/Ratings/Radial-0.svg"; +import Rating1 from "@/assets/images/Ratings/Radial-1.svg"; +import Rating2 from "@/assets/images/Ratings/Radial-2.svg"; +import Rating3 from "@/assets/images/Ratings/Radial-3.svg"; +import Rating4 from "@/assets/images/Ratings/Radial-4.svg"; +import Rating5 from "@/assets/images/Ratings/Radial-5.svg"; +import Rating6 from "@/assets/images/Ratings/Radial-6.svg"; +import Rating7 from "@/assets/images/Ratings/Radial-7.svg"; +import Rating8 from "@/assets/images/Ratings/Radial-8.svg"; +import Rating9 from "@/assets/images/Ratings/Radial-9.svg"; +import Rating10 from "@/assets/images/Ratings/Radial-10.svg"; +import Downvote from "@/assets/images/ReviewPreview/downvote.svg"; +import Upvote from "@/assets/images/ReviewPreview/upvote.svg"; +import Comment from "@/assets/images/ReviewPreview/comment.svg"; +import Share from "@/assets/images/ReviewPreview/share.svg"; +import Upload from "@/assets/images/Icons/Upload.svg"; +import { useAuthContext } from "@/components/AuthProvider"; interface ReviewPageProps { - route: { - params: { - review_id: string; - user_id: string; - }; - }; + route: { + params: { + review_id: string; + user_id: string; + }; + }; } const ReviewPage: React.FC = ({ route }) => { - const { review_id, user_id } = useLocalSearchParams<{ - review_id: string; - user_id: string; - }>(); - const [review, setReview] = useState(); - const [comments, setComments] = useState(); - const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; - const { userId } = useAuthContext(); - const MusicDisk = require('../assets/images/music-disk.png'); - - const [currentVote, setCurrentVote] = useState(false); // does a vote currently exist? - const [currentVoteValue, setCurrentVoteValue] = useState(false); // what is the current vote's value? - - const [upvoteCount, setUpvoteCount] = useState(0); - const [downvoteCount, setDownvoteCount] = useState(0); - const [commentCount, setCommentCount] = useState(0); - const [newComment, setNewComment] = useState(''); - - const [isEditable, setIsEditable] = useState(false); - const [editedComment, setEditedComment] = useState(''); - const [showPopup, setShowPopup] = useState(false); - - const ratingImages = { - 0: Rating0, - 1: Rating1, - 2: Rating2, - 3: Rating3, - 4: Rating4, - 5: Rating5, - 6: Rating6, - 7: Rating7, - 8: Rating8, - 9: Rating9, - 10: Rating10, - }; - - const [sharePopupVisible, setSharePopupVisible] = useState(false); - - const handleSharePress = () => { - setSharePopupVisible(true); // Show the share popup - }; - - const closeSharePopup = () => { - setSharePopupVisible(false); // Close the share popup - }; - - const getRatingImage = (rating: keyof typeof ratingImages) => { - return ratingImages[rating]; // Access the image from the preloaded images object - }; - - const handleVotePress = async (newVoteValue: boolean) => { - if (currentVote) { - // if there is already a vote value, we have to delete or swap it - if (currentVoteValue && newVoteValue) { - // if there is an upvote and the user clicks upvote again - setCurrentVote(false); // cancel out the vote - setUpvoteCount(upvoteCount - 1); - } else if (!currentVoteValue && !newVoteValue) { - // if there is a downvote and the user clicks downvote again - setCurrentVote(false); // cancel out the vote - setDownvoteCount(downvoteCount - 1); - } else if (currentVoteValue && !newVoteValue) { - // if there is an upvote and the user clicks downvote - setCurrentVoteValue(false); - setUpvoteCount(upvoteCount - 1); - setDownvoteCount(downvoteCount + 1); - } else if (!currentVoteValue && newVoteValue) { - // if there is a downvote and the user clicks upvote - setCurrentVoteValue(true); - setUpvoteCount(upvoteCount + 1); - setDownvoteCount(downvoteCount - 1); - } - } else { - setCurrentVote(true); - setCurrentVoteValue(newVoteValue); - if (newVoteValue) { - setUpvoteCount(upvoteCount + 1); - } else { - setDownvoteCount(downvoteCount + 1); - } - } - - try { - await axios.post(`${BASE_URL}/reviews/vote`, { - user_id: userId, - post_id: review_id, - upvote: newVoteValue, - }); - } catch (error) { - console.error('Error downvoting comment:', error); - } - }; - - const handleCommentPress = () => { - console.log('comment icon pressed'); - }; - - const handleCommentSubmit = async () => { - if (!newComment.trim()) return; // Do not submit if the comment is empty - setCommentCount(commentCount + 1); - - try { - await axios.post( - `${BASE_URL}/reviews/comment`, - { - user_id: userId, - review_id: parseInt(review_id, 10), - text: newComment, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - setNewComment(''); // Clear the input after submitting - // Fetch updated comments after submitting - fetchComments(); - } catch (error) { - console.error('Error submitting comment:', error); - } - }; - - const handleEditSave = async () => { - try { - const requestBody = { - user_id: userId, // User ID to validate ownership - comment: editedComment, // The updated comment - }; - - await axios.patch(`${BASE_URL}/reviews/${review_id}`, requestBody); - setIsEditable(false); - setReview((prev) => (prev ? { ...prev, comment: editedComment } : prev)); - } catch (error) { - console.error('Error saving edited review:', error); - } - }; - - const handleMenuOption = (option: string) => { - setShowPopup(false); - if (option === 'edit') { - setIsEditable(true); - setEditedComment(review?.comment || ''); - } else if (option === 'delete') { - // Add delete functionality - } else if (option === 'manageComments') { - // Add manage comments functionality - } else if (option === 'share') { - // Add share functionality - } - }; - - // Fetch the review data using the review_id - useEffect(() => { - const fetchReview = async () => { - try { - const response = await axios.get(`${BASE_URL}/reviews/${review_id}`); - const review = response.data; - setReview(review); - if (review) { - setUpvoteCount(review.review_stat.upvotes); - setDownvoteCount(review.review_stat.downvotes); - setCommentCount(review.review_stat.comment_count); - } - } catch (error) { - console.error('Error fetching review:', error); - } - }; - - fetchReview(); - fetchComments(); - }, [review_id, userId, user_id, newComment]); - - const fetchComments = async () => { - try { - const response = await axios.get(`${BASE_URL}/reviews/comments/${review_id}`); - setComments(response.data); - } catch (error) { - console.error('Error fetching comments:', error); - } - }; - - useEffect(() => { - const fetchVote = async () => { - try { - if (review) { - setUpvoteCount(review.review_stat.upvotes); - setDownvoteCount(review.review_stat.downvotes); - setCommentCount(review.review_stat.comment_count); - } - const response = await axios.get(`${BASE_URL}/reviews/vote/${userId}/${review_id}`); - if (response.data) { - setCurrentVote(true); - const { upvote } = response.data; // Assuming the API returns { user_id, post_id, upvote } - setCurrentVoteValue(upvote); - } else { - setCurrentVote(false); - } - } catch (error) { - console.error('Error fetching vote:', error); - } - }; - fetchVote(); - }, [review_id, userId, newComment]); - - const handleUserPress = () => { - // Navigate to the UserPage when the user is clicked - const pathName = review?.user_id === userId ? '/(tabs)/profile' : '/(tabs)/user'; - router.push({ - pathname: pathName, - params: { - userId: review?.user_id, - }, - }); - }; - - const handleMediaPress = () => { - // Navigate to the MediaPage - console.log('Media pressed'); - router.push({ - pathname: '/MediaPage', - params: { mediaId: review?.media_id, mediaType: review?.media_type }, - }); - }; - - return review ? ( - - - - - - - - - - {review.display_name} - @{review.username} - - - - - - - {review.media_cover && ( - - )} - - - - - - - - {review.media_title} - - {review.media_artist} - - - - - {React.createElement( - getRatingImage(review.rating as keyof typeof ratingImages), - { - width: 150, // Adjust size as needed - height: 150, - }, - { - style: styles.ratingImage, - } as any, - )} - - - setShowPopup(false)} - activeOpacity={1} // Prevent the modal itself from closing on tap - > - - handleMenuOption('edit')}> - Edit Review - - handleMenuOption('manageComments')}> - Manage Comments - - handleMenuOption('share')}> - Share - - handleMenuOption('delete')}> - Delete - - - - - - - - Share This Review - console.log('Share to Friends Pressed')}> - Share to Friends - - - - - {isEditable ? ( - - - - Save - - - ) : ( - {review.comment} - )} - {/* Tags Section */} - {review.tags && review.tags.length > 0 && ( - - {review.tags.map((tag, index) => ( - - {tag} - - ))} - - )} - {/* Action Buttons */} - - - handleVotePress(true)} style={styles.voteButton}> - - {upvoteCount} - - handleVotePress(false)} style={styles.voteButton}> - - {downvoteCount} - - - - - {review.review_stat.comment_count} - - - - - - - {review.user_id === userId && ( - setShowPopup(true)}> - - - )} - - - {comments && comments.length > 0 ? ( - comments.map((comment, index) => { - return ; - }) - ) : ( - No comments found. - )} - - - - - - {/* Fixed TextBox for Comment */} - - - - - - - - - ) : ( - - Loading... - - ); + const { review_id, user_id } = useLocalSearchParams<{ + review_id: string; + user_id: string; + }>(); + const [review, setReview] = useState(); + const [comments, setComments] = useState(); + const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; + const { userId } = useAuthContext(); + const MusicDisk = require("../assets/images/music-disk.png"); + + const [currentVote, setCurrentVote] = useState(false); // does a vote currently exist? + const [currentVoteValue, setCurrentVoteValue] = useState(false); // what is the current vote's value? + + const [upvoteCount, setUpvoteCount] = useState(0); + const [downvoteCount, setDownvoteCount] = useState(0); + const [commentCount, setCommentCount] = useState(0); + const [newComment, setNewComment] = useState(""); + + const [isEditable, setIsEditable] = useState(false); + const [editedComment, setEditedComment] = useState(""); + const [showPopup, setShowPopup] = useState(false); + + const ratingImages = { + 0: Rating0, + 1: Rating1, + 2: Rating2, + 3: Rating3, + 4: Rating4, + 5: Rating5, + 6: Rating6, + 7: Rating7, + 8: Rating8, + 9: Rating9, + 10: Rating10, + }; + + const [sharePopupVisible, setSharePopupVisible] = useState(false); + + const handleSharePress = () => { + setSharePopupVisible(true); // Show the share popup + }; + + const closeSharePopup = () => { + setSharePopupVisible(false); // Close the share popup + }; + + const getRatingImage = (rating: keyof typeof ratingImages) => { + return ratingImages[rating]; // Access the image from the preloaded images object + }; + + const handleVotePress = async (newVoteValue: boolean) => { + if (currentVote) { + // if there is already a vote value, we have to delete or swap it + if (currentVoteValue && newVoteValue) { + // if there is an upvote and the user clicks upvote again + setCurrentVote(false); // cancel out the vote + setUpvoteCount(upvoteCount - 1); + } else if (!currentVoteValue && !newVoteValue) { + // if there is a downvote and the user clicks downvote again + setCurrentVote(false); // cancel out the vote + setDownvoteCount(downvoteCount - 1); + } else if (currentVoteValue && !newVoteValue) { + // if there is an upvote and the user clicks downvote + setCurrentVoteValue(false); + setUpvoteCount(upvoteCount - 1); + setDownvoteCount(downvoteCount + 1); + } else if (!currentVoteValue && newVoteValue) { + // if there is a downvote and the user clicks upvote + setCurrentVoteValue(true); + setUpvoteCount(upvoteCount + 1); + setDownvoteCount(downvoteCount - 1); + } + } else { + setCurrentVote(true); + setCurrentVoteValue(newVoteValue); + if (newVoteValue) { + setUpvoteCount(upvoteCount + 1); + } else { + setDownvoteCount(downvoteCount + 1); + } + } + + try { + await axios.post(`${BASE_URL}/reviews/vote`, { + user_id: userId, + post_id: review_id, + upvote: newVoteValue, + }); + } catch (error) { + console.error("Error downvoting comment:", error); + } + }; + + const handleCommentPress = () => { + console.log("comment icon pressed"); + }; + + const handleCommentSubmit = async () => { + if (!newComment.trim()) return; // Do not submit if the comment is empty + setCommentCount(commentCount + 1); + + try { + await axios.post( + `${BASE_URL}/reviews/comment`, + { + user_id: userId, + review_id: parseInt(review_id, 10), + text: newComment, + }, + { + headers: { + "Content-Type": "application/json", + }, + }, + ); + setNewComment(""); // Clear the input after submitting + // Fetch updated comments after submitting + fetchComments(); + } catch (error) { + console.error("Error submitting comment:", error); + } + }; + + const handleEditSave = async () => { + try { + const requestBody = { + user_id: userId, // User ID to validate ownership + comment: editedComment, // The updated comment + }; + + await axios.patch(`${BASE_URL}/reviews/${review_id}`, requestBody); + setIsEditable(false); + setReview((prev) => (prev ? { ...prev, comment: editedComment } : prev)); + } catch (error) { + console.error("Error saving edited review:", error); + } + }; + + const handleMenuOption = (option: string) => { + setShowPopup(false); + if (option === "edit") { + setIsEditable(true); + setEditedComment(review?.comment || ""); + } else if (option === "delete") { + // Add delete functionality + } else if (option === "manageComments") { + // Add manage comments functionality + } else if (option === "share") { + // Add share functionality + } + }; + + // Fetch the review data using the review_id + useEffect(() => { + const fetchReview = async () => { + try { + const response = await axios.get(`${BASE_URL}/reviews/${review_id}`); + const review = response.data; + setReview(review); + if (review) { + setUpvoteCount(review.review_stat.upvotes); + setDownvoteCount(review.review_stat.downvotes); + setCommentCount(review.review_stat.comment_count); + } + } catch (error) { + console.error("Error fetching review:", error); + } + }; + + fetchReview(); + fetchComments(); + }, [review_id, userId, user_id, newComment]); + + const fetchComments = async () => { + try { + const response = await axios.get( + `${BASE_URL}/reviews/comments/${review_id}`, + ); + setComments(response.data); + } catch (error) { + console.error("Error fetching comments:", error); + } + }; + + useEffect(() => { + const fetchVote = async () => { + try { + if (review) { + setUpvoteCount(review.review_stat.upvotes); + setDownvoteCount(review.review_stat.downvotes); + setCommentCount(review.review_stat.comment_count); + } + const response = await axios.get( + `${BASE_URL}/reviews/vote/${userId}/${review_id}`, + ); + if (response.data) { + setCurrentVote(true); + const { upvote } = response.data; // Assuming the API returns { user_id, post_id, upvote } + setCurrentVoteValue(upvote); + } else { + setCurrentVote(false); + } + } catch (error) { + console.error("Error fetching vote:", error); + } + }; + fetchVote(); + }, [review_id, userId, newComment]); + + const handleUserPress = () => { + // Navigate to the UserPage when the user is clicked + const pathName = + review?.user_id === userId ? "/(tabs)/profile" : "/(tabs)/user"; + router.push({ + pathname: pathName, + params: { + userId: review?.user_id, + }, + }); + }; + + const handleMediaPress = () => { + // Navigate to the MediaPage + console.log("Media pressed"); + router.push({ + pathname: "/MediaPage", + params: { mediaId: review?.media_id, mediaType: review?.media_type }, + }); + }; + + return review ? ( + + + + + + + + + + {review.display_name} + @{review.username} + + + + + + + {review.media_cover && ( + + )} + + + + + + + + {review.media_title} + + {review.media_artist} + + + + + {React.createElement( + getRatingImage(review.rating as keyof typeof ratingImages), + { + width: 150, // Adjust size as needed + height: 150, + }, + { + style: styles.ratingImage, + } as any, + )} + + + setShowPopup(false)} + activeOpacity={1} // Prevent the modal itself from closing on tap + > + + handleMenuOption("edit")} + > + Edit Review + + handleMenuOption("manageComments")} + > + Manage Comments + + handleMenuOption("share")} + > + Share + + handleMenuOption("delete")} + > + Delete + + + + + + + + Share This Review + console.log("Share to Friends Pressed")} + > + Share to Friends + + + + + {isEditable ? ( + + + + Save + + + ) : ( + {review.comment} + )} + {/* Tags Section */} + {review.tags && review.tags.length > 0 && ( + + {review.tags.map((tag, index) => ( + + {tag} + + ))} + + )} + {/* Action Buttons */} + + + handleVotePress(true)} + style={styles.voteButton} + > + + {upvoteCount} + + handleVotePress(false)} + style={styles.voteButton} + > + + {downvoteCount} + + + + + {review.review_stat.comment_count} + + + + + + + {review.user_id === userId && ( + setShowPopup(true)} + > + + + )} + + + {comments && comments.length > 0 ? ( + comments.map((comment, index) => { + return ; + }) + ) : ( + No comments found. + )} + + + + + + {/* Fixed TextBox for Comment */} + + + + + + + + + ) : ( + + Loading... + + ); }; const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: '#fff', - marginTop: 20, - paddingBottom: 80, // Add padding at the bottom equal to the height of the bottom tab bar - }, - reviewContainer: { - alignItems: 'flex-start', - padding: 20, - paddingBottom: 30, - }, - coverImage: { - width: 200, - height: 200, - borderRadius: 10, - marginBottom: 20, - overflow: 'scroll', - }, - voteButton: { - flexDirection: 'row', - alignItems: 'center', - marginHorizontal: 5, - }, - ratingImageWrapper: { - width: '100%', - height: 100, - overflow: 'hidden', // This ensures cropping of the image - alignItems: 'center', - }, - ratingContainer: { - justifyContent: 'center', - alignItems: 'flex-start', - }, - mediaContainer: { - flexDirection: 'row', - alignItems: 'flex-start', - width: '95%', - marginTop: 20, - }, - songName: { - fontSize: 20, // Adjust size as needed - fontWeight: 'bold', - color: '#000', // Adjust color as needed - flexWrap: 'wrap', - marginTop: 12, - textAlign: 'left', - }, - artistName: { - fontSize: 16, - color: '#888', - }, - comment: { - fontSize: 16, - }, - - noReviewsText: { - textAlign: 'center', - color: '#888', - marginVertical: 20, - }, - comments: { - width: '100%', - marginTop: 20, - marginBottom: 50, - marginLeft: -20, - }, - rating: { - width: '100%', - justifyContent: 'center', - alignItems: 'center', - marginTop: 32, - paddingRight: 20, // Add this to account for the left padding of reviewContainer - }, - - ratingImage: { - alignSelf: 'center', // Add this - }, - - tagsContainer: { - flexDirection: 'row', - marginBottom: 10, - paddingVertical: 15, - minHeight: 60, // Change from fixed height to minHeight - flexWrap: 'wrap', // Allows wrapping to a new line - gap: 8, // Space between tags - }, - - tag: { - backgroundColor: 'rgba(242, 128, 55, 0.65)', - paddingVertical: 5, - paddingHorizontal: 12, - borderRadius: 20, - marginHorizontal: 5, - borderWidth: 1, - borderColor: '#C0C0C0', - minHeight: 25, // Change from height to minHeight - justifyContent: 'center', // Add this - }, - - tagText: { - color: '#333', - fontSize: 12, - lineHeight: 16, // Add this to ensure proper text spacing - textAlignVertical: 'center', // Add this - }, - actionsContainer: { - flexDirection: 'row', - justifyContent: 'space-between', - width: '100%', - marginTop: 15, - }, - voteContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - vote: { - marginHorizontal: 5, - }, - voteIcon: { - marginHorizontal: 10, - }, - vinyl: { - position: 'absolute', - top: 0, - right: 0, - alignItems: 'center', - }, - musicDisk: { - position: 'absolute', - top: 0, - right: 0, - width: 100, - height: 100, - }, - mediaCover: { - position: 'absolute', - width: 80, - height: 80, - top: -12, - right: -5, - borderRadius: 40, - overflow: 'hidden', - }, - topSection: { - position: 'relative', - width: '100%', - }, - topContainer: { - flexDirection: 'row', - justifyContent: 'space-between', // Align left and right sections - width: '100%', - marginBottom: 10, - }, - leftSection: { - flexDirection: 'row', - alignItems: 'center', - }, - profilePicture: { - width: 45, - height: 45, - borderRadius: 22.5, - marginRight: 10, - backgroundColor: 'grey', - }, - textContainer: { - flexDirection: 'column', - }, - displayName: { - fontWeight: 'bold', - fontSize: 16, - color: '#333', - }, - username: { - fontSize: 13, - color: '#888', - }, - commentBoxContainer: { - position: 'absolute', - bottom: 50, - left: 0, - right: 0, - backgroundColor: '#fff', - padding: 10, - borderTopWidth: 1, - borderTopColor: '#ddd', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - commentInput: { - flex: 1, - flexDirection: 'row', - height: 50, - borderRadius: 5, - paddingHorizontal: 16, - paddingVertical: 16, - backgroundColor: '#EFF1F5', - }, - submitButtonText: { - color: '#333', - fontWeight: 'bold', - }, - menuButton: { padding: 10, marginRight: 10 }, - menuText: { fontSize: 24, marginLeft: 10 }, - editInput: { - borderColor: '#ddd', - borderWidth: 1, - margin: 10, - padding: 10, - marginRight: 25, - }, - saveButton: { - backgroundColor: '#ddd', - padding: 10, - borderRadius: 10, - margin: 10, - width: 60, - }, - modalOverlay: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.5)', // Semi-transparent background - justifyContent: 'center', // Center the modal vertically - alignItems: 'center', // Center the modal horizontally - }, - popupContainer: { - backgroundColor: '#fff', // Modal background - borderRadius: 10, - padding: 20, - width: '80%', // Adjust width as needed - alignItems: 'center', - zIndex: 1000, // Ensure the modal is on top - }, - popupOption: { - padding: 10, - borderBottomWidth: 1, - borderBottomColor: '#ddd', - width: '100%', - alignItems: 'center', - }, - sharePopupContainer: { - width: '80%', - backgroundColor: '#fff', - borderRadius: 10, - padding: 20, - alignItems: 'center', - }, - sharePopupTitle: { - fontSize: 18, - fontWeight: 'bold', - marginBottom: 20, - }, - shareButton: { - backgroundColor: '#6200ee', - padding: 10, - borderRadius: 5, - width: '100%', - alignItems: 'center', - }, - shareButtonText: { - color: '#fff', - fontSize: 16, - fontWeight: '600', - }, + container: { + flex: 1, + backgroundColor: "#fff", + marginTop: 20, + paddingBottom: 80, // Add padding at the bottom equal to the height of the bottom tab bar + }, + reviewContainer: { + alignItems: "flex-start", + padding: 20, + paddingBottom: 30, + }, + coverImage: { + width: 200, + height: 200, + borderRadius: 10, + marginBottom: 20, + overflow: "scroll", + }, + voteButton: { + flexDirection: "row", + alignItems: "center", + marginHorizontal: 5, + }, + ratingImageWrapper: { + width: "100%", + height: 100, + overflow: "hidden", // This ensures cropping of the image + alignItems: "center", + }, + ratingContainer: { + justifyContent: "center", + alignItems: "flex-start", + }, + mediaContainer: { + flexDirection: "row", + alignItems: "flex-start", + width: "95%", + marginTop: 20, + }, + songName: { + fontSize: 20, // Adjust size as needed + fontWeight: "bold", + color: "#000", // Adjust color as needed + flexWrap: "wrap", + marginTop: 12, + textAlign: "left", + }, + artistName: { + fontSize: 16, + color: "#888", + }, + comment: { + fontSize: 16, + }, + + noReviewsText: { + textAlign: "center", + color: "#888", + marginVertical: 20, + }, + comments: { + width: "100%", + marginTop: 20, + marginBottom: 50, + marginLeft: -20, + }, + rating: { + width: "100%", + justifyContent: "center", + alignItems: "center", + marginTop: 32, + paddingRight: 20, // Add this to account for the left padding of reviewContainer + }, + + ratingImage: { + alignSelf: "center", // Add this + }, + + tagsContainer: { + flexDirection: "row", + marginBottom: 10, + paddingVertical: 15, + minHeight: 60, // Change from fixed height to minHeight + flexWrap: "wrap", // Allows wrapping to a new line + gap: 8, // Space between tags + }, + + tag: { + backgroundColor: "rgba(242, 128, 55, 0.65)", + paddingVertical: 5, + paddingHorizontal: 12, + borderRadius: 20, + marginHorizontal: 5, + borderWidth: 1, + borderColor: "#C0C0C0", + minHeight: 25, // Change from height to minHeight + justifyContent: "center", // Add this + }, + + tagText: { + color: "#333", + fontSize: 12, + lineHeight: 16, // Add this to ensure proper text spacing + textAlignVertical: "center", // Add this + }, + actionsContainer: { + flexDirection: "row", + justifyContent: "space-between", + width: "100%", + marginTop: 15, + }, + voteContainer: { + flexDirection: "row", + alignItems: "center", + }, + vote: { + marginHorizontal: 5, + }, + voteIcon: { + marginHorizontal: 10, + }, + vinyl: { + position: "absolute", + top: 0, + right: 0, + alignItems: "center", + }, + musicDisk: { + position: "absolute", + top: 0, + right: 0, + width: 100, + height: 100, + }, + mediaCover: { + position: "absolute", + width: 80, + height: 80, + top: -12, + right: -5, + borderRadius: 40, + overflow: "hidden", + }, + topSection: { + position: "relative", + width: "100%", + }, + topContainer: { + flexDirection: "row", + justifyContent: "space-between", // Align left and right sections + width: "100%", + marginBottom: 10, + }, + leftSection: { + flexDirection: "row", + alignItems: "center", + }, + profilePicture: { + width: 45, + height: 45, + borderRadius: 22.5, + marginRight: 10, + backgroundColor: "grey", + }, + textContainer: { + flexDirection: "column", + }, + displayName: { + fontWeight: "bold", + fontSize: 16, + color: "#333", + }, + username: { + fontSize: 13, + color: "#888", + }, + commentBoxContainer: { + position: "absolute", + bottom: 50, + left: 0, + right: 0, + backgroundColor: "#fff", + padding: 10, + borderTopWidth: 1, + borderTopColor: "#ddd", + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + }, + commentInput: { + flex: 1, + flexDirection: "row", + height: 50, + borderRadius: 5, + paddingHorizontal: 16, + paddingVertical: 16, + backgroundColor: "#EFF1F5", + }, + submitButtonText: { + color: "#333", + fontWeight: "bold", + }, + menuButton: { padding: 10, marginRight: 10 }, + menuText: { fontSize: 24, marginLeft: 10 }, + editInput: { + borderColor: "#ddd", + borderWidth: 1, + margin: 10, + padding: 10, + marginRight: 25, + }, + saveButton: { + backgroundColor: "#ddd", + padding: 10, + borderRadius: 10, + margin: 10, + width: 60, + }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.5)", // Semi-transparent background + justifyContent: "center", // Center the modal vertically + alignItems: "center", // Center the modal horizontally + }, + popupContainer: { + backgroundColor: "#fff", // Modal background + borderRadius: 10, + padding: 20, + width: "80%", // Adjust width as needed + alignItems: "center", + zIndex: 1000, // Ensure the modal is on top + }, + popupOption: { + padding: 10, + borderBottomWidth: 1, + borderBottomColor: "#ddd", + width: "100%", + alignItems: "center", + }, + sharePopupContainer: { + width: "80%", + backgroundColor: "#fff", + borderRadius: 10, + padding: 20, + alignItems: "center", + }, + sharePopupTitle: { + fontSize: 18, + fontWeight: "bold", + marginBottom: 20, + }, + shareButton: { + backgroundColor: "#6200ee", + padding: 10, + borderRadius: 5, + width: "100%", + alignItems: "center", + }, + shareButtonText: { + color: "#fff", + fontSize: 16, + fontWeight: "600", + }, }); export default ReviewPage; diff --git a/frontend/components/search/AlbumSearchCard.tsx b/frontend/components/search/AlbumSearchCard.tsx index 80ea51a0..7307f3e8 100644 --- a/frontend/components/search/AlbumSearchCard.tsx +++ b/frontend/components/search/AlbumSearchCard.tsx @@ -1,104 +1,117 @@ -import React from 'react'; -import { router } from 'expo-router'; -import { View, Text, StyleSheet, Image, TouchableOpacity } from 'react-native'; +import React from "react"; +import { router } from "expo-router"; +import { View, Text, StyleSheet, Image, TouchableOpacity } from "react-native"; interface AlbumSearchCardProps { - id: number; - rank: number; - artist_name: string; - album_name: string; - cover: string; + id: number; + rank: number; + artist_name: string; + album_name: string; + cover: string; } -const AlbumSearchCard: React.FC = ({ id, rank, artist_name, album_name, cover }) => { - const placeholderImage = - 'https://upload.wikimedia.org/wikipedia/en/thumb/d/d5/Taylor_Swift_-_1989_%28Taylor%27s_Version%29.png/220px-Taylor_Swift_-_1989_%28Taylor%27s_Version%29.png'; +const AlbumSearchCard: React.FC = ({ + id, + rank, + artist_name, + album_name, + cover, +}) => { + const placeholderImage = + "https://upload.wikimedia.org/wikipedia/en/thumb/d/d5/Taylor_Swift_-_1989_%28Taylor%27s_Version%29.png/220px-Taylor_Swift_-_1989_%28Taylor%27s_Version%29.png"; - return ( - - router.push({ - pathname: '/MediaPage', - params: { - mediaType: 'album', - mediaId: id, - }, - }) - }> - - {/* Rank */} - {rank}. + return ( + + router.push({ + pathname: "/MediaPage", + params: { + mediaType: "album", + mediaId: id, + }, + }) + } + > + + {/* Rank */} + {rank}. - {/* Album Cover */} - - - + {/* Album Cover */} + + + - {/* Record Image */} - - - - + {/* Record Image */} + + + + - {/* Album and Artist Name */} - {album_name} - {artist_name} - - ); + {/* Album and Artist Name */} + {album_name} + {artist_name} + + ); }; const styles = StyleSheet.create({ - cardContainer: { - marginBottom: 16, - width: 160, - }, - albumContainer: { - flexDirection: 'row', // Set horizontal layout to align rank and cover side-by-side - alignItems: 'center', // Align items vertically centered - position: 'relative', - width: 140, - }, - rank: { - color: '#000', - fontSize: 18, - fontWeight: '600', - lineHeight: 20, - marginRight: 6, // Spacing between rank and cover image - marginTop: -85, - }, - coverContainer: { - zIndex: 2, // Ensure cover is on top - }, - recordContainer: { - position: 'absolute', // Position record on top of cover - bottom: 5, - left: '50%', - transform: [{ translateX: 0 }], - }, - recordImage: { - width: 100, - height: 100, - }, - albumCover: { - width: 110, - height: 110, - borderRadius: 8, - }, - albumName: { - fontSize: 16, - fontWeight: 'bold', - color: '#434343', - marginTop: 4, - textAlign: 'left', - marginLeft: 24, - }, - artistName: { - fontSize: 14, - color: '#434343', - textAlign: 'left', - marginLeft: 24, - }, + cardContainer: { + marginBottom: 16, + width: 160, + }, + albumContainer: { + flexDirection: "row", // Set horizontal layout to align rank and cover side-by-side + alignItems: "center", // Align items vertically centered + position: "relative", + width: 140, + }, + rank: { + color: "#000", + fontSize: 18, + fontWeight: "600", + lineHeight: 20, + marginRight: 6, // Spacing between rank and cover image + marginTop: -85, + }, + coverContainer: { + zIndex: 2, // Ensure cover is on top + }, + recordContainer: { + position: "absolute", // Position record on top of cover + bottom: 5, + left: "50%", + transform: [{ translateX: 0 }], + }, + recordImage: { + width: 100, + height: 100, + }, + albumCover: { + width: 110, + height: 110, + borderRadius: 8, + }, + albumName: { + fontSize: 16, + fontWeight: "bold", + color: "#434343", + marginTop: 4, + textAlign: "left", + marginLeft: 24, + }, + artistName: { + fontSize: 14, + color: "#434343", + textAlign: "left", + marginLeft: 24, + }, }); export default AlbumSearchCard; diff --git a/frontend/components/search/SearchResults.tsx b/frontend/components/search/SearchResults.tsx index 7e1c1b9e..5f05f35b 100644 --- a/frontend/components/search/SearchResults.tsx +++ b/frontend/components/search/SearchResults.tsx @@ -1,158 +1,168 @@ -import React, { useState } from 'react'; -import { StyleSheet, View, Text, ScrollView, Dimensions } from 'react-native'; -import SongChip from '@/components/search/SongChip'; -import AlbumSearchCard from '@/components/search/AlbumSearchCard'; -import ProfileChip from '@/components/search/ProfileChip'; -import Filter from '@/components/search/Filter'; -import { takeWhile } from 'lodash'; +import React, { useState } from "react"; +import { StyleSheet, View, Text, ScrollView, Dimensions } from "react-native"; +import SongChip from "@/components/search/SongChip"; +import AlbumSearchCard from "@/components/search/AlbumSearchCard"; +import ProfileChip from "@/components/search/ProfileChip"; +import Filter from "@/components/search/Filter"; +import { takeWhile } from "lodash"; interface SearchResultsProps { - songs: Media[]; - albums: Media[]; - profiles: UserProfile[]; - isLoading: boolean; - filter: 'all' | 'songs' | 'albums' | 'profile'; + songs: Media[]; + albums: Media[]; + profiles: UserProfile[]; + isLoading: boolean; + filter: "all" | "songs" | "albums" | "profile"; } -const SearchResults: React.FC = ({ songs, albums, profiles, isLoading }) => { - if (isLoading) { - return Searching...; - } +const SearchResults: React.FC = ({ + songs, + albums, + profiles, + isLoading, +}) => { + if (isLoading) { + return Searching...; + } - if (songs?.length === 0 && albums?.length === 0 && profiles?.length == 0) { - return No results found; - } + if (songs?.length === 0 && albums?.length === 0 && profiles?.length == 0) { + return No results found; + } - const filterOptions = ['all', 'songs', 'albums', 'profile']; + const filterOptions = ["all", "songs", "albums", "profile"]; - const [selectedFilter, setSelectedFilter] = useState('all'); + const [selectedFilter, setSelectedFilter] = useState("all"); - const handleFilterChange = (filter: FilterOption) => { - setSelectedFilter(filter); - }; + const handleFilterChange = (filter: FilterOption) => { + setSelectedFilter(filter); + }; - return ( - - - - - {(selectedFilter === 'all' || selectedFilter === 'profile') && profiles?.length > 0 && ( - - Profiles - {profiles?.map((profile, idx) => ( - - ))} - - )} + return ( + + + + + {(selectedFilter === "all" || selectedFilter === "profile") && + profiles?.length > 0 && ( + + Profiles + {profiles?.map((profile, idx) => ( + + ))} + + )} - {(selectedFilter === 'all' || selectedFilter === 'albums') && ( - - Albums - - {albums.map((album, index) => ( - - - - ))} - - - )} - {(selectedFilter === 'all' || selectedFilter === 'songs') && ( - - Songs - - {songs?.map((song, index) => ( - - ))} - - - )} - - - - ); + {(selectedFilter === "all" || selectedFilter === "albums") && ( + + Albums + + {albums.map((album, index) => ( + + + + ))} + + + )} + {(selectedFilter === "all" || selectedFilter === "songs") && ( + + Songs + + {songs?.map((song, index) => ( + + ))} + + + )} + + + + ); }; const styles = StyleSheet.create({ - title: { - fontSize: 24, - fontWeight: 'bold', - padding: 16, - }, - container: { - flex: 1, - paddingHorizontal: 16, - flexWrap: 'wrap', - flexDirection: 'row', - }, - headerContainer: { - marginTop: 10, - marginBottom: 20, - fontSize: 16, - textAlign: 'center', - fontWeight: '600', - color: '#000000', - }, - section: { - marginTop: 20, - }, - sectionTitle: { - fontSize: 20, - fontWeight: '600', - marginBottom: 10, - color: '#434343', - }, - loadingText: { - textAlign: 'center', - marginTop: 20, - color: '#666666', - }, - twoColumnList: { - flex: 1, - flexWrap: 'wrap', - flexDirection: 'row', - width: '100%', - }, - resultGrid: { - flexDirection: 'row', - flexWrap: 'wrap', - gap: 10, - width: '100%', - }, - songsList: { - marginRight: 20, - width: '100%', - }, - albumsList: { - marginBottom: 16, - paddingHorizontal: 4, - flex: 1, - flexWrap: 'wrap', - }, - noResults: { - textAlign: 'center', - marginTop: 20, - color: '#666666', - }, + title: { + fontSize: 24, + fontWeight: "bold", + padding: 16, + }, + container: { + flex: 1, + paddingHorizontal: 16, + flexWrap: "wrap", + flexDirection: "row", + }, + headerContainer: { + marginTop: 10, + marginBottom: 20, + fontSize: 16, + textAlign: "center", + fontWeight: "600", + color: "#000000", + }, + section: { + marginTop: 20, + }, + sectionTitle: { + fontSize: 20, + fontWeight: "600", + marginBottom: 10, + color: "#434343", + }, + loadingText: { + textAlign: "center", + marginTop: 20, + color: "#666666", + }, + twoColumnList: { + flex: 1, + flexWrap: "wrap", + flexDirection: "row", + width: "100%", + }, + resultGrid: { + flexDirection: "row", + flexWrap: "wrap", + gap: 10, + width: "100%", + }, + songsList: { + marginRight: 20, + width: "100%", + }, + albumsList: { + marginBottom: 16, + paddingHorizontal: 4, + flex: 1, + flexWrap: "wrap", + }, + noResults: { + textAlign: "center", + marginTop: 20, + color: "#666666", + }, }); export default SearchResults; From 64cc3ef0da444e90dad8e5388ab0c8138bba248f Mon Sep 17 00:00:00 2001 From: Abhik Ray Date: Thu, 5 Dec 2024 01:16:59 -0500 Subject: [PATCH 3/3] lint --- frontend/app/ReviewPage.tsx | 1503 ++++++++++++++++++----------------- 1 file changed, 773 insertions(+), 730 deletions(-) diff --git a/frontend/app/ReviewPage.tsx b/frontend/app/ReviewPage.tsx index 2d6644fe..06884cc5 100644 --- a/frontend/app/ReviewPage.tsx +++ b/frontend/app/ReviewPage.tsx @@ -1,742 +1,785 @@ -import HeaderComponent from '@/components/HeaderComponent'; -import CommentComponent from '@/components/CommentComponent'; -import axios from 'axios'; -import { router, useLocalSearchParams } from 'expo-router'; -import React, { useEffect, useState } from 'react'; +import HeaderComponent from "@/components/HeaderComponent"; +import CommentComponent from "@/components/CommentComponent"; +import axios from "axios"; +import { router, useLocalSearchParams } from "expo-router"; +import React, { useEffect, useState } from "react"; import { - View, - Text, - StyleSheet, - Image, - ScrollView, - TouchableOpacity, - TextInput, - Modal, - KeyboardAvoidingView, -} from 'react-native'; -import Rating0 from '@/assets/images/Ratings/Property0.svg'; -import Rating1 from '@/assets/images/Ratings/Property1.svg'; -import Rating2 from '@/assets/images/Ratings/Property2.svg'; -import Rating3 from '@/assets/images/Ratings/Property3.svg'; -import Rating4 from '@/assets/images/Ratings/Property4.svg'; -import Rating5 from '@/assets/images/Ratings/Property5.svg'; -import Rating6 from '@/assets/images/Ratings/Property6.svg'; -import Rating7 from '@/assets/images/Ratings/Property7.svg'; -import Rating8 from '@/assets/images/Ratings/Property8.svg'; -import Rating9 from '@/assets/images/Ratings/Property9.svg'; -import Rating10 from '@/assets/images/Ratings/Property10.svg'; -import Downvote from '@/assets/images/ReviewPreview/downvote.svg'; -import Upvote from '@/assets/images/ReviewPreview/upvote.svg'; -import Comment from '@/assets/images/ReviewPreview/comment.svg'; -import Share from '@/assets/images/ReviewPreview/share.svg'; -import Upload from '@/assets/images/Icons/Upload.svg'; -import { useAuthContext } from '@/components/AuthProvider'; + View, + Text, + StyleSheet, + Image, + ScrollView, + TouchableOpacity, + TextInput, + Modal, + KeyboardAvoidingView, +} from "react-native"; +import Rating0 from "@/assets/images/Ratings/Property0.svg"; +import Rating1 from "@/assets/images/Ratings/Property1.svg"; +import Rating2 from "@/assets/images/Ratings/Property2.svg"; +import Rating3 from "@/assets/images/Ratings/Property3.svg"; +import Rating4 from "@/assets/images/Ratings/Property4.svg"; +import Rating5 from "@/assets/images/Ratings/Property5.svg"; +import Rating6 from "@/assets/images/Ratings/Property6.svg"; +import Rating7 from "@/assets/images/Ratings/Property7.svg"; +import Rating8 from "@/assets/images/Ratings/Property8.svg"; +import Rating9 from "@/assets/images/Ratings/Property9.svg"; +import Rating10 from "@/assets/images/Ratings/Property10.svg"; +import Downvote from "@/assets/images/ReviewPreview/downvote.svg"; +import Upvote from "@/assets/images/ReviewPreview/upvote.svg"; +import Comment from "@/assets/images/ReviewPreview/comment.svg"; +import Share from "@/assets/images/ReviewPreview/share.svg"; +import Upload from "@/assets/images/Icons/Upload.svg"; +import { useAuthContext } from "@/components/AuthProvider"; interface ReviewPageProps { - route: { - params: { - review_id: string; - user_id: string; - }; - }; + route: { + params: { + review_id: string; + user_id: string; + }; + }; } const ReviewPage: React.FC = ({ route }) => { - const { review_id, user_id } = useLocalSearchParams<{ - review_id: string; - user_id: string; - }>(); - const [review, setReview] = useState(); - const [comments, setComments] = useState(); - const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; - const { userId } = useAuthContext(); - const MusicDisk = require('../assets/images/music-disk.png'); - - const [currentVote, setCurrentVote] = useState(false); // does a vote currently exist? - const [currentVoteValue, setCurrentVoteValue] = useState(false); // what is the current vote's value? - - const [upvoteCount, setUpvoteCount] = useState(0); - const [downvoteCount, setDownvoteCount] = useState(0); - const [commentCount, setCommentCount] = useState(0); - const [newComment, setNewComment] = useState(''); - - const [isEditable, setIsEditable] = useState(false); - const [editedComment, setEditedComment] = useState(''); - const [showPopup, setShowPopup] = useState(false); - - const ratingImages = { - 0: Rating0, - 1: Rating1, - 2: Rating2, - 3: Rating3, - 4: Rating4, - 5: Rating5, - 6: Rating6, - 7: Rating7, - 8: Rating8, - 9: Rating9, - 10: Rating10, - }; - - const [sharePopupVisible, setSharePopupVisible] = useState(false); - - const handleSharePress = () => { - setSharePopupVisible(true); // Show the share popup - }; - - const closeSharePopup = () => { - setSharePopupVisible(false); // Close the share popup - }; - - const getRatingImage = (rating: keyof typeof ratingImages) => { - return ratingImages[rating]; // Access the image from the preloaded images object - }; - - const handleVotePress = async (newVoteValue: boolean) => { - if (currentVote) { - // if there is already a vote value, we have to delete or swap it - if (currentVoteValue && newVoteValue) { - // if there is an upvote and the user clicks upvote again - setCurrentVote(false); // cancel out the vote - setUpvoteCount(upvoteCount - 1); - } else if (!currentVoteValue && !newVoteValue) { - // if there is a downvote and the user clicks downvote again - setCurrentVote(false); // cancel out the vote - setDownvoteCount(downvoteCount - 1); - } else if (currentVoteValue && !newVoteValue) { - // if there is an upvote and the user clicks downvote - setCurrentVoteValue(false); - setUpvoteCount(upvoteCount - 1); - setDownvoteCount(downvoteCount + 1); - } else if (!currentVoteValue && newVoteValue) { - // if there is a downvote and the user clicks upvote - setCurrentVoteValue(true); - setUpvoteCount(upvoteCount + 1); - setDownvoteCount(downvoteCount - 1); - } - } else { - setCurrentVote(true); - setCurrentVoteValue(newVoteValue); - if (newVoteValue) { - setUpvoteCount(upvoteCount + 1); - } else { - setDownvoteCount(downvoteCount + 1); - } - } - - try { - await axios.post(`${BASE_URL}/reviews/vote`, { - user_id: userId, - post_id: review_id, - upvote: newVoteValue, - }); - } catch (error) { - console.error('Error downvoting comment:', error); - } - }; - - const handleCommentPress = () => { - console.log('comment icon pressed'); - }; - - const handleCommentSubmit = async () => { - if (!newComment.trim()) return; // Do not submit if the comment is empty - setCommentCount(commentCount + 1); - - try { - await axios.post( - `${BASE_URL}/reviews/comment`, - { - user_id: userId, - review_id: parseInt(review_id, 10), - text: newComment, - }, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ); - setNewComment(''); // Clear the input after submitting - // Fetch updated comments after submitting - fetchComments(); - } catch (error) { - console.error('Error submitting comment:', error); - } - }; - - const handleEditSave = async () => { - try { - const requestBody = { - user_id: userId, // User ID to validate ownership - comment: editedComment, // The updated comment - }; - - await axios.patch(`${BASE_URL}/reviews/${review_id}`, requestBody); - setIsEditable(false); - setReview((prev) => (prev ? { ...prev, comment: editedComment } : prev)); - } catch (error) { - console.error('Error saving edited review:', error); - } - }; - - const handleMenuOption = (option: string) => { - setShowPopup(false); - if (option === 'edit') { - setIsEditable(true); - setEditedComment(review?.comment || ''); - } else if (option === 'delete') { - // Add delete functionality - } else if (option === 'manageComments') { - // Add manage comments functionality - } else if (option === 'share') { - // Add share functionality - } - }; - - // Fetch the review data using the review_id - useEffect(() => { - const fetchReview = async () => { - try { - const response = await axios.get(`${BASE_URL}/reviews/${review_id}`); - const review = response.data; - setReview(review); - if (review) { - setUpvoteCount(review.review_stat.upvotes); - setDownvoteCount(review.review_stat.downvotes); - setCommentCount(review.review_stat.comment_count); - } - } catch (error) { - console.error('Error fetching review:', error); - } - }; - - fetchReview(); - fetchComments(); - }, [review_id, userId, user_id, newComment]); - - const fetchComments = async () => { - try { - const response = await axios.get(`${BASE_URL}/reviews/comments/${review_id}`); - setComments(response.data); - } catch (error) { - console.error('Error fetching comments:', error); - } - }; - - useEffect(() => { - const fetchVote = async () => { - try { - if (review) { - setUpvoteCount(review.review_stat.upvotes); - setDownvoteCount(review.review_stat.downvotes); - setCommentCount(review.review_stat.comment_count); - } - const response = await axios.get(`${BASE_URL}/reviews/vote/${userId}/${review_id}`); - if (response.data) { - setCurrentVote(true); - const { upvote } = response.data; // Assuming the API returns { user_id, post_id, upvote } - setCurrentVoteValue(upvote); - } else { - setCurrentVote(false); - } - } catch (error) { - console.error('Error fetching vote:', error); - } - }; - fetchVote(); - }, [review_id, userId, newComment]); - - const handleUserPress = () => { - // Navigate to the UserPage when the user is clicked - const pathName = review?.user_id === userId ? '/(tabs)/profile' : '/(tabs)/user'; - router.push({ - pathname: pathName, - params: { - userId: review?.user_id, - }, - }); - }; - - const handleMediaPress = () => { - // Navigate to the MediaPage - console.log('Media pressed'); - router.push({ - pathname: '/(tabs)/MediaPage', - params: { mediaId: review?.media_id, mediaType: review?.media_type }, - }); - }; - - return review ? ( - - - - - - - - - - - {review.display_name} - @{review.username} - - - - - - - - {review.media_cover && ( - - )} - - - - - - - - {review.media_title} - - {review.media_artist} - - - - - {React.createElement( - getRatingImage(review.rating as keyof typeof ratingImages), - { - width: 150, // Adjust size as needed - height: 150, - }, - { - style: styles.ratingImage, - } as any, - )} - - - setShowPopup(false)} - activeOpacity={1} // Prevent the modal itself from closing on tap - > - - handleMenuOption('edit')}> - Edit Review - - handleMenuOption('manageComments')}> - Manage Comments - - handleMenuOption('share')}> - Share - - handleMenuOption('delete')}> - Delete - - - - - - - - Share This Review - console.log('Share to Friends Pressed')}> - Share to Friends - - - - - {isEditable ? ( - - - - Save - - - ) : ( - {review.comment} - )} - {/* Tags Section */} - {review.tags && review.tags.length > 0 && ( - - {review.tags.map((tag, index) => ( - - {tag} - - ))} - - )} - {/* Action Buttons */} - - - handleVotePress(true)} style={styles.voteButton}> - - {upvoteCount} - - handleVotePress(false)} style={styles.voteButton}> - - {downvoteCount} - - - - - {review.review_stat.comment_count} - - - - - - - {review.user_id === userId && ( - setShowPopup(true)}> - - - )} - - - {comments && comments.length > 0 ? ( - comments.map((comment, index) => { - return ; - }) - ) : ( - No comments found. - )} - - - - - - {/* Fixed TextBox for Comment */} - - - - - - - - - ) : ( - - Loading... - - ); + const { review_id, user_id } = useLocalSearchParams<{ + review_id: string; + user_id: string; + }>(); + const [review, setReview] = useState(); + const [comments, setComments] = useState(); + const BASE_URL = process.env.EXPO_PUBLIC_BASE_URL; + const { userId } = useAuthContext(); + const MusicDisk = require("../assets/images/music-disk.png"); + + const [currentVote, setCurrentVote] = useState(false); // does a vote currently exist? + const [currentVoteValue, setCurrentVoteValue] = useState(false); // what is the current vote's value? + + const [upvoteCount, setUpvoteCount] = useState(0); + const [downvoteCount, setDownvoteCount] = useState(0); + const [commentCount, setCommentCount] = useState(0); + const [newComment, setNewComment] = useState(""); + + const [isEditable, setIsEditable] = useState(false); + const [editedComment, setEditedComment] = useState(""); + const [showPopup, setShowPopup] = useState(false); + + const ratingImages = { + 0: Rating0, + 1: Rating1, + 2: Rating2, + 3: Rating3, + 4: Rating4, + 5: Rating5, + 6: Rating6, + 7: Rating7, + 8: Rating8, + 9: Rating9, + 10: Rating10, + }; + + const [sharePopupVisible, setSharePopupVisible] = useState(false); + + const handleSharePress = () => { + setSharePopupVisible(true); // Show the share popup + }; + + const closeSharePopup = () => { + setSharePopupVisible(false); // Close the share popup + }; + + const getRatingImage = (rating: keyof typeof ratingImages) => { + return ratingImages[rating]; // Access the image from the preloaded images object + }; + + const handleVotePress = async (newVoteValue: boolean) => { + if (currentVote) { + // if there is already a vote value, we have to delete or swap it + if (currentVoteValue && newVoteValue) { + // if there is an upvote and the user clicks upvote again + setCurrentVote(false); // cancel out the vote + setUpvoteCount(upvoteCount - 1); + } else if (!currentVoteValue && !newVoteValue) { + // if there is a downvote and the user clicks downvote again + setCurrentVote(false); // cancel out the vote + setDownvoteCount(downvoteCount - 1); + } else if (currentVoteValue && !newVoteValue) { + // if there is an upvote and the user clicks downvote + setCurrentVoteValue(false); + setUpvoteCount(upvoteCount - 1); + setDownvoteCount(downvoteCount + 1); + } else if (!currentVoteValue && newVoteValue) { + // if there is a downvote and the user clicks upvote + setCurrentVoteValue(true); + setUpvoteCount(upvoteCount + 1); + setDownvoteCount(downvoteCount - 1); + } + } else { + setCurrentVote(true); + setCurrentVoteValue(newVoteValue); + if (newVoteValue) { + setUpvoteCount(upvoteCount + 1); + } else { + setDownvoteCount(downvoteCount + 1); + } + } + + try { + await axios.post(`${BASE_URL}/reviews/vote`, { + user_id: userId, + post_id: review_id, + upvote: newVoteValue, + }); + } catch (error) { + console.error("Error downvoting comment:", error); + } + }; + + const handleCommentPress = () => { + console.log("comment icon pressed"); + }; + + const handleCommentSubmit = async () => { + if (!newComment.trim()) return; // Do not submit if the comment is empty + setCommentCount(commentCount + 1); + + try { + await axios.post( + `${BASE_URL}/reviews/comment`, + { + user_id: userId, + review_id: parseInt(review_id, 10), + text: newComment, + }, + { + headers: { + "Content-Type": "application/json", + }, + }, + ); + setNewComment(""); // Clear the input after submitting + // Fetch updated comments after submitting + fetchComments(); + } catch (error) { + console.error("Error submitting comment:", error); + } + }; + + const handleEditSave = async () => { + try { + const requestBody = { + user_id: userId, // User ID to validate ownership + comment: editedComment, // The updated comment + }; + + await axios.patch(`${BASE_URL}/reviews/${review_id}`, requestBody); + setIsEditable(false); + setReview((prev) => (prev ? { ...prev, comment: editedComment } : prev)); + } catch (error) { + console.error("Error saving edited review:", error); + } + }; + + const handleMenuOption = (option: string) => { + setShowPopup(false); + if (option === "edit") { + setIsEditable(true); + setEditedComment(review?.comment || ""); + } else if (option === "delete") { + // Add delete functionality + } else if (option === "manageComments") { + // Add manage comments functionality + } else if (option === "share") { + // Add share functionality + } + }; + + // Fetch the review data using the review_id + useEffect(() => { + const fetchReview = async () => { + try { + const response = await axios.get(`${BASE_URL}/reviews/${review_id}`); + const review = response.data; + setReview(review); + if (review) { + setUpvoteCount(review.review_stat.upvotes); + setDownvoteCount(review.review_stat.downvotes); + setCommentCount(review.review_stat.comment_count); + } + } catch (error) { + console.error("Error fetching review:", error); + } + }; + + fetchReview(); + fetchComments(); + }, [review_id, userId, user_id, newComment]); + + const fetchComments = async () => { + try { + const response = await axios.get( + `${BASE_URL}/reviews/comments/${review_id}`, + ); + setComments(response.data); + } catch (error) { + console.error("Error fetching comments:", error); + } + }; + + useEffect(() => { + const fetchVote = async () => { + try { + if (review) { + setUpvoteCount(review.review_stat.upvotes); + setDownvoteCount(review.review_stat.downvotes); + setCommentCount(review.review_stat.comment_count); + } + const response = await axios.get( + `${BASE_URL}/reviews/vote/${userId}/${review_id}`, + ); + if (response.data) { + setCurrentVote(true); + const { upvote } = response.data; // Assuming the API returns { user_id, post_id, upvote } + setCurrentVoteValue(upvote); + } else { + setCurrentVote(false); + } + } catch (error) { + console.error("Error fetching vote:", error); + } + }; + fetchVote(); + }, [review_id, userId, newComment]); + + const handleUserPress = () => { + // Navigate to the UserPage when the user is clicked + const pathName = + review?.user_id === userId ? "/(tabs)/profile" : "/(tabs)/user"; + router.push({ + pathname: pathName, + params: { + userId: review?.user_id, + }, + }); + }; + + const handleMediaPress = () => { + // Navigate to the MediaPage + console.log("Media pressed"); + router.push({ + pathname: "/(tabs)/MediaPage", + params: { mediaId: review?.media_id, mediaType: review?.media_type }, + }); + }; + + return review ? ( + + + + + + + + + + + + {review.display_name} + + @{review.username} + + + + + + + + {review.media_cover && ( + + )} + + + + + + + + {review.media_title} + + {review.media_artist} + + + + + {React.createElement( + getRatingImage(review.rating as keyof typeof ratingImages), + { + width: 150, // Adjust size as needed + height: 150, + }, + { + style: styles.ratingImage, + } as any, + )} + + + setShowPopup(false)} + activeOpacity={1} // Prevent the modal itself from closing on tap + > + + handleMenuOption("edit")} + > + Edit Review + + handleMenuOption("manageComments")} + > + Manage Comments + + handleMenuOption("share")} + > + Share + + handleMenuOption("delete")} + > + Delete + + + + + + + + Share This Review + console.log("Share to Friends Pressed")} + > + Share to Friends + + + + + {isEditable ? ( + + + + Save + + + ) : ( + {review.comment} + )} + {/* Tags Section */} + {review.tags && review.tags.length > 0 && ( + + {review.tags.map((tag, index) => ( + + {tag} + + ))} + + )} + {/* Action Buttons */} + + + handleVotePress(true)} + style={styles.voteButton} + > + + {upvoteCount} + + handleVotePress(false)} + style={styles.voteButton} + > + + {downvoteCount} + + + + + {review.review_stat.comment_count} + + + + + + + {review.user_id === userId && ( + setShowPopup(true)} + > + + + )} + + + {comments && comments.length > 0 ? ( + comments.map((comment, index) => { + return ; + }) + ) : ( + No comments found. + )} + + + + + + {/* Fixed TextBox for Comment */} + + + + + + + + + ) : ( + + Loading... + + ); }; const styles = StyleSheet.create({ - page: { - flex: 1, - backgroundColor: '#fff', - paddingBottom: 80, // Add padding at the bottom equal to the height of the bottom tab bar - }, - container: { - flex: 1, - backgroundColor: '#fff', - marginTop: 20, - }, - reviewContainer: { - alignItems: 'flex-start', - paddingLeft: 20, - paddingBottom: 30, - }, - coverImage: { - width: 200, - height: 200, - borderRadius: 10, - marginBottom: 20, - overflow: 'scroll', - }, - voteButton: { - flexDirection: 'row', - alignItems: 'center', - marginHorizontal: 5, - }, - ratingImageWrapper: { - width: '100%', - height: 100, - overflow: 'hidden', // This ensures cropping of the image - alignItems: 'center', - }, - ratingContainer: { - justifyContent: 'center', - alignItems: 'flex-start', - }, - mediaContainer: { - flexDirection: 'row', - alignItems: 'flex-start', - width: '95%', - height: '15%', - marginTop: 20, - }, - songName: { - fontSize: 20, // Adjust size as needed - fontWeight: 'bold', - color: '#000', // Adjust color as needed - flexWrap: 'wrap', - marginTop: 12, - textAlign: 'left', - width: '100%', - }, - artistName: { - fontSize: 16, - color: '#888', - }, - comment: { - fontSize: 16, - }, - - noReviewsText: { - textAlign: 'center', - color: '#888', - marginVertical: 20, - }, - comments: { - width: '100%', - marginTop: 20, - marginBottom: 50, - marginLeft: -20, - }, - rating: { - width: '100%', - justifyContent: 'center', - alignItems: 'center', - marginTop: 8, - paddingRight: 20, // Add this to account for the left padding of reviewContainer - }, - - ratingImage: { - alignSelf: 'center', // Add this - }, - - tagsContainer: { - flexDirection: 'row', - marginBottom: 10, - paddingVertical: 15, - minHeight: 60, // Change from fixed height to minHeight - flexWrap: 'wrap', // Allows wrapping to a new line - gap: 8, // Space between tags - }, - - tag: { - backgroundColor: 'rgba(242, 128, 55, 0.65)', - paddingVertical: 5, - paddingHorizontal: 12, - borderRadius: 20, - marginHorizontal: 5, - borderWidth: 1, - borderColor: '#C0C0C0', - minHeight: 25, // Change from height to minHeight - justifyContent: 'center', // Add this - }, - - tagText: { - color: '#333', - fontSize: 12, - lineHeight: 16, // Add this to ensure proper text spacing - textAlignVertical: 'center', // Add this - }, - actionsContainer: { - flexDirection: 'row', - justifyContent: 'space-between', - width: '100%', - marginTop: 15, - }, - voteContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - vote: { - marginHorizontal: 5, - }, - voteIcon: { - marginHorizontal: 10, - }, - vinyl: { - position: 'absolute', - top: 0, - right: 0, - alignItems: 'center', - }, - musicDisk: { - position: 'absolute', - top: 0, - right: 0, - width: 100, - height: 100, - }, - mediaCover: { - position: 'absolute', - width: 80, - height: 80, - top: -12, - right: -5, - borderRadius: 40, - overflow: 'hidden', - }, - topSection: { - position: 'relative', - width: '100%', - }, - topContainer: { - flexDirection: 'row', - justifyContent: 'space-between', // Align left and right sections - width: '100%', - marginBottom: 10, - }, - leftSection: { - flexDirection: 'row', - alignItems: 'center', - }, - profilePicture: { - width: 45, - height: 45, - borderRadius: 22.5, - marginRight: 10, - backgroundColor: 'grey', - }, - textContainer: { - flexDirection: 'column', - }, - displayName: { - fontWeight: 'bold', - fontSize: 16, - color: '#333', - }, - username: { - fontSize: 13, - color: '#888', - }, - commentBoxContainer: { - position: 'absolute', - bottom: 50, - left: 0, - right: 0, - backgroundColor: '#fff', - padding: 10, - borderTopWidth: 1, - borderTopColor: '#ddd', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - }, - commentInput: { - flex: 1, - flexDirection: 'row', - height: 50, - borderRadius: 5, - paddingHorizontal: 16, - paddingVertical: 16, - backgroundColor: '#EFF1F5', - }, - submitButtonText: { - color: '#333', - fontWeight: 'bold', - }, - menuButton: { padding: 10, marginRight: 10 }, - menuText: { fontSize: 24, marginLeft: 10 }, - editInput: { - borderColor: '#ddd', - borderWidth: 1, - margin: 10, - padding: 10, - marginRight: 25, - }, - saveButton: { - backgroundColor: '#ddd', - padding: 10, - borderRadius: 10, - margin: 10, - width: 60, - }, - modalOverlay: { - flex: 1, - backgroundColor: 'rgba(0, 0, 0, 0.5)', // Semi-transparent background - justifyContent: 'center', // Center the modal vertically - alignItems: 'center', // Center the modal horizontally - }, - popupContainer: { - backgroundColor: '#fff', // Modal background - borderRadius: 10, - padding: 20, - width: '80%', // Adjust width as needed - alignItems: 'center', - zIndex: 1000, // Ensure the modal is on top - }, - popupOption: { - padding: 10, - borderBottomWidth: 1, - borderBottomColor: '#ddd', - width: '100%', - alignItems: 'center', - }, - sharePopupContainer: { - width: '80%', - backgroundColor: '#fff', - borderRadius: 10, - padding: 20, - alignItems: 'center', - }, - sharePopupTitle: { - fontSize: 18, - fontWeight: 'bold', - marginBottom: 20, - }, - shareButton: { - backgroundColor: '#6200ee', - padding: 10, - borderRadius: 5, - width: '100%', - alignItems: 'center', - }, - shareButtonText: { - color: '#fff', - fontSize: 16, - fontWeight: '600', - }, + page: { + flex: 1, + backgroundColor: "#fff", + paddingBottom: 80, // Add padding at the bottom equal to the height of the bottom tab bar + }, + container: { + flex: 1, + backgroundColor: "#fff", + marginTop: 20, + }, + reviewContainer: { + alignItems: "flex-start", + paddingLeft: 20, + paddingBottom: 30, + }, + coverImage: { + width: 200, + height: 200, + borderRadius: 10, + marginBottom: 20, + overflow: "scroll", + }, + voteButton: { + flexDirection: "row", + alignItems: "center", + marginHorizontal: 5, + }, + ratingImageWrapper: { + width: "100%", + height: 100, + overflow: "hidden", // This ensures cropping of the image + alignItems: "center", + }, + ratingContainer: { + justifyContent: "center", + alignItems: "flex-start", + }, + mediaContainer: { + flexDirection: "row", + alignItems: "flex-start", + width: "95%", + height: "15%", + marginTop: 20, + }, + songName: { + fontSize: 20, // Adjust size as needed + fontWeight: "bold", + color: "#000", // Adjust color as needed + flexWrap: "wrap", + marginTop: 12, + textAlign: "left", + width: "100%", + }, + artistName: { + fontSize: 16, + color: "#888", + }, + comment: { + fontSize: 16, + }, + + noReviewsText: { + textAlign: "center", + color: "#888", + marginVertical: 20, + }, + comments: { + width: "100%", + marginTop: 20, + marginBottom: 50, + marginLeft: -20, + }, + rating: { + width: "100%", + justifyContent: "center", + alignItems: "center", + marginTop: 8, + paddingRight: 20, // Add this to account for the left padding of reviewContainer + }, + + ratingImage: { + alignSelf: "center", // Add this + }, + + tagsContainer: { + flexDirection: "row", + marginBottom: 10, + paddingVertical: 15, + minHeight: 60, // Change from fixed height to minHeight + flexWrap: "wrap", // Allows wrapping to a new line + gap: 8, // Space between tags + }, + + tag: { + backgroundColor: "rgba(242, 128, 55, 0.65)", + paddingVertical: 5, + paddingHorizontal: 12, + borderRadius: 20, + marginHorizontal: 5, + borderWidth: 1, + borderColor: "#C0C0C0", + minHeight: 25, // Change from height to minHeight + justifyContent: "center", // Add this + }, + + tagText: { + color: "#333", + fontSize: 12, + lineHeight: 16, // Add this to ensure proper text spacing + textAlignVertical: "center", // Add this + }, + actionsContainer: { + flexDirection: "row", + justifyContent: "space-between", + width: "100%", + marginTop: 15, + }, + voteContainer: { + flexDirection: "row", + alignItems: "center", + }, + vote: { + marginHorizontal: 5, + }, + voteIcon: { + marginHorizontal: 10, + }, + vinyl: { + position: "absolute", + top: 0, + right: 0, + alignItems: "center", + }, + musicDisk: { + position: "absolute", + top: 0, + right: 0, + width: 100, + height: 100, + }, + mediaCover: { + position: "absolute", + width: 80, + height: 80, + top: -12, + right: -5, + borderRadius: 40, + overflow: "hidden", + }, + topSection: { + position: "relative", + width: "100%", + }, + topContainer: { + flexDirection: "row", + justifyContent: "space-between", // Align left and right sections + width: "100%", + marginBottom: 10, + }, + leftSection: { + flexDirection: "row", + alignItems: "center", + }, + profilePicture: { + width: 45, + height: 45, + borderRadius: 22.5, + marginRight: 10, + backgroundColor: "grey", + }, + textContainer: { + flexDirection: "column", + }, + displayName: { + fontWeight: "bold", + fontSize: 16, + color: "#333", + }, + username: { + fontSize: 13, + color: "#888", + }, + commentBoxContainer: { + position: "absolute", + bottom: 50, + left: 0, + right: 0, + backgroundColor: "#fff", + padding: 10, + borderTopWidth: 1, + borderTopColor: "#ddd", + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + }, + commentInput: { + flex: 1, + flexDirection: "row", + height: 50, + borderRadius: 5, + paddingHorizontal: 16, + paddingVertical: 16, + backgroundColor: "#EFF1F5", + }, + submitButtonText: { + color: "#333", + fontWeight: "bold", + }, + menuButton: { padding: 10, marginRight: 10 }, + menuText: { fontSize: 24, marginLeft: 10 }, + editInput: { + borderColor: "#ddd", + borderWidth: 1, + margin: 10, + padding: 10, + marginRight: 25, + }, + saveButton: { + backgroundColor: "#ddd", + padding: 10, + borderRadius: 10, + margin: 10, + width: 60, + }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.5)", // Semi-transparent background + justifyContent: "center", // Center the modal vertically + alignItems: "center", // Center the modal horizontally + }, + popupContainer: { + backgroundColor: "#fff", // Modal background + borderRadius: 10, + padding: 20, + width: "80%", // Adjust width as needed + alignItems: "center", + zIndex: 1000, // Ensure the modal is on top + }, + popupOption: { + padding: 10, + borderBottomWidth: 1, + borderBottomColor: "#ddd", + width: "100%", + alignItems: "center", + }, + sharePopupContainer: { + width: "80%", + backgroundColor: "#fff", + borderRadius: 10, + padding: 20, + alignItems: "center", + }, + sharePopupTitle: { + fontSize: 18, + fontWeight: "bold", + marginBottom: 20, + }, + shareButton: { + backgroundColor: "#6200ee", + padding: 10, + borderRadius: 5, + width: "100%", + alignItems: "center", + }, + shareButtonText: { + color: "#fff", + fontSize: 16, + fontWeight: "600", + }, }); export default ReviewPage;