diff --git a/package-lock.json b/package-lock.json index a66ff08..03a2d69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-navigation-menu": "^1.1.2", "@radix-ui/react-separator": "^1.0.2", + "@radix-ui/react-slot": "^1.0.2", "@tanstack/react-query": "^4.28.0", "@trpc/client": "^10.18.0", "@trpc/next": "^10.18.0", @@ -726,6 +727,18 @@ "react-dom": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", @@ -863,6 +876,18 @@ "react-dom": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/@radix-ui/react-navigation-menu": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.1.2.tgz", @@ -951,6 +976,18 @@ "react-dom": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.3.tgz", @@ -986,15 +1023,38 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", - "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", "dependencies": { "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0" + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "dependencies": { + "@babel/runtime": "^7.13.10" }, "peerDependencies": { + "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, "node_modules/@radix-ui/react-use-callback-ref": { diff --git a/package.json b/package.json index 1062add..8237677 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-navigation-menu": "^1.1.2", "@radix-ui/react-separator": "^1.0.2", + "@radix-ui/react-slot": "^1.0.2", "@tanstack/react-query": "^4.28.0", "@trpc/client": "^10.18.0", "@trpc/next": "^10.18.0", diff --git a/src/components/movie-recommendation.tsx b/src/components/movie-recommendation.tsx index e2a510c..4b71a1b 100644 --- a/src/components/movie-recommendation.tsx +++ b/src/components/movie-recommendation.tsx @@ -5,6 +5,11 @@ import { api } from "~/utils/api"; import type { MovieInfo } from "~/pages/api/tmdb-fetch-movie-info"; import type { Movie } from "./movie-recommendations-button"; +type MovieRecommendationsProps = { + movies: Movie[]; + recommendationId: string; +}; + // Display the movie recommendations modal with a poster, title, release year, overview and links to TMDB and Letterboxd export const MovieModal = ({ movieInfo, @@ -151,11 +156,6 @@ export const MovieCard = (movieInfo: MovieInfo) => { ); }; -type MovieRecommendationsProps = { - movies: Movie[]; - recommendationId: string; -}; - // Render all aggregated movie recommendations export const MovieRecommendations = ({ movies, @@ -231,10 +231,9 @@ export const MovieRecommendations = ({ return (
- {moviesInfo && - moviesInfo.map((movieInfo: MovieInfo) => { - return ; - })} + {moviesInfo?.map((movieInfo: MovieInfo) => ( + + ))}
); diff --git a/src/components/movie-recommendations-button.tsx b/src/components/movie-recommendations-button.tsx index d31584c..a94f464 100644 --- a/src/components/movie-recommendations-button.tsx +++ b/src/components/movie-recommendations-button.tsx @@ -2,6 +2,8 @@ import { toast } from "react-hot-toast"; import getMovieRecommendations from "~/utils/get-movie-recommendations"; import { useEffect, useState } from "react"; import { api } from "~/utils/api"; +import { Loader2 } from "lucide-react"; +import { Button } from "./ui/button"; import type { TrackData } from "~/utils/hooks/use-recent-tracks"; export type MovieData = { @@ -38,6 +40,8 @@ const FetchMovieRecommendationsButton = ({ const { mutate: mutateRecommendation } = api.recommendation.create.useMutation(); + const [isFetching, setIsFetching] = useState(false); + useEffect(() => { if (recommendedMovies.length > 0 && recommendationId) { handleMovieData({ @@ -50,6 +54,7 @@ const FetchMovieRecommendationsButton = ({ const handleClick = async () => { try { + setIsFetching(true); let movies = await getMovieRecommendations(songs, temperature); // Increase temperature on each request to get more diverse results @@ -74,6 +79,7 @@ const FetchMovieRecommendationsButton = ({ // Set unique key to force re-render of MovieRecommendations component setUniqueKey(Date.now()); setRecommendedMovies(mappedMovies as Movie[]); + setIsFetching(false); mutateRecommendation( { @@ -92,12 +98,31 @@ const FetchMovieRecommendationsButton = ({ }; return ( - + <> + {isFetching && ( + + )} + + {!isFetching && ( + + )} + ); }; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..cf8908a --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,55 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "~/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "underline-offset-4 hover:underline text-primary", + }, + size: { + default: "h-10 py-2 px-4", + sm: "h-9 px-3 rounded-md", + lg: "h-11 px-8 rounded-md", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants }