diff --git a/src/app/api/media/[...path]/route.ts b/src/app/api/media/[...path]/route.ts index fb71486..74c52fc 100644 --- a/src/app/api/media/[...path]/route.ts +++ b/src/app/api/media/[...path]/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { prisma } from '@/lib/db'; -import { validateMediaPath } from '@/lib/security/path'; +import { validateSession } from '@/lib/auth/session'; import { getSafeContentType } from '@/lib/media/types'; import { getThumbnailPath, getGalleryPath } from '@/lib/processing/images'; import { getVideoPosterPath, getVideoPreviewPath } from '@/lib/processing/videos'; @@ -34,9 +34,12 @@ export async function GET( return new NextResponse('Not found', { status: 404 }); } - // CRITICAL: Unpublished media returns 403 — no metadata leak + // CRITICAL: Unpublished media returns 403 for public, but allows admin if (!mediaItem.published) { - return new NextResponse('Forbidden', { status: 403 }); + const isAuthenticated = await validateSession(request); + if (!isAuthenticated) { + return new NextResponse('Forbidden', { status: 403 }); + } } // Determine which file to serve diff --git a/src/app/globals.css b/src/app/globals.css index 5c42963..62f3921 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -53,27 +53,16 @@ body { } .media-wall { - display: grid; - grid-template-columns: repeat(2, 1fr); - grid-auto-rows: 150px; - grid-auto-flow: dense; + display: flex; + flex-wrap: wrap; gap: 10px; padding: 80px 10px 20px; max-width: 1920px; margin: 0 auto; } -@media (min-width: 480px) { - .media-wall { - grid-template-columns: repeat(3, 1fr); - grid-auto-rows: 180px; - } -} - @media (min-width: 768px) { .media-wall { - grid-template-columns: repeat(4, 1fr); - grid-auto-rows: 200px; gap: 15px; padding: 80px 15px 20px; } @@ -81,20 +70,11 @@ body { @media (min-width: 1024px) { .media-wall { - grid-template-columns: repeat(5, 1fr); - grid-auto-rows: 250px; gap: 20px; padding: 90px 20px 30px; } } -@media (min-width: 1440px) { - .media-wall { - grid-template-columns: repeat(6, 1fr); - grid-auto-rows: 250px; - } -} - .media-tile { position: relative; overflow: hidden; @@ -102,8 +82,7 @@ body { background: var(--color-border); cursor: pointer; transition: transform var(--transition), box-shadow var(--transition); - height: 100%; - width: 100%; + /* The width will be controlled by flex-basis and flex-grow dynamically */ } .media-tile:hover { @@ -114,10 +93,13 @@ body { .media-tile img, .media-tile video { + position: absolute; + top: 0; + left: 0; display: block; width: 100%; height: 100%; - object-fit: cover; + object-fit: cover; /* We still use cover, but the container's aspect ratio perfectly matches the image, so no cropping occurs! */ } .media-tile .play-icon { @@ -304,6 +286,36 @@ body { gap: 4px; } +.tile-like-wrapper { + position: absolute; + bottom: 8px; + right: 8px; + display: flex; + align-items: center; + justify-content: center; + z-index: 10; +} + +.tile-like-btn { + width: auto; + height: auto; + min-width: 40px; + min-height: 40px; + background: rgba(0, 0, 0, 0.6); + padding: 6px; + border-radius: 20px; +} + +.tile-like-btn svg { + width: 16px; + height: 16px; +} + +.tile-like-btn .like-count { + font-size: 0.75rem; + margin-top: 2px; +} + .unpublished { opacity: 0.5; } diff --git a/src/components/LikeButton.tsx b/src/components/LikeButton.tsx index b619908..d731dd6 100644 --- a/src/components/LikeButton.tsx +++ b/src/components/LikeButton.tsx @@ -5,15 +5,17 @@ import React, { useState } from 'react'; type LikeButtonProps = { mediaId: number; initialLikes: number; + className?: string; }; -export default function LikeButton({ mediaId, initialLikes }: LikeButtonProps) { +export default function LikeButton({ mediaId, initialLikes, className = '' }: LikeButtonProps) { const [likes, setLikes] = useState(initialLikes); const [isLiked, setIsLiked] = useState(false); const [isLoading, setIsLoading] = useState(false); const handleLike = async (e: React.MouseEvent) => { e.stopPropagation(); + e.preventDefault(); if (isLoading) return; setIsLoading(true); @@ -53,7 +55,7 @@ export default function LikeButton({ mediaId, initialLikes }: LikeButtonProps) { return (