Skip to content

Commit

Permalink
Improved UI
Browse files Browse the repository at this point in the history
  • Loading branch information
luloxi committed Oct 15, 2024
1 parent 09bcc91 commit 290c822
Show file tree
Hide file tree
Showing 14 changed files with 1,037 additions and 219 deletions.
43 changes: 2 additions & 41 deletions packages/foundry/contracts/BasedShop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ contract BasedShop {
uint256 price,
uint256 date
);

event ArticlePriceUpdated(
uint256 indexed articleId, uint256 oldPrice, uint256 newPrice
);
Expand All @@ -40,12 +41,7 @@ contract BasedShop {
uint256 indexed articleId, uint256 oldAmount, uint256 newAmount
);
event ArticleDeleted(uint256 indexed articleId, uint256 date);
event ArticleLiked(
uint256 indexed articleID, address indexed user, uint256 date
);
event ArticleUnliked(
uint256 indexed articleID, address indexed user, uint256 date
);

event ArticleCommented(
uint256 indexed articleID,
address indexed user,
Expand Down Expand Up @@ -88,11 +84,6 @@ contract BasedShop {
mapping(uint256 => uint256) public articleAmounts;
mapping(uint256 => mapping(address => bool)) public articleBuyers;

// Likes
mapping(uint256 article => uint256 likes) public articleToLikes;
mapping(address user => mapping(uint256 article => bool liked)) public
userToArticleLikes;

// Comments
mapping(uint256 articleId => Comment[]) public articleToComments;
mapping(uint256 articleId => mapping(uint256 commentId => address user))
Expand Down Expand Up @@ -209,36 +200,6 @@ contract BasedShop {
emit ArticleDeleted(_articleId, block.timestamp);
}

function likeArticle(
uint256 _articleId
) public {
require(
articleBuyers[_articleId][msg.sender],
"You must buy the article to like it"
);
_requireArticleExists(_articleId);
require(
!userToArticleLikes[msg.sender][_articleId],
"You have already liked this article"
);
userToArticleLikes[msg.sender][_articleId] = true;
articleToLikes[_articleId]++;
emit ArticleLiked(_articleId, msg.sender, block.timestamp);
}

function unlikeArticle(
uint256 _articleId
) public {
_requireArticleExists(_articleId);
require(
userToArticleLikes[msg.sender][_articleId],
"You have not liked this article yet"
);
userToArticleLikes[msg.sender][_articleId] = false;
articleToLikes[_articleId]--;
emit ArticleUnliked(_articleId, msg.sender, block.timestamp);
}

function commentOnArticle(uint256 _articleId, string memory _text) public {
require(
articleBuyers[_articleId][msg.sender],
Expand Down
22 changes: 2 additions & 20 deletions packages/nextjs/app/create/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ import { InputBase } from "~~/components/scaffold-eth";
const Create = ({ onClose }: { onClose: any }) => {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [externalUrl, setExternalUrl] = useState("");
const [price, setPrice] = useState("");
const [amount, setAmount] = useState("");
const [urlError, setUrlError] = useState("");
const [yourJSON, setYourJSON] = useState<object>({});
const [uploadedImageIpfsPath, setUploadedImageIpfsPath] = useState(""); // NEW: For image IPFS path

Expand All @@ -31,29 +29,15 @@ const Create = ({ onClose }: { onClose: any }) => {
window.location.reload();
};

const validateUrl = (url: string) => {
const pattern = /^(https?:\/\/)/;
return pattern.test(url);
};

const handleUrlChange = (url: string) => {
setExternalUrl(url);
if (!validateUrl(url)) {
setUrlError("URL must start with http:// or https://");
} else {
setUrlError("");
}
};

useEffect(() => {
const generateTokenURIString = () => {
const fullImageUrl = `https://ipfs.io/ipfs/${uploadedImageIpfsPath}`;
const tokenURI = generateTokenURI(name, description, fullImageUrl, externalUrl);
const tokenURI = generateTokenURI(name, description, fullImageUrl);
setYourJSON(JSON.parse(atob(tokenURI.split(",")[1])));
};

generateTokenURIString();
}, [name, externalUrl, description, uploadedImageIpfsPath]);
}, [name, description, uploadedImageIpfsPath]);

return (
<>
Expand All @@ -75,8 +59,6 @@ const Create = ({ onClose }: { onClose: any }) => {
<div className="flex flex-col gap-3 text-left flex-shrink-0 w-full">
<InputBase placeholder="Article name" value={name} onChange={setName} />
<TextInput description={description} setDescription={setDescription} />
<InputBase placeholder="URL for your article (https://)" value={externalUrl} onChange={handleUrlChange} />
{externalUrl && urlError && <span className="text-red-500 text-sm">{urlError}</span>}
<div className="flex flex-row gap-3">
<div className="w-1/2">
<InputBase placeholder="Price in ETH" value={price} onChange={setPrice} />
Expand Down
3 changes: 1 addition & 2 deletions packages/nextjs/app/create/_components/generateTokenURI.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
const generateTokenURI = (name: string, description: string, image: string, externalUrl: string) => {
const generateTokenURI = (name: string, description: string, image: string) => {
// Base metadata object
const metadata: any = {
name,
description,
image,
external_url: externalUrl,
};

// Convert the metadata object to a JSON string
Expand Down
16 changes: 0 additions & 16 deletions packages/nextjs/app/explore/Explore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ export const Explore = () => {
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [page, setPage] = useState(0);
const [activeTab, setActiveTab] = useState("All");

const handleTabClick = (tab: any) => {
setActiveTab(tab);
};

const observer = useRef<IntersectionObserver | null>(null);

Expand Down Expand Up @@ -122,17 +117,6 @@ export const Explore = () => {

return (
<div className="flex flex-col items-center justify-center">
<div className="tabs-bar ">
<button className={`tab ${activeTab === "All" ? "active" : ""}`} onClick={() => handleTabClick("All")}>
All
</button>
<button
className={`tab text-red-600 ${activeTab === "Following" ? "active" : ""}`}
onClick={() => handleTabClick("Following")}
>
Following
</button>
</div>
<NewsFeed articles={articles} />
<div ref={lastPostElementRef}></div>
{loadingMore && <LoadingBars />}
Expand Down
133 changes: 9 additions & 124 deletions packages/nextjs/app/profile/[address]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
"use client";

import { useCallback, useEffect, useRef, useState } from "react";
import { useState } from "react";
import { usePathname } from "next/navigation";
import { ErrorComponent } from "../../../components/punk-society/ErrorComponent";
import { LoadingBars } from "../../../components/punk-society/LoadingBars";
import { NewsFeed } from "../../../components/punk-society/NewsFeed";
import BookmarkedArticles from "../_components/BookmarkedArticles";
import BoughtArticles from "../_components/BoughtArticles";
import ListedArticles from "../_components/ListedArticles";
import ProfileInfo from "../_components/ProfileInfo";
import { NextPage } from "next";
import { useScaffoldEventHistory } from "~~/hooks/scaffold-eth";
import { notification } from "~~/utils/scaffold-eth";
import { getMetadataFromIPFS } from "~~/utils/simpleNFT/ipfs-fetch";
import { NFTMetaData } from "~~/utils/simpleNFT/nftsMetadata";

export interface Post extends Partial<NFTMetaData> {
Expand All @@ -24,120 +21,20 @@ export interface Post extends Partial<NFTMetaData> {
}

const ProfilePage: NextPage = () => {
const [articles, setArticles] = useState<Post[]>([]);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(true);
const [page, setPage] = useState(1); // Start from page 1 to get the last post first
const [activeTab, setActiveTab] = useState("Listed");

const handleTabClick = (tab: any) => {
setActiveTab(tab);
};

const observer = useRef<IntersectionObserver | null>(null);

const pathname = usePathname();
const address = pathname.split("/").pop();

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

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

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 articlesUpdate: Post[] = [];

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

if (args?.user !== address) continue;
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;
}

articlesUpdate.push({
listingId: undefined,
uri: tokenURI,
user: user || "",
date: date?.toString() || "",
price: price?.toString() || "",
amount: amount?.toString() || "",
...nftMetadata,
});
} catch (e) {
notification.error("Error fetching articles");
console.error(e);
}
}

setArticles(prevArticles => [...prevArticles, ...articlesUpdate]);
} catch (error) {
notification.error("Failed to load articles");
} finally {
setLoadingMore(false);
}
},
[createEvents, address],
);

useEffect(() => {
setLoading(true);
fetchArticles(page).finally(() => setLoading(false));
}, [page, fetchArticles]);

const lastPostElementRef = useCallback(
(node: any) => {
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],
);

// Ensure the address is available before rendering the component
if (!address) {
return <p>Inexistent address, try again...</p>;
}

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

return (
<>
<ProfileInfo address={address} />
Expand All @@ -146,16 +43,10 @@ const ProfilePage: NextPage = () => {
<button className={`tab ${activeTab === "Listed" ? "active" : ""}`} onClick={() => handleTabClick("Listed")}>
Listed
</button>
<button
className={`tab text-red-600 ${activeTab === "Bought" ? "active" : ""}`}
onClick={() => handleTabClick("Bought")}
>
<button className={`tab ${activeTab === "Bought" ? "active" : ""}`} onClick={() => handleTabClick("Bought")}>
Bought
</button>
<button
className={`tab text-red-600 ${activeTab === "Saved" ? "active" : ""}`}
onClick={() => handleTabClick("Saved")}
>
<button className={`tab 0 ${activeTab === "Saved" ? "active" : ""}`} onClick={() => handleTabClick("Saved")}>
Saved
</button>
<button
Expand All @@ -171,15 +62,9 @@ const ProfilePage: NextPage = () => {
Revenue
</button>
</div>
{loading && page === 1 ? (
<LoadingBars />
) : articles.length === 0 ? (
<p>This user has no articles</p>
) : (
<NewsFeed articles={articles} />
)}
<div ref={lastPostElementRef}></div>
{page !== 1 && loadingMore && <LoadingBars />}
{activeTab === "Listed" && <ListedArticles />}
{activeTab === "Bought" && <BoughtArticles />}
{activeTab === "Saved" && <BookmarkedArticles />}
</div>
</>
);
Expand Down
Loading

0 comments on commit 290c822

Please sign in to comment.