Added MovieCard and Billboard

master
MickSlash 2023-08-18 14:00:58 +02:00
parent ca2049d363
commit 2116b70e22
12 changed files with 262 additions and 15 deletions

View File

@ -1,9 +1,25 @@
import React from 'react'
import { signOut } from 'next-auth/react'
const AccountMenu = () => {
interface AccountMenuProps {
visible?: boolean;
}
const AccountMenu: React.FC<AccountMenuProps> = ({visible}) => {
if(!visible){
return null;
}
return (
<div>AccountMenu</div>
<div className='bg-black w-56 absolute top-14 right-0 py-5 flex-col border-2 border-gray-800 flex '>
<div className='flex flex-col gap-3'>
<div className='px-3 group/item flex flex-row gap-3 items-center w-full'>
<img className='w-8 rounded-md' src='' alt='Immagine Profilo' />
<p className='text-white text-sm group-hover/item:underline'>Username</p>
</div>
<hr className='bg-gray-600 border-0 h-px my-4' />
<div onClick={() => signOut()} className='px-3 text-center text-white text-sm hover:underline'>Logout</div>
</div>
</div>
)
}

28
components/Billboard.tsx Normal file
View File

@ -0,0 +1,28 @@
import useBillBoard from "@/hooks/useBillboard";
import React from "react";
import {AiOutlineInfoCircle} from "react-icons/ai"
const Billboard = () => {
const { data } = useBillBoard();
return (
<div className="relative aspect-video w-full">
<video className="w-full aspect-video object-cover brightness-[60%]" autoPlay muted loop poster={data?.thumbnailUrl} src={data?.videoUrl}></video>
<div className="absolute top-[30%] md:top-[40%] ml-4 md:ml-16">
<p className="text-white text-1xl md:text-5xl h-full w-[50%] lg:text-6xl font-bold drop-shadow-xl">
{data?.title}
</p>
<p className="text-white text-[8px] md:text-lg mt-3 md:mt-8 w-[90%] md:w-[80%] lg:w-[50%] drop-shadow-xl">
{data?.description}
</p>
<div className="flex flex-row items-center mt-3 md:mt-4 gap-3">
<button className="bg-white text-white bg-opacity-30 rounded-md py-1 md:py-2 px-2 md:px-4 w-auto text-xs lg:text-lg font-semibold flex flex-row items-center hover:bg-opacity-20 transition">
<AiOutlineInfoCircle className="mr-1" />
More Info
</button>
</div>
</div>
</div>
)
}
export default Billboard;

46
components/MovieCard.tsx Normal file
View File

@ -0,0 +1,46 @@
import React from "react";
import { BsFillPlayFill } from "react-icons/bs";
interface MovieCardProps {
data: Record<string,any>[];
}
const MovieCard: React.FC<MovieCardProps> = ({ data }) => {
return (
<div className="group bg-zinc-900 col-span relative h-[12vw]">
<img className="cursor-pointer object-cover transition duration shadow-xl rounded-md group-hover:opacity-90 sm:group-hover:opacity-0 delay-300 w-full h-[12vw]"
src={data.thumbnailUrl} alt="Thumbnail" />
<div className="opacity-0 absolute top-0 transition duration-200 z-10 invisible sm:visible delay-300 w-full scale-0 group-hover:scale-110 group-hover:-translate-y-[6vw] group-hover:translate-x-[2vw] group-hover:opacity-100">
<img className="cursor-pointer object-cover transition duration shadow-xl rounded-md w-full h-[12vw]" src={data.thumbnailUrl} alt="Thumbnail" />
<div className="
z-10
bg-zinc-800
p-2
lg:p-4
absolute
w-full
transition
shadow-md
rounded-b-md
">
<div className="flex flex-row items-center gap-3">
<div className="cursor-pointer w-6 h-6 lg:w-10 lg:h-10 bg-white rounded-full flex justify-center items-center transition hover:bg-neutral-300" onClick={() => {}}>
<BsFillPlayFill size={30} />
</div>
</div>
<p className="text-green-400 font-semibold mt-4">
New <span className="text-white">2023</span>
</p>
<div className="flex flex-row mt-4 gap-2 items-center">
<p className="text-white text-[10px] lg:text-sm">{data.duration}</p>
</div>
<div className="flex flex-row mt-4 gap-2 items-center">
<p className="text-white text-[10px] lg:text-sm">{data.genre}</p>
</div>
</div>
</div>
</div>
)
}
export default MovieCard;

29
components/MovieList.tsx Normal file
View File

@ -0,0 +1,29 @@
import React from 'react'
import { isEmpty } from 'lodash';
import MovieCard from './MovieCard';
interface MovieListProps {
data: Record<string, any>[];
title: string;
}
const MovieList: React.FC<MovieListProps> = ({ data, title }) => {
if (isEmpty(data)) {
return null;
}
return (
<div className='px-4 md:px-12 mt-4 space-y-8'>
<div className='text-white text-md md:text-xl lg:text-2xl font-semibold mb-4'>
<p>{title}</p>
<div className='grid grid-cols-4 gap-2'>
{data.map((film) => (
<MovieCard key={film.id} data={film} />
))}
</div>
</div>
</div>
)
}
export default MovieList;

View File

@ -1,17 +1,42 @@
import MobileMenu from "./MobileMenu"
import NavbarItem from "./NavbarItem"
import { BsChevronDown,BsSearch,BsBell } from "react-icons/bs"
import { useCallback,useState } from "react"
import { useCallback,useState,useEffect } from "react"
import AccountMenu from "./AccountMenu";
function Navbar() {
const [showMobileMenu,setShowMobileMenu] = useState(false);
const [showAccountMenu,setshowAccountMenu] = useState(false);
const [showBackground,setShowBackground] = useState(false);
const TOP_OFFSET = 66;
useEffect(() => {
const handleScroll = () => {
if(window.scrollY >= TOP_OFFSET){
setShowBackground(true);
}else{
setShowBackground(false)
}
}
window.addEventListener('scroll',handleScroll);
return () => {
window.removeEventListener('scroll',handleScroll);
}
},[])
const toggleMobileMenu = useCallback(()=>{
setShowMobileMenu((currentvisible) => !currentvisible)
},[])
},[]);
const toggleAccountMenu = useCallback(()=>{
setshowAccountMenu((currentvisible) => !currentvisible)
},[]);
return (
<nav className="w-full fixed z-40">
<div className="px-4 md:px-16 py-6 flex flex-row items-center transition duration-500 bg-zinc-900 bg-opacity-90">
<div className={`px-4 md:px-16 py-6 flex flex-row items-center transition duration-500 ${showBackground ? 'bg-zinc-900 bg-opacity-90' : ''}`}>
<img className="h-10 lg:h-16" src="https://i.imgur.com/6awt6G7.png" alt="Logo" />
<div className="flex-row ml-8 gap-7 hidden lg:flex">
<NavbarItem label="Home"/>
@ -23,7 +48,7 @@ function Navbar() {
</div>
<div onClick={toggleMobileMenu} className="lg:hidden flex flex-row items-center gap-2 ml-8 cursor-pointer relative">
<p className="text-white text-sm">Sfoglia</p>
<BsChevronDown className="text-white transition" />
<BsChevronDown className={`text-white transition ${showMobileMenu ? 'rotate-180' : 'rotate-0'}`} />
<MobileMenu visible={showMobileMenu} />
</div>
<div className="flex flex-row ml-auto gap-7 items-center">
@ -33,13 +58,13 @@ function Navbar() {
<div className="text-gray-200 hover:text-gray-300 cursor-pointer transition ">
<BsBell />
</div>
<div className="flex flex-row items-center gap-2 cursor-pointer relative">
<div onClick={toggleAccountMenu} className="flex flex-row items-center gap-2 cursor-pointer relative">
<div className="w-6 h-6 lg:w-10 lg:h-10 rounded-md overflow-hidden">
<img src="https://api.dicebear.com/6.x/fun-emoji/svg?seed=Michael" alt="Profilo" />
</div>
<BsChevronDown className="text-white transition" />
<BsChevronDown className={`text-white transition ${showAccountMenu ? 'rotate-180' : 'rotate-0'}`} />
</div>
<AccountMenu />
<AccountMenu visible={showAccountMenu} />
</div>
</div>
</nav>

17
hooks/useBillboard.ts Normal file
View File

@ -0,0 +1,17 @@
import useSWR from "swr";
import fetcher from "@/lib/fetcher";
const useBillBoard = () => {
const { data,error,isLoading } = useSWR('/api/random',fetcher,{
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false
});
return {
data,
error,
isLoading
}
}
export default useBillBoard;

17
hooks/useMoviesList.ts Normal file
View File

@ -0,0 +1,17 @@
import useSWR from "swr";
import fetcher from "@/lib/fetcher";
const useMoviesList = () => {
const {data,error,isLoading} = useSWR('/api/movies',fetcher,{
revalidateIfStale: false,
revalidateOnReconnect: false,
revalidateOnFocus: false
});
return {
data,
error,
isLoading
}
}
export default useMoviesList;

20
package-lock.json generated
View File

@ -18,6 +18,8 @@
"bcrypt": "^5.1.0",
"eslint": "8.42.0",
"eslint-config-next": "13.4.4",
"loadash": "^1.0.0",
"lodash": "^4.17.21",
"next": "13.4.4",
"next-auth": "^4.22.1",
"postcss": "8.4.24",
@ -30,6 +32,7 @@
},
"devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/lodash": "^4.14.195",
"prisma": "^4.13.0"
}
},
@ -479,6 +482,12 @@
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="
},
"node_modules/@types/lodash": {
"version": "4.14.195",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
"integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==",
"dev": true
},
"node_modules/@types/node": {
"version": "20.2.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.5.tgz",
@ -2989,6 +2998,12 @@
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/loadash": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/loadash/-/loadash-1.0.0.tgz",
"integrity": "sha512-xlX5HBsXB3KG0FJbJJG/3kYWCfsCyCSus3T+uHVu6QL6YxAdggmm3QeyLgn54N2yi5/UE6xxL5ZWJAAiHzHYEg==",
"deprecated": "Package is unsupport. Please use the lodash package instead."
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -3003,6 +3018,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",

View File

@ -11,27 +11,28 @@
"dependencies": {
"@next-auth/prisma-adapter": "^1.0.6",
"@prisma/client": "^4.13.0",
"axios": "^1.4.0",
"bcrypt": "^5.1.0",
"next-auth": "^4.22.1",
"react-icons": "^4.8.0",
"swr": "^2.1.5",
"@types/node": "20.2.5",
"@types/react": "18.2.8",
"@types/react-dom": "18.2.4",
"autoprefixer": "10.4.14",
"axios": "^1.4.0",
"bcrypt": "^5.1.0",
"eslint": "8.42.0",
"eslint-config-next": "13.4.4",
"lodash": "^4.17.21",
"next": "13.4.4",
"next-auth": "^4.22.1",
"postcss": "8.4.24",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.8.0",
"swr": "^2.1.5",
"tailwindcss": "3.3.2",
"typescript": "5.1.3"
},
"devDependencies": {
"@types/bcrypt": "^5.0.0",
"@types/lodash": "^4.14.195",
"prisma": "^4.13.0"
}
}

