Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7821818
chore: upgrade packages
gratestas May 31, 2023
94c6098
refactor: use new router construction with createBrowserRouter
gratestas May 31, 2023
0068e08
refactor: move redirection logic based on chainId up to Router level
gratestas Jun 5, 2023
f2902b4
refactor: remove reduntan useEffect logic from all route components
gratestas Jun 5, 2023
0454688
refactor: apply required changes to comply with routing updates
gratestas Jun 5, 2023
17c1e03
chore: delete commented out code
gratestas Jun 5, 2023
a8ab74a
fix: update getAllContributors query
gratestas Jun 7, 2023
436c221
refactor: implement loader router API to article route
gratestas Jun 7, 2023
359dd4a
fix: navigation path to Account route
gratestas Jun 7, 2023
f0cf593
refactor: implement loader route API to account page
gratestas Jun 10, 2023
831bd0f
refactor: apply loader route API to Browse page & reduce code complexity
gratestas Jun 10, 2023
8e84876
refactor: update Account route loader
gratestas Jun 10, 2023
a589646
fix: use absolute path in article route link
gratestas Jun 10, 2023
129b03a
fix: continuous update of blockNumber in graphMetada based on chainId
gratestas Jun 10, 2023
6c4af06
refactor: apply route loader API to Court page
gratestas Jun 12, 2023
23b9d44
fix: update ethersProvider when app's network is changed
gratestas Jun 12, 2023
f27f905
feat: add default network based on environment
gratestas Jun 12, 2023
2696a42
fix: use chainId from networkMap instead of hardcoded
gratestas Jun 12, 2023
b598abb
refactor: remove duplicated code in Navigation component
gratestas Jun 12, 2023
ed1d543
fix: enable missing usage of metaEvidenceContents
gratestas Jun 12, 2023
f172f9c
feat: revalidate data using router API manually
gratestas Jun 12, 2023
acfec33
chore: remove unnecessary function declarations
gratestas Jun 12, 2023
c2d66ad
refactor: reduce code duplication
gratestas Jun 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
"color": "^4.2.3",
"ethers": "^5.6.5",
"os-browserify": "^0.3.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-interval-rerender": "^1.1.0",
"react-markdown": "^8.0.7",
"react-router-dom": "^6.2.1",
"react-router-dom": "^6.11.2",
"react-tageditor": "^0.2.3",
"react-toastify": "^9.1.2",
"rehype-katex": "^6.0.3",
Expand Down
15 changes: 0 additions & 15 deletions src/App.jsx

This file was deleted.

11 changes: 11 additions & 0 deletions src/components/AuthRequired.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { useContext } from "react";
import { EthereumContext } from "../data/ethereumProvider";
import { Navigate, Outlet } from "react-router-dom";

export default function AuthRequired() {
const { accounts, chainId } = useContext(EthereumContext);
if (accounts[0] === undefined) {
return <Navigate to={chainId} replace />;
}
return <Outlet />;
}
36 changes: 36 additions & 0 deletions src/components/RouteRedirect.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useContext, useEffect } from "react";
import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom";
import { EthereumContext, networkMap } from "../data/ethereumProvider";

export default function RouteRedirect() {
const location = useLocation();
const params = useParams();
const navigate = useNavigate();
const { chainId, accounts } = useContext(EthereumContext);

useEffect(() => {
const pathSegments = location.pathname.split("/");
pathSegments[1] = chainId;

let newPath = pathSegments.join("/");

if (location.pathname.includes("account")) {
if (!accounts[0]) {
newPath = chainId;
} else {
pathSegments[3] = accounts[0];
newPath = pathSegments.join("/");
}
}

if (params.contract && !Object.keys(networkMap[chainId].deployments).includes(params.contract)) {
newPath = chainId;
}

if (newPath !== location.pathname) {
navigate(newPath, { replace: true });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to erase the history?

}
}, [chainId, accounts[0], location.pathname, params.contract, navigate]);

return <Outlet />;
}
9 changes: 7 additions & 2 deletions src/components/others/bountyModal/index.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { useContext, useState } from "react";
import { useRevalidator } from "react-router-dom";
import { constants, utils } from "ethers";
import * as styles from "./index.module.scss";

