Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ improve filters UX (spacing) #220

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/web/app/components/movie-discover-filters.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it, vi } from "vitest";

import { discoverMoviesSortOptions } from "@/config/options";
import { render, screen } from "@/testing/utils";

import { MovieDiscoverFilters } from "./movie-discover-filters";
Expand Down Expand Up @@ -30,6 +31,7 @@ describe("MovieDiscoverFilters", () => {
genres={mockGenres}
providers={mockProviders}
regions={mockRegions}
sortOptions={discoverMoviesSortOptions}
/>,
{ path: "/_layout/movies/discover/_layout" },
);
Expand All @@ -56,6 +58,7 @@ describe("MovieDiscoverFilters", () => {
genres={mockGenres}
providers={mockProviders}
regions={mockRegions}
sortOptions={discoverMoviesSortOptions}
/>,
{ path: "/_layout/movies/discover/_layout" },
);
Expand All @@ -73,6 +76,7 @@ describe("MovieDiscoverFilters", () => {
genres={mockGenres}
providers={mockProviders}
regions={mockRegions}
sortOptions={discoverMoviesSortOptions}
/>,
{ path: "/_layout/movies/discover/_layout" },
);
Expand All @@ -93,6 +97,7 @@ describe("MovieDiscoverFilters", () => {
genres={mockGenres}
providers={mockProviders}
regions={mockRegions}
sortOptions={discoverMoviesSortOptions}
/>,
{ path: "/_layout/movies/discover/_layout" },
);
Expand Down
62 changes: 32 additions & 30 deletions apps/web/app/components/movie-discover-filters.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type * as v from "valibot";

import { valibotResolver } from "@hookform/resolvers/valibot";
import { Button } from "@popcorn.fyi/ui/button";
import { useNavigate, useSearch } from "@tanstack/react-router";
Expand All @@ -6,23 +8,6 @@ import { useForm, useWatch } from "react-hook-form";

import { DiscoverSchema } from "@/lib/movies";

const sortOptions = [
{ label: "Original Title (A-Z)", value: "original_title.asc" },
{ label: "Original Title (Z-A)", value: "original_title.desc" },
{ label: "Popularity (Low to High)", value: "popularity.asc" },
{ label: "Popularity (High to Low)", value: "popularity.desc" },
{ label: "Revenue (Low to High)", value: "revenue.asc" },
{ label: "Revenue (High to Low)", value: "revenue.desc" },
{ label: "Release Date (Oldest First)", value: "primary_release_date.asc" },
{ label: "Release Date (Newest First)", value: "primary_release_date.desc" },
{ label: "Title (A-Z)", value: "title.asc" },
{ label: "Title (Z-A)", value: "title.desc" },
{ label: "Rating (Low to High)", value: "vote_average.asc" },
{ label: "Rating (High to Low)", value: "vote_average.desc" },
{ label: "Vote Count (Low to High)", value: "vote_count.asc" },
{ label: "Vote Count (High to Low)", value: "vote_count.desc" },
];

interface MovieDiscoverFiltersOptions {
genres: {
id: number;
Expand All @@ -40,12 +25,14 @@ interface MovieDiscoverFiltersOptions {
iso_3166_1?: string | undefined;
native_name?: string | undefined;
}[];
sortOptions: readonly { label: string; value: string }[];
}

export const MovieDiscoverFilters = ({
genres,
providers,
regions,
sortOptions,
}: MovieDiscoverFiltersOptions) => {
const search = useSearch({ from: "/_layout/movies/discover/_layout" });
const navigate = useNavigate();
Expand All @@ -58,13 +45,28 @@ export const MovieDiscoverFilters = ({
const values = useWatch({ control });

useEffect(() => {
void navigate({
search: (prevSearch) => {
return { ...prevSearch, ...values };
},
to: "/movies/discover",
});
}, [values, navigate]);
const debouncedNavigate = setTimeout(() => {
if (JSON.stringify(values) !== JSON.stringify(search)) {
void navigate({
search: (prevSearch) => {
return { ...prevSearch, ...values };
},
to: "/movies/discover",
});
}
}, 200);

return () => {
clearTimeout(debouncedNavigate);
};
}, [values, navigate, search]);

const handleReset = (
field: keyof v.InferInput<typeof DiscoverSchema>,
defaultValue = "",
) => {
resetField(field, { defaultValue });
};

return (
<form
Expand Down Expand Up @@ -100,7 +102,7 @@ export const MovieDiscoverFilters = ({
className="dsy-join-item"
color="neutral"
onClick={() => {
resetField("with_genres", { defaultValue: "" });
handleReset("with_genres");
}}
>
<span className="icon-[lucide--x]" />
Expand Down Expand Up @@ -130,7 +132,7 @@ export const MovieDiscoverFilters = ({
className="dsy-join-item"
color="neutral"
onClick={() => {
resetField("with_watch_providers", { defaultValue: "" });
handleReset("with_watch_providers");
}}
>
<span className="icon-[lucide--x]" />
Expand All @@ -154,7 +156,7 @@ export const MovieDiscoverFilters = ({
className="dsy-join-item"
color="neutral"
onClick={() => {
resetField("watch_region", { defaultValue: "US" });
handleReset("watch_region", "US");
}}
>
<span className="icon-[lucide--x]" />
Expand All @@ -172,7 +174,7 @@ export const MovieDiscoverFilters = ({
className="dsy-join-item"
color="neutral"
onClick={() => {
resetField("primary_release_date_gte", { defaultValue: "" });
handleReset("primary_release_date_gte");
}}
>
<span className="icon-[lucide--x]" />
Expand All @@ -188,7 +190,7 @@ export const MovieDiscoverFilters = ({
className="dsy-join-item"
color="neutral"
onClick={() => {
resetField("primary_release_date_lte", { defaultValue: "" });
handleReset("primary_release_date_lte");
}}
>
<span className="icon-[lucide--x]" />
Expand All @@ -213,7 +215,7 @@ export const MovieDiscoverFilters = ({
className="dsy-join-item"
color="neutral"
onClick={() => {
resetField("sort_by", { defaultValue: "popularity.desc" });
handleReset("sort_by", "popularity.desc");
}}
>
<span className="icon-[lucide--x]" />
Expand Down
16 changes: 16 additions & 0 deletions apps/web/app/config/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const discoverMoviesSortOptions = [
{ label: "Original Title (A–Z)", value: "original_title.asc" },
{ label: "Original Title (Z–A)", value: "original_title.desc" },
{ label: "Popularity (Low → High)", value: "popularity.asc" },
{ label: "Popularity (High → Low)", value: "popularity.desc" },
{ label: "Revenue (Low → High)", value: "revenue.asc" },
{ label: "Revenue (High → Low)", value: "revenue.desc" },
{ label: "Release Date (Oldest)", value: "primary_release_date.asc" },
{ label: "Release Date (Newest)", value: "primary_release_date.desc" },
{ label: "Title (A–Z)", value: "title.asc" },
{ label: "Title (Z–A)", value: "title.desc" },
{ label: "Rating (Low → High)", value: "vote_average.asc" },
{ label: "Rating (High → Low)", value: "vote_average.desc" },
{ label: "Votes (Low → High)", value: "vote_count.asc" },
{ label: "Votes (High → Low)", value: "vote_count.desc" },
] as const;
67 changes: 0 additions & 67 deletions apps/web/app/lib/genres.ts

This file was deleted.

44 changes: 43 additions & 1 deletion apps/web/app/lib/movies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,48 @@ export const discoverMoviesOptions = (
queryFn: () => {
return discoverMoviesFn({ data: query });
},
queryKey: ["movie", "list", "discover", query],
queryKey: ["movie", "discover", { ...query }],
});
};

const movieGenresFn = createServerFn({ method: "GET" }).handler(
async (context) => {
const { data } = await client.GET("/3/genre/movie/list", {
params: {
query: context.data,
},
});

return data.genres ?? [];
},
);

export const movieGenresOptions = () => {
return queryOptions({
queryFn: () => {
return movieGenresFn();
},
queryKey: ["movie", "genres", "list"],
});
};

const movieProvidersFn = createServerFn({ method: "GET" }).handler(
async (context) => {
const { data } = await client.GET("/3/watch/providers/movie", {
params: {
query: context.data,
},
});

return data.results ?? [];
},
);

export const movieProvidersOptions = () => {
return queryOptions({
queryFn: () => {
return movieProvidersFn();
},
queryKey: ["movie", "providers", "list"],
});
};
25 changes: 25 additions & 0 deletions apps/web/app/lib/providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { queryOptions } from "@tanstack/react-query";
import { createServerFn } from "@tanstack/start";

import { client } from "./tmdb";

const providerRegionsFn = createServerFn({ method: "GET" }).handler(
async (context) => {
const { data } = await client.GET("/3/watch/providers/regions", {
params: {
query: context.data,
},
});

return data.results ?? [];
},
);

export const providerRegionsOptions = () => {
return queryOptions({
queryFn: () => {
return providerRegionsFn();
},
queryKey: ["provider", "regions", "list"],
});
};
10 changes: 6 additions & 4 deletions apps/web/app/routes/_layout/movies.discover._layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { createFileRoute, Outlet } from "@tanstack/react-router";

import { MovieDiscoverFilters } from "@/components/movie-discover-filters";
import { Prose } from "@/components/prose";
import { discoverMoviesSortOptions } from "@/config/options";
import { site } from "@/config/site";
import {
DiscoverSchema,
movieGenresOptions,
movieProvidersOptions,
providerRegionsOptions,
} from "@/lib/genres";
import { DiscoverSchema } from "@/lib/movies";
} from "@/lib/movies";
import { providerRegionsOptions } from "@/lib/providers";

export const Route = createFileRoute("/_layout/movies/discover/_layout")({
component: RouteComponent,
Expand All @@ -29,7 +30,7 @@ function RouteComponent() {
const { data: regions } = useSuspenseQuery(providerRegionsOptions());

return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-8">
<Prose>
<h1>{site.pages.discoverMovies.title}</h1>
<p>{site.pages.discoverMovies.description}</p>
Expand All @@ -38,6 +39,7 @@ function RouteComponent() {
genres={genres}
providers={providers}
regions={regions}
sortOptions={discoverMoviesSortOptions}
/>
<Outlet />
</div>
Expand Down
Loading