Skip to content

Commit

Permalink
Extend frontend to support discussions (#96)
Browse files Browse the repository at this point in the history
* feat: add copy icon for comment sender address

* feat: display comment UI

* feat: New comment UI

* draft

* feat: consume submit api endpoint

* feat: inovoke contract to update proposal

* feat: implement fetch ipfs file

* feat: implement address copy

* fix: proposal update

* fix: update starknet version

* fix: add comment

* feat: fetch comments from contract

* fix: remove add comments success toast

* fix: transaction not activating

* fix: format ipfs_hash

* to be deployed again

* to be deployed again

* Update RPC URL as Nethermind Sepolia is buggy

* feat: implement balance_of

* fix: alignment

* fix: padd address from start

---------

Co-authored-by: Ondřej Sojka <[email protected]>
  • Loading branch information
zintarh and tensojka authored Jul 27, 2024
1 parent 4d56ae2 commit 2407d94
Show file tree
Hide file tree
Showing 14 changed files with 1,555 additions and 538 deletions.
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
"@starknet-react/chains": "^0.1.0",
"@starknet-react/core": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"axios": "^1.7.2",
"get-starknet-core": "^3.2.0",
"next": "^14.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"starknet": "^5.25.0",
"starknet": "^6.0.0",
"tailwindcss": "^3.3.5"
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ function App() {
args: [],
abi,
address: CONTRACT_ADDR,
watch: true,
watch: false,
retry: false
});

// Check if there is an error, if there is, display the error message
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/api/apiService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import axios from "axios";
import { BASE_API_URL } from "../constants/amm";

type CommentPayload = {
address: string;
text: string;
};

export const submitCommentApi = async (payload: CommentPayload) => {
const { data } = await axios.post(`${BASE_API_URL}/submit`, payload);
return data;
};



export const fetchIpfsFile = async (file: string) => {
try {
const response = await axios.get(`https://ipfs.io/ipfs/${file}`);
if (response.status === 200) {
return response.data;
} else {
throw new Error(`Failed to fetch IPFS file with status: ${response.status}`);
}
} catch (error) {
console.error('Error fetching IPFS file:', error);
throw error; // Re-throw the error for further handling if necessary
}
};
40 changes: 40 additions & 0 deletions frontend/src/assets/icons/icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from "react";

export const TickIcon = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={24}
height={24}
fill="none"
viewBox="0 -0.5 25 25"
{...props}
>
<path
stroke="#000"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={1.5}
d="m5.5 12.5 4.667 4.5L19.5 8"
/>
</svg>
);

export const CopyIcon = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={18}
height={18}
fill="none"
viewBox="0 0 24 24"
{...props}
>
<path
fill="#3B82F6"
d="M15.24 2h-3.894c-1.764 0-3.162 0-4.255.148-1.126.152-2.037.472-2.755 1.193-.719.721-1.038 1.636-1.189 2.766C3 7.205 3 8.608 3 10.379v5.838c0 1.508.92 2.8 2.227 3.342-.067-.91-.067-2.185-.067-3.247v-5.01c0-1.281 0-2.386.118-3.27.127-.948.413-1.856 1.147-2.593.734-.737 1.639-1.024 2.583-1.152.88-.118 1.98-.118 3.257-.118H15.335c1.276 0 2.374 0 3.255.118A3.601 3.601 0 0 0 15.24 2Z"
/>
<path
fill="#3B82F6"
d="M6.6 11.397c0-2.726 0-4.089.844-4.936.843-.847 2.2-.847 4.916-.847h2.88c2.715 0 4.073 0 4.917.847.843.847.843 2.21.843 4.936v4.82c0 2.726 0 4.089-.843 4.936-.844.847-2.202.847-4.917.847h-2.88c-2.715 0-4.073 0-4.916-.847-.844-.847-.844-2.21-.844-4.936v-4.82Z"
/>
</svg>
);
42 changes: 42 additions & 0 deletions frontend/src/components/CommentCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useContractRead } from '@starknet-react/core';
import React, { } from 'react';
import { TOKEN_CONTRACT } from '../lib/config';
import { TokenABI } from '../lib/tokenContractABI';

interface CommentCardProps {
address: string;
text: string;

}
const CommentCard = ({address, text}: CommentCardProps) => {

const { data, isSuccess } = useContractRead({
functionName: "balance_of",
args: [address.toString()],
abi:TokenABI,
address: TOKEN_CONTRACT,
watch: false,
retry: false
});

return (
<div >
<div className="grid grid-cols-[1fr_3fr] gap-3 ">
<p className="text-sm font-[400] ">Senders Address:</p>
<div className="flex items-center gap-2">
<p className="font-[600] text-sm">{address.slice(0, 20)}</p>

</div>

<p className="text-sm font-[400] ">Comment:</p>
<p className="font-[600] text-sm">{text}</p>

<p className="text-sm font-[400]">Token Balance:</p>
<p className="font-[600] text-sm">{isSuccess && parseInt(data?.toString()) / 10**18}</p>
</div>
<div className="border border-b-gray-200 mt-2" />
</div>
);
}

