feat: blog header images, npub display, and meetups load more
Show longform image tags on posts, use shortened npubs for author names, and paginate the homepage meetups section with responsive load more. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { MapPin, Clock, ArrowRight } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { AddToCalendarButton } from "@/components/public/AddToCalendarDialog";
|
||||
import { formatMeetupCivilDate } from "@/lib/meetupEventTime";
|
||||
|
||||
// Initial number of meetups shown before "Load more". Mobile is enforced via
|
||||
// CSS (3) so there's no SSR/viewport mismatch; desktop shows up to 6.
|
||||
const INITIAL_MOBILE = 3;
|
||||
const INITIAL_DESKTOP = 6;
|
||||
|
||||
interface MeetupData {
|
||||
id?: string;
|
||||
title: string;
|
||||
@@ -19,6 +27,26 @@ interface MeetupsSectionProps {
|
||||
}
|
||||
|
||||
export function MeetupsSection({ meetups }: MeetupsSectionProps) {
|
||||
// Number of extra batches revealed via "Load more". Each batch adds 3 on
|
||||
// mobile and 6 on desktop, so visible counts are 3*(pages+1) / 6*(pages+1).
|
||||
const [pages, setPages] = useState(0);
|
||||
|
||||
const mobileVisible = INITIAL_MOBILE * (pages + 1);
|
||||
const desktopVisible = INITIAL_DESKTOP * (pages + 1);
|
||||
|
||||
// Visibility per card. Enforced with CSS so the server-rendered markup matches
|
||||
// both viewports (desktopVisible >= mobileVisible, so "mobile-only" never occurs).
|
||||
const cardVisibilityClass = (i: number) => {
|
||||
if (i < mobileVisible) return "flex";
|
||||
if (i < desktopVisible) return "hidden md:flex";
|
||||
return "hidden";
|
||||
};
|
||||
|
||||
// Show the button only when content is still hidden at the given breakpoint.
|
||||
let loadMoreClass = "hidden";
|
||||
if (meetups.length > desktopVisible) loadMoreClass = "flex";
|
||||
else if (meetups.length > mobileVisible) loadMoreClass = "flex md:hidden";
|
||||
|
||||
return (
|
||||
<section className="py-24 px-8 border-t border-zinc-800/50">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
@@ -59,7 +87,7 @@ export function MeetupsSection({ meetups }: MeetupsSectionProps) {
|
||||
<Link
|
||||
key={meetup.id ?? i}
|
||||
href={href}
|
||||
className="group flex flex-col bg-zinc-900 border border-zinc-800 rounded-xl p-6 hover:border-zinc-700 hover:-translate-y-0.5 hover:shadow-xl transition-all duration-200"
|
||||
className={`group ${cardVisibilityClass(i)} flex-col bg-zinc-900 border border-zinc-800 rounded-xl p-6 hover:border-zinc-700 hover:-translate-y-0.5 hover:shadow-xl transition-all duration-200`}
|
||||
>
|
||||
<div className="flex items-start gap-4 mb-4">
|
||||
<div className="bg-zinc-800 rounded-lg px-3 py-2 text-center shrink-0 min-w-[52px]">
|
||||
@@ -113,6 +141,16 @@ export function MeetupsSection({ meetups }: MeetupsSectionProps) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`${loadMoreClass} justify-center mt-10`}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPages((p) => p + 1)}
|
||||
className="flex items-center gap-2 rounded-lg border border-zinc-700 px-6 py-2.5 text-sm font-semibold text-on-surface hover:border-primary hover:text-primary transition-colors"
|
||||
>
|
||||
Load more
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="md:hidden flex flex-col items-center gap-3 mt-8">
|
||||
<Link
|
||||
href="/events"
|
||||
|
||||
Reference in New Issue
Block a user