Compare commits

..

No commits in common. "main" and "tailwind" have entirely different histories.

11 changed files with 40 additions and 144 deletions

View File

@ -3,9 +3,9 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/popcorn.svg" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Movie Explorer</title>
<title>Vite + React + TS</title>
</head>
<body>

21
package-lock.json generated
View File

@ -10,11 +10,9 @@
"dependencies": {
"@types/axios": "^0.14.0",
"axios": "^1.6.8",
"lucide-react": "^0.451.0",
"react": "^18.2.0",
"react-crossfade-image": "^1.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^2.5.3"
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "*",
@ -2999,14 +2997,6 @@
"yallist": "^3.0.2"
}
},
"node_modules/lucide-react": {
"version": "0.451.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.451.0.tgz",
"integrity": "sha512-OwQ3uljZLp2cerj8sboy5rnhtGTCl9UCJIhT1J85/yOuGVlEH+xaUPR7tvNdddPvmV5M5VLdr7cQuWE3hzA4jw==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -4009,15 +3999,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tailwind-merge": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.3.tgz",
"integrity": "sha512-d9ZolCAIzom1nf/5p4LdD5zvjmgSxY0BGgdSvmXIoMYAiPdAW/dSpP7joCDYFY7r/HkEa2qmPtkgsu0xjQeQtw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/dcastil"
}
},
"node_modules/tailwindcss": {
"version": "3.4.13",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",

View File

@ -12,11 +12,9 @@
"dependencies": {
"@types/axios": "^0.14.0",
"axios": "^1.6.8",
"lucide-react": "^0.451.0",
"react": "^18.2.0",
"react-crossfade-image": "^1.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^2.5.3"
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "*",

View File

@ -1,56 +0,0 @@
<!-- <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-popcorn"><path d="M18 8a2 2 0 0 0 0-4 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0-4 0 2 2 0 0 0 0 4"/><path d="M10 22 9 8"/><path d="m14 22 1-14"/><path d="M20 8c.5 0 .9.4.8 1l-2.6 12c-.1.5-.7 1-1.2 1H7c-.6 0-1.1-.4-1.2-1L3.2 9c-.1-.6.3-1 .8-1Z"/></svg> -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512 512" xml:space="preserve">
<g>
<path style="fill:#FFF7E6;" d="M380.131,103.539L380.131,103.539L380.131,103.539c23.05-26.728-10.89-64.423-39.88-44.292l0,0l0,0
c10.186-33.793-36.151-54.423-54.448-24.241l0,0l0,0c-4.439-35.015-55.161-35.015-59.601,0l0,0l0,0
c-18.297-30.182-64.635-9.552-54.448,24.241l0,0l0,0c-28.993-20.13-62.933,17.564-39.883,44.292l0,0l0,0
c-34.672-6.598-50.346,41.642-18.417,56.684l0,0v23.024h285.095v-23.024l0,0C430.477,145.182,414.803,96.941,380.131,103.539z"/>
<polygon style="fill:#FFF7E6;" points="173.592,183.248 179.901,206.273 166.338,218.082 188.411,490.445 200.884,503.255
366.853,503.255 413.52,183.248 "/>
</g>
<g>
<polyline style="fill:#FF6465;" points="256.001,206.273 256.001,503.255 311.118,503.255 332.101,206.273 314.63,184.086
269.652,184.086 256.001,206.273 "/>
<polygon style="fill:#FF6465;" points="173.592,183.248 98.483,183.248 145.148,503.255 200.884,503.255 179.901,206.273 "/>
</g>
<polygon style="opacity:0.1;enable-background:new ;" points="98.483,183.248 106.09,235.422 405.912,235.422 413.52,183.248
173.592,183.248 "/>
<path style="fill:#F2CC7B;" d="M406.894,160.224H105.109c-12.716,0-23.024,10.308-23.024,23.024l0,0
c0,12.716,10.308,23.024,23.024,23.024h301.784c12.716,0,23.024-10.308,23.024-23.024l0,0
C429.918,170.532,419.609,160.224,406.894,160.224z"/>
<path d="M246.911,98.6c4.551,1.614,9.549-0.77,11.163-5.321c1.35-3.81,4.103-6.866,7.751-8.605c3.649-1.74,7.757-1.954,11.565-0.605
c7.864,2.787,11.995,11.452,9.209,19.318c-1.392,3.932,0.184,8.299,3.767,10.433c3.581,2.134,8.174,1.442,10.967-1.654
c7.543-8.359,20.477-9.025,28.833-1.484c4.243,3.828,6.635,9.084,6.737,14.798c0.085,4.776,3.983,8.588,8.74,8.588
c0.054,0,0.106,0,0.16-0.001c4.829-0.086,8.672-4.07,8.587-8.9c-0.187-10.455-4.745-20.47-12.51-27.473
c-10.656-9.612-25.318-12.042-37.993-7.596c-2.653-10.104-10.093-18.77-20.66-22.513c-8.211-2.909-17.067-2.447-24.934,1.302
c-7.864,3.75-13.799,10.339-16.709,18.552C239.976,91.989,242.359,96.988,246.911,98.6z"/>
<path d="M93.984,213.003l30.229,207.296c0.635,4.35,4.371,7.484,8.642,7.483c0.42,0,0.846-0.03,1.273-0.093
c4.779-0.697,8.089-5.136,7.392-9.915l-29.568-202.755h59.798l19.747,279.493h-38.79l-6.089-41.758
c-0.698-4.779-5.143-8.087-9.915-7.391c-4.779,0.697-8.089,5.136-7.392,9.914l7.18,49.241c0.627,4.298,4.312,7.483,8.654,7.483
h55.721c0.002,0,0.004,0,0.006,0s0.006,0,0.008,0h110.238c0.002,0,0.006,0,0.008,0c0.002,0,0.003,0,0.006,0h55.721
c4.342,0,8.026-3.185,8.654-7.483l29.964-205.478c0.697-4.779-2.613-9.218-7.392-9.915c-4.78-0.696-9.217,2.612-9.915,7.391
l-28.873,197.996h-38.79l19.747-279.493h59.798l-6.782,46.518c-0.697,4.779,2.613,9.218,7.392,9.914
c4.779,0.696,9.217-2.612,9.915-7.391l7.445-51.056c12.047-4.518,20.644-16.15,20.644-29.755c0-13.286-8.201-24.687-19.804-29.422
c6.465-10.262,7.044-22.699,3.773-32.762c-3.78-11.633-13.573-22.819-27.82-26c4.369-13.929-0.422-28.004-8.607-37.093
c-8.184-9.09-21.682-15.327-35.991-12.437c-1.674-14.502-11.777-25.411-22.951-30.386c-11.175-4.975-26.042-5.182-37.938,3.278
C281.898,5.857,268.232,0,256.001,0c-12.23,0-25.897,5.858-33.325,18.424c-11.895-8.458-26.765-8.251-37.936-3.278
c-11.175,4.975-21.278,15.884-22.952,30.386c-14.307-2.888-27.809,3.349-35.991,12.437c-8.185,9.089-12.976,23.164-8.607,37.093
c-14.246,3.182-24.041,14.368-27.82,26c-3.269,10.063-2.692,22.501,3.773,32.762c-11.604,4.736-19.804,16.137-19.804,29.423
C73.34,196.853,81.938,208.484,93.984,213.003z M189.285,215.018h57.971v279.493h-38.225L189.285,215.018z M302.969,494.511h-38.225
V215.018h57.971L302.969,494.511z M105.109,168.968h8.345h43.306c4.395,0,8.256-3.479,8.699-7.85
c0.478-4.703-3.103-9.115-7.805-9.595c-0.294-0.029-0.592-0.045-0.894-0.045h-41.207c-11.011-6.12-12.103-17.149-9.548-25.011
c2.676-8.234,10.631-16.926,24.232-14.338c3.648,0.695,7.348-0.996,9.208-4.218c1.86-3.222,1.478-7.266-0.95-10.083
c-9.046-10.489-5.492-21.723,0.301-28.156c5.794-6.433,16.593-11.144,27.971-3.243c3.055,2.121,7.117,2.079,10.128-0.108
c3.009-2.186,4.306-6.037,3.232-9.598c-3.997-13.262,3.818-22.079,11.728-25.599c7.906-3.521,19.692-3.429,26.871,8.415
c1.927,3.18,5.654,4.794,9.296,4.02c3.638-0.773,6.388-3.764,6.857-7.454c1.741-13.74,12.468-18.616,21.124-18.616
c8.656,0,19.383,4.875,21.126,18.616c0.468,3.69,3.219,6.68,6.858,7.454c3.642,0.774,7.368-0.841,9.296-4.02
c7.178-11.845,18.961-11.935,26.868-8.414c7.909,3.521,15.724,12.338,11.728,25.599c-1.074,3.561,0.223,7.412,3.232,9.598
c3.012,2.187,7.072,2.229,10.128,0.108c11.381-7.902,22.178-3.189,27.971,3.243c5.792,6.434,9.346,17.667,0.301,28.156
c-2.429,2.817-2.81,6.862-0.95,10.083c1.86,3.222,5.556,4.913,9.208,4.218c13.596-2.59,21.556,6.105,24.232,14.338
c2.553,7.861,1.463,18.891-9.548,25.011c0,0-204.547,0-204.712,0c-4.692,0-8.745,4.062-8.745,8.745c0,4.748,3.996,8.745,8.745,8.745
h206.809h8.345c7.874,0,14.28,6.406,14.28,14.279c0,7.129-5.256,13.037-12.092,14.094c-0.043,0.006-0.086,0.005-0.129,0.01
c-0.387,0.056-0.73,0.096-1.051,0.124c-0.335,0.024-0.668,0.051-1.009,0.051c0,0-150.89,0-150.895,0H105.109
c-0.688,0-1.378-0.076-2.059-0.175c-0.043-0.006-0.086-0.005-0.129-0.01c-6.836-1.059-12.092-6.967-12.092-14.094
C90.829,175.374,97.235,168.968,105.109,168.968z"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,7 +1,7 @@
import './spinner.css'
import { MovieWall } from './objects/movie-wall'
import { HamburgerMenu } from './objects/HamburgerMenu'
import { useState, useEffect } from 'react'
import { useState, useEffect, useRef, useLayoutEffect } from 'react'
import tmdb from './objects/tmdb'
import { Sidebar } from './objects/sidebar'
import { Movie } from './objects/movie-wall'
@ -24,15 +24,6 @@ class MovieClass implements Movie {
vote_average = 0
}
const splash: Movie = {
id: 0,
overview: "Select a movie to see others like it. Keep going until you find something to watch!",
title: "Welcome to Movie Explorer!",
vote_average: 0,
poster_path: null,
backdrop_path: null
}
class WatchProvidersClass implements WatchProviders {
flatrate = [];
rent = [];
@ -50,7 +41,7 @@ function App() {
})
const [movies, setMovies] = useState([new MovieClass()])
const [backdrop, setBackdrop] = useState("")
const [chosenMovie, setChosenMovie] = useState(splash)
const [chosenMovie, setChosenMovie] = useState(new MovieClass())
const [similarMoviesAvailable, setSimilarMoviesAvailable] = useState(true)
const [watchProviders, setWatchProviders] = useState(new WatchProvidersClass())
useEffect(() => { tmdb.getPopular(config, setMovies) }, [])
@ -83,14 +74,14 @@ function App() {
let desiredLength = 0
if (width > 1280) {
desiredLength = 18
} else if (width > 400) {
} else if (width > 640) {
desiredLength = 16
} else { desiredLength = 15 }
//ensure movies.length is a multiple of 6 (so it makes an orphanless grid)
while (movies.length < desiredLength && movies.length >= 6) { desiredLength = desiredLength - 6 }
return movies.slice(0, desiredLength)
}
//TODO: search bar
return (
<>
@ -102,10 +93,10 @@ function App() {
<header className="bg-black py-2">
<HamburgerMenu setConfig={setConfig} />
</header>
<main className="flex flex-col-reverse xl:flex-row justify-start sm:justify-center items-center sm:gap-8 grow">
<Sidebar className="flex flex-col justify-around sm:justify-between items-center bg-black/80 text-white w-full sm:w-96 h-64 sm:h-full" movie={chosenMovie} similarMoviesAvailable={similarMoviesAvailable} watchProviders={watchProviders} config={config} />
<main className="flex flex-col sm:flex-row sm:justify-center items-center gap-8 grow">
<Sidebar className="flex flex-col justify-between items-center max-w-sm bg-black/80 text-white w-full sm:w-96 h-64 sm:h-full" movie={chosenMovie} similarMoviesAvailable={similarMoviesAvailable} watchProviders={watchProviders} config={config} />
<MovieWall
className={"flex flex-wrap gap-2 flex-grow w-full md:w-11/12 justify-center items-center xl:max-w-5xl p-2"}
className={"flex flex-wrap gap-2 w-fit justify-center sm:max-w-lg xl:max-w-5xl "}
movies={trimMoviesForScreen(movies)}
setMovies={setMovies}
config={config}

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
function getWindowDimensions() {
const { innerWidth: width, innerHeight: height } = window;

View File

@ -1,10 +1,9 @@
import React, { useState } from "react"
import React, { useEffect, useLayoutEffect, useRef, useState } from "react"
import { Config } from "../App"
import tmdb from "./tmdb"
import { twMerge } from "tailwind-merge"
export interface Movie {
id: number
id: number | null
poster_path: string | null
backdrop_path: string | null
title: string | null
@ -46,17 +45,10 @@ export function MovieWall({ movies, setMovies, config, setChosenMovie, setSimila
for (let i = 0; i < movies.length; i++) {
const movie = movies[i]
const isHighlighted = movie.vote_average ? movie.vote_average > 6 : false
const widths = [
" ",
"w-16",
"largeMobile:w-20",
"sm:w-32",
"md:w-36",
"lg:w-40"
]
const borderColor = isHighlighted ? "border-orange-300" : "border-gray-300"
posters.push(
<Poster className={twMerge("aspect-poster rounded-xl border-2 overflow-hidden relative " + borderColor + widths.join(" "))} movie={movie} key={movie.id} index={i}
<Poster className={"w-16 sm:w-28 xl:w-36 aspect-poster rounded-xl border-2 overflow-hidden relative " + borderColor} isHighlighted={isHighlighted} movie={movie} key={movie.id} index={i}
listSimilar={tmdb.getSimilar}
config={config}
setMovies={setMovies}
@ -77,11 +69,12 @@ interface PosterProps extends React.ComponentPropsWithRef<"div"> {
setMovies: React.Dispatch<React.SetStateAction<Array<Movie>>>;
setChosenMovie: React.Dispatch<React.SetStateAction<Movie>>;
setSimilarMoviesAvailable: React.Dispatch<React.SetStateAction<boolean>>;
isHighlighted: boolean;
className: string
}
function Poster({ className, movie, config, listSimilar, setMovies, setChosenMovie, setSimilarMoviesAvailable }: PosterProps) {
function Poster({ className, movie, config, listSimilar, setMovies, setChosenMovie, setSimilarMoviesAvailable, isHighlighted }: PosterProps) {
function clickHandler() {
setChosenMovie(movie)
listSimilar(config, movie, setMovies, setSimilarMoviesAvailable)
@ -90,7 +83,7 @@ function Poster({ className, movie, config, listSimilar, setMovies, setChosenMov
const [hasError, setHasError] = useState(false)
return <><div className={className} >
{isLoading ?
<div className="m-auto h-full w-full flex justify-center items-center">
<div className="m-auto text-center h-fit w-fit">
<div className="lds-spinner align-middle"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div></div>
: <div className="z-10 h-full w-full absolute cursor-pointer hover:bg-white/40" onClick={() => clickHandler()}></div>
}

View File

@ -4,7 +4,6 @@ import tmdb from "./tmdb";
import { WatchProviders, WhereToWatch } from "./whereToWatch";
import CrossfadeImage from 'react-crossfade-image'
import { Config } from "../App";
import { Popcorn } from "lucide-react";
export interface SidebarProps extends React.ComponentPropsWithRef<"div"> {
movie: Movie;
@ -23,19 +22,17 @@ export function Sidebar({ movie, similarMoviesAvailable, watchProviders, config,
<div className="sm:h-14 w-fit flex justify-center items-center">
<h1 className="font-bold text-base sm:text-lg sm:text-wrap my-1 sm:my-4 overflow-hidden text-center text-balance">{movie?.title ?? "loading"}</h1>
</div>
<div className="flex flex-row sm:flex-col gap-2 justify-center items-center">
{movie.poster_path ?
<a href={tmdb.makeMovieLink(movie)} target="_blank" rel="noopener noreferrer" className="w-3/12 sm:w-60 aspect-poster flex justify-center">
<CrossfadeImage src={tmdb.makeImgUrl(movie.poster_path)} style={{
objectFit: "clip",
height: "100%",
apectRatio: "2 / 3"
}} />
</a>
: <Popcorn height="100%" size="60%" />
}
<div className="flex flex-row sm:flex-col gap-2 justify-center sm:items-center">
<a href={tmdb.makeMovieLink(movie)} target="_blank" rel="noopener noreferrer" className="h-32 sm:h-96 aspect-poster flex justify-center">
<CrossfadeImage src={tmdb.makeImgUrl(movie.poster_path ?? "")} style={{
objectFit: "clip",
height: "100%",
apectRatio: "2 / 3"
}} />
</a>
{similarMoviesAvailable ? "" : "No hay data suficiente para darte pelílculas relacionadas."}
<div className="h-36 sm:h-72 w-8/12 sm:w-full overflow-y-scroll overflow-x-hidden sm:mt-4 p-2 sm:p-4 text-sm sm:text-base flex justify-center items-center border-2 border-slate-800 rounded-lg ">
<div className="h-32 sm:h-72 w-64 sm:w-full overflow-y-scroll overflow-x-hidden sm:mt-4 p-2 sm:p-4 text-sm sm:text-base flex justify-center items-center border-2 border-slate-800 rounded-lg ">
{/* TODO: make the apology vertically centered */}
{movie.overview === null ?
<p>Loading...</p>
: movie.overview === "" ?
@ -45,7 +42,7 @@ export function Sidebar({ movie, similarMoviesAvailable, watchProviders, config,
}
</div>
</div>
<WhereToWatch watchProviders={watchProviders} config={config} className="w-full h-32 sm:h-32 flex flex-col justify-center mb-1 sm:mb-4 text-xs sm:text-base" />
<WhereToWatch watchProviders={watchProviders} config={config} className="w-full h-16 sm:h-32 flex flex-col justify-center mb-1 sm:mb-4 text-xs sm:text-base" />
</div >

View File

@ -62,14 +62,13 @@ interface ProviderIconsProps extends React.ComponentPropsWithRef<"div"> {
link: string;
}
function ProviderIcons({ providerList, link }: ProviderIconsProps) {
if (!providerList) { return "" }
const list = providerList.map((e: any, i: number) => {
return <div className="w-6 aspect-square" key={e.link}>
return <div className="w-6 aspect-square">
<a href={link}>
<img src={tmdb.makeLogoPath(e.logo_path)} key={e.provider_id + "_" + i} /></a>
</div>
})
return <div className="w-full flex justify-center">
return <div id="logos-container" className="w-full flex">
{list}
</div>
}
@ -82,7 +81,7 @@ export function WhereToWatch({ watchProviders, config, className }: WhereToWatch
{watchProvidersExist(watchProviders) ?
<>
<div className="flex w-full justify-around">
{watchProviders?.flatrate?.length > 0 ? <div><h1>Streaming:</h1><ProviderIcons providerList={watchProviders.flatrate} link={watchProviders.link} /></div> : ""}
{watchProviders?.flatrate?.length > 0 ? <div className=""><h1>Streaming:</h1><ProviderIcons providerList={watchProviders.flatrate} link={watchProviders.link} /></div> : ""}
{watchProviders?.rent?.length > 0 ? <div className=""><h1>{config.language == "es" ? "Alquiler:" : config.language == "en" ? "To Rent:" : ""}</h1><ProviderIcons providerList={watchProviders.rent} link={watchProviders.link} /></div> : ""}
{watchProviders?.buy?.length > 0 ? <div className="watch-providers"><h1>{config.language == "es" ? "Comprar:" : config.language == "en" ? "To Buy:" : ""}</h1><ProviderIcons providerList={watchProviders.buy} link={watchProviders.link} /></div> : ""}
</div>

View File

@ -23,12 +23,12 @@
color: currentColor;
display: inline-block;
position: relative;
width: 40px;
height: 40px;
width: 80px;
height: 80px;
}
.lds-spinner div {
transform-origin: 20px 20px;
transform-origin: 40px 40px;
animation: lds-spinner 1.2s linear infinite;
}
@ -36,10 +36,10 @@
content: " ";
display: block;
position: absolute;
top: 1.6px;
left: 18.4px;
width: 3.2px;
height: 8.8px;
top: 3.2px;
left: 36.8px;
width: 6.4px;
height: 17.6px;
border-radius: 20%;
background: currentColor;
}

View File

@ -2,13 +2,6 @@
export default {
content: ["./dist/index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: {
screens: {
largeMobile: "390px",
sm: "640px",
md: "767px",
lg: "1023px",
xl: "1280px",
},
extend: {
aspectRatio: {
poster: "2 / 3",