export default CommentCard;
68 changes: 68 additions & 0 deletions frontend/src/components/Comments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useEffect, useState } from "react";
import { useContractRead } from "@starknet-react/core";
import { abi } from "../lib/abi";
import { fetchIpfsFile } from "../api/apiService";
import { CONTRACT_ADDR, formatIpfsHash } from "../lib/config";
import CommentCard from "./CommentCard";
type CommentProps = {
address: string;
text: string;
starknet_id: string;
}[];
type Props = {
proposalId: string;
};
export default function Comments({ proposalId }: Props) {
const [comments, setComments] = useState<CommentProps>([]);
const { data, isLoading } = useContractRead({
functionName: "get_comments",
args: [proposalId.toString()],
abi,
address: CONTRACT_ADDR,
watch: false,
retry: false

});



useEffect(() => {
const fetchData = async () => {
try {
if (Array.isArray(data)) {
const ipfsFetchPromises = data.map((item: { ipfs_hash: string }) =>
fetchIpfsFile(formatIpfsHash(item.ipfs_hash))
);
const ipfsResults = await Promise.all(ipfsFetchPromises);

setComments(ipfsResults);
}
} catch (e) {
console.error("Error fetching from IPFS:", e);
}
};

fetchData();
}, [data]);







return isLoading ? (
<div>loading.... </div>
) : (
<div className="w-[35rem] pt-5">
<div className="py-2">
{comments.length > 0 ?
comments.map(({ address, text }, i) => (
<div key={i}>
<CommentCard address={address} text={text} />
</div>
)): <div className="flex justify-center items-center"> <p className="text-sm font-extrabold ">No comments available </p> </div>}
</div>
</div>
);
}
110 changes: 110 additions & 0 deletions frontend/src/components/NewProposalCommentForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
import { useAccount, useContractWrite } from "@starknet-react/core";
import { CONTRACT_ADDR, formatAddress, } from "../lib/config";
import {byteArray} from "starknet"

import { submitCommentApi } from "../api/apiService";
export default function NewcommentCommentForm({
setIsModalOpen,
propId,
}: {
setIsModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
propId: string;
}) {
const { isConnected, address } = useAccount();
const [comment, setComment] = React.useState<string>("");
const [ipfsHash, setIpfsHash] = useState<string>("");
const [isLoading, setIsLoading] = React.useState<boolean>(false);

const calls = useMemo(() => {
const tx = {
contractAddress: CONTRACT_ADDR,
entrypoint: "add_comment",
calldata: [
propId.toString(),
byteArray.byteArrayFromString(ipfsHash)
],
};
return [tx];
}, [ipfsHash]);




const { writeAsync } = useContractWrite({ calls });
async function submitComment(e: React.FormEvent<HTMLFormElement>) {

e.preventDefault();

if (!isConnected) {
toast.error("Please connect your wallet");
return;
}

if (!comment) {
toast.error("Please fill out all fields");
return;
}

const payload = {
text: comment,
address: formatAddress(address),
};

setIsLoading(true);
try {
const result = await submitCommentApi(payload);
if (result) {
setIpfsHash(result?.ipfs_hash);
}
} catch (error) {
toast.error("Something went wrong");
setIsModalOpen(false);
console.error(error);
}
}

useEffect(() => {
const updateProposal = async () => {
try {
await writeAsync();
toast.success("Proposal updated successfully");
setIsModalOpen(false);
} catch (error) {
toast.error("Unable to update proposal with comment");
console.error(error);
setIsModalOpen(false);
}
};

if (ipfsHash.length > 0) {
updateProposal();
}
}, [ipfsHash]);




return (
<div className="w-[35rem]">
<form onSubmit={submitComment}>
<label htmlFor="#comment">Comment</label>
<input
id="#comment"
type="text"
placeholder="Leave a comment here"
className="w-full p-2 mb-2 border rounded-lg border-slate-300"
onChange={(e) => setComment(e.target.value)}
/>

<button
type="submit"
className="w-full p-2 mt-4 text-white bg-blue-500 rounded-lg"
>
{isLoading ? "Loading..." : "Submit"}
</button>
</form>
</div>
);
}
Loading

0 comments on commit 2407d94

Please sign in to comment.