import CustomButton from "/src/components/presentational/button";
import Modal from "../../presentational/modal";
import LoadingSpinner from "/src/components/presentational/loadingSpinner";
import { useContext, useState } from "react";

import { EthereumContext } from "/src/data/ethereumProvider";
import { constants, utils } from "ethers";

export default function BountyModal({ articleStorageAddress, currentBounty, visible, onCancel }) {
const ethereumContext = useContext(EthereumContext);
const [amount, setAmount] = useState(0.001);
const [isSubmitting, setIsSubmitting] = useState(false);
const revalidator = useRevalidator();

function handleControlChange(e) {
setAmount(e.target.value);
Expand All @@ -24,6 +28,7 @@ export default function BountyModal({ articleStorageAddress, currentBounty, visi
utils.parseEther(amount.toString())
);
onCancel();
revalidator.revalidate();
} catch (error) {
console.error(error);
} finally {
Expand Down
27 changes: 11 additions & 16 deletions src/components/others/buttonConnect/index.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import React, {useContext} from "react";
import {EthereumContext, networkMap} from "../../../data/ethereumProvider";
import React, { useContext } from "react";
import { EthereumContext, networkMap } from "../../../data/ethereumProvider";
import CustomButton from "/src/components/presentational/button";

export default function ButtonConnect() {

return (
<EthereumContext.Consumer>
{(ethereumContext) => {
console.log(ethereumContext);
return (
<CustomButton
modifiers="small secondary"
Expand All @@ -16,17 +14,14 @@ export default function ButtonConnect() {
onClick={() => {
if (ethereumContext?.accounts.length < 1) {
ethereumContext.requestAccounts().then(() => {
ethereum.request({
method: "wallet_switchEthereumChain",
params: [{chainId: ethereumContext.chainId}]
});
}
)
ethereum.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: ethereumContext.chainId }],
});
});
} else {
console.log("There is a connected account already.")
console.log("There is a connected account already.");
}


}}
>
<a
Expand All @@ -40,10 +35,10 @@ export default function ButtonConnect() {
key={ethereumContext?.accounts[0]}
className="blink"
>
{!ethereumContext?.accounts[0] && ethereumContext?.awaitingUserPermission
&& "Awaiting User Permission"}
{!ethereumContext?.accounts[0] && ethereumContext?.awaitingUserPermission && "Awaiting User Permission"}
{!ethereumContext?.accounts[0] && !ethereumContext?.awaitingUserPermission && "Connect Account"}
{ethereumContext?.accounts[0] && `${ethereumContext?.accounts[0]?.substring(0, 6)}...${ethereumContext?.accounts[0]?.slice(-4)}`}
{ethereumContext?.accounts[0] &&
`${ethereumContext?.accounts[0]?.substring(0, 6)}...${ethereumContext?.accounts[0]?.slice(-4)}`}
</a>
</CustomButton>
);
Expand Down
17 changes: 3 additions & 14 deletions src/components/others/buttonSelectNetwork/index.jsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,19 @@
import React, { useContext } from "react";
import { useNavigate } from "react-router-dom";

import Select from "/src/components/presentational/select";
import { EthereumContext, networkMap } from "../../../data/ethereumProvider";

export default function ButtonSelectNetwork() {
const navigate = useNavigate();
const ethereumContext = useContext(EthereumContext);
const { chainId, switchAppChain } = useContext(EthereumContext);

const selectOptions = Object.entries(networkMap).map(([chainId, props], index) => ({
const selectOptions = Object.entries(networkMap).map(([chainId, props]) => ({
value: chainId,
label: props.shortname,
}));

function handleOnChange(chainId) {
console.log({ chainId });
navigate("/" + chainId + "/");
// if (ethereumContext?.isProviderDetected)
// ethereum.request({
// method: "wallet_switchEthereumChain",
// params: [{ chainId }],
// });
}
return (
<div style={{ marginLeft: "10px" }}>
<Select options={selectOptions} placeholder={networkMap[ethereumContext?.chainId]?.shortname} onChange={handleOnChange} />
<Select options={selectOptions} placeholder={networkMap[chainId].shortname} onChange={switchAppChain} />
</div>
);
}
6 changes: 5 additions & 1 deletion src/components/others/evidenceModal/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import addToIPFS from "/src/utils/addToIPFS";
import notifyWithToast, { MESSAGE_TYPE } from "../../../utils/notifyWithTost";

import UploadIcon from "jsx:/src/assets/upload.svg";
import { useRevalidator } from "react-router-dom";

const INITIAL_STATE = { title: "", description: "", file: null };
const IPFS_ENDPOINT = "https://ipfs.kleros.io/add";
Expand All @@ -20,6 +21,8 @@ export default function EvidenceModal({ disputeID, visible, onCancel }) {
const [isDragOver, setIsDragOver] = useState(false);
const [sumbitSuccess, setSumbitSuccess] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const revalidator = useRevalidator();

useEffect(() => {
if (sumbitSuccess) {
setControlsState(INITIAL_STATE);
Expand Down Expand Up @@ -91,11 +94,12 @@ export default function EvidenceModal({ disputeID, visible, onCancel }) {
MESSAGE_TYPE.ipfs
);

await ethereumContext.invokeTransaction("submitEvidence", [disputeID, `/ipfs/${ipfsResponse[0].hash}`])
await ethereumContext.invokeTransaction("submitEvidence", [disputeID, `/ipfs/${ipfsResponse[0].hash}`]);

setIsSubmitting(false);
setSumbitSuccess(true);
onCancel();
revalidator.revalidate();
} catch (err) {
console.error(err);
setIsSubmitting(false);
Expand Down
142 changes: 30 additions & 112 deletions src/components/others/listArticles/index.jsx
Original file line number Diff line number Diff line change
@@ -1,138 +1,56 @@
import React, { useState, useEffect, useContext } from "react";
import React, { useState, useContext } from "react";
import * as styles from "./index.module.scss";

import ListArticlesItem from "/src/components/presentational/listArticlesItem";
import Pagination from "/src/components/presentational/pagination";
import Pill from "/src/components/presentational/pill";

import { EthereumContext } from "/src/data/ethereumProvider";
import getTrustScore from "/src/businessLogic/getTrustScore";
import getTimePastSinceLastBountyUpdate from "/src/businessLogic/getTimePastSinceLastBountyUpdate";
import { ipfsGateway } from "/src/utils/addToIPFS";
import Pagination from "../../presentational/pagination";

const PAGE_SIZE = 8;
const ITEMS_PER_PAGE = 8;

export default function ListArticles({ articles, isFetching }) {
const ethereumContext = useContext(EthereumContext);
const [articleContents, setArticleContents] = useState();
const [loadingFetchingContents, setFetchingArticlesContents] = useState(true);
export default function ListArticles({ articles }) {
const [currentPage, setCurrentPage] = useState(1);

useEffect(() => {
let didCancel = false;

if (!articles) {
setFetchingArticlesContents(false);
return;
}
const fetchArticleData = async () => {
try {
const fetchPromises = articles
.filter((a) => a != null)
.map(async (article) => {
try {
const response = await fetch(ipfsGateway + article?.articleID);
if (!response.ok) {
throw new Error("Network response was not OK");
}
const data = await response.json();
return {
articleID: article.articleID,
title: data.title,
description: data.description,
tags: data.tags,
format: data.format
};
} catch (error) {
console.error(error);
return null;
}
});

const articleData = await Promise.all(fetchPromises);
const fetchedArticleContents = articleData.reduce(
(prevState, data) => ({
...prevState,
[data.articleID]: { title: data.title, description: data.description, tags: data.tags, format: data.format },
}),
{}
);

if (!didCancel) {
setArticleContents(fetchedArticleContents);
}
} catch (error) {
console.error(error);
} finally {
if (!didCancel) {
setFetchingArticlesContents(false);
}
}
};

fetchArticleData();
return () => {
didCancel = true;
};
}, [articles]);
const { blockNumber, chainId, graphMetadata } = useContext(EthereumContext);
const currentBlockNumber = graphMetadata?.block?.number || blockNumber;

if (articles.length === 0) return <div>No news articles</div>;
return (
<>
<div className={styles.containerItems}>
{articles &&
Object.entries(articles.filter((c) => c != null))
.sort(([, item1], [, item2]) => sortAccordingToTrustScore(item1, item2, ethereumContext))
.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE)
.map(([_key, value], index) => (
<ListArticlesItem
key={value?.id}
title={
articleContents?.[value?.articleID]?.title ||
(!loadingFetchingContents && `Unable to fetch article data from ${value?.articleID}`)
}
description={articleContents?.[value?.articleID]?.description}
format={articleContents?.[value?.articleID]?.format}
linkTo={`/${ethereumContext?.chainId}/${value?.contractAddress}/${value?.id}/`}
score={getTrustScore(
value,
getTimePastSinceLastBountyUpdate(
value?.lastBalanceUpdate,
ethereumContext?.graphMetadata?.block?.number || ethereumContext?.blockNumber
)
)}
createdAt={value?.createdAtTimestamp}
excerptSize={index % 2 == 1 ? 3 : 1}
>
<Pill modifiers="small">{value?.status}</Pill>
{articles
.sort((item1, item2) => sortAccordingToTrustScore(item1, item2, currentBlockNumber))
.slice((currentPage - 1) * ITEMS_PER_PAGE, currentPage * ITEMS_PER_PAGE)
.map((article, index) => {
const { id, title, description, contractAddress, status, lastBalanceUpdate } = article;
const linkTo = `/${chainId}/${contractAddress}/${id}`;
const score = getTrustScore(
article,
getTimePastSinceLastBountyUpdate(lastBalanceUpdate, currentBlockNumber)
);
const createdAt = article.createdAtTimestamp;
const excerptSize = index % 2 === 1 ? 3 : 1;
return (
<ListArticlesItem {...{ key: id, title, description, linkTo, score, createdAt, excerptSize }}>
<Pill modifiers="small">{status}</Pill>
</ListArticlesItem>
))}
);
})}
</div>
{!isFetching &&
(articles == null || (articles && articles.filter((a) => a != null).length == 0)) &&
"No news articles."}
{articles && loadingFetchingContents && "Fetching article details."}

<Pagination
current={currentPage}
pageSize={PAGE_SIZE}
total={articles?.length}
pageSize={ITEMS_PER_PAGE}
total={articles.length}
onChange={(pageIndex) => setCurrentPage(pageIndex)}
/>
</>
);
}

const sortAccordingToTrustScore = (item1, item2, ethereumContext) =>
getTrustScore(
item2,
getTimePastSinceLastBountyUpdate(
item2?.lastBalanceUpdate,
ethereumContext?.graphMetadata?.block?.number || ethereumContext?.blockNumber
)
) -
getTrustScore(
item1,
getTimePastSinceLastBountyUpdate(
item1?.lastBalanceUpdate,
ethereumContext?.graphMetadata?.block?.number || ethereumContext?.blockNumber
)
);
const sortAccordingToTrustScore = (item1, item2, currentBlockNumber) =>
getTrustScore(item2, getTimePastSinceLastBountyUpdate(item2.lastBalanceUpdate, currentBlockNumber)) -
getTrustScore(item1, getTimePastSinceLastBountyUpdate(item1.lastBalanceUpdate, currentBlockNumber));
Loading