Skip to content
Merged
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
477 changes: 355 additions & 122 deletions README.md

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions cinetrack-app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ RUN npm ci --include=dev
COPY client/ ./client/
COPY server/ ./server/

ARG VITE_TMDB_API_READ_ACCESS_TOKEN
ENV VITE_TMDB_API_READ_ACCESS_TOKEN=$VITE_TMDB_API_READ_ACCESS_TOKEN

RUN npm run build

FROM node:20-alpine AS production
Expand Down
73 changes: 0 additions & 73 deletions cinetrack-app/README.md

This file was deleted.

85 changes: 44 additions & 41 deletions cinetrack-app/client/src/components/layout/BottomNavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,40 @@ const NavItem: React.FC<{
}> = ({ label, icon, isActive, onClick }) => (
<button
onClick={onClick}
className="relative flex flex-col items-center justify-center w-full pt-2 pb-2"
className="relative flex flex-col items-center justify-center flex-1 py-2"
aria-current={isActive ? "page" : undefined}
>
{isActive && (
<motion.div
layoutId="nav-indicator"
className="absolute inset-0 top-0 bottom-0 w-16 mx-auto bg-brand-primary/10 rounded-xl"
transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
layoutId="tab-indicator"
className="absolute inset-1 bg-brand-primary/15 rounded-2xl"
transition={{ type: "spring", bounce: 0.15, duration: 0.5 }}
/>
)}

<motion.div
animate={{
scale: isActive ? 1.1 : 1,
color: isActive ? "var(--color-brand-primary)" : "var(--color-brand-text-dim)"
y: isActive ? -2 : 0,
}}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
className="relative z-10"
>
{icon}
<motion.div
animate={{
color: isActive ? "var(--color-brand-primary)" : "var(--color-brand-text-dim)",
}}
>
{icon}
</motion.div>
</motion.div>

<motion.span
animate={{
color: isActive ? "var(--color-brand-primary)" : "var(--color-brand-text-dim)",
fontWeight: isActive ? 600 : 500
opacity: isActive ? 1 : 0.7,
}}
className="text-[10px] mt-1 relative z-10"
className="text-[10px] mt-1 relative z-10 font-medium"
>
{label}
</motion.span>
Expand All @@ -54,42 +60,39 @@ export const BottomNavBar: React.FC<BottomNavBarProps> = ({
onTabChange,
}) => {
const navItems = [
{
id: "discover",
label: "Discover",
icon: <FiCompass className="h-6 w-6" />,
},
{
id: "lists",
label: "My List",
icon: <FiList className="h-6 w-6" />,
},
{
id: "recommendations",
label: "For You",
icon: <FiHeart className="h-6 w-6" />,
},
{
id: "stats",
label: "Stats",
icon: <FiBarChart2 className="h-6 w-6" />,
},
{ id: "discover", label: "Discover", icon: <FiCompass className="h-5 w-5" /> },
{ id: "lists", label: "My List", icon: <FiList className="h-5 w-5" /> },
{ id: "recommendations", label: "For You", icon: <FiHeart className="h-5 w-5" /> },
{ id: "stats", label: "Stats", icon: <FiBarChart2 className="h-5 w-5" /> },
] as const;

return (
<nav
className="lg:hidden fixed bottom-0 left-0 right-0 bg-brand-bg/90 backdrop-blur-xl border-t border-white/5 z-20 pb-[env(safe-area-inset-bottom)]"
>
<div className="flex justify-around items-center max-w-xl mx-auto px-2 py-2">
{navItems.map((item) => (
<NavItem
key={item.id}
label={item.label}
icon={item.icon}
isActive={activeTab === item.id}
onClick={() => onTabChange(item.id)}
/>
))}
<nav className="lg:hidden fixed bottom-0 left-0 right-0 z-20 px-4 pb-[calc(env(safe-area-inset-bottom)+8px)]">
<div
className="relative overflow-hidden rounded-[28px] border border-white/10 shadow-2xl shadow-black/30"
style={{
background: 'linear-gradient(to bottom, rgba(22, 22, 24, 0.75), rgba(10, 10, 11, 0.85))',
backdropFilter: 'blur(40px) saturate(180%)',
WebkitBackdropFilter: 'blur(40px) saturate(180%)',
}}
>
<div className="absolute inset-0 rounded-[28px] pointer-events-none"
style={{
background: 'linear-gradient(to bottom, rgba(255,255,255,0.08) 0%, transparent 50%)',
}}
/>

<div className="relative flex justify-around items-center px-2 py-1">
{navItems.map((item) => (
<NavItem
key={item.id}
label={item.label}
icon={item.icon}
isActive={activeTab === item.id}
onClick={() => onTabChange(item.id)}
/>
))}
</div>
</div>
</nav>
);
Expand Down
37 changes: 35 additions & 2 deletions cinetrack-app/client/src/components/media/MediaCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { memo } from "react";
import { FiImage, FiCheck, FiPlay } from "react-icons/fi";
import { TMDB_IMAGE_BASE_URL, TMDB_IMAGE_BASE_URL_MOBILE } from "../../constants/constants";
import type { Media } from "../../types/types";
import { useUIStore } from "../../store/useUIStore";
import { useLongPress } from "../../hooks/useLongPress";

interface MediaCardProps {
media: Media;
Expand All @@ -26,18 +28,49 @@ const MediaCardComponent: React.FC<MediaCardProps> = ({
}) => {
const title = media.media_type === "movie" ? media.title : media.name;
const isCurrentlyWatching = progress !== undefined && progress > 0;
const openMenu = useUIStore(state => state.openMenu);

const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault();
openMenu(e, {
id: media.id,
title: title || 'Unknown',
poster_path: media.poster_path,
media_type: media.media_type || 'movie'
}, 'desktop');
};

const handleClick = (e: React.MouseEvent | React.TouchEvent) => {
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
onClick(media, rect);
};

const longPressProps = useLongPress(
(e) => {
openMenu(e, {
id: media.id,
title: title || 'Unknown',
poster_path: media.poster_path,
media_type: media.media_type || 'movie'
}, 'mobile');
},
() => {
}
);

return (
<div
className="cursor-pointer transform transition-transform duration-300 hover:scale-105"
onClick={(e) => onClick(media, e.currentTarget.getBoundingClientRect())}
onContextMenu={handleContextMenu}
{...longPressProps}
onClick={(e) => {
handleClick(e);
}}
role="button"
aria-label={`View details for ${title}`}
style={{ opacity: isDimmed ? 0 : 1, transition: "opacity 0.3s" }}
>
<div className="relative rounded-lg overflow-hidden shadow-lg bg-brand-surface aspect-[2/3] ring-1 ring-white/5 group-hover:ring-brand-primary/30 transition-all">
<div className="relative rounded-lg overflow-hidden shadow-lg bg-brand-surface aspect-2/3 ring-1 ring-white/5 group-hover:ring-brand-primary/30 transition-all">
{media.poster_path ? (
<img
src={`${TMDB_IMAGE_BASE_URL}${media.poster_path}`}
Expand Down
Loading