Skip to content

Commit f9c1322

Browse files
committed
UI improvement, new contract
1 parent 73c16d0 commit f9c1322

File tree

17 files changed

+1970
-564
lines changed

17 files changed

+1970
-564
lines changed

packages/foundry/broadcast/Deploy.s.sol/84532/run-1728941287.json

Lines changed: 210 additions & 0 deletions
Large diffs are not rendered by default.

packages/foundry/broadcast/Deploy.s.sol/84532/run-1728941643.json

Lines changed: 210 additions & 0 deletions
Large diffs are not rendered by default.

packages/foundry/broadcast/Deploy.s.sol/84532/run-1728947572.json

Lines changed: 210 additions & 0 deletions
Large diffs are not rendered by default.

packages/foundry/broadcast/Deploy.s.sol/84532/run-latest.json

Lines changed: 78 additions & 78 deletions
Large diffs are not rendered by default.

packages/foundry/contracts/BasedShop.sol

Lines changed: 156 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,24 @@ contract BasedShop {
2121
uint256 indexed articleId,
2222
address indexed user,
2323
string tokenURI,
24+
uint256 date,
25+
uint256 price,
26+
uint256 amount
27+
);
28+
event ArticleBought(
29+
uint256 indexed articleId,
30+
address indexed buyer,
31+
address indexed seller,
32+
uint256 price,
2433
uint256 date
2534
);
35+
event ArticlePriceUpdated(
36+
uint256 indexed articleId, uint256 oldPrice, uint256 newPrice
37+
);
38+
39+
event ArticleAmountUpdated(
40+
uint256 indexed articleId, uint256 oldAmount, uint256 newAmount
41+
);
2642
event ArticleDeleted(uint256 indexed articleId, uint256 date);
2743
event ArticleLiked(
2844
uint256 indexed articleID, address indexed user, uint256 date
@@ -43,7 +59,7 @@ contract BasedShop {
4359
event ArticleBookmarked(
4460
uint256 indexed articleID, address indexed user, uint256 date
4561
);
46-
event ArticleUnshared(
62+
event RemoveBookmark(
4763
uint256 indexed articleID, address indexed user, uint256 date
4864
);
4965
event UserFollowed(
@@ -67,6 +83,11 @@ contract BasedShop {
6783
mapping(uint256 => address) public articleIdToUser;
6884
mapping(address => uint256[]) public userArticles;
6985

86+
// Article details
87+
mapping(uint256 => uint256) public articlePrices;
88+
mapping(uint256 => uint256) public articleAmounts;
89+
mapping(uint256 => mapping(address => bool)) public articleBuyers;
90+
7091
// Likes
7192
mapping(uint256 article => uint256 likes) public articleToLikes;
7293
mapping(address user => mapping(uint256 article => bool liked)) public
@@ -78,7 +99,10 @@ contract BasedShop {
7899
public articleCommentToUser;
79100

80101
// Bookmarked
81-
mapping(address user => uint256[] sharedArticles) public
102+
mapping(address user => mapping(uint256 article => bool)) public
103+
userToArticleBookmark;
104+
mapping(uint256 article => uint256 bookmarks) public articleToBookmarks;
105+
mapping(address user => uint256[] bookmarkedArticles) public
82106
userToBookmarkedArticles;
83107
mapping(address user => mapping(uint256 article => uint256 index)) public
84108
userToBookmarkedArticleIndex;
@@ -103,15 +127,67 @@ contract BasedShop {
103127
//////////////////////////////////////////////////////////////*/
104128

105129
function createArticle(
106-
string memory _tokenURI
130+
string memory _tokenURI,
131+
uint256 _price,
132+
uint256 _amount
107133
) public {
108134
uint256 articleId = articleIds++;
109135
articleIdToUser[articleId] = msg.sender;
110136
userArticles[msg.sender].push(articleId);
111137

138+
articlePrices[articleId] = _price;
139+
articleAmounts[articleId] = _amount;
140+
112141
basedArticles.mint(_tokenURI);
113142

114-
emit ArticleCreated(articleId, msg.sender, _tokenURI, block.timestamp);
143+
emit ArticleCreated(
144+
articleId, msg.sender, _tokenURI, block.timestamp, _price, _amount
145+
);
146+
}
147+
148+
function buyArticle(
149+
uint256 _articleId
150+
) public payable {
151+
require(articleAmounts[_articleId] > 0, "Article is sold out");
152+
uint256 price = articlePrices[_articleId];
153+
require(msg.value >= price, "Not enough ETH sent");
154+
155+
address seller = articleIdToUser[_articleId];
156+
require(seller != address(0), "Invalid seller address");
157+
158+
// Transfer the ETH to the seller
159+
(bool success,) = seller.call{ value: msg.value }("");
160+
require(success, "Transfer failed");
161+
162+
// Decrease the amount of the article by one
163+
articleAmounts[_articleId]--;
164+
165+
// Record the buyer
166+
articleBuyers[_articleId][msg.sender] = true;
167+
168+
emit ArticleBought(_articleId, msg.sender, seller, price, block.timestamp);
169+
}
170+
171+
function updateArticlePrice(uint256 _articleId, uint256 _newPrice) public {
172+
require(
173+
articleIdToUser[_articleId] == msg.sender, "Not the owner of the article"
174+
);
175+
176+
uint256 oldPrice = articlePrices[_articleId];
177+
articlePrices[_articleId] = _newPrice;
178+
179+
emit ArticlePriceUpdated(_articleId, oldPrice, _newPrice);
180+
}
181+
182+
function updateArticleAmount(uint256 _articleId, uint256 _newAmount) public {
183+
require(
184+
articleIdToUser[_articleId] == msg.sender, "Not the owner of the article"
185+
);
186+
187+
uint256 oldAmount = articleAmounts[_articleId];
188+
articleAmounts[_articleId] = _newAmount;
189+
190+
emit ArticleAmountUpdated(_articleId, oldAmount, _newAmount);
115191
}
116192

117193
function deleteArticle(
@@ -123,85 +199,118 @@ contract BasedShop {
123199

124200
basedArticles.burn(_articleId);
125201

202+
// Remove the price and amount data
203+
delete articlePrices[_articleId];
204+
delete articleAmounts[_articleId];
205+
206+
// Remove the articleId to user mapping
207+
delete articleIdToUser[_articleId];
208+
126209
emit ArticleDeleted(_articleId, block.timestamp);
127210
}
128211

129212
function likeArticle(
130-
uint256 _articleID
213+
uint256 _articleId
131214
) public {
132-
_requireArticleExists(_articleID);
133215
require(
134-
!userToArticleLikes[msg.sender][_articleID],
216+
articleBuyers[_articleId][msg.sender],
217+
"You must buy the article to like it"
218+
);
219+
_requireArticleExists(_articleId);
220+
require(
221+
!userToArticleLikes[msg.sender][_articleId],
135222
"You have already liked this article"
136223
);
137-
userToArticleLikes[msg.sender][_articleID] = true;
138-
articleToLikes[_articleID]++;
139-
emit ArticleLiked(_articleID, msg.sender, block.timestamp);
224+
userToArticleLikes[msg.sender][_articleId] = true;
225+
articleToLikes[_articleId]++;
226+
emit ArticleLiked(_articleId, msg.sender, block.timestamp);
140227
}
141228

142229
function unlikeArticle(
143-
uint256 _articleID
230+
uint256 _articleId
144231
) public {
145-
_requireArticleExists(_articleID);
232+
_requireArticleExists(_articleId);
146233
require(
147-
userToArticleLikes[msg.sender][_articleID],
234+
userToArticleLikes[msg.sender][_articleId],
148235
"You have not liked this article yet"
149236
);
150-
userToArticleLikes[msg.sender][_articleID] = false;
151-
articleToLikes[_articleID]--;
152-
emit ArticleUnliked(_articleID, msg.sender, block.timestamp);
237+
userToArticleLikes[msg.sender][_articleId] = false;
238+
articleToLikes[_articleId]--;
239+
emit ArticleUnliked(_articleId, msg.sender, block.timestamp);
153240
}
154241

155-
function commentOnArticle(uint256 _articleID, string memory _text) public {
156-
_requireArticleExists(_articleID);
242+
function commentOnArticle(uint256 _articleId, string memory _text) public {
243+
require(
244+
articleBuyers[_articleId][msg.sender],
245+
"You must buy the article to comment on it"
246+
);
247+
_requireArticleExists(_articleId);
157248
// set max length at 250 characters
158249
require(
159250
bytes(_text).length <= 250, "Comment must be less than 250 characters"
160251
);
161-
uint256 commentIndex = articleToComments[_articleID].length;
162-
articleToComments[_articleID].push(Comment(msg.sender, _text, commentIndex));
252+
uint256 commentIndex = articleToComments[_articleId].length;
253+
articleToComments[_articleId].push(Comment(msg.sender, _text, commentIndex));
163254
emit ArticleCommented(
164-
_articleID, msg.sender, _text, commentIndex, block.timestamp
255+
_articleId, msg.sender, _text, commentIndex, block.timestamp
165256
);
166257
}
167258

168-
function deleteComment(uint256 _articleID, uint256 _commentID) public {
169-
_requireArticleExists(_articleID);
259+
function deleteComment(uint256 _articleId, uint256 _commentID) public {
260+
_requireArticleExists(_articleId);
170261
require(
171-
articleCommentToUser[_articleID][_commentID] == msg.sender,
262+
articleCommentToUser[_articleId][_commentID] == msg.sender,
172263
"You can't erase what you didn't article!"
173264
);
174-
delete articleCommentToUser[_articleID][_commentID];
175-
delete articleToComments[_articleID][_commentID];
176-
emit ArticleCommentDeleted(_articleID, msg.sender, block.timestamp);
265+
delete articleCommentToUser[_articleId][_commentID];
266+
delete articleToComments[_articleId][_commentID];
267+
emit ArticleCommentDeleted(_articleId, msg.sender, block.timestamp);
177268
}
178269

179-
function shareArticle(
180-
uint256 _articleID
181-
) public {
182-
_requireArticleExists(_articleID);
183-
userToBookmarkedArticles[msg.sender].push(_articleID);
184-
// userToBookmarkedArticleIndex[msg.sender][_articleID] =
185-
// userToBookmarkedArticles[msg.sender].length - 1;
186-
emit ArticleBookmarked(_articleID, msg.sender, block.timestamp);
270+
// Function to bookmark an article
271+
function bookmarkArticle(
272+
uint256 _articleId
273+
) external {
274+
_requireArticleExists(_articleId);
275+
require(
276+
!userToArticleBookmark[msg.sender][_articleId],
277+
"Article already bookmarked"
278+
);
279+
280+
userToArticleBookmark[msg.sender][_articleId] = true;
281+
articleToBookmarks[_articleId] += 1;
282+
283+
userToBookmarkedArticles[msg.sender].push(_articleId);
284+
userToBookmarkedArticleIndex[msg.sender][_articleId] =
285+
userToBookmarkedArticles[msg.sender].length - 1;
286+
287+
emit ArticleBookmarked(_articleId, msg.sender, block.timestamp);
187288
}
188289

189-
function deleteBookmarkedArticle(
190-
uint256 _articleID
290+
function removeBookmark(
291+
uint256 _articleId
191292
) public {
192-
_requireArticleExists(_articleID);
293+
_requireArticleExists(_articleId);
294+
295+
require(
296+
userToArticleBookmark[msg.sender][_articleId], "Article not bookmarked"
297+
);
298+
299+
userToArticleBookmark[msg.sender][_articleId] = false;
300+
articleToBookmarks[_articleId] -= 1;
193301

194-
// Retrieve the index of the article to be deleted
195-
uint256 index = userToBookmarkedArticleIndex[msg.sender][_articleID];
302+
uint256 index = userToBookmarkedArticleIndex[msg.sender][_articleId];
303+
uint256 lastArticleId = userToBookmarkedArticles[msg.sender][userToBookmarkedArticles[msg
304+
.sender].length - 1];
196305

197-
// Set the article to a default value (e.g., 0)
198-
userToBookmarkedArticles[msg.sender][index] = 0;
306+
userToBookmarkedArticles[msg.sender][index] = lastArticleId;
307+
userToBookmarkedArticleIndex[msg.sender][lastArticleId] = index;
199308

200-
// Delete the index entry for the deleted article
201-
delete userToBookmarkedArticleIndex[msg.sender][_articleID];
309+
userToBookmarkedArticles[msg.sender].pop();
310+
delete userToBookmarkedArticleIndex[msg.sender][_articleId];
202311

203312
// Emit the ArticleUnshared event
204-
emit ArticleUnshared(_articleID, msg.sender, block.timestamp);
313+
emit RemoveBookmark(_articleId, msg.sender, block.timestamp);
205314
}
206315

207316
function followUser(
@@ -244,8 +353,8 @@ contract BasedShop {
244353
//////////////////////////////////////////////////////////////*/
245354

246355
function _requireArticleExists(
247-
uint256 _articleID
356+
uint256 _articleId
248357
) internal view {
249-
require(basedArticles.tokenId() >= _articleID, "Article does not exist");
358+
require(basedArticles.tokenId() >= _articleId, "Article does not exist");
250359
}
251360
}

packages/nextjs/app/create/Create.tsx

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,17 @@ import { ImageUploader } from "./_components/ImageUploader";
55
import { MintingButtons } from "./_components/MintingButtons";
66
import { TextInput } from "./_components/TextInput";
77
import generateTokenURI from "./_components/generateTokenURI";
8+
import { InputBase } from "~~/components/scaffold-eth";
89

910
// import type { NextPage } from "next";
1011

1112
const Create = ({ onClose }: { onClose: any }) => {
13+
const [name, setName] = useState("");
1214
const [description, setDescription] = useState("");
15+
const [externalUrl, setExternalUrl] = useState("");
16+
const [price, setPrice] = useState("");
17+
const [amount, setAmount] = useState("");
18+
const [urlError, setUrlError] = useState("");
1319
const [yourJSON, setYourJSON] = useState<object>({});
1420
const [uploadedImageIpfsPath, setUploadedImageIpfsPath] = useState(""); // NEW: For image IPFS path
1521

@@ -25,10 +31,24 @@ const Create = ({ onClose }: { onClose: any }) => {
2531
window.location.reload();
2632
};
2733

34+
const validateUrl = (url: string) => {
35+
const pattern = /^(https?:\/\/)/;
36+
return pattern.test(url);
37+
};
38+
39+
const handleUrlChange = (url: string) => {
40+
setExternalUrl(url);
41+
if (!validateUrl(url)) {
42+
setUrlError("URL must start with http:// or https://");
43+
} else {
44+
setUrlError("");
45+
}
46+
};
47+
2848
useEffect(() => {
2949
const generateTokenURIString = () => {
3050
const fullImageUrl = `https://ipfs.io/ipfs/${uploadedImageIpfsPath}`;
31-
const tokenURI = generateTokenURI(description, fullImageUrl);
51+
const tokenURI = generateTokenURI(name, description, fullImageUrl, externalUrl);
3252
setYourJSON(JSON.parse(atob(tokenURI.split(",")[1])));
3353
};
3454

@@ -52,17 +72,28 @@ const Create = ({ onClose }: { onClose: any }) => {
5272
setUploadedImageIpfsPath={setUploadedImageIpfsPath} // NEW: Set the uploaded image IPFS path here
5373
/>
5474
</div>
55-
<div className="text-left flex-shrink-0 w-full">
75+
<div className="flex flex-col gap-3 text-left flex-shrink-0 w-full">
76+
<InputBase placeholder="Article name" value={name} onChange={setName} />
5677
<TextInput description={description} setDescription={setDescription} />
78+
<InputBase placeholder="URL for your article (https://)" value={externalUrl} onChange={handleUrlChange} />
79+
{externalUrl && urlError && <span className="text-red-500 text-sm">{urlError}</span>}
80+
<div className="flex flex-row gap-3">
81+
<div className="w-1/2">
82+
<InputBase placeholder="Price in ETH" value={price} onChange={setPrice} />
83+
</div>
84+
<div className="w-1/2">
85+
<InputBase placeholder="Amount" value={amount} onChange={setAmount} />
86+
</div>
87+
</div>
5788
</div>
5889
</div>
5990

6091
{/* JSON Viewer */}
6192
{/* <JSONViewer yourJSON={yourJSON} setYourJSON={setYourJSON} /> */}
6293

6394
<MintingButtons
64-
description={description}
65-
image={uploadedImageIpfsPath} // Pass the uploaded image IPFS path to MintingForm
95+
price={price}
96+
amount={amount}
6697
yourJSON={yourJSON}
6798
resetForm={resetForm}
6899
onPostCreated={handlePostCreated}

0 commit comments

Comments
 (0)