Skip to content

Commit

Permalink
Implement queue and top recommendation
Browse files Browse the repository at this point in the history
  • Loading branch information
frlis21 committed Dec 16, 2023
1 parent 4f579b7 commit 7a7b90e
Show file tree
Hide file tree
Showing 23 changed files with 75,451 additions and 9,448 deletions.
26 changes: 21 additions & 5 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
import { createSignal, Show } from "solid-js";
import { Route } from "@solidjs/router";
import { Router, Route } from "@solidjs/router";
import Home from "./pages/Home";
import Queue from "./pages/Queue";
import Landing from "./pages/Landing";
import Login from "./pages/Login";
import Setup from "./pages/Setup";
import Layout from "./pages/Layout";
import { useUser } from "./store";
import { BASE } from "./common/constants";

function Root() {
const [user] = useUser();

return (
<Show when={user.isLoggedIn} fallback={<Landing />}>
<Home />
</Show>
);
}

function App() {
const [user] = useUser();

return (
<>
<Route path="/setup" component={Setup} />
<Route path="/" component={user.isLoggedIn ? Home : Landing} />
<Router base={BASE}>
<Route path="/" component={Root} />
<Show when={user.isLoggedIn}>
<Route path="/queue" component={Queue} />
<Route path="/setup" component={Setup} />
</Show>
<Show when={!user.isLoggedIn}>
<Route path="/login" component={Login} />
</Show>
</>
</Router>
);
}

Expand Down
41 changes: 41 additions & 0 deletions src/api/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { useUser } from "../store";
import { API_BASE } from "../common/constants";

// Cache
const moviesById = {};

export async function fetchMovies(searchParams) {
const response = await fetch(API_BASE + "/movies?" + (searchParams || ""));
const data = await response.json();
for (const movie of data.movies) {
moviesById[movie.id] = movie
}
return data
}

export async function fetchRecommendations(page) {
const [user] = useUser();
const response = await fetch(
`${API_BASE}/users/${user.email}/recommendations?page=${page || 1}`,
{
headers: {
Authorization: `Bearer ${user.token}`,
},
},
);
const data = await response.json();
for (const movie of data.movies) {
moviesById[movie.id] = movie
}
return data
}

export async function fetchMovie(id) {
if (!(id in moviesById)) {
const response = await fetch(`${API_BASE}/movies/${id}`);
const data = await response.json();
moviesById[id] = data.movie;
}

return moviesById[id];
}
1 change: 0 additions & 1 deletion src/assets/solid.svg

This file was deleted.

1 change: 1 addition & 0 deletions src/common/constants.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const BASE = "/nextflix-web"
export const API_BASE = BASE + "/api" // for now
export const IMG_BASE = "http://image.tmdb.org/t/p/"
1 change: 0 additions & 1 deletion src/common/createHovering.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createSignal } from "solid-js";
import createToggle from "./createToggle";

function createHovering() {
const [hovering, setHovering] = createSignal(false);
Expand Down
51 changes: 51 additions & 0 deletions src/common/createLikePair.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useUser } from "../store";
import {
ThumbsUp,
ThumbsDown,
} from "phosphor-solid";

function createLikePair(id) {
const [user, { like, dislike }] = useUser();

const likes = () => id() in user.ratings && user.ratings[id()];
const dislikes = () => id() in user.ratings && !user.ratings[id()];

const Like = (props) => (
<button
onClick={() => like(id())}
classList={{
"bg-neutral-300": !likes(),
"bg-green-500": likes(),
}}
class="aspect-square p-4 rounded-full group hover:bg-green-300 transition"
>
<ThumbsUp
class="m-auto group-hover:-rotate-6 group-active:-rotate-12 transition"
weight="duotone"
color="white"
size={props.size}
/>
</button>
);
const Dislike = (props) => (
<button
onClick={() => dislike(id())}
classList={{
"bg-neutral-300": !dislikes(),
"bg-red-500": dislikes(),
}}
class="aspect-square p-4 rounded-full group hover:bg-red-300 transition"
>
<ThumbsDown
class="m-auto group-hover:rotate-6 group-active:rotate-12 transition"
weight="duotone"
color="white"
size={props.size}
/>
</button>
);

return [Like, Dislike]
}

export default createLikePair;
8 changes: 8 additions & 0 deletions src/common/createMovie.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createResource } from "solid-js";
import { fetchMovie } from "../api";

function createMovie(id) {
return createResource(id, fetchMovie)[0];
}

export default createMovie;
49 changes: 17 additions & 32 deletions src/common/createMovies.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
import {
createSignal,
untrack,
} from "solid-js";
import { createSignal, untrack } from "solid-js";
import { createStore } from "solid-js/store";
import { API_BASE } from "./constants";
import { fetchMovies } from "../api";

// Movies cache
// TODO: do this the real way :T
export const moviesById = {};

async function fetchMovies(searchParams) {
const response = await fetch(
API_BASE + "/movies?" + (searchParams || ""),
);
return await response.json();
}

