Skip to content

Commit

Permalink
First version of lazy loading, may be glitchy
Browse files Browse the repository at this point in the history
  • Loading branch information
luloxi committed Sep 30, 2024
1 parent 1fb5268 commit cd767d4
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 218 deletions.
160 changes: 76 additions & 84 deletions packages/nextjs/app/explore/Explore.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
"use client";

import { useEffect, useState } from "react";
import { ErrorComponent } from "./_components/ErrorComponent";
import { useCallback, useEffect, useRef, useState } from "react";
import { LoadingSpinner } from "./_components/LoadingSpinner";
import { NewsFeed } from "./_components/NewsFeed";
import { useAccount } from "wagmi";
import { RainbowKitCustomConnectButton } from "~~/components/scaffold-eth";
import { useScaffoldEventHistory } from "~~/hooks/scaffold-eth";
import { notification } from "~~/utils/scaffold-eth";
import { getMetadataFromIPFS } from "~~/utils/simpleNFT/ipfs-fetch";
Expand All @@ -19,108 +16,103 @@ export interface Post extends Partial<NFTMetaData> {
}

export const Explore = () => {
const { address: isConnected, isConnecting } = useAccount();
const [listedPosts, setListedPosts] = useState<Post[]>([]);
const [posts, setPosts] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [page, setPage] = useState(0);
const observer = useRef<IntersectionObserver | null>(null);

const {
data: createEvents,
isLoading: createIsLoadingEvents,
error: createErrorReadingEvents,
// isLoading: createIsLoadingEvents,
// error: createErrorReadingEvents,
} = useScaffoldEventHistory({
contractName: "PunkPosts",
eventName: "PostCreated",
fromBlock: 0n,
watch: true,
});

// const {
// data: deleteEvents,
// isLoading: deleteIsLoadingEvents,
// error: deleteErrorReadingEvents,
// } = useScaffoldEventHistory({
// contractName: "PunkPosts",
// eventName: "PostDeleted",
// fromBlock: 0n,
// watch: true,
// });

useEffect(() => {
const fetchListedNFTs = async () => {
const fetchPosts = useCallback(
async (page: number) => {
if (!createEvents) return;

const postsUpdate: Post[] = [];

for (const event of createEvents || []) {
try {
const { args } = event;
const user = args?.user;
const tokenURI = args?.tokenURI;

if (!tokenURI) continue;

const ipfsHash = tokenURI.replace("https://ipfs.io/ipfs/", "");
const nftMetadata: NFTMetaData = await getMetadataFromIPFS(ipfsHash);

// Temporary fix for V1
// Check if the image attribute is valid and does not point to [object Object]
if (nftMetadata.image === "https://ipfs.io/ipfs/[object Object]") {
console.warn(`Skipping post with invalid image URL: ${nftMetadata.image}`);
continue;
setLoadingMore(true);
try {
// Calculate the start and end indices for the current page
const start = (page - 1) * 8;
const end = page * 8;
const eventsToFetch = createEvents.slice(start, end);

const postsUpdate: Post[] = [];

for (const event of eventsToFetch) {
try {
const user = event.args?.user;
const tokenURI = event.args?.tokenURI;

if (!tokenURI) continue;

const ipfsHash = tokenURI.replace("https://ipfs.io/ipfs/", "");
const nftMetadata: NFTMetaData = await getMetadataFromIPFS(ipfsHash);

// Temporary fix for V1
// Check if the image attribute is valid and does not point to [object Object]
if (nftMetadata.image === "https://ipfs.io/ipfs/[object Object]") {
console.warn(`Skipping post with invalid image URL: ${nftMetadata.image}`);
continue;
}

postsUpdate.push({
listingId: undefined,
uri: tokenURI,
user: user || "",
...nftMetadata,
});
} catch (e) {
notification.error("Error fetching posts");
console.error(e);
}

postsUpdate.push({
listingId: undefined,
uri: tokenURI,
user: user || "",
...nftMetadata,
});
} catch (e) {
notification.error("Error fetching posts");
console.error(e);
}
}

setListedPosts(postsUpdate);
};

fetchListedNFTs();
}, [createEvents]);
setPosts(prevPosts => [...prevPosts, ...postsUpdate]);
} catch (error) {
notification.error("Failed to load posts");
} finally {
setLoadingMore(false);
}
},
[createEvents],
);

useEffect(() => {
if (listedPosts.length > 0) {
setLoading(false); // Stop loading after Posts are updated
}
}, [listedPosts]);

// const filteredPosts = listedPosts.filter(Post => {
// return true;
// });

if (createIsLoadingEvents) {
return <LoadingSpinner />;
}
setLoading(true);
fetchPosts(page).finally(() => setLoading(false));
}, [page, fetchPosts]);

const lastPostElementRef = useCallback(
(node: HTMLDivElement | null) => {
if (loadingMore) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
setPage(prevPage => prevPage + 1);
}
});
if (node) observer.current.observe(node);
},
[loadingMore],
);

