From 59847435f6513b7ddff8322171fd7279b5fb67a8 Mon Sep 17 00:00:00 2001 From: Crossoufire <47953962+Crossoufire@users.noreply.github.com> Date: Thu, 30 May 2024 16:34:04 +0200 Subject: [PATCH] Squashed commit of the following: commit 98a232f656619760fd5f05fca26409a44946e111 Author: Crossoufire <47953962+Crossoufire@users.noreply.github.com> Date: Thu May 30 16:25:19 2024 +0200 Added Changelog v1.4.0 commit 20224abb6e43f031b90ef34e36cd8e9fb6cdbbf1 Author: Crossoufire <47953962+Crossoufire@users.noreply.github.com> Date: Thu May 30 16:07:56 2024 +0200 MyLists 1.4 update commit e3e02064c53e6684f957ce290781e7ffd17cac08 Author: Crossoufire <47953962+Crossoufire@users.noreply.github.com> Date: Thu May 9 14:29:17 2024 +0200 Replaced react router with tanstack router + loaders commit 86c5d79710213a413227d61c0410c5abd6421308 Author: Crossoufire <47953962+Crossoufire@users.noreply.github.com> Date: Wed May 1 16:28:42 2024 +0200 alpha 1.4.0 - Fix comments on /lists: subtles bad behaviors + no saving - Fix remove Seasons and episodes and times in general in Plan to - Fix books: avoid update page if page did not change - Remove the ":" in follow card in /details - Fix frontend React keys and nested / ); }; - diff --git a/frontend/src/components/app/base/FormError.jsx b/frontend/src/components/app/base/FormError.jsx new file mode 100644 index 00000000..510bab48 --- /dev/null +++ b/frontend/src/components/app/base/FormError.jsx @@ -0,0 +1,28 @@ +import {cn} from "@/lib/utils"; +import {useState} from "react"; +import {LuX} from "react-icons/lu"; +import {ExclamationTriangleIcon} from "@radix-ui/react-icons"; + + +export const FormError = ({ message, className }) => { + const [isVisible, setIsVisible] = useState(true); + + const handleDismiss = () => { + setIsVisible(false); + }; + + if (!isVisible) { + return null; + } + + return ( +
+ +

{message}

+
+ +
+
+ ); +}; diff --git a/frontend/src/components/primitives/Loading.jsx b/frontend/src/components/app/base/Loading.jsx similarity index 89% rename from frontend/src/components/primitives/Loading.jsx rename to frontend/src/components/app/base/Loading.jsx index d4a25b3c..97694855 100644 --- a/frontend/src/components/primitives/Loading.jsx +++ b/frontend/src/components/app/base/Loading.jsx @@ -1,9 +1,9 @@ import {cn} from "@/lib/utils"; -export const Loading = ({ className, forPage = true }) => { +export const Loading = ({ className }) => { return ( -
+
{ 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor" /> - { - return ; -} + return ; +}; diff --git a/frontend/src/components/primitives/MediaIcon.jsx b/frontend/src/components/app/base/MediaIcon.jsx similarity index 99% rename from frontend/src/components/primitives/MediaIcon.jsx rename to frontend/src/components/app/base/MediaIcon.jsx index b7f0d9f5..5726a534 100644 --- a/frontend/src/components/primitives/MediaIcon.jsx +++ b/frontend/src/components/app/base/MediaIcon.jsx @@ -11,6 +11,7 @@ const iconMappings = { user: FaUser, }; + export const MediaIcon = ({ mediaType, size, className }) => { const IconComp = iconMappings[mediaType]; diff --git a/frontend/src/components/primitives/Payload.jsx b/frontend/src/components/app/base/Payload.jsx similarity index 100% rename from frontend/src/components/primitives/Payload.jsx rename to frontend/src/components/app/base/Payload.jsx diff --git a/frontend/src/components/primitives/Return.jsx b/frontend/src/components/app/base/Return.jsx similarity index 90% rename from frontend/src/components/primitives/Return.jsx rename to frontend/src/components/app/base/Return.jsx index 1b36534c..7b3badb3 100644 --- a/frontend/src/components/primitives/Return.jsx +++ b/frontend/src/components/app/base/Return.jsx @@ -1,6 +1,6 @@ import {cn} from "@/lib/utils"; -import {Link} from "react-router-dom"; import {FaCaretLeft} from "react-icons/fa"; +import {Link} from "@tanstack/react-router"; import {Button} from "@/components/ui/button"; diff --git a/frontend/src/components/comingNext/nextMedia.jsx b/frontend/src/components/comingNext/nextMedia.jsx deleted file mode 100644 index 01fc4755..00000000 --- a/frontend/src/components/comingNext/nextMedia.jsx +++ /dev/null @@ -1,22 +0,0 @@ -import {cn, zeroPad} from "@/lib/utils"; -import {MediaCard} from "@/components/reused/MediaCard"; - - -export const NextMedia = ({ mediaType, media }) => { - const position = mediaType === ("movies" || "games") ? "bottom-[0px]" : "bottom-[32px]"; - - return ( - - {(mediaType === "anime" || mediaType === "series") && -
- S{zeroPad(media.season_to_air)} - E{zeroPad(media.episode_to_air)} -
- } -
- {media.date} -
- - ) -}; diff --git a/frontend/src/components/hof/HoFCard.jsx b/frontend/src/components/hof/HoFCard.jsx deleted file mode 100644 index fb5f5fe6..00000000 --- a/frontend/src/components/hof/HoFCard.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import {capitalize, cn} from "@/lib/utils"; -import {Link} from "react-router-dom"; -import {Badge} from "@/components/ui/badge"; -import {useUser} from "@/providers/UserProvider"; -import {Skeleton} from "@/components/ui/skeleton"; -import {Card, CardContent} from "@/components/ui/card"; - - -export const HoFCard = ({ item }) => { - const { currentUser } = useUser(); - - return ( - - -
-
-
- #{item.rank} -
-
-
-
- profile-image - frame-image - - {item.profile_level} - -
- - {item.username} - -
-
-
-
-
- - - - - -
-
-
-
-
- ) -}; - - -const ListItem = ({ item, mediaType }) => { - const checkDisabled = item.hasOwnProperty(`add_${mediaType}`) ? item[`add_${mediaType}`] === true : true; - - return ( -
-
{capitalize(mediaType)}
- {checkDisabled ? - -
{`${mediaType}-grade`}
-
{item[`${mediaType}_level`]}
- - : -
Disabled
- } -
- ); -}; - - -HoFCard.Skeleton = function SkeletonHoFCard() { - return ; -}; diff --git a/frontend/src/components/homepage/FormError.jsx b/frontend/src/components/homepage/FormError.jsx deleted file mode 100644 index ab6beeb5..00000000 --- a/frontend/src/components/homepage/FormError.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import {cn} from "@/lib/utils"; -import {ExclamationTriangleIcon} from "@radix-ui/react-icons"; - - -export const FormError = ({ message, className }) => { - if (!message) return null; - - return ( -
- -

{message}

-
- ); -}; \ No newline at end of file diff --git a/frontend/src/components/homepage/LoginForm.jsx b/frontend/src/components/homepage/LoginForm.jsx index 67573360..a0fa49b4 100644 --- a/frontend/src/components/homepage/LoginForm.jsx +++ b/frontend/src/components/homepage/LoginForm.jsx @@ -2,52 +2,54 @@ import {toast} from "sonner"; import {useState} from "react"; import {useForm} from "react-hook-form"; import {Input} from "@/components/ui/input"; -import {useApi} from "@/providers/ApiProvider"; -import {useUser} from "@/providers/UserProvider"; +import {api, userClient} from "@/api/MyApiClient"; import {FaGithub, FaGoogle} from "react-icons/fa"; -import {useNavigate, Link} from "react-router-dom"; import {Separator} from "@/components/ui/separator"; -import {FormError} from "@/components/homepage/FormError"; -import {FormButton} from "@/components/primitives/FormButton"; +import {Link, useNavigate} from "@tanstack/react-router"; +import {FormError} from "@/components/app/base/FormError"; +import {FormButton} from "@/components/app/base/FormButton"; import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; export const LoginForm = () => { - const api = useApi(); - const { login } = useUser(); const navigate = useNavigate(); - const [errors, setErrors] = useState(""); - const [pending, setIsPending] = useState(false); + const [error, setError] = useState(""); const form = useForm({ shouldFocusError: false }); + const [pending, setIsPending] = useState(false); const onSubmit = async (data) => { - setErrors(""); - - setIsPending(true); - const response = await login(data.username, data.password); - setIsPending(false); - - if (response.status === 401) { - return setErrors("Username or password incorrect"); + setError(""); + try { + setIsPending(true); + const response = await userClient.login(data.username, data.password); + if (response.status === 401) { + return setError("Username or password incorrect"); + } + if (!response.ok) { + return toast.error(response.body.description); + } + await navigate({ to: `/profile/${data.username}` }); } - - if (!response.ok) { - return toast.error(response.body.description); + finally { + setIsPending(false); } - - navigate(`/profile/${data.username}`); }; const withProvider = async (provider) => { - const response = await api.get(`/tokens/oauth2/${provider}`, { - callback: import.meta.env.VITE_OAUTH2_CALLBACK.replace("{provider}", provider), - }); - - if (!response.ok) { - return toast.error(response.body.description); + setError(""); + try { + setIsPending(true); + const response = await api.get(`/tokens/oauth2/${provider}`, { + callback: import.meta.env.VITE_OAUTH2_CALLBACK.replace("{provider}", provider), + }); + if (!response.ok) { + return toast.error(response.body.description); + } + window.location.href = response.body.redirect_url; + } + finally { + setIsPending(false); } - - window.location.href= response.body.redirect_url; }; return ( @@ -91,7 +93,7 @@ export const LoginForm = () => { )} />
- {errors && } + {error && } Login diff --git a/frontend/src/components/homepage/RegisterForm.jsx b/frontend/src/components/homepage/RegisterForm.jsx index d505d9df..06a311e0 100644 --- a/frontend/src/components/homepage/RegisterForm.jsx +++ b/frontend/src/components/homepage/RegisterForm.jsx @@ -1,16 +1,15 @@ import {toast} from "sonner"; import {useState} from "react"; +import {api} from "@/api/MyApiClient.js"; import {useForm} from "react-hook-form"; import {Input} from "@/components/ui/input"; -import {useApi} from "@/providers/ApiProvider"; -import {FormError} from "@/components/homepage/FormError"; -import {FormButton} from "@/components/primitives/FormButton"; +import {FormError} from "@/components/app/base/FormError"; +import {FormButton} from "@/components/app/base/FormButton"; import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card"; import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form"; export const RegisterForm = () => { - const api = useApi() const [errors, setErrors] = useState({}); const [pending, setPending] = useState(false); const form = useForm({ @@ -25,7 +24,7 @@ export const RegisterForm = () => { const onSubmit = async (data) => { - setErrors({}) + setErrors({}); setPending(true); const response = await api.post("/register_user", { @@ -44,7 +43,7 @@ export const RegisterForm = () => { return toast.error(response.body.description); } - toast.success(response.body.message); + toast.success("Your account has been created. Check your email to activate your account"); }; return ( diff --git a/frontend/src/components/media/books/BooksDetails.jsx b/frontend/src/components/media/books/BooksDetails.jsx index 70973211..9ea2661a 100644 --- a/frontend/src/components/media/books/BooksDetails.jsx +++ b/frontend/src/components/media/books/BooksDetails.jsx @@ -1,7 +1,6 @@ import {capitalize, formatTime} from "@/lib/utils"; import {Synopsis} from "@/components/media/general/Synopsis"; import {MapDetails} from "@/components/media/general/MapDetails"; -import {ReleaseDate} from "@/components/media/general/ReleaseDate"; import {GenericDetails} from "@/components/media/general/GenericDetails"; @@ -17,9 +16,9 @@ export const BooksDetails = ({ mediaData, mediaType }) => { mediaType={mediaType} valueList={mediaData.authors} /> -
diff --git a/frontend/src/components/media/books/BooksUserDetails.jsx b/frontend/src/components/media/books/BooksUserDetails.jsx index 034bc3a3..3a655680 100644 --- a/frontend/src/components/media/books/BooksUserDetails.jsx +++ b/frontend/src/components/media/books/BooksUserDetails.jsx @@ -1,5 +1,4 @@ import {useState} from "react"; -import {useUser} from "@/providers/UserProvider"; import {Separator} from "@/components/ui/separator"; import {RedoDrop} from "@/components/media/general/RedoDrop"; import {PageInput} from "@/components/media/books/PageInput"; @@ -8,11 +7,10 @@ import {StatusDrop} from "@/components/media/general/StatusDrop"; export const BooksUserDetails = ({ userData, totalPages, updatesAPI }) => { - const { currentUser } = useUser(); const [redo, setRedo] = useState(userData.rewatched); const [status, setStatus] = useState(userData.status); + const [rating, setRating] = useState(userData.rating); const [page, setPage] = useState(userData.actual_page); - const [rating, setRating] = useState(userData.add_feeling ? userData.feeling : userData.score); const callbackStatus = (value) => { setStatus(value); @@ -29,8 +27,8 @@ export const BooksUserDetails = ({ userData, totalPages, updatesAPI }) => { }; const callbackRating = (value) => { - setRating(value); - } + setRating({ ...rating, value }); + }; return ( <> @@ -50,7 +48,6 @@ export const BooksUserDetails = ({ userData, totalPages, updatesAPI }) => { diff --git a/frontend/src/components/media/books/PageInput.jsx b/frontend/src/components/media/books/PageInput.jsx index f5b37781..63ccbd72 100644 --- a/frontend/src/components/media/books/PageInput.jsx +++ b/frontend/src/components/media/books/PageInput.jsx @@ -3,7 +3,7 @@ import {Input} from "@/components/ui/input"; import {useLoading} from "@/hooks/LoadingHook"; -export const PageInput = ({initPage, totalPages, updatePage}) => { +export const PageInput = ({ initPage, totalPages, updatePage }) => { const [page, setPage] = useState(initPage || 0); const [isLoading, handleLoading] = useLoading(); const [backupPage, setBackupPage] = useState(initPage || 0); diff --git a/frontend/src/components/media/games/GamesDetails.jsx b/frontend/src/components/media/games/GamesDetails.jsx index 478d5eb0..0cf58d3f 100644 --- a/frontend/src/components/media/games/GamesDetails.jsx +++ b/frontend/src/components/media/games/GamesDetails.jsx @@ -3,7 +3,6 @@ import {formatTime} from "@/lib/utils"; import {Separator} from "@/components/ui/separator"; import {Synopsis} from "@/components/media/general/Synopsis"; import {MapDetails} from "@/components/media/general/MapDetails"; -import {ReleaseDate} from "@/components/media/general/ReleaseDate"; import {GenericDetails} from "@/components/media/general/GenericDetails"; @@ -27,9 +26,9 @@ export const GamesDetails = ({ mediaData, mediaType }) => { mediaType={mediaType} valueList={mediaData.developers} /> -
diff --git a/frontend/src/components/media/games/GamesUserDetails.jsx b/frontend/src/components/media/games/GamesUserDetails.jsx index 53ffa158..f92c84fb 100644 --- a/frontend/src/components/media/games/GamesUserDetails.jsx +++ b/frontend/src/components/media/games/GamesUserDetails.jsx @@ -1,5 +1,4 @@ import {useState} from "react"; -import {useUser} from "@/providers/UserProvider"; import {Separator} from "@/components/ui/separator"; import {RatingDrop} from "@/components/media/general/RatingDrop"; import {StatusDrop} from "@/components/media/general/StatusDrop"; @@ -7,10 +6,9 @@ import {PlaytimeDrop} from "@/components/media/games/PlaytimeDrop"; export const GamesUserDetails = ({ userData, updatesAPI }) => { - const { currentUser } = useUser(); const [status, setStatus] = useState(userData.status); + const [rating, setRating] = useState(userData.rating); const [playtime, setPlaytime] = useState(userData.playtime / 60); - const [rating, setRating] = useState(currentUser.add_feeling ? userData.feeling : userData.score); const callbackStatus = (value) => { setStatus(value); @@ -21,8 +19,8 @@ export const GamesUserDetails = ({ userData, updatesAPI }) => { }; const callbackRating = (value) => { - setRating(value); - } + setRating({ ...rating, value }); + }; return ( <> @@ -41,7 +39,6 @@ export const GamesUserDetails = ({ userData, updatesAPI }) => { /> diff --git a/frontend/src/components/media/games/PlaytimeDrop.jsx b/frontend/src/components/media/games/PlaytimeDrop.jsx index 76e324a5..0562665a 100644 --- a/frontend/src/components/media/games/PlaytimeDrop.jsx +++ b/frontend/src/components/media/games/PlaytimeDrop.jsx @@ -1,7 +1,6 @@ import {useState} from "react"; import {getPlaytimeValues} from "@/lib/utils"; import {useLoading} from "@/hooks/LoadingHook"; -import {LoadingIcon} from "@/components/primitives/LoadingIcon"; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"; @@ -22,9 +21,9 @@ export const PlaytimeDrop = ({ initPlaytime, updatePlaytime }) => { return (
Playtime
- - }/> + {playValues.map(p => {p} hours)} diff --git a/frontend/src/components/media/general/Commentary.jsx b/frontend/src/components/media/general/Commentary.jsx index 5b1f3b64..88ac7d2f 100644 --- a/frontend/src/components/media/general/Commentary.jsx +++ b/frontend/src/components/media/general/Commentary.jsx @@ -4,7 +4,6 @@ import {useLoading} from "@/hooks/LoadingHook"; import {Tooltip} from "@/components/ui/tooltip"; import {Textarea} from "@/components/ui/textarea"; import {Separator} from "@/components/ui/separator"; -import {LoadingIcon} from "@/components/primitives/LoadingIcon"; export const Commentary = ({ initContent, updateComment }) => { @@ -13,27 +12,30 @@ export const Commentary = ({ initContent, updateComment }) => { const [contents, setContents] = useState(initContent || ""); const [initContents, setInitContents] = useState(initContent || ""); + const buttonText = contents ? "Edit" : "Add"; + const commentText = contents ? "Edit comment" : "Add comment"; + const isContentsEmpty = contents === "" || contents === null; + const handleComment = () => { setCommentInput(!commentInput); - } + setContents(initContents); + }; const handleSave = async () => { - if (initContent === contents) { - return; - } + if (initContent === contents) return; await handleLoading(updateComment, contents); setInitContents(contents); setCommentInput(false); - } + }; return ( <>

Comment - - - {contents ? "Edit" : "Add"} + + + {buttonText}

@@ -52,13 +54,13 @@ export const Commentary = ({ initContent, updateComment }) => { Cancel
:

- {(contents === "" || contents === null) ? <>No comments added yet : `${contents}`} + {isContentsEmpty ? "No comments added yet" : `${contents}`}

} diff --git a/frontend/src/components/media/general/FollowCard.jsx b/frontend/src/components/media/general/FollowCard.jsx index a073ad0e..5e84d412 100644 --- a/frontend/src/components/media/general/FollowCard.jsx +++ b/frontend/src/components/media/general/FollowCard.jsx @@ -1,13 +1,19 @@ -import {Link} from "react-router-dom"; -import {getRatingValues} from "@/lib/utils"; +import {Link} from "@tanstack/react-router"; import {Separator} from "@/components/ui/separator"; +import {getFeelingValues, zeroPad} from "@/lib/utils"; import {Card, CardContent} from "@/components/ui/card"; -import {MoreFollowDetails} from "@/components/media/general/MoreFollowDetails"; import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover"; -import {FaAlignJustify, FaCommentAlt, FaHeart, FaRedoAlt, FaRegCommentAlt, FaRegHeart, FaStar} from "react-icons/fa"; +import {LuAlignJustify, LuHeart, LuMessageSquare, LuPlay, LuRotateCw, LuStar} from "react-icons/lu"; export const FollowCard = ({ follow, mediaType }) => { + const formatRating = () => { + if (follow.rating.type === "feeling") { + return getFeelingValues(17).find(data => data.value === follow.rating.value).icon; + } + return follow.rating.value === null ? "--" : follow.rating.value.toFixed(1); + }; + return ( @@ -27,41 +33,28 @@ export const FollowCard = ({ follow, mediaType }) => {
- -
- {follow.add_feeling ? - (follow.feeling === -1 || follow.feeling === null) ? - "---" : - <>{getRatingValues(follow.add_feeling).slice(1)[follow.feeling].icon} - : - (follow.score === -1 || follow.score === null) ? - "---" : <>{follow.score} - } -
+ +
{formatRating()}
{(follow.status === "Completed" && mediaType !== "games") &&
- {follow.rewatched} + {follow.rewatched}
}
{follow.comment ? - + {follow.comment} : - + }
- {follow.favorite ? - - : - - } +
@@ -69,7 +62,7 @@ export const FollowCard = ({ follow, mediaType }) => {
- {follow.status} + {follow.status}
{ ); -}; \ No newline at end of file +}; + + +const MoreFollowDetails = ({ mediaType, follow }) => { + if (mediaType === "series" || mediaType === "anime") { + if (!["Random", "Plan to Watch"].includes(follow.status)) { + return ( +
+ + Season {zeroPad(follow.current_season)} - Episode {zeroPad(follow.last_episode_watched)} +
+ ); + } + } + else if (mediaType === "books" && follow.status !== "Plan to Read") { + return ( +
+ Page {follow.actual_page}/{follow.total_pages} +
+ ); + } + else if (mediaType === "games" && follow.status !== "Plan to Play") { + return ( +
+ Played {follow.playtime / 60} h +
+ ); + } +}; diff --git a/frontend/src/components/media/general/GenericDetails.jsx b/frontend/src/components/media/general/GenericDetails.jsx index 44334167..69ae3e6d 100644 --- a/frontend/src/components/media/general/GenericDetails.jsx +++ b/frontend/src/components/media/general/GenericDetails.jsx @@ -1,7 +1,7 @@ -export const GenericDetails = ({name, value, isCentered}) => ( +export const GenericDetails = ({ name, value }) => (
{name}
-
{value}
+
{value}
); \ No newline at end of file diff --git a/frontend/src/components/media/general/HistoryDetails.jsx b/frontend/src/components/media/general/HistoryDetails.jsx index e70e16cd..98fa438e 100644 --- a/frontend/src/components/media/general/HistoryDetails.jsx +++ b/frontend/src/components/media/general/HistoryDetails.jsx @@ -1,24 +1,24 @@ import {Fragment} from "react"; -import {UserUpdate} from "@/components/reused/UserUpdate"; +import {UserUpdate} from "@/components/app/UserUpdate.jsx"; export const HistoryDetails = ({ history }) => { + if (history.length === 0) { + return
No history to display
; + } + return ( <> - {history.length === 0 ? -
No history to display
- : - history.map(hist => - - ) - } + {history.map(data => + + )} ); }; diff --git a/frontend/src/components/media/general/LabelLists.jsx b/frontend/src/components/media/general/LabelLists.jsx index 78cf06d3..a6d66be7 100644 --- a/frontend/src/components/media/general/LabelLists.jsx +++ b/frontend/src/components/media/general/LabelLists.jsx @@ -1,115 +1,38 @@ -import {Link} from "react-router-dom"; import {Fragment, useState} from "react"; -import {Input} from "@/components/ui/input"; import {Badge} from "@/components/ui/badge"; -import {useLoading} from "@/hooks/LoadingHook"; +import {userClient} from "@/api/MyApiClient"; import {Tooltip} from "@/components/ui/tooltip"; -import {useUser} from "@/providers/UserProvider"; import {Separator} from "@/components/ui/separator"; -import {FormError} from "@/components/homepage/FormError"; -import {FormButton} from "@/components/primitives/FormButton"; -import {Dialog, DialogContent, DialogTitle, DialogTrigger} from "@/components/ui/dialog"; +import {Link, useParams} from "@tanstack/react-router"; +import {LabelsDialog} from "@/components/app/LabelsDialog"; -export const LabelLists = ({ mediaType, updatesAPI, initIn, initAvailable }) => { - const [error, setError] = useState(""); - const [newLabel, setNewLabel] = useState(""); - const { currentUser: { username } } = useUser(); - const [isLoading, handleLoading] = useLoading(); - const [labelsInList, setLabelsInList] = useState(initIn); - const [labelsToAdd, setLabelsToAdd] = useState(initAvailable); +export const LabelLists = ({ mediaId, alreadyIn }) => { + const username = userClient.currentUser.username; + const [isOpen, setIsOpen] = useState(false); + const { mediaType } = useParams({strict: false }); + const [labelsInList, setLabelsInList] = useState(alreadyIn); - const handleCreateLabel = async () => { - setError(""); + const updateLabelsInList = (labels) => setLabelsInList(labels); - if (labelsInList.includes(newLabel) || labelsToAdd.includes(newLabel)) { - setError("This label already exists"); - } - - if (newLabel.trim() !== "" && !labelsInList.includes(newLabel) && !labelsToAdd.includes(newLabel)) { - setNewLabel(""); - setLabelsInList([...labelsInList, newLabel]); - await handleLoading(updatesAPI.addMediaToLabel, newLabel); - } - }; - - const handleMoveLabel = async (label, fromList) => { - if (fromList === "inList") { - setLabelsInList(labelsInList.filter(lab => lab !== label)); - setLabelsToAdd([...labelsToAdd, label]); - await updatesAPI.removeLabelFromMedia(label); - } - - if (fromList === "toAdd") { - setLabelsToAdd(labelsToAdd.filter(lab => lab !== label)); - setLabelsInList([...labelsInList, label]); - await updatesAPI.addMediaToLabel(label); - } - }; + const onClose = () => setIsOpen(false); return ( <>

Labels - - - - - Manage - - - - - Labels Manager -
-
Already in
- -
- {labelsInList.length === 0 ? - No labels added to this media yet - : - labelsInList.map(lab => - handleMoveLabel(lab, "inList")}> - {lab} - - ) - } -
-
-
-
Add to
- -
- {labelsToAdd.length === 0 ? - No labels available yet - : - labelsToAdd.map(lab => - handleMoveLabel(lab, "toAdd")}> - {lab} - - ) - } -
-
-
-
Create a new label
- -
{error && }
-
- setNewLabel(ev.target.value)} - onKeyPress={(ev) => ev.key === "Enter" && handleCreateLabel()} - disabled={isLoading} - /> - - Create - -
-
-
-
+ + setIsOpen(true)}> + Manage + + +

@@ -117,7 +40,7 @@ export const LabelLists = ({ mediaType, updatesAPI, initIn, initAvailable }) =>
Not labels added yet
: labelsInList.map(lab => - +
{lab}
diff --git a/frontend/src/components/media/general/ManageFavorite.jsx b/frontend/src/components/media/general/ManageFavorite.jsx index 9033bd4f..350289fa 100644 --- a/frontend/src/components/media/general/ManageFavorite.jsx +++ b/frontend/src/components/media/general/ManageFavorite.jsx @@ -1,14 +1,13 @@ import {useState} from "react"; +import {LuHeart} from "react-icons/lu"; import {useLoading} from "@/hooks/LoadingHook"; import {Tooltip} from "@/components/ui/tooltip"; -import {FaHeart, FaRegHeart} from "react-icons/fa"; -import {LoadingIcon} from "@/components/primitives/LoadingIcon"; +import {LoadingIcon} from "@/components/app/base/LoadingIcon"; export const ManageFavorite = ({ initFav, updateFavorite, isCurrent = true }) => { const [isLoading, handleLoading] = useLoading(); - const [favorite, setFavorite] = useState(initFav || false); - const Icon = favorite ? FaHeart : FaRegHeart; + const [favorite, setFavorite] = useState(initFav); const handleFavorite = async () => { const response = await handleLoading(updateFavorite, !favorite); @@ -17,17 +16,23 @@ export const ManageFavorite = ({ initFav, updateFavorite, isCurrent = true }) => } }; + if (isLoading) { + return ( +
+ +
+ ); + } + return ( - <> - {isLoading ? - + + {isCurrent ? +
+ +
: - -
- -
-
+ } - +
); }; \ No newline at end of file diff --git a/frontend/src/components/media/general/MapDetails.jsx b/frontend/src/components/media/general/MapDetails.jsx index 9c34da47..114b5b77 100644 --- a/frontend/src/components/media/general/MapDetails.jsx +++ b/frontend/src/components/media/general/MapDetails.jsx @@ -1,5 +1,5 @@ import {Fragment} from "react"; -import {Link} from "react-router-dom"; +import {Link} from "@tanstack/react-router"; export const MapDetails = ({ name, valueList, mediaType, job, asJoin }) => { diff --git a/frontend/src/components/media/general/MediaDataDetails.jsx b/frontend/src/components/media/general/MediaDataDetails.jsx index 1ce75401..e48006a7 100644 --- a/frontend/src/components/media/general/MediaDataDetails.jsx +++ b/frontend/src/components/media/general/MediaDataDetails.jsx @@ -12,10 +12,8 @@ const mediaDetailsMap = (value) => { anime: TvDetails, games: GamesDetails, books: BooksDetails, - default: undefined, }; - - return components[value] || components.default; + return components[value]; }; diff --git a/frontend/src/components/media/general/MoreFollowDetails.jsx b/frontend/src/components/media/general/MoreFollowDetails.jsx deleted file mode 100644 index b817cd59..00000000 --- a/frontend/src/components/media/general/MoreFollowDetails.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import {FaPlay} from "react-icons/fa"; - - -export const MoreFollowDetails = ({ mediaType, follow }) => { - if (mediaType === "series" || mediaType === "anime") { - if (!["Random", "Plan to Watch"].includes(follow.status)) { - return ( -
- Season: {follow.current_season} - Episode: {follow.last_episode_watched} -
- ); - } - } - else if (mediaType === "books") { - if (follow.status !== "Plan to Read") { - return ( -
- Page: {follow.actual_page}/{follow.total_pages} -
- ); - } - } - else if (mediaType === "games") { - if (follow.status !== "Plan to Play") { - return ( -
- Playtime: {follow.playtime / 60} h -
- ); - } - } -}; diff --git a/frontend/src/components/media/general/RatingDrop.jsx b/frontend/src/components/media/general/RatingDrop.jsx index b67ab3a2..952daf8e 100644 --- a/frontend/src/components/media/general/RatingDrop.jsx +++ b/frontend/src/components/media/general/RatingDrop.jsx @@ -1,12 +1,26 @@ -import {getRatingValues} from "@/lib/utils"; import {useLoading} from "@/hooks/LoadingHook"; -import {LoadingIcon} from "@/components/primitives/LoadingIcon"; +import {getFeelingValues, getScoreValues} from "@/lib/utils"; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"; -export const RatingDrop = ({ isFeeling, rating, updateRating, callbackRating }) => { +export const RatingDrop = ({ rating, updateRating, callbackRating }) => { const [isLoading, handleLoading] = useLoading(); - const ratingValues = getRatingValues(isFeeling, 16); + + let selectItems; + if (rating.type === "feeling") { + selectItems = getFeelingValues(16).map(val => + + {val.icon} + + ); + } + else { + selectItems = getScoreValues().map(val => + + {typeof val === "number" ? val.toFixed(1) : "--"} + + ); + } const handleSelectChange = async (value) => { const response = await handleLoading(updateRating, value); @@ -16,42 +30,16 @@ export const RatingDrop = ({ isFeeling, rating, updateRating, callbackRating }) }; return ( - <> - {isFeeling ? -
-
Rating
- -
- : -
-
Rating
- -
- } - - ) +
+
Rating
+ +
+ ); }; diff --git a/frontend/src/components/media/general/RedoDrop.jsx b/frontend/src/components/media/general/RedoDrop.jsx index 26fae2b8..d1151d55 100644 --- a/frontend/src/components/media/general/RedoDrop.jsx +++ b/frontend/src/components/media/general/RedoDrop.jsx @@ -1,12 +1,10 @@ import {useState} from "react"; import {getRedoValues} from "@/lib/utils"; import {useLoading} from "@/hooks/LoadingHook"; -import {LoadingIcon} from "@/components/primitives/LoadingIcon"; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"; export const RedoDrop = ({ name, initRedo, updateRedo }) => { - const redoValues = getRedoValues(); const [isLoading, handleLoading] = useLoading(); const [redo, setRedo] = useState(initRedo || 0); @@ -20,12 +18,12 @@ export const RedoDrop = ({ name, initRedo, updateRedo }) => { return (
{name}
- - }/> + - {redoValues.map(val => {`${val}`})} + {getRedoValues().map(val => {`${val}`})}
diff --git a/frontend/src/components/media/general/RefreshMedia.jsx b/frontend/src/components/media/general/RefreshMedia.jsx index 0076b0ae..e4f29890 100644 --- a/frontend/src/components/media/general/RefreshMedia.jsx +++ b/frontend/src/components/media/general/RefreshMedia.jsx @@ -3,7 +3,7 @@ import {FaRedo} from "react-icons/fa"; import {createLocalDate} from "@/lib/utils"; import {useLoading} from "@/hooks/LoadingHook"; import {Tooltip} from "@/components/ui/tooltip"; -import {LoadingIcon} from "@/components/primitives/LoadingIcon"; +import {LoadingIcon} from "@/components/app/base/LoadingIcon"; export const RefreshMedia = ({ updateRefresh, mutateData, lastApiUpdate }) => { @@ -18,17 +18,15 @@ export const RefreshMedia = ({ updateRefresh, mutateData, lastApiUpdate }) => { } }; + if (isLoading) { + return ; + } + return ( - <> - {isLoading ? - - : - -
- -
-
- } - + +
+ +
+
); }; diff --git a/frontend/src/components/media/general/ReleaseDate.jsx b/frontend/src/components/media/general/ReleaseDate.jsx deleted file mode 100644 index baebd9e8..00000000 --- a/frontend/src/components/media/general/ReleaseDate.jsx +++ /dev/null @@ -1,9 +0,0 @@ - -export const ReleaseDate = ({name, start, end}) => { - return ( -
-
{name}
- {end ?
{start}
{end}
:
{start}
} -
- ); -}; diff --git a/frontend/src/components/media/general/SimilarMedia.jsx b/frontend/src/components/media/general/SimilarMedia.jsx index 6ab0b97a..081b4589 100644 --- a/frontend/src/components/media/general/SimilarMedia.jsx +++ b/frontend/src/components/media/general/SimilarMedia.jsx @@ -1,26 +1,28 @@ -import {Link} from "react-router-dom"; import {capitalize} from "@/lib/utils"; +import {Link} from "@tanstack/react-router"; import {Tooltip} from "@/components/ui/tooltip"; import {MediaTitle} from "@/components/media/general/MediaTitle"; -export const SimilarMedia = ({ mediaType, similarMedia }) => ( -
- Similar {capitalize(mediaType)} -
- {similarMedia.map(media => -
- - - {media.media_name} - - -
- )} +export const SimilarMedia = ({ mediaType, similarMedia }) => { + return ( +
+ Similar {capitalize(mediaType)} +
+ {similarMedia.map(media => +
+ + + {media.media_name} + + +
+ )} +
-
-); + ); +}; diff --git a/frontend/src/components/media/general/StatusDrop.jsx b/frontend/src/components/media/general/StatusDrop.jsx index 610807c8..8a0f772a 100644 --- a/frontend/src/components/media/general/StatusDrop.jsx +++ b/frontend/src/components/media/general/StatusDrop.jsx @@ -1,5 +1,4 @@ import {useLoading} from "@/hooks/LoadingHook"; -import {LoadingIcon} from "@/components/primitives/LoadingIcon"; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"; @@ -16,9 +15,9 @@ export const StatusDrop = ({ status, allStatus, updateStatus, callbackStatus }) return (
Status
- - }/> + {allStatus.map(s => {s})} diff --git a/frontend/src/components/media/general/Synopsis.jsx b/frontend/src/components/media/general/Synopsis.jsx index 8aa16820..926de526 100644 --- a/frontend/src/components/media/general/Synopsis.jsx +++ b/frontend/src/components/media/general/Synopsis.jsx @@ -5,9 +5,10 @@ export const Synopsis = ({ synopsis, tagLine }) => (
Synopsis
{synopsis}
- {(tagLine && tagLine !== "-") && -
— {tagLine}
+
+ — {tagLine} +
}
); diff --git a/frontend/src/components/media/general/UserListDetails.jsx b/frontend/src/components/media/general/UserListDetails.jsx index 38e5cd51..4a8f9c3b 100644 --- a/frontend/src/components/media/general/UserListDetails.jsx +++ b/frontend/src/components/media/general/UserListDetails.jsx @@ -1,7 +1,8 @@ -import {FaMinus, FaPlus} from "react-icons/fa"; +import {toast} from "sonner"; import {useLoading} from "@/hooks/LoadingHook"; +import {FaMinus, FaPlus} from "react-icons/fa"; import {useApiUpdater} from "@/hooks/UserUpdaterHook"; -import {FormButton} from "@/components/primitives/FormButton"; +import {FormButton} from "@/components/app/base/FormButton"; import {Commentary} from "@/components/media/general/Commentary"; import {LabelLists} from "@/components/media/general/LabelLists"; import {TvUserDetails} from "@/components/media/tv/TvUserDetails"; @@ -20,14 +21,12 @@ const mediaComponentMap = (value) => { anime: TvUserDetails, games: GamesUserDetails, books: BooksUserDetails, - default: undefined, }; - - return components[value] || components.default; + return components[value]; }; -export const UserListDetails = ({ apiData, mediaType, mutateData }) => { +export const UserListDetails = ({ apiData, setApiData, mediaType }) => { const [isLoading, handleLoading] = useLoading(); const MediaUserDetails = mediaComponentMap(mediaType); const updatesAPI = useApiUpdater(apiData.media.id, mediaType); @@ -35,19 +34,18 @@ export const UserListDetails = ({ apiData, mediaType, mutateData }) => { const handleAddMediaUser = async () => { const response = await handleLoading(updatesAPI.addMedia); if (response) { - await mutateData({ ...apiData, user_data: response }, false); + setApiData({ ...apiData, user_data: response }); + toast.success("Media added to your list"); } }; const handleDeleteMedia = async () => { - const confirm = window.confirm("Do you want to remove this media from your list?") - if (!confirm) { - return; - } - + const confirm = window.confirm("Do you want to remove this media from your list?"); + if (!confirm) return; const response = await handleLoading(updatesAPI.deleteMedia); if (response) { - await mutateData({ ...apiData, user_data: false }, false); + setApiData({ ...apiData, user_data: false }); + toast.warning("Media deleted from your list"); } }; @@ -85,14 +83,12 @@ export const UserListDetails = ({ apiData, mediaType, mutateData }) => { initContent={apiData.user_data.comment} /> + hover:overflow-y-auto max-h-[355px]"> diff --git a/frontend/src/components/media/movies/MoviesDetails.jsx b/frontend/src/components/media/movies/MoviesDetails.jsx index 714a91f5..6b813d22 100644 --- a/frontend/src/components/media/movies/MoviesDetails.jsx +++ b/frontend/src/components/media/movies/MoviesDetails.jsx @@ -2,7 +2,6 @@ import {FaStar} from "react-icons/fa"; import {formatTime} from "@/lib/utils"; import {Synopsis} from "@/components/media/general/Synopsis"; import {MapDetails} from "@/components/media/general/MapDetails"; -import {ReleaseDate} from "@/components/media/general/ReleaseDate"; import {GenericDetails} from "@/components/media/general/GenericDetails"; @@ -23,9 +22,9 @@ export const MoviesDetails = ({ mediaType, mediaData }) => ( mediaType={mediaType} valueList={[mediaData.director_name]} /> -
diff --git a/frontend/src/components/media/movies/MoviesUserDetails.jsx b/frontend/src/components/media/movies/MoviesUserDetails.jsx index 5cb001a6..3835ee07 100644 --- a/frontend/src/components/media/movies/MoviesUserDetails.jsx +++ b/frontend/src/components/media/movies/MoviesUserDetails.jsx @@ -1,5 +1,4 @@ import {useState} from "react"; -import {useUser} from "@/providers/UserProvider"; import {Separator} from "@/components/ui/separator"; import {RedoDrop} from "@/components/media/general/RedoDrop"; import {RatingDrop} from "@/components/media/general/RatingDrop"; @@ -7,10 +6,9 @@ import {StatusDrop} from "@/components/media/general/StatusDrop"; export const MoviesUserDetails = ({ userData, updatesAPI }) => { - const { currentUser } = useUser(); const [redo, setRedo] = useState(userData.rewatched); const [status, setStatus] = useState(userData.status); - const [rating, setRating] = useState(currentUser.add_feeling ? userData.feeling : userData.score); + const [rating, setRating] = useState(userData.rating); const callbackStatus = (value) => { setStatus(value); @@ -18,8 +16,8 @@ export const MoviesUserDetails = ({ userData, updatesAPI }) => { }; const callbackRating = (value) => { - setRating(value); - } + setRating({ ...rating, value }); + }; return ( <> @@ -34,7 +32,6 @@ export const MoviesUserDetails = ({ userData, updatesAPI }) => { diff --git a/frontend/src/components/media/tv/EpsSeasonsDrop.jsx b/frontend/src/components/media/tv/EpsSeasonsDrop.jsx index 1eec6d98..7ba3072f 100644 --- a/frontend/src/components/media/tv/EpsSeasonsDrop.jsx +++ b/frontend/src/components/media/tv/EpsSeasonsDrop.jsx @@ -1,6 +1,5 @@ import {useEffect, useState} from "react"; import {useLoading} from "@/hooks/LoadingHook"; -import {LoadingIcon} from "@/components/primitives/LoadingIcon"; import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"; @@ -24,7 +23,7 @@ export const EpsSeasonsDrop = ({ initSeason, initEpisode, epsPerSeason, updateSe setSeason(newVal); setEpisode(1); } - } + }; const handleEpisode = async (eps) => { const newVal = parseInt(eps); @@ -34,16 +33,16 @@ export const EpsSeasonsDrop = ({ initSeason, initEpisode, epsPerSeason, updateSe setEpisode(newVal); } setLoadFromEps(false); - } + }; return ( <>
Season
- - }/> + {seasons.map(s => {s})} @@ -52,9 +51,9 @@ export const EpsSeasonsDrop = ({ initSeason, initEpisode, epsPerSeason, updateSe
Episode
- - }/> + {episodes.map(e => {e})} diff --git a/frontend/src/components/media/tv/TvDetails.jsx b/frontend/src/components/media/tv/TvDetails.jsx index 8f6b20a6..10d43cbf 100644 --- a/frontend/src/components/media/tv/TvDetails.jsx +++ b/frontend/src/components/media/tv/TvDetails.jsx @@ -3,7 +3,6 @@ import {createLocalDate, formatTime} from "@/lib/utils"; import {Synopsis} from "@/components/media/general/Synopsis"; import {EpsPerSeason} from "@/components/media/tv/EpsPerSeason"; import {MapDetails} from "@/components/media/general/MapDetails"; -import {ReleaseDate} from "@/components/media/general/ReleaseDate"; import {GenericDetails} from "@/components/media/general/GenericDetails"; @@ -27,10 +26,9 @@ export const TvDetails = ({ mediaData, mediaType }) => { mediaType={mediaType} valueList={creators} /> - {mediaData.formatted_date[0]}
{mediaData.formatted_date[1]}} /> { value={mediaData.origin_country} /> {mediaData.next_episode_to_air && - }
diff --git a/frontend/src/components/media/tv/TvUserDetails.jsx b/frontend/src/components/media/tv/TvUserDetails.jsx index aab6b4f8..9129397a 100644 --- a/frontend/src/components/media/tv/TvUserDetails.jsx +++ b/frontend/src/components/media/tv/TvUserDetails.jsx @@ -1,41 +1,42 @@ import {useState} from "react"; -import {useUser} from "@/providers/UserProvider"; import {Separator} from "@/components/ui/separator"; import {RedoDrop} from "@/components/media/general/RedoDrop"; import {RatingDrop} from "@/components/media/general/RatingDrop"; import {StatusDrop} from "@/components/media/general/StatusDrop"; import {EpsSeasonsDrop} from "@/components/media/tv/EpsSeasonsDrop"; -// TODO: separate loading and disable states between season episodes and status export const TvUserDetails = ({ userData, updatesAPI }) => { - const { currentUser } = useUser(); const [redo, setRedo] = useState(userData.rewatched); const [status, setStatus] = useState(userData.status); + const [rating, setRating] = useState(userData.rating); const [season, setSeason] = useState(userData.current_season); const [episode, setEpisode] = useState(userData.last_episode_watched); - const [rating, setRating] = useState(currentUser.add_feeling ? userData.feeling : userData.score); - const callbackStatus = (value) => { - setStatus(value); + const callbackStatus = (status) => { + setStatus(status); - if (["Plan to Watch", "Random"].includes(value)) { + if (episode === 0 && !["Plan to Watch", "Random"].includes(status)) { + setEpisode(1); + } + + if (["Plan to Watch", "Random"].includes(status)) { setSeason(1); setEpisode(1); } - if (value === "Completed") { + if (status === "Completed") { setSeason(userData.eps_per_season.length); setEpisode(userData.eps_per_season[userData.eps_per_season.length - 1]); } - setRating(rating); + setRating({ ...rating }); setRedo(0); }; const callbackRating = (value) => { - setRating(value); - } + setRating({ ...rating, value }); + }; return ( <> @@ -60,7 +61,6 @@ export const TvUserDetails = ({ userData, updatesAPI }) => { {status !== "Plan to Watch" && diff --git a/frontend/src/components/medialist/CommentPopover.jsx b/frontend/src/components/medialist/CommentPopover.jsx index 1afa8f59..d3f3dea5 100644 --- a/frontend/src/components/medialist/CommentPopover.jsx +++ b/frontend/src/components/medialist/CommentPopover.jsx @@ -1,88 +1,100 @@ -import {useEffect, useState} from "react"; +import {useState} from "react"; import {Button} from "@/components/ui/button"; import {useLoading} from "@/hooks/LoadingHook"; +import {LuMessageSquare} from "react-icons/lu"; import {Tooltip} from "@/components/ui/tooltip"; import {Textarea} from "@/components/ui/textarea"; import {Separator} from "@/components/ui/separator"; -import {FaCommentAlt, FaRegCommentAlt} from "react-icons/fa"; import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover"; export const CommentPopover = ({ isCurrent, initContent, updateComment }) => { const [isLoading, handleLoading] = useLoading(); const [isEdit, setIsEdit] = useState(false); - const [contents, setContents] = useState(initContent || ""); - const [initContents, setInitContents] = useState(initContent || ""); + const [contentsState, setContentsState] = useState(initContent || ""); + const [initContentState, setInitContentState] = useState(initContent || ""); - useEffect(() => { - if (isCurrent) { - setIsEdit(initContents === ""); + const checkInitContent = () => { + if (isCurrent && contentsState === "") { + setIsEdit(true); } - }, [isCurrent, initContent]); + }; const handleSave = async () => { - if (initContent === contents) { + if (initContentState === contentsState) { return; } - await handleLoading(updateComment, contents); - setInitContents(contents); + + await handleLoading(updateComment, contentsState); + setInitContentState(contentsState); setIsEdit(false); }; + const onEditCancel = () => { + setContentsState(initContentState); + setIsEdit(false); + }; + + const onPopoverClickOutside = (ev) => { + if (isEdit && contentsState !== "") { + ev.preventDefault(); + } + }; + return (
- {(!isCurrent && !contents) && - - } - {(!isCurrent && contents) && - - - - } - {(isCurrent && !contents) && - - - - } - {(isCurrent && contents) && - - + {isCurrent ? + + + : + <> + {contentsState ? + + + + : + + } + }
- + {(isCurrent && isEdit) ? <>