Skip to content

Commit

Permalink
feat(tracking): add context menu button to detail page
Browse files Browse the repository at this point in the history
and several tweaks
  • Loading branch information
koenoe committed Jan 26, 2025
1 parent c2c7645 commit e0b00f5
Show file tree
Hide file tree
Showing 12 changed files with 276 additions and 96 deletions.
6 changes: 5 additions & 1 deletion src/app/(default)/track/[id]/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ export default async function TrackPage({ params: paramsFromProps }: Props) {
</>
}
>
<ActionButtons id={tvSeries.id} showWatchButton={false} />
<ActionButtons
id={tvSeries.id}
showWatchButton={false}
showContextMenuButton={false}
/>
</Suspense>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/app/(default)/tv/[id]/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,11 @@ export default async function TvSeriesDetailsPage({

<div className="mb-6 flex items-center">
<Suspense
fallback={<SkeletonRating className="mr-auto md:mr-10" />}
fallback={<SkeletonRating className="mr-auto md:mr-12" />}
>
<ImdbRating id={tvSeries.id} className="mr-auto md:mr-10" />
<ImdbRating id={tvSeries.id} className="mr-auto md:mr-12" />
</Suspense>
<div className="flex gap-3">
<div className="flex gap-2 md:gap-3">
<Suspense
fallback={
<>
Expand Down
25 changes: 19 additions & 6 deletions src/components/Buttons/ActionButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ import {
} from '@/lib/tmdb';
import { type TvSeries } from '@/types/tv-series';

import ActionButtonsProvider from './ActionButtonsProvider';
import AddButton from './AddButton';
import ContextMenuButton from './ContextMenuButton';
import LikeButton from './LikeButton';
import WatchButton from './WatchButton';

export default async function ActionButtons({
id,
showWatchButton = true,
showContextMenuButton = true,
}: Readonly<{
id: number | string;
showWatchButton?: boolean;
showContextMenuButton?: boolean;
}>) {
const tvSeries = (await cachedTvSeries(id)) as TvSeries;
const shouldShowWatchButton =
Expand Down Expand Up @@ -111,19 +115,28 @@ export default async function ActionButtons({
const isWatchlisted = await isInWatchlist(payload);

return (
<>
<ActionButtonsProvider
isFavorited={isFavorited}
isWatchlisted={isWatchlisted}
>
{shouldShowWatchButton && <WatchButton tvSeriesId={Number(id)} />}
<LikeButton isActive={isFavorited} action={addToOrRemoveAction} />
<AddButton isActive={isWatchlisted} action={addToOrRemoveAction} />
</>
<LikeButton action={addToOrRemoveAction} />
<AddButton action={addToOrRemoveAction} />
{showContextMenuButton && (
<ContextMenuButton tvSeries={tvSeries} action={addToOrRemoveAction} />
)}
</ActionButtonsProvider>
);
}

return (
<>
<ActionButtonsProvider isFavorited={false} isWatchlisted={false}>
{shouldShowWatchButton && <WatchButton tvSeriesId={Number(id)} />}
<LikeButton action={addToOrRemoveAction} />
<AddButton action={addToOrRemoveAction} />
</>
{showContextMenuButton && (
<ContextMenuButton tvSeries={tvSeries} action={addToOrRemoveAction} />
)}
</ActionButtonsProvider>
);
}
48 changes: 48 additions & 0 deletions src/components/Buttons/ActionButtonsProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use client';

import {
createContext,
useContext,
useState,
type PropsWithChildren,
} from 'react';

type State = Readonly<{
isFavorited: boolean;
isWatchlisted: boolean;
}>;

const ActionButtonsContext = createContext<
[State, (action: State | ((prevState: State) => State)) => void] | null
>(null);

export const ActionButtonsProvider = ({
children,
isFavorited,
isWatchlisted,
}: PropsWithChildren & State) => {
const state = useState<State>({
isFavorited,
isWatchlisted,
});

return (
<ActionButtonsContext.Provider value={state}>
{children}
</ActionButtonsContext.Provider>
);
};

export const useActionButtons = () => {
const context = useContext(ActionButtonsContext);

if (!context) {
throw new Error(
`useActionButtons must be used within <ActionButtonsProvider />`,
);
}

return context;
};

export default ActionButtonsProvider;
20 changes: 11 additions & 9 deletions src/components/Buttons/AddButton.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
'use client';

import { useCallback, useState, useTransition } from 'react';
import { useCallback, useTransition } from 'react';

import { motion } from 'framer-motion';

import { useActionButtons } from './ActionButtonsProvider';
import CircleButton from './CircleButton';

export default function AddButton({
isActive: isActiveFromProps = false,
action,
}: Readonly<{
isActive?: boolean;
action: (value: boolean, listType: 'favorites' | 'watchlist') => void;
}>) {
const [isActive, setIsActive] = useState(isActiveFromProps);
const [{ isWatchlisted }, setState] = useActionButtons();
const [isPending, startTransition] = useTransition();
const handleOnClick = useCallback(
(value: boolean) => {
setIsActive(value);
setState((prevState) => ({
...prevState,
isWatchlisted: value,
}));
startTransition(async () => {
try {
await action(value, 'watchlist');
Expand All @@ -26,21 +28,21 @@ export default function AddButton({
}
});
},
[action],
[action, setState],
);

return (
<CircleButton
isActive={isActive}
isActive={isWatchlisted}
onClick={handleOnClick}
isDisabled={isPending}
>
<svg
className="h-6 w-6"
className="size-5 md:size-6"
viewBox="0 0 512 512"
xmlns="http://www.w3.org/2000/svg"
>
{isActive ? (
{isWatchlisted ? (
<motion.polyline
points="416 128 192 384 96 288"
style={{
Expand Down
18 changes: 13 additions & 5 deletions src/components/Buttons/CircleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { useCallback } from 'react';
import { cva, cx, type VariantProps } from 'class-variance-authority';
import { motion } from 'framer-motion';

import { React } from '../../../.sst/platform/src/components/aws';

export const circleButtonStyles = cva(
'relative flex aspect-square h-12 w-12 items-center justify-center rounded-full border-2 focus:outline-none',
'relative flex aspect-square items-center justify-center rounded-full border-2 focus:outline-none',
{
variants: {
size: {
small: ['h-8 w-8'],
medium: ['h-12 w-12'],
small: ['size-8'],
medium: ['size-10 md:size-12'],
},
},
defaultVariants: {
Expand All @@ -29,8 +31,10 @@ export default function CircleButton({
isActive = false,
isDisabled,
size,
ref,
}: ButtonVariantProps &
Readonly<{
ref?: React.Ref<HTMLButtonElement>;
className?: string;
children: React.ReactNode;
onClick?: (
Expand All @@ -49,10 +53,11 @@ export default function CircleButton({

return (
<motion.button
ref={ref}
className={cx(circleButtonStyles({ className, size }))}
disabled={isDisabled}
whileTap="tap"
whileHover={isActive ? undefined : 'hover'}
whileHover={isActive ? 'hover-active' : 'hover-inactive'}
onClick={handleClick}
initial={false}
animate={isActive ? 'active' : 'inactive'}
Expand All @@ -68,9 +73,12 @@ export default function CircleButton({
borderColor: 'rgba(255, 255, 255, 0.2)',
color: 'rgba(255, 255, 255, 0.6)',
},
hover: {
'hover-inactive': {
borderColor: 'rgba(255, 255, 255, 0.4)',
},
'hover-active': {
borderColor: 'rgba(255, 255, 255, 1)',
},
}}
layout
>
Expand Down
Loading

0 comments on commit e0b00f5

Please sign in to comment.