18
pages/api/movies/index.ts Normal file
View File

@ -0,0 +1,18 @@
import {NextApiRequest,NextApiResponse} from "next";
import prismadb from "@/lib/prismadb";
import serverAuth from "@/lib/serverAuth";
export default async function handler(req: NextApiRequest,res: NextApiResponse) {
if(req.method !== "GET"){
return res.status(405).end();
}
try{
await serverAuth(req);
const movies = await prismadb.movie.findMany();
return res.status(200).json(movies);
}catch(error){
console.log(error);
return res.status(400).end();
}
}

22
pages/api/random.ts Normal file
View File

@ -0,0 +1,22 @@
import { NextApiRequest, NextApiResponse } from "next";
import prismadb from "@/lib/prismadb";
import serverAuth from "@/lib/serverAuth";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'GET') {
return res.status(405).end
}
try {
await serverAuth(req);
const movieCount = await prismadb.movie.count();
const randomIndex = Math.floor(Math.random() * movieCount);
const randomMovies = await prismadb.movie.findMany({
take: 1,
skip: randomIndex
});
return res.status(200).json(randomMovies[0])
} catch (error) {
console.log(error);
return res.status(400).end()
}
}

View File

@ -1,5 +1,8 @@
import Billboard from "@/components/Billboard"
import MovieList from "@/components/MovieList"
import Navbar from "@/components/Navbar"
import useCurrentUser from "@/hooks/useCurrentUser"
import useMoviesList from "@/hooks/useMoviesList"
import { NextPageContext } from "next"
import { signOut,getSession } from "next-auth/react"
@ -20,9 +23,14 @@ export async function getServerSideProps(context: NextPageContext){
export default function Home() {
const { data: film = [] } = useMoviesList();
return (
<>
<Navbar />
<Billboard />
<div>
<MovieList title="Popolari" data={film} />
</div>
</>
)
}