Docker Build and Publish / build-and-push (push) Failing after 10s
Tapping a post on a phone now opens a real feed page — header, media, actions, caption, next post peeking in below — scrolled with the browser's own vertical scrolling rather than swipe gestures. Desktop keeps the modal, where a centred sheet with side arrows suits a pointer. Only a window of posts is mounted: a profile here holds up to 1129 posts and mounting them all would mean as many full-size images. The window grows in both directions as you scroll. Growing upwards shifts everything below it, so the scroll offset is corrected in the same frame, before paint — measured against the real archive, an anchored post moves exactly one screen per scroll with no jump. Only the post crossing the viewport centre plays its video; the rest stay paused, so a feed of reels doesn't play ten at once. The URL tracks that same post, so scrolling updates /<archive>/p/<shortcode>/ the way Instagram does, and the back button returns to the grid with its scroll position intact. Feed video sizes to the container width rather than its intrinsic size: a <video> reports 300x150 until metadata loads, which made it render narrow and then jump to full width. It also gets a taller height ceiling than the modal so ordinary portrait media fills the width instead of sitting in side bars. The carousel is extracted into a shared MediaCarousel used by both surfaces, so horizontal paging behaves identically; touch-action keeps vertical scrolling passing through to the feed. PostModal loses its now-dead mobile swipe branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011uBWhwV3wFQ5MBCcMHHem7
68 lines
2.7 KiB
TypeScript
68 lines
2.7 KiB
TypeScript
import React from 'react';
|
|
import { Bookmark, Heart, MessageCircle, MoreHorizontal, Send } from 'lucide-react';
|
|
import { Post } from '../types';
|
|
import { formatDateSafe } from '../lib/utils';
|
|
import { MediaCarousel } from './MediaCarousel';
|
|
|
|
interface FeedPostProps {
|
|
post: Post;
|
|
profilePic: string | null;
|
|
/** Off-screen posts keep their video paused. */
|
|
paused: boolean;
|
|
}
|
|
|
|
/**
|
|
* One post in the mobile feed, laid out like Instagram's: header, media,
|
|
* action row, then caption.
|
|
*
|
|
* Media is capped below full viewport height so the next post always peeks in
|
|
* at the bottom — that overlap is what tells you the page scrolls rather than
|
|
* pages.
|
|
*/
|
|
export const FeedPost: React.FC<FeedPostProps> = ({ post, profilePic, paused }) => (
|
|
<article className="bg-white border-b border-gray-200">
|
|
<header className="flex items-center justify-between px-3 py-2.5">
|
|
<div className="flex items-center gap-2.5 min-w-0">
|
|
<div className="w-8 h-8 rounded-full bg-gradient-to-tr from-yellow-400 to-purple-600 p-0.5 shrink-0">
|
|
<div className="w-full h-full rounded-full bg-white p-0.5">
|
|
<div className="w-full h-full rounded-full bg-gray-200 overflow-hidden flex items-center justify-center text-[10px] font-bold uppercase">
|
|
{profilePic
|
|
? <img src={profilePic} alt="" className="w-full h-full object-cover" referrerPolicy="no-referrer" />
|
|
: <span>{post.username[0]}</span>}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<span className="font-semibold text-sm truncate">{post.username}</span>
|
|
</div>
|
|
<MoreHorizontal size={20} className="text-gray-500 shrink-0" />
|
|
</header>
|
|
|
|
{/*
|
|
Taller ceiling than the modal so ordinary portrait media (9:16 reels,
|
|
4:5 photos) fills the feed width instead of sitting in side bars, while
|
|
still stopping anything extreme from swallowing the screen.
|
|
*/}
|
|
<MediaCarousel post={post} paused={paused} heightCap="max-h-[85vh]" fillWidth className="bg-black" />
|
|
|
|
<div className="px-3 pt-3 pb-1 flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Heart size={24} className="cursor-pointer" />
|
|
<MessageCircle size={24} className="cursor-pointer" />
|
|
<Send size={24} className="cursor-pointer" />
|
|
</div>
|
|
<Bookmark size={24} className="cursor-pointer" />
|
|
</div>
|
|
|
|
{post.caption && (
|
|
<div className="px-3 pb-1 text-sm">
|
|
<span className="font-semibold mr-2">{post.username}</span>
|
|
<span className="whitespace-pre-wrap">{post.caption}</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="px-3 pb-3 pt-1 text-[10px] uppercase tracking-wide text-gray-400">
|
|
{formatDateSafe(post.date, 'MMMM d, yyyy')}
|
|
</div>
|
|
</article>
|
|
);
|