if (loading) {
if (loading && page === 0) {
return <LoadingSpinner />;
}

if (createErrorReadingEvents) {
return <ErrorComponent message={createErrorReadingEvents?.message || "Error loading events"} />;
}

return (
<>
<div className="flex justify-center">{!isConnected || isConnecting ? <RainbowKitCustomConnectButton /> : ""}</div>
{listedPosts.length === 0 ? (
<div className="flex justify-center items-center mt-10">
<div className="text-2xl text-primary-content">No posts found</div>
</div>
) : loading ? (
<LoadingSpinner />
) : (
<NewsFeed filteredPosts={listedPosts} />
)}
</>
<div>
<NewsFeed posts={posts} />
<div ref={lastPostElementRef}></div>
{loadingMore && <LoadingSpinner />}
</div>
);
};
10 changes: 5 additions & 5 deletions packages/nextjs/app/explore/_components/NewsFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React from "react";
import { Post } from "../Explore";
import { NFTCard } from "./NFTCard";
import { PostCard } from "./PostCard";

type NewsFeedProps = {
filteredPosts: Post[];
posts: Post[];
};

export const NewsFeed: React.FC<NewsFeedProps> = ({ filteredPosts }) => {
export const NewsFeed: React.FC<NewsFeedProps> = ({ posts }) => {
return (
<div className="mt-4 lg:mt-0 lg:flex lg:justify-center lg:items-center">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 lg:my-4">
{filteredPosts.map(item => (
<NFTCard nft={item} key={item.uri} />
{posts.map(post => (
<PostCard post={post} key={post.uri} />
))}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface Post extends Partial<NFTMetaData> {
date?: string;
}

export const NFTCard = ({ nft }: { nft: Post }) => {
export const PostCard = ({ post }: { post: Post }) => {
const [isModalOpen, setIsModalOpen] = useState(false);

const handleOpenModal = () => {
Expand All @@ -29,11 +29,11 @@ export const NFTCard = ({ nft }: { nft: Post }) => {
<div className="flex justify-center items-center">
<div className={`card-compact bg-base-300 w-[75%] lg:w-[300px] relative group rounded-lg`}>
{/* Image Section */}
{nft.image && nft.image !== "https://ipfs.io/ipfs/" && (
{post.image && post.image !== "https://ipfs.io/ipfs/" && (
<div className="relative w-full h-0 pb-[100%] overflow-hidden">
<figure className="absolute inset-0">
<Image
src={nft.image || "/path/to/default/image.png"}
src={post.image || "/path/to/default/image.png"}
alt="NFT Image"
className="w-full h-full rounded-lg object-cover"
layout="fill" // Ensures the image fills the container
Expand All @@ -50,19 +50,19 @@ export const NFTCard = ({ nft }: { nft: Post }) => {

<div className="card-body space-y-3">
<div className="flex flex-col justify-center mt-1">
<p className="my-0 text-lg">{nft.description ?? "No description available."}</p>
<p className="my-0 text-lg">{post.description ?? "No description available."}</p>
</div>

<div className="flex space-x-3 mt-1 items-center">
<ProfileAddress address={nft.user} />
<ProfileAddress address={post.user} />
</div>
</div>

{/* Modal for fullscreen image */}
{isModalOpen && (
<Modal onClose={handleCloseModal}>
<Image
src={nft.image || "/path/to/default/image.png"}
src={post.image || "/path/to/default/image.png"}
alt="NFT Image"
className="w-full h-auto rounded-lg object-cover"
layout="responsive"
Expand Down
Loading

0 comments on commit cd767d4

Please sign in to comment.