fix: improve image handling and loading states across components

* Refactored image rendering in CommentCard, NoteCard, GalleryCard, QuickViewNoteCard, and TrendingImage components to use Next.js Image component for better performance and responsiveness.
* Updated loading state management in FollowButton and LoginForm components to ensure proper dependency tracking.
* Cleaned up unused Head imports in Tag and Upload pages for better code clarity.
* Enhanced dependency arrays in useEffect hooks for improved reliability in various components.
This commit is contained in:
2025-08-07 23:44:34 +02:00
parent fdc1d3bc02
commit e61c11b8d4
15 changed files with 109 additions and 88 deletions

View File

@@ -55,7 +55,7 @@ export default function RelaysPage() {
}
return () => clearTimeout(loadingTimeout);
}, [connectedRelays, refreshKey]);
}, [connectedRelays, refreshKey, loading]);
// Function to refresh NIP-65 relays for the current user
const refreshNip65Relays = async () => {

View File

@@ -1,6 +1,4 @@
'use client';
import Head from "next/head";
import { useNostrEvents } from "nostr-react";
import { useState, useEffect } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -68,12 +66,6 @@ export default function TagPage() {
return (
<>
<Head>
<title>LUMINA.rocks - Tags</title>
<meta name="description" content="Explore tags on LUMINA" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="px-2 md:px-6">
<Tabs defaultValue="trending" className="mt-4">
<TabsList className="mb-4 w-full grid grid-cols-1">

View File

@@ -1,6 +1,4 @@
'use client';
import Head from "next/head";
import { useEffect } from "react";
import UploadComponent from "@/components/UploadComponent";
@@ -19,12 +17,6 @@ export default function UploadPage() {
return (
<>
<Head>
<title>LUMINA.rocks</title>
<meta name="description" content="Yet another nostr web ui" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="py-2 px-2">
<UploadComponent />
</div>

View File

@@ -28,6 +28,7 @@ import { Avatar, AvatarImage } from '@/components/ui/avatar';
import ViewRawButton from '@/components/ViewRawButton';
import ViewNoteButton from './ViewNoteButton';
import Link from 'next/link';
import Image from 'next/image';
interface CommentCardProps {
pubkey: string;
@@ -77,17 +78,21 @@ const NoteCard: React.FC<CommentCardProps> = ({ pubkey, text, eventId, tags, eve
<div className='py-4'>
{
<div className='w-full h-full px-10'>
{imageSrc && imageSrc.length > 1 ? (
{imageSrc && imageSrc.length > 1 ? (
<Carousel>
<CarouselContent>
{imageSrc.map((src, index) => (
<CarouselItem key={index}>
<img
key={index}
src={src}
className='rounded lg:rounded-lg'
style={{ maxWidth: '100%', maxHeight: '100vh', objectFit: 'contain', margin: 'auto' }}
/>
<div className="relative w-full" style={{ minHeight: '300px', maxHeight: '100vh' }}>
<Image
src={src}
alt={textWithoutImage || 'Image'}
fill
sizes="100vw"
className="rounded lg:rounded-lg object-contain"
priority={index === 0}
/>
</div>
</CarouselItem>
))}
</CarouselContent>
@@ -95,7 +100,17 @@ const NoteCard: React.FC<CommentCardProps> = ({ pubkey, text, eventId, tags, eve
<CarouselNext />
</Carousel>
) : (
imageSrc ? <img src={imageSrc[0]} className='rounded lg:rounded-lg' style={{ maxWidth: '100%', maxHeight: '100vh', objectFit: 'contain', margin: 'auto' }} /> : ""
imageSrc ? (
<div className="relative w-full" style={{ minHeight: '300px', maxHeight: '100vh' }}>
<Image
src={imageSrc[0]}
alt={textWithoutImage || 'Image'}
fill
sizes="100vw"
className="rounded lg:rounded-lg object-contain"
/>
</div>
) : ""
)}
</div>
}

View File

@@ -37,11 +37,11 @@ const FollowButton: React.FC<FollowButtonProps> = ({ pubkey, userPubkey }) => {
followingPubkeys = followingPubkeys.filter((tag) => tag);
useEffect(() => {
if (followingPubkeys.includes(pubkey)) {
setIsFollowing(true);
}
}, [followingPubkeys, isFollowing, setIsFollowing]);
useEffect(() => {
if (followingPubkeys.includes(pubkey)) {
setIsFollowing(true);
}
}, [followingPubkeys, pubkey]);
const handleFollow = async () => {
// if (isLoggedIn) {

View File

@@ -31,13 +31,16 @@ const GalleryCard: React.FC<GalleryCardProps> = ({ pubkey, eventId, imageUrl, li
<div>
<div className='d-flex justify-content-center align-items-center'>
<div style={{ position: 'relative' }}>
<img
src={imageUrl}
className='rounded lg:rounded-lg w-full h-full object-cover'
style={{ maxHeight: '75vh', margin: 'auto' }}
alt={eventId}
loading="lazy"
/>
<div className="relative w-full" style={{ minHeight: '200px', maxHeight: '75vh' }}>
<Image
src={imageUrl}
alt={eventId}
fill
sizes="100vw"
className="rounded lg:rounded-lg object-cover"
priority={false}
/>
</div>
</div>
</div>
</div>

View File

@@ -118,7 +118,7 @@ export function LoginForm() {
qrScannerRef.current.stop().catch(console.error);
}
};
}, []);
}, [completeLogin]);
// Handle QR Scanner dialog
const startQRScanner = () => {

View File

@@ -27,6 +27,7 @@ import ReactionButton from '@/components/ReactionButton';
import { Avatar, AvatarImage } from '@/components/ui/avatar';
import ViewNoteButton from './ViewNoteButton';
import Link from 'next/link';
import Image from 'next/image';
import { Event as NostrEvent } from "nostr-tools";
import ZapButton from './ZapButton';
import CardOptionsDropdown from './CardOptionsDropdown';
@@ -91,14 +92,16 @@ const NoteCard: React.FC<NoteCardProps> = ({ pubkey, text, eventId, tags, event,
<CarouselContent>
{imageSrc.map((src, index) => (
<CarouselItem key={index}>
<img
key={index}
src={src}
className='rounded lg:rounded-lg w-full h-auto object-contain'
style={{ maxHeight: '66vh', margin: 'auto' }}
alt={textWithoutImage || "Post image"}
loading="lazy"
/>
<div className="relative w-full" style={{ minHeight: '300px', maxHeight: '66vh' }}>
<Image
src={src}
alt={textWithoutImage || "Post image"}
fill
sizes="100vw"
className="rounded lg:rounded-lg object-contain"
priority={index === 0}
/>
</div>
</CarouselItem>
))}
</CarouselContent>
@@ -106,14 +109,17 @@ const NoteCard: React.FC<NoteCardProps> = ({ pubkey, text, eventId, tags, event,
<CarouselNext />
</Carousel>
) : (
imageSrc ?
<img
src={imageSrc[0]}
className='rounded lg:rounded-lg w-full h-auto object-contain'
style={{ maxHeight: '66vh', margin: 'auto' }}
alt={textWithoutImage || "Post image"}
loading="lazy"
/> : ""
imageSrc ? (
<div className="relative w-full" style={{ minHeight: '300px', maxHeight: '66vh' }}>
<Image
src={imageSrc[0]}
alt={textWithoutImage || "Post image"}
fill
sizes="100vw"
className="rounded lg:rounded-lg object-contain"
/>
</div>
) : ""
)}
</div>
<div className='w-full h-full px-10'>

View File

@@ -45,12 +45,15 @@ const QuickViewNoteCard: React.FC<NoteCardProps> = ({ pubkey, text, eventId, tag
<div className="absolute top-2 right-2 w-7 h-7 lg:w-12 lg:h-12 bg-black bg-opacity-40 rounded-lg flex items-center justify-center">
<StackIcon className='absolute w-7 h-7 lg:w-12 lg:h-12'/>
</div>
<img src={imageSrc[0]}
className='rounded lg:rounded-lg w-full h-auto object-cover'
style={{ maxHeight: '75vh', margin: 'auto' }}
alt={text}
loading="lazy"
/>
<div className="relative w-full" style={{ minHeight: '200px', maxHeight: '75vh' }}>
<Image
src={imageSrc[0]}
alt={text}
fill
sizes="100vw"
className="rounded lg:rounded-lg object-cover"
/>
</div>
</div>
) : imageSrc && imageSrc.length > 0 ? (
<div style={{ position: 'relative' }}>
@@ -59,12 +62,15 @@ const QuickViewNoteCard: React.FC<NoteCardProps> = ({ pubkey, text, eventId, tag
<PlayIcon className='absolute w-7 h-7 lg:w-12 lg:h-12' />
</div>
}
<img src={imageSrc[0]}
className='rounded lg:rounded-lg w-full h-auto object-cover'
style={{ maxHeight: '75vh', margin: 'auto' }}
alt={text}
loading="lazy"
/>
<div className="relative w-full" style={{ minHeight: '200px', maxHeight: '75vh' }}>
<Image
src={imageSrc[0]}
alt={text}
fill
sizes="100vw"
className="rounded lg:rounded-lg object-cover"
/>
</div>
</div>
) : videoSrc && videoSrc.length > 0 ? (
<div style={{ position: 'relative' }}>

View File

@@ -66,7 +66,15 @@ const TrendingImage: React.FC<TrendingImageProps> = ({ eventId, pubkey }) => {
{imageSrc && imageSrc.length > 0 && (
<div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }}>
<Link href={hrefNote}>
<img src={imageSrc[0]} className='rounded lg:rounded-lg' style={{ width: '100%', height: '100%', objectFit: 'cover' }} alt={text} />
<div className="relative w-full" style={{ minHeight: '200px' }}>
<Image
src={imageSrc[0]}
alt={text}
fill
sizes="100vw"
className="rounded lg:rounded-lg object-cover"
/>
</div>
</Link>
</div>
// <img src={imageSrc[0]} style={{ maxWidth: '100%', maxHeight: '100vh', objectFit: 'cover', margin: 'auto' }} alt={text} />

View File

@@ -76,13 +76,15 @@ const TrendingImageNew: React.FC<TrendingImageNewProps> = ({ event }) => {
{imageUrl && (
<div style={{ position: 'relative', width: '100%', height: '100%', overflow: 'hidden' }}>
<Link href={hrefNote} onClick={hasNsfwTag && !showSensitiveContent ? (e) => e.preventDefault() : undefined}>
<img
src={imageUrl}
className={`rounded lg:rounded-lg w-full h-full object-cover ${hasNsfwTag && !showSensitiveContent ? 'blur-xl' : ''}`}
style={{ margin: 'auto' }}
alt={text}
loading="lazy"
/>
<div className="relative w-full h-full">
<img
src={imageUrl}
className={`rounded lg:rounded-lg w-full h-full object-cover ${hasNsfwTag && !showSensitiveContent ? 'blur-xl' : ''}`}
style={{ margin: 'auto' }}
alt={text}
loading="lazy"
/>
</div>
{hasNsfwTag && !showSensitiveContent && (
<div
className="absolute inset-0 flex flex-col items-center justify-center"

View File

@@ -63,11 +63,10 @@ export default function ZapButton({ event }: { event: any }) {
// Effect to check for new zap receipts when showing an invoice
useEffect(() => {
if (invoice) {
// Store the current count of zap receipts when invoice is generated
invoiceEventsCountRef.current = events.length;
setPaymentComplete(false);
}
}, [invoice]);
}, [invoice, events.length]);
// Effect to detect new zap receipts after invoice is shown
useEffect(() => {

View File

@@ -9,6 +9,7 @@ import ConnectedRelaysButton from "@/components/headerComponents/ConnectedRelays
import AuthButton from "./AuthButton"
import { Button } from "@/components/ui/button"
import { UserIcon } from "lucide-react"
import { Badge } from "@/components/ui/badge"
export function TopNavigation() {
const [pubkey, setPubkey] = useState<string | null>(null)
@@ -47,6 +48,7 @@ export function TopNavigation() {
<TopNavigationItems items={siteConfig.mainNav} />
<div className="flex flex-1 items-center justify-end space-x-4">
<nav className="flex items-center space-x-2">
<Badge variant="secondary" className="hidden sm:inline">v{siteConfig.version}</Badge>
<ConnectedRelaysButton />
<DropdownThemeMode />
{pubkey !== null ? <AvatarDropdown /> : <AuthButton />}

View File

@@ -15,19 +15,15 @@ const NIP05: React.FC<NIP05Props> = ({ nip05, pubkey }) => {
let domain = nip05.split('@')[1]
useEffect(() => {
if(nip05.length > 0) {
if (nip05.length > 0) {
fetch(`https://${domain}/.well-known/nostr.json?name=${name}`)
.then(response => response.json())
.then(data => {
if (data.names[name] === pubkey) {
setIsValid(true);
} else {
setIsValid(false);
}
setIsLoading(false);
})
.then(response => response.json())
.then(data => {
setIsValid(data.names[name] === pubkey);
setIsLoading(false);
});
}
}, [nip05, pubkey]);
}, [nip05, pubkey, domain, name]);
return (
<>

View File

@@ -54,7 +54,7 @@ function TabsContentWrapper(props: {
return pathname + (asString ? "?" + asString : "");
},
[searchParams, props.searchParam],
[searchParams, searchParam, pathname, props.defaultValue],
);
return (