diff --git a/web/src/app.tsx b/web/src/app.tsx
index a1f35108d..97c05cfd8 100644
--- a/web/src/app.tsx
+++ b/web/src/app.tsx
@@ -14,6 +14,8 @@ import Cases from "./pages/Cases";
import Dashboard from "./pages/Dashboard";
import Courts from "./pages/Courts";
import DisputeTemplateView from "./pages/DisputeTemplateView";
+import DisputeResolver from "./pages/Resolver";
+import { NewDisputeProvider } from "./context/NewDisputeContext";
const App: React.FC = () => {
return (
@@ -22,16 +24,19 @@ const App: React.FC = () => {
-
- }>
- } />
- } />
- } />
- } />
- } />
- Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} />
-
-
+
+
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} />
+
+
+
diff --git a/web/src/assets/svgs/icons/dispute.svg b/web/src/assets/svgs/icons/dispute.svg
new file mode 100644
index 000000000..e3622bf1d
--- /dev/null
+++ b/web/src/assets/svgs/icons/dispute.svg
@@ -0,0 +1,11 @@
+
diff --git a/web/src/assets/svgs/icons/ellipse.svg b/web/src/assets/svgs/icons/ellipse.svg
new file mode 100644
index 000000000..05629b571
--- /dev/null
+++ b/web/src/assets/svgs/icons/ellipse.svg
@@ -0,0 +1,3 @@
+
diff --git a/web/src/assets/svgs/icons/minus.svg b/web/src/assets/svgs/icons/minus.svg
new file mode 100644
index 000000000..76b4d2a91
--- /dev/null
+++ b/web/src/assets/svgs/icons/minus.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/web/src/assets/svgs/icons/plus.svg b/web/src/assets/svgs/icons/plus.svg
new file mode 100644
index 000000000..7a1ce9bc3
--- /dev/null
+++ b/web/src/assets/svgs/icons/plus.svg
@@ -0,0 +1,10 @@
+
diff --git a/web/src/components/ConnectWallet/AccountDisplay.tsx b/web/src/components/ConnectWallet/AccountDisplay.tsx
index 1d677637e..80b5e43d6 100644
--- a/web/src/components/ConnectWallet/AccountDisplay.tsx
+++ b/web/src/components/ConnectWallet/AccountDisplay.tsx
@@ -4,6 +4,7 @@ import { landscapeStyle } from "styles/landscapeStyle";
import { useAccount, useNetwork, useEnsAvatar, useEnsName } from "wagmi";
import Identicon from "react-identicons";
import { shortenAddress } from "utils/shortenAddress";
+import { isAddress } from "viem";
const Container = styled.div`
display: flex;
@@ -134,7 +135,7 @@ export const AddressOrName: React.FC = ({ address: propAddress }
chainId: 1,
});
- return ;
+ return ;
};
export const ChainDisplay: React.FC = () => {
diff --git a/web/src/components/DisputePreview/Alias.tsx b/web/src/components/DisputePreview/Alias.tsx
new file mode 100644
index 000000000..53355aa4f
--- /dev/null
+++ b/web/src/components/DisputePreview/Alias.tsx
@@ -0,0 +1,50 @@
+import React from "react";
+import styled from "styled-components";
+import { AddressOrName, IdenticonOrAvatar } from "../ConnectWallet/AccountDisplay";
+import { Alias } from "context/NewDisputeContext";
+import { isUndefined } from "utils/index";
+import { useEnsAddress } from "wagmi";
+import { isAddress } from "viem";
+import Skeleton from "react-loading-skeleton";
+
+const AliasContainer = styled.div`
+ min-height: 32px;
+ display: flex;
+ gap: 8px;
+ align-items: center;
+`;
+
+const TextContainer = styled.div`
+ display: flex;
+ > label {
+ color: ${({ theme }) => theme.primaryText};
+ font-size: 14px;
+ }
+`;
+
+interface IAlias {
+ alias: Alias;
+}
+
+const AliasDisplay: React.FC = ({ alias }) => {
+ const { data: addressFromENS, isLoading } = useEnsAddress({
+ enabled: !isAddress(alias.address), // if alias.address is not an Address, we treat it as ENS and try to fetch address from there
+ name: alias.address,
+ chainId: 1,
+ });
+
+ // try fetching ens name, else go with address
+ const address = addressFromENS ?? alias.address;
+
+ return (
+
+ {isLoading ? : }
+
+ {isLoading ? : }
+ {!isUndefined(alias.name) && alias.name !== "" ? : null}
+
+
+ );
+};
+
+export default AliasDisplay;
diff --git a/web/src/pages/Cases/CaseDetails/Overview/DisputeContext.tsx b/web/src/components/DisputePreview/DisputeContext.tsx
similarity index 63%
rename from web/src/pages/Cases/CaseDetails/Overview/DisputeContext.tsx
rename to web/src/components/DisputePreview/DisputeContext.tsx
index f1367755c..65d0c1217 100644
--- a/web/src/pages/Cases/CaseDetails/Overview/DisputeContext.tsx
+++ b/web/src/components/DisputePreview/DisputeContext.tsx
@@ -3,6 +3,9 @@ import ReactMarkdown from "components/ReactMarkdown";
import styled from "styled-components";
import { StyledSkeleton } from "components/StyledSkeleton";
import { isUndefined } from "utils/index";
+import { Answer as IAnswer, IDisputeTemplate } from "context/NewDisputeContext";
+import AliasDisplay from "./Alias";
+import { responsiveSize } from "styles/responsiveSize";
const StyledH1 = styled.h1`
margin: 0;
@@ -11,6 +14,10 @@ const StyledH1 = styled.h1`
const QuestionAndDescription = styled.div`
display: flex;
flex-direction: column;
+ div:first-child p:first-of-type {
+ font-size: 16px;
+ font-weight: 600;
+ }
`;
const StyledReactMarkDown = styled(ReactMarkdown)`
@@ -31,32 +38,24 @@ const AnswersContainer = styled.div`
const Answer = styled.div`
margin: 0px;
display: flex;
- gap: 8px;
+ flex-wrap: wrap;
+ gap: ${responsiveSize(2, 8)};
`;
-interface IAnswer {
- id?: string;
- title: string;
- description?: string;
- reserved?: boolean;
-}
-
-interface IDisputeTemplate {
- answers: IAnswer[];
- arbitrableAddress: string;
- arbitrableChainID: string;
- arbitratorAddress: string;
- arbitratorChainID: string;
- category?: string;
- description: string;
- frontendUrl?: string;
- lang?: string;
- policyURI?: string;
- question: string;
- specification?: string;
- title: string;
-}
+const AliasesContainer = styled.div`
+ display: flex;
+ flex-wrap: wrap;
+ gap: ${responsiveSize(8, 20)};
+`;
+const Divider = styled.hr`
+ width: 100%;
+ display: flex;
+ border: none;
+ height: 1px;
+ background-color: ${({ theme }) => theme.stroke};
+ margin: 0;
+`;
interface IDisputeContext {
disputeTemplate: IDisputeTemplate;
}
@@ -86,13 +85,27 @@ export const DisputeContext: React.FC = ({ disputeTemplate }) =
{isUndefined(disputeTemplate) ? null : Voting Options
}
{disputeTemplate?.answers?.map((answer: IAnswer, i: number) => (
-
+
Option {i + 1}:
-
+
))}
+
+ {isUndefined(disputeTemplate?.aliases) ? null : (
+ <>
+
+
+ {disputeTemplate.aliases.map((alias) => (
+
+ ))}
+
+ >
+ )}
>
);
};
diff --git a/web/src/pages/Cases/CaseDetails/Overview/Policies.tsx b/web/src/components/DisputePreview/Policies.tsx
similarity index 100%
rename from web/src/pages/Cases/CaseDetails/Overview/Policies.tsx
rename to web/src/components/DisputePreview/Policies.tsx
diff --git a/web/src/pages/Home/HeroImage.tsx b/web/src/components/HeroImage.tsx
similarity index 100%
rename from web/src/pages/Home/HeroImage.tsx
rename to web/src/components/HeroImage.tsx
diff --git a/web/src/components/LabeledInput.tsx b/web/src/components/LabeledInput.tsx
new file mode 100644
index 000000000..b7fb5958c
--- /dev/null
+++ b/web/src/components/LabeledInput.tsx
@@ -0,0 +1,36 @@
+import { Field, FieldProps } from "@kleros/ui-components-library";
+import React from "react";
+import styled from "styled-components";
+import { isUndefined } from "utils/index";
+
+const Container = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+`;
+const StyledField = styled(Field)`
+ width: 100%;
+ > small {
+ margin-top: 16px;
+ margin-bottom: 16px;
+ }
+`;
+
+const StyledLabel = styled.label`
+ width: 100%;
+ margin-bottom: 12px;
+`;
+
+interface ILabeledInput extends FieldProps {
+ label?: string;
+}
+const LabeledInput: React.FC = (props) => {
+ return (
+
+ {!isUndefined(props.label) ? {props.label} : null}
+
+
+ );
+};
+
+export default LabeledInput;
diff --git a/web/src/components/PlusMinusField.tsx b/web/src/components/PlusMinusField.tsx
new file mode 100644
index 000000000..7ef2a679c
--- /dev/null
+++ b/web/src/components/PlusMinusField.tsx
@@ -0,0 +1,63 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import Ellipse from "assets/svgs/icons/ellipse.svg";
+import Plus from "assets/svgs/icons/plus.svg";
+import Minus from "assets/svgs/icons/minus.svg";
+
+const Container = styled.div`
+ display: flex;
+ gap: 8px;
+ margin: 32px 0px 48px;
+`;
+
+const IconContainer = styled.button`
+ position: relative;
+ padding: 0;
+ border-radius: 50%;
+ border: none;
+ background-color: transparent;
+ cursor: pointer;
+`;
+
+const StyledEllipseIcon = styled(Ellipse)<{ isDisabled?: boolean }>`
+ circle {
+ ${({ isDisabled }) =>
+ isDisabled &&
+ css`
+ fill-opacity: 0.12;
+ `};
+ }
+`;
+
+const Icon = styled.svg`
+ fill: ${({ theme }) => theme.white};
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+`;
+
+interface IPlusMinusField {
+ currentValue: number;
+ updateValue: (currentValue: number) => void;
+ minValue?: number;
+ className?: string;
+}
+const PlusMinusField: React.FC = ({ currentValue, updateValue, minValue = 0, className }) => {
+ const incrementValue = () => updateValue(++currentValue);
+ const decrementValue = () => currentValue > minValue && updateValue(--currentValue);
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default PlusMinusField;
diff --git a/web/src/components/Popup/Description/DisputeCreated.tsx b/web/src/components/Popup/Description/DisputeCreated.tsx
new file mode 100644
index 000000000..22d4a10d7
--- /dev/null
+++ b/web/src/components/Popup/Description/DisputeCreated.tsx
@@ -0,0 +1,65 @@
+import React, { useMemo } from "react";
+import Skeleton from "react-loading-skeleton";
+import styled from "styled-components";
+import { responsiveSize } from "styles/responsiveSize";
+import { isUndefined } from "utils/index";
+import { formatDate, getCurrentTime } from "utils/date";
+import { useCourtDetails } from "hooks/queries/useCourtDetails";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 24px;
+`;
+
+const StyledTitle = styled.div`
+ margin-left: ${responsiveSize(8, 44, 300)};
+ margin-right: ${responsiveSize(8, 44, 300)};
+ color: ${({ theme }) => theme.secondaryText};
+ text-align: center;
+`;
+
+const StyledDateContainer = styled.span`
+ color: ${({ theme }) => theme.primaryText};
+`;
+
+const StyledSubtitle = styled(StyledTitle)`
+ margin-top: 24px;
+ color: ${({ theme }) => theme.primaryText};
+`;
+interface IDisputeCreated {
+ courtId: string;
+}
+
+const DisputeCreated: React.FC = ({ courtId }) => {
+ const { data: courtDetails } = useCourtDetails(courtId);
+
+ const date = useMemo(
+ () =>
+ !isUndefined(courtDetails?.court?.timesPerPeriod)
+ ? calculateMinResolveTime(courtDetails?.court.timesPerPeriod)
+ : undefined,
+ [courtDetails]
+ );
+
+ return (
+
+
+ 🎉 Your case was successfully submitted to Kleros. A pool of jurors will be drawn to evaluate the case and vote
+ at most{" "}
+ {isUndefined(date) ? (
+
+ ) : (
+ {formatDate(date)}
+ )}
+ . 🎉
+
+ Now, it’s time to submit evidence to support the case.
+
+ );
+};
+
+const calculateMinResolveTime = (timesPerPeriod: string[]) =>
+ timesPerPeriod.reduce((acc, val) => acc + parseInt(val), 0) + getCurrentTime();
+
+export default DisputeCreated;
diff --git a/web/src/components/Popup/ExtraInfo/DisputeCreatedExtraInfo.tsx b/web/src/components/Popup/ExtraInfo/DisputeCreatedExtraInfo.tsx
new file mode 100644
index 000000000..c9f154be4
--- /dev/null
+++ b/web/src/components/Popup/ExtraInfo/DisputeCreatedExtraInfo.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import styled from "styled-components";
+import { responsiveSize } from "styles/responsiveSize";
+
+const Container = styled.div`
+ display: flex;
+ color: ${({ theme }) => theme.secondaryText};
+ text-align: center;
+ margin-top: ${responsiveSize(8, 24, 300)};
+ margin-right: ${responsiveSize(8, 44, 300)};
+ margin-left: ${responsiveSize(8, 44, 300)};
+`;
+
+const DisputeCreatedExtraInfo: React.FC = () => {
+ return (
+
+ {
+ "In order to better track the case progress, we recommend you to subscribe to notifications: Settings > Notifications"
+ }
+
+ );
+};
+export default DisputeCreatedExtraInfo;
diff --git a/web/src/components/Popup/index.tsx b/web/src/components/Popup/index.tsx
index e1b285230..ea19f7007 100644
--- a/web/src/components/Popup/index.tsx
+++ b/web/src/components/Popup/index.tsx
@@ -10,6 +10,9 @@ import Appeal from "./Description/Appeal";
import VoteWithCommitExtraInfo from "./ExtraInfo/VoteWithCommitExtraInfo";
import StakeWithdrawExtraInfo from "./ExtraInfo/StakeWithdrawExtraInfo";
import { responsiveSize } from "styles/responsiveSize";
+import DisputeCreated from "./Description/DisputeCreated";
+import DisputeCreatedExtraInfo from "./ExtraInfo/DisputeCreatedExtraInfo";
+import { useNavigate } from "react-router-dom";
const Header = styled.h1`
display: flex;
@@ -92,6 +95,7 @@ export enum PopupType {
APPEAL = "APPEAL",
VOTE_WITHOUT_COMMIT = "VOTE_WITHOUT_COMMIT",
VOTE_WITH_COMMIT = "VOTE_WITH_COMMIT",
+ DISPUTE_CREATED = "DISPUTE_CREATED",
}
interface IStakeWithdraw {
@@ -117,6 +121,11 @@ interface IAppeal {
amount: string;
option: string;
}
+interface IDisputeCreated {
+ popupType: PopupType.DISPUTE_CREATED;
+ disputeId: number;
+ courtId: string;
+}
interface IPopup {
title: string;
icon: React.FC>;
@@ -126,7 +135,7 @@ interface IPopup {
isCommit?: boolean;
}
-type PopupProps = IStakeWithdraw | IVoteWithoutCommit | IVoteWithCommit | IAppeal;
+type PopupProps = IStakeWithdraw | IVoteWithoutCommit | IVoteWithCommit | IAppeal | IDisputeCreated;
const Popup: React.FC = ({
title,
@@ -138,6 +147,7 @@ const Popup: React.FC = ({
...props
}) => {
const containerRef = useRef(null);
+ const navigate = useNavigate();
const resetValue = () => {
if (setAmount) {
@@ -178,6 +188,11 @@ const Popup: React.FC = ({
PopupComponent = ;
break;
}
+ case PopupType.DISPUTE_CREATED: {
+ const { courtId } = props as IDisputeCreated;
+ PopupComponent = ;
+ break;
+ }
default:
break;
}
@@ -193,12 +208,17 @@ const Popup: React.FC = ({
{popupType === PopupType.STAKE_WITHDRAW && }
{popupType === PopupType.VOTE_WITH_COMMIT && }
+ {popupType === PopupType.DISPUTE_CREATED && }
{
setIsOpen(false);
resetValue();
+ if (popupType === PopupType.DISPUTE_CREATED) {
+ const { disputeId } = props as IDisputeCreated;
+ navigate(`/cases/${disputeId}`);
+ }
}}
/>
diff --git a/web/src/context/NewDisputeContext.tsx b/web/src/context/NewDisputeContext.tsx
new file mode 100644
index 000000000..9d69bbeb9
--- /dev/null
+++ b/web/src/context/NewDisputeContext.tsx
@@ -0,0 +1,120 @@
+import React, { createContext, useState, useContext, useMemo } from "react";
+import { isUndefined } from "utils/index";
+import { Address } from "viem";
+import { useLocalStorage } from "hooks/useLocalStorage";
+
+export type Answer = {
+ id?: string;
+ title: string;
+ description?: string;
+ reserved?: boolean;
+};
+
+export type Alias = {
+ id?: string;
+ name: string;
+ address: string | Address;
+};
+
+export interface IDisputeTemplate {
+ answers: Answer[];
+ aliases?: Alias[];
+ arbitrableAddress?: string;
+ arbitrableChainID?: string;
+ arbitratorAddress?: string;
+ arbitratorChainID?: string;
+ category?: string;
+ description: string;
+ frontendUrl?: string;
+ lang?: string;
+ policyURI?: string;
+ question: string;
+ specification?: string;
+ title: string;
+}
+
+interface IDisputeData extends IDisputeTemplate {
+ courtId?: string;
+ numberOfJurors: number;
+ arbitrationCost?: string;
+}
+
+interface INewDisputeContext {
+ disputeData: IDisputeData;
+ setDisputeData: (disputeData: IDisputeData) => void;
+ disputeTemplate: IDisputeTemplate;
+ resetDisputeData: () => void;
+ isSubmittingCase: boolean;
+ setIsSubmittingCase: (isSubmittingCase: boolean) => void;
+ isPolicyUploading: boolean;
+ setIsPolicyUploading: (isPolicyUploading: boolean) => void;
+}
+
+const initialDisputeData: IDisputeData = {
+ numberOfJurors: 3,
+ title: "",
+ description: "",
+ question: "",
+ answers: [
+ { title: "", id: "1" },
+ { title: "", id: "2" },
+ ],
+ aliases: [
+ { name: "", address: "", id: "1" },
+ { name: "", address: "", id: "2" },
+ ],
+};
+const initialDisputeTemplate = initialDisputeData as IDisputeTemplate;
+
+const NewDisputeContext = createContext({
+ disputeData: initialDisputeData,
+ setDisputeData: () => {},
+ disputeTemplate: initialDisputeTemplate,
+ resetDisputeData: () => {},
+ isSubmittingCase: false,
+ setIsSubmittingCase: () => {},
+ isPolicyUploading: false,
+ setIsPolicyUploading: () => {},
+});
+
+export const useNewDisputeContext = () => useContext(NewDisputeContext);
+
+export const NewDisputeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const [disputeData, setDisputeData] = useLocalStorage("disputeData", initialDisputeData);
+ const [isSubmittingCase, setIsSubmittingCase] = useState(false);
+ const [isPolicyUploading, setIsPolicyUploading] = useState(false);
+
+ const disputeTemplate = useMemo(() => constructDisputeTemplate(disputeData), [disputeData]);
+
+ const resetDisputeData = () => {
+ setDisputeData(initialDisputeData);
+ };
+
+ const contextValues = useMemo(
+ () => ({
+ disputeData,
+ setDisputeData,
+ disputeTemplate,
+ resetDisputeData,
+ isSubmittingCase,
+ setIsSubmittingCase,
+ isPolicyUploading,
+ setIsPolicyUploading,
+ }),
+ [disputeData, disputeTemplate, resetDisputeData, isSubmittingCase, isPolicyUploading]
+ );
+
+ return {children};
+};
+
+const constructDisputeTemplate = (disputeData: IDisputeData) => {
+ const baseTemplate = { ...disputeData } as IDisputeTemplate;
+
+ if (!isUndefined(baseTemplate.aliases)) {
+ baseTemplate.aliases = baseTemplate.aliases.filter((item) => item.name !== "" && item.address !== "");
+ if (baseTemplate.aliases.length === 0) delete baseTemplate.aliases;
+ }
+ if (!isUndefined(baseTemplate.policyURI) && baseTemplate.policyURI === "") delete baseTemplate.policyURI;
+
+ return baseTemplate;
+};
diff --git a/web/src/hooks/queries/useCourtDetails.ts b/web/src/hooks/queries/useCourtDetails.ts
index 14c708e34..0cbf5546c 100644
--- a/web/src/hooks/queries/useCourtDetails.ts
+++ b/web/src/hooks/queries/useCourtDetails.ts
@@ -17,6 +17,7 @@ const courtDetailsQuery = graphql(`
stake
paidETH
paidPNK
+ timesPerPeriod
}
}
`);
diff --git a/web/src/layout/Header/navbar/DappList.tsx b/web/src/layout/Header/navbar/DappList.tsx
index eead0d5dd..bc584d5cd 100644
--- a/web/src/layout/Header/navbar/DappList.tsx
+++ b/web/src/layout/Header/navbar/DappList.tsx
@@ -106,7 +106,8 @@ const ITEMS = [
{
text: "Resolver",
Icon: Resolver,
- url: "https://resolve.kleros.io",
+ url: "#/resolver",
+ isNewTab: false,
},
{
text: "Linguo",
diff --git a/web/src/layout/Header/navbar/Product.tsx b/web/src/layout/Header/navbar/Product.tsx
index a4ad9e43e..dc25e3ed4 100644
--- a/web/src/layout/Header/navbar/Product.tsx
+++ b/web/src/layout/Header/navbar/Product.tsx
@@ -38,11 +38,12 @@ interface IProduct {
text: string;
url: string;
Icon: React.FC> | string;
+ isNewTab?: boolean;
}
-const Product: React.FC = ({ text, url, Icon }) => {
+const Product: React.FC = ({ text, url, Icon, isNewTab = true }) => {
return (
-
+
{typeof Icon === "string" ? : }
{text}
diff --git a/web/src/pages/Cases/CaseDetails/Overview/index.tsx b/web/src/pages/Cases/CaseDetails/Overview/index.tsx
index 84f22374b..bfa74cbf8 100644
--- a/web/src/pages/Cases/CaseDetails/Overview/index.tsx
+++ b/web/src/pages/Cases/CaseDetails/Overview/index.tsx
@@ -11,8 +11,8 @@ import DisputeInfo from "components/DisputeCard/DisputeInfo";
import Verdict from "components/Verdict/index";
import { useVotingHistory } from "hooks/queries/useVotingHistory";
import { getLocalRounds } from "utils/getLocalRounds";
-import { DisputeContext } from "./DisputeContext";
-import { Policies } from "./Policies";
+import { DisputeContext } from "components/DisputePreview/DisputeContext";
+import { Policies } from "components/DisputePreview/Policies";
import { responsiveSize } from "styles/responsiveSize";
const Container = styled.div`
diff --git a/web/src/pages/Cases/CaseDetails/Voting/index.tsx b/web/src/pages/Cases/CaseDetails/Voting/index.tsx
index 087ad4199..813c27f48 100644
--- a/web/src/pages/Cases/CaseDetails/Voting/index.tsx
+++ b/web/src/pages/Cases/CaseDetails/Voting/index.tsx
@@ -17,6 +17,7 @@ import { useDisputeKitClassicIsVoteActive } from "hooks/contracts/generated";
import VoteIcon from "assets/svgs/icons/voted.svg";
import InfoCircle from "tsx:svgs/icons/info-circle.svg";
import { responsiveSize } from "styles/responsiveSize";
+import { formatDate } from "utils/date";
const Container = styled.div`
padding: ${responsiveSize(16, 32)};
@@ -35,12 +36,6 @@ const InfoContainer = styled.div`
}
`;
-function formatDate(unixTimestamp: number): string {
- const date = new Date(unixTimestamp * 1000);
- const options: Intl.DateTimeFormatOptions = { month: "long", day: "2-digit", year: "numeric" };
- return date.toLocaleDateString("en-US", options);
-}
-
interface IVoting {
arbitrable?: `0x${string}`;
currentPeriodIndex?: number;
diff --git a/web/src/pages/Home/CourtOverview/Header.tsx b/web/src/pages/Home/CourtOverview/Header.tsx
new file mode 100644
index 000000000..dddc02b68
--- /dev/null
+++ b/web/src/pages/Home/CourtOverview/Header.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import styled from "styled-components";
+import { Button } from "@kleros/ui-components-library";
+import Bookmark from "svgs/icons/bookmark.svg";
+import { useNavigate } from "react-router-dom";
+import { responsiveSize } from "styles/responsiveSize";
+
+const StyledHeader = styled.div`
+ display: flex;
+ justify-content: space-between;
+`;
+
+const StyledH1 = styled.h1`
+ font-size: ${responsiveSize(21, 24)};
+`;
+
+const Header: React.FC = () => {
+ const navigate = useNavigate();
+ return (
+
+ Court Overview
+
+ );
+};
+
+export default Header;
diff --git a/web/src/pages/Home/CourtOverview/index.tsx b/web/src/pages/Home/CourtOverview/index.tsx
index c42ac334a..6eda162b3 100644
--- a/web/src/pages/Home/CourtOverview/index.tsx
+++ b/web/src/pages/Home/CourtOverview/index.tsx
@@ -2,6 +2,7 @@ import React from "react";
import styled from "styled-components";
import Stats from "./Stats";
import Chart from "./Chart";
+import Header from "./Header";
const Container = styled.div`
width: 100%;
@@ -10,7 +11,7 @@ const Container = styled.div`
const CourtOverview: React.FC = () => (
- Court Overview
+
diff --git a/web/src/pages/Home/index.tsx b/web/src/pages/Home/index.tsx
index 5f8909920..e0b578e6e 100644
--- a/web/src/pages/Home/index.tsx
+++ b/web/src/pages/Home/index.tsx
@@ -3,7 +3,7 @@ import styled from "styled-components";
import LatestCases from "components/LatestCases";
import CourtOverview from "./CourtOverview";
import Community from "./Community";
-import HeroImage from "./HeroImage";
+import HeroImage from "components/HeroImage";
import { HomePageProvider } from "hooks/useHomePageContext";
import { getOneYearAgoTimestamp } from "utils/date";
import TopJurors from "./TopJurors";
diff --git a/web/src/pages/Resolver/Briefing/Description.tsx b/web/src/pages/Resolver/Briefing/Description.tsx
new file mode 100644
index 000000000..d650098b4
--- /dev/null
+++ b/web/src/pages/Resolver/Briefing/Description.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+import Header from "pages/Resolver/Header";
+import styled, { css } from "styled-components";
+import { Textarea } from "@kleros/ui-components-library";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { responsiveSize } from "styles/responsiveSize";
+import NavigationButtons from "../NavigationButtons";
+import { useNewDisputeContext } from "context/NewDisputeContext";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+const StyledTextArea = styled(Textarea)`
+ width: 84vw;
+ height: 300px;
+ ${landscapeStyle(
+ () => css`
+ width: ${responsiveSize(442, 700, 900)};
+ `
+ )}
+`;
+const Description: React.FC = () => {
+ const { disputeData, setDisputeData } = useNewDisputeContext();
+
+ const handleWrite = (event: React.ChangeEvent) => {
+ setDisputeData({ ...disputeData, description: event.target.value });
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+export default Description;
diff --git a/web/src/pages/Resolver/Briefing/Title.tsx b/web/src/pages/Resolver/Briefing/Title.tsx
new file mode 100644
index 000000000..454e998c9
--- /dev/null
+++ b/web/src/pages/Resolver/Briefing/Title.tsx
@@ -0,0 +1,44 @@
+import React from "react";
+import Header from "pages/Resolver/Header";
+import styled, { css } from "styled-components";
+import { Field } from "@kleros/ui-components-library";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { responsiveSize } from "styles/responsiveSize";
+import NavigationButtons from "../NavigationButtons";
+import { useNewDisputeContext } from "context/NewDisputeContext";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const StyledField = styled(Field)`
+ width: 84vw;
+
+ ${landscapeStyle(
+ () => css`
+ width: ${responsiveSize(442, 700, 900)};
+ `
+ )}
+`;
+const Title: React.FC = () => {
+ const { disputeData, setDisputeData } = useNewDisputeContext();
+
+ const handleWrite = (event: React.ChangeEvent) => {
+ setDisputeData({ ...disputeData, title: event.target.value });
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+export default Title;
diff --git a/web/src/pages/Resolver/Header.tsx b/web/src/pages/Resolver/Header.tsx
new file mode 100644
index 000000000..005bb47b8
--- /dev/null
+++ b/web/src/pages/Resolver/Header.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+
+const Container = styled.h1`
+ margin-bottom: 32px;
+ width: 84vw;
+ text-align: center;
+
+ ${landscapeStyle(
+ () => css`
+ width: auto;
+ `
+ )}
+`;
+
+interface IHeader {
+ text: string;
+}
+
+const Header: React.FC = ({ text }) => {
+ return {text};
+};
+export default Header;
diff --git a/web/src/pages/Resolver/NavigationButtons/NextButton.tsx b/web/src/pages/Resolver/NavigationButtons/NextButton.tsx
new file mode 100644
index 000000000..8bdb3963f
--- /dev/null
+++ b/web/src/pages/Resolver/NavigationButtons/NextButton.tsx
@@ -0,0 +1,31 @@
+import React from "react";
+import { Button } from "@kleros/ui-components-library";
+import { useLocation, useNavigate } from "react-router-dom";
+import { useNewDisputeContext } from "context/NewDisputeContext";
+
+interface INextButton {
+ nextRoute: string;
+}
+
+const NextButton: React.FC = ({ nextRoute }) => {
+ const navigate = useNavigate();
+ const { disputeData, isPolicyUploading } = useNewDisputeContext();
+ const location = useLocation();
+
+ //checks if each answer is filled in
+ const areVotingOptionsFilled =
+ disputeData.question !== "" && disputeData.answers.every((answer) => answer.title !== "");
+
+ const isButtonDisabled =
+ (location.pathname.includes("/resolver/title") && !disputeData.title) ||
+ (location.pathname.includes("/resolver/description") && !disputeData.description) ||
+ (location.pathname.includes("/resolver/court") && !disputeData.courtId) ||
+ (location.pathname.includes("/resolver/jurors") && (!disputeData.numberOfJurors || !disputeData.arbitrationCost)) ||
+ (location.pathname.includes("/resolver/votingoptions") && !areVotingOptionsFilled) ||
+ (location.pathname.includes("/resolver/counterparties") && !disputeData.aliases) ||
+ (location.pathname.includes("/resolver/policy") && (isPolicyUploading || !disputeData.policyURI));
+
+ return