diff --git a/README.md b/README.md index 662a63f046f97c6a816c1c918d7a361be8ab15f4..8830b3f9c7583828d0c5ab8306f86316f95d5364 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ melihat daftar watchlist film yang ada dan menambahkan sendiri watchlist yang me  3. Halaman Profile -  +  4. Halaman Detail Watchlist  @@ -59,7 +59,7 @@ melihat daftar watchlist film yang ada dan menambahkan sendiri watchlist yang me | Create Watchlist | 13521094 | | Edit Watchlist | 13521094 | -## _Completed Bonus_ +## Completed Bonus 1. All Responsive Web Design 2. Docker diff --git a/src/pages/AddWatchlist/AddWatchlist.tsx b/src/pages/AddWatchlist/AddWatchlist.tsx index f0a49405c5135fba0f567874739d3421172b462f..0cecfa121014ff3863c9f8bb5f44ba00136e47a3 100644 --- a/src/pages/AddWatchlist/AddWatchlist.tsx +++ b/src/pages/AddWatchlist/AddWatchlist.tsx @@ -1,25 +1,25 @@ import React from "react"; import { NavLink, useParams, useNavigate } from "react-router-dom"; -import Navbar from "@/components/Navbar" +import Navbar from "@/components/Navbar"; import { ChevronLeftCircle } from "lucide-react"; import { Input } from "@/components/ui/input"; import FilmCard, { IFilm } from "@/components/FilmCard"; import ReactPaginate from "react-paginate"; -import { useForm } from "react-hook-form" -import * as z from "zod" +import { useForm } from "react-hook-form"; +import * as z from "zod"; import { ToastAction } from "@/components/ui/toast"; import { useToast } from "@/components/ui/use-toast"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Button } from "@/components/ui/button" +import { Button } from "@/components/ui/button"; import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; import axios, { AxiosError, isAxiosError } from "axios"; import { handleAxiosError } from "@/lib/utils"; import { getToken } from "@/lib/user"; @@ -27,273 +27,329 @@ import { useAuth } from "@/context/AuthContext"; import NotAuthorizedPage from "../NotAuthorized/NotAuthorized"; const formSchema = z.object({ - title: z.string().min(2).max(50), - description: z.string().min(10).max(250), - films: z.array(z.number()).refine((films) => films.length >= 1, { - message: 'At least one film must be selected', - }), -}) - + title: z.string().min(2).max(50), + description: z.string().min(10).max(250), + films: z.array(z.number()).refine((films) => films.length >= 1, { + message: "At least one film must be selected", + }), +}); const AddWatchlist = () => { - const { userid } = useAuth(); - const { userId, watchlistId } = useParams(); - const navigate = useNavigate(); - const { toast } = useToast(); + const { userid } = useAuth(); + const { userId, watchlistId } = useParams(); + const navigate = useNavigate(); + const { toast } = useToast(); - const [selectedFilms, setSelectedFilms] = React.useState<IFilm[]>([]); - const [films, setFilms] = React.useState<IFilm[]>([]); - const [totalPages, setTotalPages] = React.useState<number>(1); - const [currentPage, setCurrentPage] = React.useState<number>(1); - const [searchInput, setSearchInput] = React.useState(""); - const [isPrevButtonDisabled, setIsPrevButtonDisabled] = React.useState(true); - const [isNextButtonDisabled, setIsNextButtonDisabled] = React.useState(false); + const [selectedFilms, setSelectedFilms] = React.useState<IFilm[]>([]); + const [films, setFilms] = React.useState<IFilm[]>([]); + const [totalPages, setTotalPages] = React.useState<number>(1); + const [currentPage, setCurrentPage] = React.useState<number>(1); + const [searchInput, setSearchInput] = React.useState(""); + const [isPrevButtonDisabled, setIsPrevButtonDisabled] = React.useState(true); + const [isNextButtonDisabled, setIsNextButtonDisabled] = React.useState(false); - if (userId && userid !== parseInt(userId)) { - console.log(userid, userId); - - return <NotAuthorizedPage /> - } + if (userId && userid !== parseInt(userId)) { + console.log(userid, userId); - const handleRemoveFilm = (film: IFilm) => { - const newSelectedFilms = selectedFilms.filter((selectedFilm) => selectedFilm.film_id !== film.film_id); - setSelectedFilms(newSelectedFilms); - form.setValue("films", newSelectedFilms.map((film) => film.film_id)); - } + return <NotAuthorizedPage />; + } - const handleAddFilm = (film: IFilm) => { - const isDuplicate = selectedFilms.some((selectedFilm) => selectedFilm.film_id === film.film_id); + const handleRemoveFilm = (film: IFilm) => { + const newSelectedFilms = selectedFilms.filter( + (selectedFilm) => selectedFilm.film_id !== film.film_id, + ); + setSelectedFilms(newSelectedFilms); + form.setValue( + "films", + newSelectedFilms.map((film) => film.film_id), + ); + }; - if (!isDuplicate) { - setSelectedFilms([...selectedFilms, film]); - form.setValue("films", [...selectedFilms, film].map((film) => film.film_id)); - } - } + const handleAddFilm = (film: IFilm) => { + const isDuplicate = selectedFilms.some( + (selectedFilm) => selectedFilm.film_id === film.film_id, + ); - const handlePageChange = (selectedPage: { selected: number }) => { - const newPage = selectedPage.selected + 1; - console.log(newPage); - - setCurrentPage(newPage); - }; + if (!isDuplicate) { + setSelectedFilms([...selectedFilms, film]); + form.setValue( + "films", + [...selectedFilms, film].map((film) => film.film_id), + ); + } + }; - const fetchFilms = async (searchValue: string, page: number) => { - try { - const token = getToken() - const response = await axios.get(`${import.meta.env.VITE_REACT_APP_REST_SERVICE_URL}/film`, { - params: { - q: searchValue, - page: page, - }, - headers: { - Authorization: `bearer ${token}` - } - }); - console.log(response.data.data.films); - console.log(watchlistId); + const handlePageChange = (selectedPage: { selected: number }) => { + const newPage = selectedPage.selected + 1; + console.log(newPage); - setFilms(response.data.data.films); - setTotalPages(response.data.data.total_page); - } catch (error) { - if (isAxiosError(error)) { - handleAxiosError(error, toast) - } else { - toast({ - variant: "destructive", - title: "Request failed!", - description: error as string, - action: <ToastAction altText="Try again">Try again</ToastAction>, - }); - } - } - }; + setCurrentPage(newPage); + }; - const form = useForm<z.infer<typeof formSchema>>({ - resolver: zodResolver(formSchema), - defaultValues: { - title: "", - description: "", - films: [] + const fetchFilms = async (searchValue: string, page: number) => { + try { + const token = getToken(); + const response = await axios.get( + `${import.meta.env.VITE_REACT_APP_REST_SERVICE_URL}/film`, + { + params: { + q: searchValue, + page: page, + }, + headers: { + Authorization: `bearer ${token}`, + }, }, - }) + ); + console.log(response.data.data.films); + console.log(watchlistId); - async function onSubmit(values: z.infer<typeof formSchema>) { - try { - let response; - const token = getToken() - if (watchlistId) { - response = await axios.put(`${import.meta.env.VITE_REACT_APP_REST_SERVICE_URL}/watchlist/${watchlistId}`, values, { - headers: { - Authorization: `bearer ${token}` - } - }) - } - else { - response = await axios.post(`${import.meta.env.VITE_REACT_APP_REST_SERVICE_URL}/watchlist/user`, values, { - headers: { - Authorization: `bearer ${token}` - } - }) - } - if (response) { - navigate(`/watchlist/${response.data.data.user_id}/${response.data.data.wl_id}`) - } - } catch (error) { - if (isAxiosError(error)) { - handleAxiosError(error, toast) - } else { - toast({ - variant: "destructive", - title: "Request failed!", - description: error as string, - action: <ToastAction altText="Try again">Try again</ToastAction>, - }); - } - } + setFilms(response.data.data.films); + setTotalPages(response.data.data.total_page); + } catch (error) { + if (isAxiosError(error)) { + handleAxiosError(error, toast); + } else { + toast({ + variant: "destructive", + title: "Request failed!", + description: error as string, + action: <ToastAction altText="Try again">Try again</ToastAction>, + }); + } } + }; - const fetchWatchlist = async () => { - try { - console.log("tes bro tesss astafhnisufud"); - const token = getToken() - const response = await axios.get( - `${import.meta.env.VITE_REACT_APP_REST_SERVICE_URL}/watchlist/${userId}/${watchlistId}`, { - headers: { - Authorization: `bearer ${token}` - } - } - ); - const watchlistData = response.data.data; - console.log(watchlistData); + const form = useForm<z.infer<typeof formSchema>>({ + resolver: zodResolver(formSchema), + defaultValues: { + title: "", + description: "", + films: [], + }, + }); - form.setValue("title", watchlistData.wl_title); - form.setValue("description", watchlistData.wl_description); - setSelectedFilms(watchlistData.films) - form.setValue("films", watchlistData.films.map((film: IFilm) => film.film_id)); - } catch (error) { - if (isAxiosError(error)) { - handleAxiosError(error, toast) - } else { - toast({ - variant: "destructive", - title: "Request failed!", - description: error as string, - action: <ToastAction altText="Try again">Try again</ToastAction>, - }); - } - console.log("Error fetching watchlist:", error); - } - }; + async function onSubmit(values: z.infer<typeof formSchema>) { + try { + let response; + const token = getToken(); + if (watchlistId) { + response = await axios.put( + `${ + import.meta.env.VITE_REACT_APP_REST_SERVICE_URL + }/watchlist/${watchlistId}`, + values, + { + headers: { + Authorization: `bearer ${token}`, + }, + }, + ); + } else { + response = await axios.post( + `${import.meta.env.VITE_REACT_APP_REST_SERVICE_URL}/watchlist/user`, + values, + { + headers: { + Authorization: `bearer ${token}`, + }, + }, + ); + } + if (response) { + navigate( + `/watchlist/${response.data.data.user_id}/${response.data.data.wl_id}`, + ); + } + } catch (error) { + if (isAxiosError(error)) { + handleAxiosError(error, toast); + } else { + toast({ + variant: "destructive", + title: "Request failed!", + description: error as string, + action: <ToastAction altText="Try again">Try again</ToastAction>, + }); + } + } + } + const fetchWatchlist = async () => { + try { + console.log("tes bro tesss astafhnisufud"); + const token = getToken(); + const response = await axios.get( + `${ + import.meta.env.VITE_REACT_APP_REST_SERVICE_URL + }/watchlist/${userId}/${watchlistId}`, + { + headers: { + Authorization: `bearer ${token}`, + }, + }, + ); + const watchlistData = response.data.data; + console.log(watchlistData); - React.useEffect(() => { - if (watchlistId) { - fetchWatchlist(); - } - }, [watchlistId]); + form.setValue("title", watchlistData.wl_title); + form.setValue("description", watchlistData.wl_description); + setSelectedFilms(watchlistData.films); + form.setValue( + "films", + watchlistData.films.map((film: IFilm) => film.film_id), + ); + } catch (error) { + if (isAxiosError(error)) { + handleAxiosError(error, toast); + } else { + toast({ + variant: "destructive", + title: "Request failed!", + description: error as string, + action: <ToastAction altText="Try again">Try again</ToastAction>, + }); + } + console.log("Error fetching watchlist:", error); + } + }; - React.useEffect(() => { - setIsPrevButtonDisabled(currentPage === 1); - setIsNextButtonDisabled(currentPage === totalPages); + React.useEffect(() => { + if (watchlistId) { + fetchWatchlist(); + } + }, [watchlistId]); - fetchFilms(searchInput, currentPage); - }, [searchInput, currentPage, totalPages]); + React.useEffect(() => { + setIsPrevButtonDisabled(currentPage === 1); + setIsNextButtonDisabled(currentPage === totalPages); + fetchFilms(searchInput, currentPage); + }, [searchInput, currentPage, totalPages]); - return ( - <div className="w-full min-h-screen bg-[#17181D]"> - <div className="fixed top-0 w-full z-10"> - <Navbar /> - </div> - <div className="flex flex-col justify-center pt-28 text-white p-8 md:px-16 gap-4 md:gap-8"> - <div className="flex gap-4 md:gap-8 justify-start w-full"> - <NavLink to="/watchlists"> - <ChevronLeftCircle className="h-400 w-400" /> - </NavLink> - </div> - <Form {...form}> - <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> - <FormField - control={form.control} - name="title" - render={({ field }) => ( - <FormItem> - <FormLabel>Title</FormLabel> - <FormControl> - <Input placeholder="shadcn" {...field} className="text-black" /> - </FormControl> - <FormDescription> - This is your watchlist name. - </FormDescription> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name="description" - render={({ field }) => ( - <FormItem> - <FormLabel>Description</FormLabel> - <FormControl> - <Input placeholder="shadcn" {...field} className="text-black" /> - </FormControl> - <FormDescription> - This is your watchlist description. - </FormDescription> - <FormMessage /> - </FormItem> - )} - /> - <FormField - control={form.control} - name="films" - render={() => ( - <FormItem> - <FormLabel>Films</FormLabel> - <FormMessage /> - </FormItem> - )} - /> - <div className="flex justify-start py-4 w-screen"> - {selectedFilms.length === 0 && <p>You haven't choose any film yet</p>} - {selectedFilms.map((film) => ( - <FilmCard key={film.film_id} item={film} isAdd={false} handleClick={() => handleRemoveFilm(film)} /> - ))} - </div> - <Button type="submit" className="bg-purple-500">Submit</Button> - </form> - </Form> - <div className="flex justify-start py-4 w-screen"> + return ( + <div className="w-full min-h-screen bg-[#17181D]"> + <div className="fixed top-0 w-full z-10"> + <Navbar /> + </div> + <div className="w-full flex flex-col justify-center pt-28 text-white p-8 md:px-16 gap-4 md:gap-8"> + <div className="flex gap-4 md:gap-8 justify-start w-full"> + <NavLink to="/watchlists"> + <ChevronLeftCircle className="h-400 w-400" /> + </NavLink> + </div> + <Form {...form}> + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8"> + <FormField + control={form.control} + name="title" + render={({ field }) => ( + <FormItem> + <FormLabel>Title</FormLabel> + <FormControl> + <Input + placeholder="shadcn" + {...field} + className="text-black" + /> + </FormControl> + <FormDescription> + This is your watchlist name. + </FormDescription> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="description" + render={({ field }) => ( + <FormItem> + <FormLabel>Description</FormLabel> + <FormControl> <Input - placeholder="Search by title ..." - value={searchInput} - onChange={(event) => setSearchInput(event.target.value)} - className="max-w-sm text-md text-black" + placeholder="shadcn" + {...field} + className="text-black" /> - </div> - <div className="flex flex-wrap justify-center"> - {films.map((film) => ( - <FilmCard key={film.film_id} item={film} isAdd={true} handleClick={() => handleAddFilm(film)} /> - ))} - </div> - <ReactPaginate - previousLabel={'Previous'} - nextLabel={'Next'} - breakLabel={'...'} - breakClassName={'break-me'} - pageCount={totalPages} - marginPagesDisplayed={2} - pageRangeDisplayed={5} - onPageChange={handlePageChange} - containerClassName={'pagination flex justify-center'} - pageClassName={'mx-2 border border-solid border-purple-500 rounded-2 rounded p-2 cursor-pointer'} - previousClassName={`mx-2 p-2 cursor-pointer ${isPrevButtonDisabled ? 'disabled' : ''}`} - nextClassName={`mx-2 p-2 cursor-pointer ${isNextButtonDisabled ? 'disabled' : ''}`} - activeClassName={'active bg-purple-500 text-white'} + </FormControl> + <FormDescription> + This is your watchlist description. + </FormDescription> + <FormMessage /> + </FormItem> + )} + /> + <FormField + control={form.control} + name="films" + render={() => ( + <FormItem> + <FormLabel>Films</FormLabel> + <FormMessage /> + </FormItem> + )} + /> + <div className="flex justify-start py-4"> + {selectedFilms.length === 0 && ( + <p>You haven't choose any film yet</p> + )} + {selectedFilms.map((film) => ( + <FilmCard + key={film.film_id} + item={film} + isAdd={false} + handleClick={() => handleRemoveFilm(film)} /> + ))} </div> - </div>) -} + <Button type="submit" className="bg-purple-500"> + Submit + </Button> + </form> + </Form> + <div className="flex justify-start py-4"> + <Input + placeholder="Search by title ..." + value={searchInput} + onChange={(event) => setSearchInput(event.target.value)} + className="max-w-sm text-md text-black" + /> + </div> + <div className="flex flex-wrap justify-center"> + {films.map((film) => ( + <FilmCard + key={film.film_id} + item={film} + isAdd={true} + handleClick={() => handleAddFilm(film)} + /> + ))} + </div> + <ReactPaginate + previousLabel={"Previous"} + nextLabel={"Next"} + breakLabel={"..."} + breakClassName={"break-me"} + pageCount={totalPages} + marginPagesDisplayed={2} + pageRangeDisplayed={5} + onPageChange={handlePageChange} + containerClassName={"pagination flex justify-center"} + pageClassName={ + "mx-2 border border-solid border-purple-500 rounded-2 rounded p-2 cursor-pointer" + } + previousClassName={`mx-2 p-2 cursor-pointer ${ + isPrevButtonDisabled ? "disabled" : "" + }`} + nextClassName={`mx-2 p-2 cursor-pointer ${ + isNextButtonDisabled ? "disabled" : "" + }`} + activeClassName={"active bg-purple-500 text-white"} + /> + </div> + </div> + ); +}; -export default AddWatchlist; \ No newline at end of file +export default AddWatchlist; diff --git a/src/pages/Login/Login.tsx b/src/pages/Login/Login.tsx index 4f5248c4ad32a30d6fcaeeb10f15d1afab3ee369..51a15091ec2b1adc3b55afaa5d74f8e2e1ce1c56 100644 --- a/src/pages/Login/Login.tsx +++ b/src/pages/Login/Login.tsx @@ -10,7 +10,7 @@ import { useToast } from "@/components/ui/use-toast"; import { useAuth } from "@/context/AuthContext"; const Login = () => { - const {login} = useAuth() + const { login } = useAuth(); const [identifier, setidentifier] = React.useState(""); const [password, setPassword] = React.useState(""); const navigate = useNavigate(); @@ -22,7 +22,7 @@ const Login = () => { identifier, password, }); - // console.log("BJIR", data); + console.log("coba masuk login", data); navigate("/"); } catch (error) { toast({