export function createMovies(params) {
function createMovies(params) {
const [store, setStore] = createStore({
fetching: false,
page: 0,
Expand All @@ -26,28 +12,27 @@ export function createMovies(params) {

async function loadMore() {
setStore({ fetching: true });
const response = await fetchMovies(new URLSearchParams([
["search", params.search],
...params.genres.map((genre) => ["genre", genre]),
...params.actors.map((actor) => ["actor", actor]),
...params.directors.map((director)=> ["director", director]),
["page", untrack(() => store.page + 1)],
]));
for (const movie of response.movies) {
// Cache the movies
moviesById[movie.id] = movie;
}
const data = await fetchMovies(
new URLSearchParams([
["search", params.search],
...params.genres.map((genre) => ["genre", genre]),
...params.actors.map((actor) => ["actor", actor]),
...params.directors.map((director) => ["director", director]),
["page", untrack(() => store.page + 1)],
]),
);
setStore({
fetching: false,
page: response.page,
pages: response.pages,
movies: untrack(() => [...store.movies, ...response.movies]),
page: data.page,
pages: data.pages,
movies: untrack(() => [...store.movies, ...data.movies]),
});
}

// Load first batch of movies
loadMore()
loadMore();

return [store, loadMore];
}

export default createMovies;
28 changes: 28 additions & 0 deletions src/common/createRecommendations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { createSignal, untrack } from "solid-js";
import { createStore } from "solid-js/store";
import { fetchRecommendations } from "../api";

export function createRecommendations() {
const [store, setStore] = createStore({
fetching: false,
page: 0,
pages: 1,
movies: [],
});

async function loadMore() {
setStore({ fetching: true, page: untrack(() => store.page + 1) });
const response = await fetchRecommendations(store.page);
console.log(response.page)
setStore({
fetching: false,
pages: response.pages,
movies: untrack(() => [...store.movies, ...response.movies]),
});
}

// Load first batch of recommendations
loadMore();

return [store, loadMore];
}
55 changes: 13 additions & 42 deletions src/components/MovieList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,28 @@ import {
} from "phosphor-solid";
import { createEventProps } from "@solid-primitives/event-props";
import Modal from "../components/Modal";
import { useUser, useGenres } from "../store";
import { useUser } from "../store";
import { Transition } from "solid-transition-group";
import { API_BASE } from "../common/constants";
import { moviesById } from "../common/createMovies";
import { IMG_BASE } from "../common/constants";
import createLikePair from "../common/createLikePair";
import createMovie from "../common/createMovie";
import { useWindowScrollPosition } from "@solid-primitives/scroll";

function MovieInfo(props) {
const movie = () => moviesById[props.id];
const movie = createMovie(() => props.id)
const [Like, Dislike] = createLikePair(() => props.id)
const [user, { like, dislike }] = useUser();

const likes = () => props.id in user.ratings && user.ratings[props.id];
const dislikes = () => props.id in user.ratings && !user.ratings[props.id];

return (
<div class="max-w-5xl rounded-lg overflow-hidden">
<div
class="bg-cover bg-center"
style={{
"background-image": `url("${movie().backdrop}")`,
"background-image": `url("${IMG_BASE}w1280${movie()?.backdrop}")`,
}}
>
<div class="flex justify-between items-center p-4 bg-black/80">
<h1 class="text-white font-black text-5xl">{movie().name}</h1>
<h1 class="text-white font-black text-5xl">{movie()?.name}</h1>
<button
onClick={props.onExit}
class="aspect-square rounded-full hover:bg-red-500 p-2 transition"
Expand All @@ -49,38 +48,10 @@ function MovieInfo(props) {
</div>
</div>
<div class="bg-white p-4 flex gap-4">
<p class="">{movie().synopsis}</p>
<p class="">{movie()?.synopsis}</p>
<div class="space-y-4">
<button
onClick={() => like(props.id)}
classList={{
"bg-neutral-300": !likes(),
"bg-green-500": likes(),
}}
class="aspect-square p-4 rounded-full group hover:bg-green-300 transition"
>
<ThumbsUp
class="m-auto group-hover:-rotate-6 group-active:-rotate-12 transition"
weight="duotone"
color="white"
size={32}
/>
</button>
<button
onClick={() => dislike(props.id)}
classList={{
"bg-neutral-300": !dislikes(),
"bg-red-500": dislikes(),
}}
class="aspect-square p-4 rounded-full group hover:bg-red-300 transition"
>
<ThumbsDown
class="m-auto group-hover:rotate-6 group-active:rotate-12 transition"
weight="duotone"
color="white"
size={32}
/>
</button>
<Like size={32} />
<Dislike size={32} />
</div>
</div>
</div>
Expand All @@ -90,7 +61,7 @@ function MovieInfo(props) {
function Movie(props) {
const [user] = useUser();
const [showMovie, setShowMovie] = createSignal(false);
const movie = () => moviesById[props.id];
const movie = createMovie(() => props.id)
const thumbClass = "absolute inset-0 pointer-events-none";

// TODO: make modal less terrible :T
Expand Down Expand Up @@ -121,7 +92,7 @@ function Movie(props) {
onClick={() => setShowMovie(true)}
class="w-full h-full bg-cover shadow-black shadow-lg cursor-pointer
group-hover:shadow-2xl group-hover:shadow-black group-hover:scale-110 transition"
src={movie().poster}
src={IMG_BASE + "w500" + movie()?.poster}
/>
<Show when={props.id in user.ratings}>
<Show
Expand Down
Loading

0 comments on commit 7a7b90e

Please sign in to comment.