-
{title}
+
{title}
-
+
{disputes?.length === 0 ? (
No cases found
diff --git a/web/src/pages/Home/TopJurors/Header/HowItWorks.tsx b/web/src/components/HowItWorks.tsx
similarity index 53%
rename from web/src/pages/Home/TopJurors/Header/HowItWorks.tsx
rename to web/src/components/HowItWorks.tsx
index 36cd00bf0..6791b400c 100644
--- a/web/src/pages/Home/TopJurors/Header/HowItWorks.tsx
+++ b/web/src/components/HowItWorks.tsx
@@ -1,19 +1,22 @@
import React from "react";
import styled from "styled-components";
-import { useToggle } from "react-use";
import BookOpenIcon from "tsx:assets/svgs/icons/book-open.svg";
-import Level from "components/Popup/MiniGuides/Level";
const Container = styled.div`
display: flex;
align-items: center;
gap: 8px;
+ color: ${({ theme }) => theme.primaryBlue};
&,
& * {
cursor: pointer;
}
+ &:hover {
+ text-decoration: underline;
+ }
+
svg {
path {
fill: ${({ theme }) => theme.primaryBlue};
@@ -25,15 +28,20 @@ const StyledLabel = styled.label`
color: ${({ theme }) => theme.primaryBlue};
`;
-const HowItWorks: React.FC = () => {
- const [isLevelMiniGuidesOpen, toggleIsLevelMiniGuidesOpen] = useToggle(false);
+interface IHowItWorks {
+ isMiniGuideOpen: boolean;
+ toggleMiniGuide: () => void;
+ MiniGuideComponent: React.ComponentType<{ toggleMiniGuide: () => void }>;
+}
+
+const HowItWorks: React.FC
= ({ isMiniGuideOpen, toggleMiniGuide, MiniGuideComponent }) => {
return (
<>
- toggleIsLevelMiniGuidesOpen()}>
+
How it works
- {isLevelMiniGuidesOpen && }
+ {isMiniGuideOpen && }
>
);
};
diff --git a/web/src/components/Popup/MiniGuides/Appeal/CrowdfundAppeal.tsx b/web/src/components/Popup/MiniGuides/Appeal/CrowdfundAppeal.tsx
new file mode 100644
index 000000000..cf2fcadd9
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Appeal/CrowdfundAppeal.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+import styled from "styled-components";
+import CrowdfundAppealSvg from "tsx:assets/svgs/mini-guides/appeal/crowdfund-appeal.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledCrowdfundAppealSvg = styled(CrowdfundAppealSvg)`
+ [class$="rect-bg"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="path-1"],
+ [class$="path-2"],
+ [class$="path-3"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+
+ [class$="rect-fg"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-accent"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="path-4"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="path-5"] {
+ fill: ${({ theme }) => theme.secondaryText};
+ }
+`;
+
+const CrowdfundAppeal: React.FC = () => ;
+
+export default CrowdfundAppeal;
diff --git a/web/src/components/Popup/MiniGuides/Appeal/PayoffSimulator.tsx b/web/src/components/Popup/MiniGuides/Appeal/PayoffSimulator.tsx
new file mode 100644
index 000000000..7e0a16680
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Appeal/PayoffSimulator.tsx
@@ -0,0 +1,77 @@
+import React from "react";
+import styled from "styled-components";
+import PayoffSimulatorSvg from "tsx:assets/svgs/mini-guides/appeal/payoff-simulator.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledPayoffSimulatorSvg = styled(PayoffSimulatorSvg)`
+ [class$="circle-1"] {
+ fill: ${({ theme }) => theme.successLight};
+ }
+
+ [class$="rect-2"] {
+ fill: ${({ theme }) => theme.mediumBlue};
+ }
+
+ [class$="circle-2"] {
+ fill: ${({ theme }) => theme.mediumPurple};
+ }
+
+ [class$="rect-1"],
+ [class$="rect-5"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-6"],
+ [class$="rect-7"],
+ [class$="rect-8"] {
+ fill: ${({ theme }) => theme.white};
+ }
+
+ [class$="path-2"] {
+ fill: ${({ theme }) => theme.success};
+ }
+
+ [class$="path-3"],
+ [class$="path-16"] {
+ fill: ${({ theme }) => theme.secondaryPurple};
+ }
+
+ [class$="path-11"],
+ [class$="path-13"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="path-4"],
+ [class$="path-5"],
+ [class$="path-6"],
+ [class$="path-7"],
+ [class$="path-8"],
+ [class$="path-9"],
+ [class$="path-10"],
+ [class$="path-12"],
+ [class$="path-14"],
+ [class$="path-15"],
+ [class$="path-17"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+
+ [class$="rect-3"],
+ [class$="rect-4"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="line-1"],
+ [class$="line-2"],
+ [class$="path-1"] {
+ stroke: ${({ theme }) => theme.mediumBlue};
+ }
+
+ [class$="path-1"] {
+ fill: ${({ theme }) => theme.lightBlue};
+ }
+`;
+
+const PayoffSimulator: React.FC = () => ;
+
+export default PayoffSimulator;
diff --git a/web/src/components/Popup/MiniGuides/Appeal/StageOne.tsx b/web/src/components/Popup/MiniGuides/Appeal/StageOne.tsx
new file mode 100644
index 000000000..fe6d47047
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Appeal/StageOne.tsx
@@ -0,0 +1,74 @@
+import React from "react";
+import styled from "styled-components";
+import StageOneSvg from "tsx:assets/svgs/mini-guides/appeal/stage-one.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledStageOneSvg = styled(StageOneSvg)`
+ [class$="rect-1"],
+ [class$="rect-2"],
+ [class$="rect-6"],
+ [class$="circle-1"],
+ [class$="circle-2"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="rect-4"],
+ [class$="rect-8"] {
+ fill: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-5"],
+ [class$="path-9"],
+ [class$="path-10"] {
+ fill: ${({ theme }) => theme.secondaryPurple};
+ }
+
+ [class$="rect-9"],
+ [class$="circle-3"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="rect-10"] {
+ fill: ${({ theme }) => theme.lightBlue};
+ stroke: ${({ theme }) => theme.mediumBlue};
+ }
+
+ [class$="rect-11"],
+ [class$="rect-12"] {
+ fill: ${({ theme }) => theme.white};
+ }
+
+ [class$="path-2"],
+ [class$="path-6"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+
+ [class$="path-1"],
+ [class$="path-5"] {
+ fill: ${({ theme }) => theme.secondaryText};
+ }
+
+ [class$="path-3"],
+ [class$="path-4"] {
+ fill: ${({ theme }) => theme.success};
+ }
+
+ [class$="path-7"],
+ [class$="path-8"] {
+ fill: ${({ theme }) => theme.warning};
+ }
+
+ [class$="rect-3"],
+ [class$="rect-7"],
+ [class$="circle-1"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="circle-2"] {
+ stroke: ${({ theme }) => theme.primaryBlue};
+ }
+`;
+
+const StageOne: React.FC = () => ;
+
+export default StageOne;
diff --git a/web/src/components/Popup/MiniGuides/Appeal/StageTwo.tsx b/web/src/components/Popup/MiniGuides/Appeal/StageTwo.tsx
new file mode 100644
index 000000000..f625d0034
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Appeal/StageTwo.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+import styled from "styled-components";
+import StageTwoSvg from "tsx:assets/svgs/mini-guides/appeal/stage-two.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledStageTwoSvg = styled(StageTwoSvg)`
+ [class$="rect-1"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="rect-2"],
+ [class$="rect-6"] {
+ fill: ${({ theme }) => theme.lightBlue};
+ stroke: ${({ theme }) => theme.mediumBlue};
+ }
+
+ [class$="rect-3"],
+ [class$="path-2"],
+ [class$="path-3"] {
+ fill: ${({ theme }) => theme.success};
+ }
+
+ [class$="rect-4"] {
+ fill: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-5"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="rect-7"] {
+ fill: ${({ theme }) => theme.white};
+ }
+
+ [class$="rect-6"],
+ [class$="line-1"] {
+ stroke: ${({ theme }) => theme.mediumBlue};
+ }
+
+ [class$="path-1"],
+ [class$="path-4"],
+ [class$="path-5"],
+ [class$="path-6"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+
+ [class$="path-7"],
+ [class$="path-8"] {
+ fill: ${({ theme }) => theme.secondaryPurple};
+ }
+`;
+
+const StageTwo: React.FC = () => ;
+
+export default StageTwo;
diff --git a/web/src/components/Popup/MiniGuides/Appeal/index.tsx b/web/src/components/Popup/MiniGuides/Appeal/index.tsx
new file mode 100644
index 000000000..fbd1da38f
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Appeal/index.tsx
@@ -0,0 +1,59 @@
+import React from "react";
+import CrowdfundAppeal from "./CrowdfundAppeal";
+import PayoffSimulator from "./PayoffSimulator";
+import StageOne from "./StageOne";
+import StageTwo from "./StageTwo";
+import PageContentsTemplate from "../PageContentsTemplate";
+
+const leftPageContents = [
+ {
+ title: "Appeal",
+ paragraphs: [
+ "If after the jury has reached a decision, a party is not satisfied (because she thinks the result was" +
+ " unfair), she can appeal and have the dispute ruled again. Each new appeal instance will have twice the" +
+ " previous number of jurors plus one.",
+ ],
+ },
+ {
+ title: "Appeal: Stage 1",
+ paragraphs: [
+ "The jury decision is appealed when stages 1 and 2 are fully funded. In stage 1, one of the losing options" +
+ " must be fully funded. If no option is fully funded in time the jury decision is maintained.",
+ ],
+ },
+ {
+ title: "Appeal: Stage 2",
+ paragraphs: [
+ "Now, options compete together against the option fully funded at stage 1. The sum of funds must reach 100%." +
+ " If it's not fully funded in time the option fully funded at stage 1 is declared the winner of the case. ",
+ ],
+ },
+ {
+ title: "Crowdfunding Rewards",
+ paragraphs: [
+ "Anyone can contribute to the crowdfunding of the appeal fees. Crowdfunders can win rewards in case the option" +
+ " they fund wins. See how much you can earn by funding appeals, at the payoff simulator.",
+ ],
+ },
+];
+
+const rightPageComponents = [CrowdfundAppeal, StageOne, StageTwo, PayoffSimulator];
+
+interface IAppeal {
+ toggleMiniGuide: () => void;
+}
+
+const Appeal: React.FC = ({ toggleMiniGuide }) => {
+ return (
+
+ );
+};
+
+export default Appeal;
diff --git a/web/src/components/Popup/MiniGuides/BinaryVoting/VotingModule.tsx b/web/src/components/Popup/MiniGuides/BinaryVoting/VotingModule.tsx
new file mode 100644
index 000000000..fcb7a4938
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/BinaryVoting/VotingModule.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+import styled from "styled-components";
+import VotingModuleSvg from "tsx:assets/svgs/mini-guides/binary-voting/voting-module.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledVotingModuleSvg = styled(VotingModuleSvg)`
+ [class$="rect-1"],
+ [class$="rect-4"],
+ [class$="path-1"],
+ [class$="path-2"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="rect-2"],
+ [class$="rect-3"],
+ [class$="path-4"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="path-3"] {
+ fill: ${({ theme }) => theme.lightBlue};
+ }
+
+ [class$="path-5"] {
+ fill: ${({ theme }) => theme.secondaryText};
+ }
+
+ [class$="rect-1"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-5"] {
+ stroke: ${({ theme }) => theme.primaryBlue};
+ }
+`;
+
+const VotingModule: React.FC = () => ;
+
+export default VotingModule;
diff --git a/web/src/components/Popup/MiniGuides/BinaryVoting/index.tsx b/web/src/components/Popup/MiniGuides/BinaryVoting/index.tsx
new file mode 100644
index 000000000..484cfc690
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/BinaryVoting/index.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+import PageContentsTemplate from "../PageContentsTemplate";
+import JurorRewards from "../Staking/JurorRewards";
+import VotingModule from "./VotingModule";
+
+const leftPageContents = [
+ {
+ title: "Binary Voting",
+ paragraphs: [
+ "Jurors choose one option to vote. The option which receives the majority of votes is considered the winner.",
+ "Refuse to Arbitrate is used when a dispute has been posted in the wrong court, has no clear outcome, or" +
+ " evidence is immoral and/or illegal. In case the majority decides to Refuse to Arbitrate, that option" +
+ " is considered the winner.",
+ ],
+ },
+ {
+ title: "Juror Rewards",
+ paragraphs: [
+ "Jurors whose vote is coherent with the final jury decision (after all the appeal instances) receive the" +
+ " Juror Rewards composed of arbitration fees (ETH) + PNK redistribution between jurors. Jurors whose vote" +
+ " is not coherent with the final jury decision, lose their locked PNK.",
+ ],
+ },
+];
+
+const rightPageComponents = [VotingModule, JurorRewards];
+
+interface IBinaryVoting {
+ toggleMiniGuide: () => void;
+}
+
+const BinaryVoting: React.FC = ({ toggleMiniGuide }) => {
+ return (
+
+ );
+};
+
+export default BinaryVoting;
diff --git a/web/src/components/Popup/MiniGuides/Level.tsx b/web/src/components/Popup/MiniGuides/JurorLevels.tsx
similarity index 50%
rename from web/src/components/Popup/MiniGuides/Level.tsx
rename to web/src/components/Popup/MiniGuides/JurorLevels.tsx
index 5e9c94ad4..4ab5cf292 100644
--- a/web/src/components/Popup/MiniGuides/Level.tsx
+++ b/web/src/components/Popup/MiniGuides/JurorLevels.tsx
@@ -4,7 +4,8 @@ import { landscapeStyle } from "styles/landscapeStyle";
import { Card as _Card } from "@kleros/ui-components-library";
import PixelArt from "pages/Dashboard/JurorInfo/PixelArt";
import Coherency from "pages/Dashboard/JurorInfo/Coherency";
-import Template from "./Template";
+import { Title, ParagraphsContainer, LeftContentContainer } from "./PageContentsTemplate";
+import Template from "./MainStructureTemplate";
const Card = styled(_Card)`
display: flex;
@@ -25,100 +26,118 @@ const Card = styled(_Card)`
)}
`;
-const Title = styled.h1`
- margin-bottom: 0;
-`;
-
-const LeftContentContainer = styled.div`
- display: flex;
- gap: 18px;
- flex-direction: column;
-`;
+const leftPageContents = [
+ {
+ title: "Juror Level 1: Phytagoras",
+ paragraphs: [
+ "Jurors are classified into distinct levels according to their performance starting from Level 1.",
+ "Level 1: Jurors with 0 cases arbitrated, OR Jurors with ≥ 1 case arbitrated with 0-70% of coherent votes.",
+ ],
+ },
+ {
+ title: "Juror Level 2: Socrates",
+ paragraphs: ["Level 2: Jurors with ≥ 3 cases arbitrated with 70%-80% of coherent votes."],
+ },
+ {
+ title: "Juror Level 3: Plato",
+ paragraphs: ["Level 3: Jurors with ≥ 7 cases arbitrated with 80%-90% of coherent votes."],
+ },
+ {
+ title: "Juror Level 4: Aristotle",
+ paragraphs: ["Level 4: Jurors with ≥ 10 cases arbitrated with more than 90% of coherent votes."],
+ },
+ {
+ title: "Juror Level 0: Diogenes",
+ paragraphs: [
+ "There's a level for the low-performance/lazy jurors. Level 0: Jurors with ≥ 3 cases arbitrated" +
+ " with less than 50% of coherent votes.",
+ ],
+ },
+];
const userLevelData = [
{
level: 1,
- title: "Pythagoras",
+ title: "Phytagoras",
totalCoherent: 6,
totalResolvedDisputes: 10,
- firstParagraph: "Jurors are classified into distinct levels according to their performance starting from Level 1.",
- secondParagraph:
- "Level 1: Jurors with 0 cases arbitrated, OR Jurors with ≥ 1 case arbitrated with 0-70% of coherent votes.",
},
{
level: 2,
title: "Socrates",
totalCoherent: 7,
totalResolvedDisputes: 10,
- firstParagraph: "Level 2: Jurors with ≥ 3 cases arbitrated with 70%-80% of coherent votes.",
},
{
level: 3,
title: "Plato",
totalCoherent: 8,
totalResolvedDisputes: 10,
- firstParagraph: "Level 3: Jurors with ≥ 7 cases arbitrated with 80%-90% of coherent votes.",
},
{
level: 4,
- title: "Aristoteles",
+ title: "Aristotle",
totalCoherent: 9,
totalResolvedDisputes: 10,
- firstParagraph: "Level 4: Jurors with ≥ 10 cases arbitrated with more than 90% of coherent votes.",
},
{
level: 0,
title: "Diogenes",
totalCoherent: 3,
totalResolvedDisputes: 10,
- firstParagraph:
- "There's a level for the low-performance/lazy jurors. Level 0: Jurors with ≥ 3 cases arbitrated with less than 50% of coherent votes.",
},
];
const LeftContent: React.FC<{ currentPage: number }> = ({ currentPage }) => {
+ const { title, paragraphs } = leftPageContents[currentPage - 1];
+
return (
-
- Juror Level {userLevelData[currentPage - 1].level}: {userLevelData[currentPage - 1].title}
-
-
-
+ {title}
+
+ {paragraphs.map((paragraph, index) => (
+
+ ))}
+
);
};
const RightContent: React.FC<{ currentPage: number }> = ({ currentPage }) => {
+ const userData = userLevelData[currentPage - 1];
return (
-
+
);
};
-interface ILevel {
- toggleIsLevelMiniGuidesOpen: () => void;
+interface IJurorLevels {
+ toggleMiniGuide: () => void;
}
-const Level: React.FC = ({ toggleIsLevelMiniGuidesOpen }) => {
+const JurorLevels: React.FC = ({ toggleMiniGuide }) => {
const [currentPage, setCurrentPage] = useState(1);
return (
}
RightContent={}
- onClose={toggleIsLevelMiniGuidesOpen}
+ onClose={toggleMiniGuide}
currentPage={currentPage}
setCurrentPage={setCurrentPage}
- numPages={userLevelData.length}
+ numPages={leftPageContents.length}
+ isOnboarding={false}
+ canClose={true}
+ isVisible={true}
/>
);
};
-export default Level;
+export default JurorLevels;
diff --git a/web/src/components/Popup/MiniGuides/Template.tsx b/web/src/components/Popup/MiniGuides/MainStructureTemplate.tsx
similarity index 63%
rename from web/src/components/Popup/MiniGuides/Template.tsx
rename to web/src/components/Popup/MiniGuides/MainStructureTemplate.tsx
index d8bcb18a5..3f511fa26 100644
--- a/web/src/components/Popup/MiniGuides/Template.tsx
+++ b/web/src/components/Popup/MiniGuides/MainStructureTemplate.tsx
@@ -6,16 +6,15 @@ import { Overlay } from "components/Overlay";
import BookOpenIcon from "tsx:assets/svgs/icons/book-open.svg";
import { useFocusOutside } from "hooks/useFocusOutside";
-const Container = styled.div`
- display: flex;
+const Container = styled.div<{ isVisible: boolean }>`
+ display: ${({ isVisible }) => (isVisible ? "flex" : "none")};
margin: 0 auto;
- width: auto;
z-index: 10;
position: fixed;
- width: 82vw;
+ width: 86vw;
flex-direction: column;
- top: 50%;
+ top: 45%;
left: 50%;
transform: translate(-50%, -50%);
max-height: 80vh;
@@ -24,6 +23,7 @@ const Container = styled.div`
${landscapeStyle(
() => css`
overflow-y: hidden;
+ top: 50%;
width: calc(700px + (900 - 700) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
flex-direction: row;
height: 500px;
@@ -34,9 +34,9 @@ const Container = styled.div`
const LeftContainer = styled.div`
display: grid;
grid-template-rows: auto 1fr auto;
- width: 82vw;
- min-height: 356px;
+ width: 86vw;
padding: calc(24px + (32 - 24) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
+ padding-bottom: 32px;
background-color: ${({ theme }) => theme.whiteBackground};
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
@@ -46,11 +46,16 @@ const LeftContainer = styled.div`
overflow-y: hidden;
width: calc(350px + (450 - 350) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
height: 500px;
- min-height: auto;
`
)}
`;
+const LeftContainerHeader = styled.div`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+`;
+
const HowItWorks = styled.div`
display: flex;
align-items: center;
@@ -62,31 +67,55 @@ const HowItWorks = styled.div`
}
`;
-const StyledCompactPagination = styled(CompactPagination)`
+const MobileCompactPagination = styled(CompactPagination)`
+ display: flex;
+ align-items: flex-start;
+
+ ${landscapeStyle(
+ () => css`
+ display: none;
+ `
+ )}
+`;
+
+const DesktopCompactPagination = styled(CompactPagination)`
+ display: none;
align-self: end;
justify-self: end;
+
+ ${landscapeStyle(
+ () => css`
+ display: block;
+ `
+ )}
`;
const Close = styled.label`
- position: absolute;
- top: calc(24px + (32 - 24) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
- right: 17px;
- display: flex;
- align-items: flex-end;
- justify-content: flex-end;
- cursor: pointer;
-
- color: ${({ theme }) => theme.primaryBlue};
+ display: none;
${landscapeStyle(
() => css`
+ display: flex;
+ position: absolute;
+ top: calc(24px + (32 - 24) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
+ right: 17px;
+ display: flex;
+ align-items: flex-end;
+ justify-content: flex-end;
+ cursor: pointer;
z-index: 11;
+
+ &:hover {
+ text-decoration: underline;
+ }
+
+ color: ${({ theme }) => theme.primaryBlue};
`
)}
`;
const RightContainer = styled.div`
- width: 82vw;
+ width: 86vw;
position: relative;
display: flex;
flex-direction: column;
@@ -96,7 +125,6 @@ const RightContainer = styled.div`
background-color: ${({ theme }) => theme.mediumBlue};
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
- height: 800px;
${landscapeStyle(
() => css`
@@ -114,6 +142,9 @@ interface ITemplate {
currentPage: number;
setCurrentPage: Dispatch>;
numPages: number;
+ isOnboarding: boolean;
+ canClose: boolean;
+ isVisible: boolean;
}
const Template: React.FC = ({
@@ -123,30 +154,45 @@ const Template: React.FC = ({
currentPage,
setCurrentPage,
numPages,
+ isOnboarding,
+ canClose,
+ isVisible,
}) => {
const containerRef = useRef(null);
useFocusOutside(containerRef, () => {
- onClose();
+ if (canClose) {
+ onClose();
+ }
});
return (
<>
-
+
-
-
-
-
- Close
+
+
+
+
+
+
+
{LeftContent}
-
- {RightContent}
+
+ Close
+ {RightContent}
+
>
);
diff --git a/web/src/components/Popup/MiniGuides/Onboarding/HowItWorks.tsx b/web/src/components/Popup/MiniGuides/Onboarding/HowItWorks.tsx
new file mode 100644
index 000000000..7fe7edaf2
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Onboarding/HowItWorks.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import styled from "styled-components";
+import HowItWorksSvg from "tsx:assets/svgs/mini-guides/onboarding/how-it-works.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledHowItWorksSvg = styled(HowItWorksSvg)`
+ [class$="rect-1"],
+ [class$="rect-3"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="path-1"],
+ [class$="path-2"] {
+ fill: ${({ theme }) => theme.secondaryPurple};
+ }
+
+ [class$="rect-2"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+`;
+
+const HowItWorks: React.FC = () => ;
+
+export default HowItWorks;
diff --git a/web/src/components/Popup/MiniGuides/Onboarding/PnkLogoAndTitle.tsx b/web/src/components/Popup/MiniGuides/Onboarding/PnkLogoAndTitle.tsx
new file mode 100644
index 000000000..40e213e33
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Onboarding/PnkLogoAndTitle.tsx
@@ -0,0 +1,46 @@
+import React from "react";
+import styled from "styled-components";
+import PnkIcon from "tsx:assets/svgs/styled/pnk.svg";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ gap: 32px;
+ align-items: center;
+`;
+
+const StyledPnkIcon = styled(PnkIcon)`
+ width: calc(220px + (280 - 220) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
+ height: calc(220px + (252 - 220) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
+
+ [class$="stop-1"] {
+ stop-color: ${({ theme }) => theme.primaryBlue};
+ }
+ [class$="stop-2"] {
+ stop-color: ${({ theme }) => theme.secondaryPurple};
+ }
+`;
+
+const StyledCourtLabel = styled.label`
+ font-size: 24px;
+ background: linear-gradient(
+ 90deg,
+ ${({ theme }) => theme.secondaryPurple} 0%,
+ ${({ theme }) => theme.primaryBlue} 100%
+ );
+ background-clip: text;
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+`;
+
+const PnkLogoAndTitle = () => {
+ return (
+
+
+ Court v.2
+
+ );
+};
+
+export default PnkLogoAndTitle;
diff --git a/web/src/components/Popup/MiniGuides/Onboarding/WhatDoINeed.tsx b/web/src/components/Popup/MiniGuides/Onboarding/WhatDoINeed.tsx
new file mode 100644
index 000000000..d11441042
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Onboarding/WhatDoINeed.tsx
@@ -0,0 +1,75 @@
+import React from "react";
+import styled from "styled-components";
+import WhatDoINeedSvg from "tsx:assets/svgs/mini-guides/onboarding/what-do-i-need.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledWhatDoINeedSvg = styled(WhatDoINeedSvg)`
+ [class$="rect-1"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="rect-2"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-3"],
+ [class$="rect-4"],
+ [class$="rect-5"] {
+ fill: ${({ theme }) => theme.mediumBlue};
+ }
+
+ [class$="rect-6"] {
+ fill: ${({ theme }) => theme.mediumPurple};
+ }
+
+ [class$="rect-7"],
+ [class$="rect-8"],
+ [class$="rect-9"],
+ [class$="rect-10"],
+ [class$="rect-11"],
+ [class$="rect-12"],
+ [class$="rect-13"] {
+ fill: ${({ theme }) => theme.white};
+ }
+
+ [class$="path-1"],
+ [class$="path-18"],
+ [class$="path-22"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+
+ [class$="path-2"],
+ [class$="path-3"],
+ [class$="path-4"],
+ [class$="path-7"],
+ [class$="path-10"],
+ [class$="path-13"],
+ [class$="path-16"],
+ [class$="path-17"],
+ [class$="path-19"],
+ [class$="path-20"],
+ [class$="path-21"],
+ [class$="path-23"],
+ [class$="path-24"],
+ [class$="path-25"] {
+ fill: ${({ theme }) => theme.secondaryText};
+ }
+
+ [class$="path-5"],
+ [class$="path-6"],
+ [class$="path-8"],
+ [class$="path-9"],
+ [class$="path-11"],
+ [class$="path-12"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="path-14"],
+ [class$="path-15"] {
+ fill: ${({ theme }) => theme.secondaryPurple};
+ }
+`;
+
+const WhatDoINeed: React.FC = () => ;
+
+export default WhatDoINeed;
diff --git a/web/src/components/Popup/MiniGuides/Onboarding/index.tsx b/web/src/components/Popup/MiniGuides/Onboarding/index.tsx
new file mode 100644
index 000000000..7ae2f4e69
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Onboarding/index.tsx
@@ -0,0 +1,101 @@
+import React from "react";
+import { useToggle } from "react-use";
+import HowItWorks from "./HowItWorks";
+import PnkLogoAndTitle from "./PnkLogoAndTitle";
+import WhatDoINeed from "./WhatDoINeed";
+import Staking from "../Staking";
+import BinaryVoting from "../BinaryVoting";
+import RankedVoting from "../RankedVoting";
+import Appeal from "../Appeal";
+import JurorLevels from "../JurorLevels";
+import PageContentsTemplate from "../PageContentsTemplate";
+
+const leftPageContents = [
+ {
+ title: "Welcome to Kleros Court",
+ paragraphs: ["The decentralized arbitration service for the disputes of the new economy.", "Learn what’s new"],
+ links: [],
+ },
+ {
+ title: "What do I need to start?",
+ paragraphs: [
+ "Do you want to be a juror? If yes, you will need PNK tokens for staking on courts, and ETH for gas fees.",
+ "I don't want to be a juror. Can I still participate in the Court? Yes, sure. Users can also participate as" +
+ " contributors by helping fund appeal fees in exchange for rewards, or by submitting evidence." +
+ " In this case, you will need ETH.",
+ "I have a case that needs resolution? What do I do? It's simple. Send your case to Kleros and receive" +
+ " the resolution. You will need a few minutes to fill up the details of your case, and ETH to pay for" +
+ " Arbitration fees (It's used to pay jurors for their work).",
+ ],
+ links: [],
+ },
+ {
+ title: "Access the Mini Guides",
+ paragraphs: [],
+ links: ["1. Staking", "2. Binary Voting", "3. Ranked Voting", "4. Appeal", "5. Juror Levels"],
+ },
+];
+
+const rightPageComponents = [PnkLogoAndTitle, WhatDoINeed, HowItWorks];
+
+interface IOnboarding {
+ toggleMiniGuide: () => void;
+}
+
+const Onboarding: React.FC = ({ toggleMiniGuide }) => {
+ const [isStakingMiniGuideOpen, toggleStakingMiniGuide] = useToggle(false);
+ const [isBinaryVotingMiniGuideOpen, toggleBinaryVotingMiniGuide] = useToggle(false);
+ const [isRankedVotingMiniGuideOpen, toggleRankedVotingMiniGuide] = useToggle(false);
+ const [isAppealMiniGuideOpen, toggleAppealMiniGuide] = useToggle(false);
+ const [isJurorLevelsMiniGuideOpen, toggleJurorLevelsMiniGuide] = useToggle(false);
+
+ const isAnyMiniGuideOpen =
+ isStakingMiniGuideOpen ||
+ isBinaryVotingMiniGuideOpen ||
+ isRankedVotingMiniGuideOpen ||
+ isAppealMiniGuideOpen ||
+ isJurorLevelsMiniGuideOpen;
+
+ const canCloseOnboarding =
+ !isStakingMiniGuideOpen &&
+ !isBinaryVotingMiniGuideOpen &&
+ !isRankedVotingMiniGuideOpen &&
+ !isAppealMiniGuideOpen &&
+ !isJurorLevelsMiniGuideOpen;
+
+ const toggleSubMiniGuide = (guideName: string) => {
+ if (guideName === "Staking") {
+ toggleStakingMiniGuide();
+ } else if (guideName === "Binary Voting") {
+ toggleBinaryVotingMiniGuide();
+ } else if (guideName === "Ranked Voting") {
+ toggleRankedVotingMiniGuide();
+ } else if (guideName === "Appeal") {
+ toggleAppealMiniGuide();
+ } else if (guideName === "Juror Levels") {
+ toggleJurorLevelsMiniGuide();
+ }
+ };
+
+ return (
+ <>
+
+
+ {isStakingMiniGuideOpen && }
+ {isBinaryVotingMiniGuideOpen && }
+ {isRankedVotingMiniGuideOpen && }
+ {isAppealMiniGuideOpen && }
+ {isJurorLevelsMiniGuideOpen && }
+ >
+ );
+};
+
+export default Onboarding;
diff --git a/web/src/components/Popup/MiniGuides/PageContentsTemplate.tsx b/web/src/components/Popup/MiniGuides/PageContentsTemplate.tsx
new file mode 100644
index 000000000..d805a6a3a
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/PageContentsTemplate.tsx
@@ -0,0 +1,132 @@
+import React, { useState } from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import MainStructureTemplate from "./MainStructureTemplate";
+
+export const ParagraphsContainer = styled.div`
+ display: flex;
+ gap: 18px;
+ flex-direction: column;
+`;
+
+export const Title = styled.h1`
+ margin-bottom: 0;
+`;
+
+export const LeftContentContainer = styled.div`
+ display: flex;
+ gap: 18px;
+ flex-direction: column;
+`;
+
+export const StyledImage = styled.div`
+ width: calc(260px + (460 - 260) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
+
+ ${landscapeStyle(
+ () => css`
+ width: 389px;
+ `
+ )}
+`;
+
+const LinksContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const StyledLabel = styled.label`
+ color: ${({ theme }) => theme.primaryBlue};
+ margin: 0;
+ cursor: pointer;
+`;
+
+const extractGuideName = (linkText) => linkText.split(". ")[1];
+
+const LeftContent: React.FC<{
+ currentPage: number;
+ leftPageContents: {
+ title: string;
+ paragraphs: string[];
+ links?: string[];
+ }[];
+ toggleSubMiniGuide?: (guideName: string) => void;
+}> = ({ currentPage, leftPageContents, toggleSubMiniGuide }) => {
+ const { title, paragraphs, links } = leftPageContents[currentPage - 1];
+
+ return (
+
+ {title}
+
+ {paragraphs.map((paragraph, index) => (
+
+ ))}
+
+ {links && links.length > 0 && toggleSubMiniGuide ? (
+
+ {links.map((link, index) => (
+ toggleSubMiniGuide(extractGuideName(link))}>
+ {link}
+
+ ))}
+
+ ) : null}
+
+ );
+};
+
+const RightContent: React.FC<{ currentPage: number; rightPageComponents: () => React.ReactNode[] }> = ({
+ currentPage,
+ rightPageComponents,
+}) => {
+ const RightPageComponent = rightPageComponents[currentPage - 1];
+
+ return ;
+};
+
+interface IPageContentsTemplate {
+ toggleMiniGuide: () => void;
+ toggleSubMiniGuide?: (guideName: string) => void;
+ leftPageContents: {
+ title: string;
+ paragraphs: string[];
+ links?: string[];
+ }[];
+ rightPageComponents: () => React.ReactNode[];
+ isOnboarding: boolean;
+ canClose: boolean;
+ isVisible: boolean;
+}
+
+const PageContentsTemplate: React.FC = ({
+ toggleMiniGuide,
+ toggleSubMiniGuide,
+ leftPageContents,
+ rightPageComponents,
+ canClose,
+ isVisible,
+ isOnboarding,
+}) => {
+ const [currentPage, setCurrentPage] = useState(1);
+
+ return (
+
+ }
+ RightContent={}
+ onClose={toggleMiniGuide}
+ currentPage={currentPage}
+ setCurrentPage={setCurrentPage}
+ numPages={leftPageContents.length}
+ isOnboarding={isOnboarding}
+ canClose={canClose}
+ isVisible={isVisible}
+ />
+ );
+};
+
+export default PageContentsTemplate;
diff --git a/web/src/components/Popup/MiniGuides/RankedVoting/VotingModule.tsx b/web/src/components/Popup/MiniGuides/RankedVoting/VotingModule.tsx
new file mode 100644
index 000000000..07629ede8
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/RankedVoting/VotingModule.tsx
@@ -0,0 +1,73 @@
+import React from "react";
+import styled from "styled-components";
+import VotingModuleSvg from "tsx:assets/svgs/mini-guides/ranked-voting/voting-module.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledVotingModuleSvg = styled(VotingModuleSvg)`
+ [class$="path-1"],
+ [class$="path-5"] {
+ fill: ${({ theme }) => theme.success};
+ }
+ [class$="rect-1"],
+ [class$="rect-2"],
+ [class$="rect-4"],
+ [class$="rect-8"],
+ [class$="rect-13"],
+ [class$="path-3"],
+ [class$="path-4"],
+ [class$="path-7"],
+ [class$="path-8"],
+ [class$="path-13"],
+ [class$="path-12"],
+ [class$="path-14"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="rect-1"],
+ [class$="rect-3"],
+ [class$="rect-5"],
+ [class$="rect-7"],
+ [class$="rect-9"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-6"] {
+ stroke: ${({ theme }) => theme.success};
+ }
+ [class$="rect-10"] {
+ stroke: ${({ theme }) => theme.error};
+ }
+ [class$="rect-14"] {
+ stroke: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="rect-11"] {
+ fill: ${({ theme }) => theme.lightBlue};
+ }
+
+ [class$="rect-12"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="path-11"],
+ [class$="path-2"],
+ [class$="path-6"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+
+ [class$="path-9"] {
+ fill: ${({ theme }) => theme.secondaryText};
+ }
+
+ [class$="path-10"] {
+ fill: ${({ theme }) => theme.error};
+ }
+
+ [class$="path-15"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+`;
+
+const VotingModule: React.FC = () => ;
+
+export default VotingModule;
diff --git a/web/src/components/Popup/MiniGuides/RankedVoting/index.tsx b/web/src/components/Popup/MiniGuides/RankedVoting/index.tsx
new file mode 100644
index 000000000..c29d4352f
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/RankedVoting/index.tsx
@@ -0,0 +1,47 @@
+import React from "react";
+import JurorRewards from "../Staking/JurorRewards";
+import VotingModule from "./VotingModule";
+import PageContentsTemplate from "../PageContentsTemplate";
+
+const leftPageContents = [
+ {
+ title: "Ranked Voting",
+ paragraphs: [
+ "Jurors rank the options in order of preference. The number of options you rank doesn’t affect the rewards" +
+ " you may win as long as you rank the winning option. Note it’s also possible to reject some options you" +
+ " consider inadequate.",
+ "Refuse to Arbitrate is used when a dispute has been posted in the wrong court, has no clear outcome, or" +
+ " evidence is immoral and/or illegal. In case the majority decides to Refuse to Arbitrate, that option is" +
+ " considered the winner.",
+ ],
+ },
+ {
+ title: "Juror Rewards",
+ paragraphs: [
+ "Jurors whose vote is coherent with the final jury decision (after all the appeal instances) receive the" +
+ " Juror Rewards composed of arbitration fees (ETH) + PNK redistribution between jurors. Jurors whose vote" +
+ " is not coherent with the final jury decision, lose their locked PNK.",
+ ],
+ },
+];
+
+const rightPageComponents = [VotingModule, JurorRewards];
+
+interface IRankedVoting {
+ toggleMiniGuide: () => void;
+}
+
+const RankedVoting: React.FC = ({ toggleMiniGuide }) => {
+ return (
+
+ );
+};
+
+export default RankedVoting;
diff --git a/web/src/components/Popup/MiniGuides/Staking/CourtHeader.tsx b/web/src/components/Popup/MiniGuides/Staking/CourtHeader.tsx
new file mode 100644
index 000000000..9c1ca3b2f
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Staking/CourtHeader.tsx
@@ -0,0 +1,111 @@
+import React from "react";
+import styled from "styled-components";
+import CourtHeaderSvg from "tsx:assets/svgs/mini-guides/staking/court-header.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledCourtHeaderSvg = styled(CourtHeaderSvg)`
+ [class$="circle-1"] {
+ fill: ${({ theme }) => theme.successLight};
+ }
+
+ [class$="circle-2"],
+ [class$="circle-3"],
+ [class$="circle-4"] {
+ fill: ${({ theme }) => theme.mediumPurple};
+ }
+
+ [class$="line-1"],
+ [class$="line-2"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-1"],
+ [class$="rect-5"],
+ [class$="path-2"],
+ [class$="rect-3"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="rect-2"],
+ [class$="rect-3"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-4"],
+ [class$="path-3"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="rect-7"],
+ [class$="rect-8"],
+ [class$="rect-9"],
+ [class$="rect-10"],
+ [class$="rect-11"],
+ [class$="rect-12"],
+ [class$="rect-13"] {
+ fill: ${({ theme }) => theme.white};
+ }
+
+ [class$="rect-6"] {
+ stroke: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="path-4"],
+ [class$="path-5"],
+ [class$="path-8"],
+ [class$="path-11"],
+ [class$="path-14"],
+ [class$="path-21"],
+ [class$="path-22"],
+ [class$="path-23"],
+ [class$="path-26"],
+ [class$="path-29"],
+ [class$="path-32"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+
+ [class$="path-1"],
+ [class$="path-7"],
+ [class$="path-10"],
+ [class$="path-13"],
+ [class$="path-15"],
+ [class$="path-16"],
+ [class$="path-17"],
+ [class$="path-18"],
+ [class$="path-19"],
+ [class$="path-20"],
+ [class$="path-24"],
+ [class$="path-25"],
+ [class$="path-27"],
+ [class$="path-28"],
+ [class$="path-30"],
+ [class$="path-31"],
+ [class$="path-33"],
+ [class$="path-34"] {
+ fill: ${({ theme }) => theme.secondaryText};
+ }
+
+ [class$="path-6"],
+ [class$="path-9"],
+ [class$="path-12"],
+ [class$="path-36"],
+ [class$="path-37"],
+ [class$="path-38"],
+ [class$="path-39"],
+ [class$="path-40"],
+ [class$="path-41"],
+ [class$="path-42"],
+ [class$="path-43"],
+ [class$="path-44"],
+ [class$="path-45"] {
+ fill: ${({ theme }) => theme.secondaryPurple};
+ }
+
+ [class$="path-35"] {
+ fill: ${({ theme }) => theme.success};
+ }
+`;
+
+const CourtHeader: React.FC = () => ;
+
+export default CourtHeader;
diff --git a/web/src/components/Popup/MiniGuides/Staking/JurorRewards.tsx b/web/src/components/Popup/MiniGuides/Staking/JurorRewards.tsx
new file mode 100644
index 000000000..4e0453f1c
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Staking/JurorRewards.tsx
@@ -0,0 +1,53 @@
+import React from "react";
+import styled from "styled-components";
+import JurorRewardsSvg from "tsx:assets/svgs/mini-guides/staking/juror-rewards.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledJurorRewardsSvg = styled(JurorRewardsSvg)`
+ [class$="rect-1"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+ [class$="rect-2"],
+ [class$="path-2"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+ [class$="rect-3"],
+ [class$="rect-4"],
+ [class$="rect-5"],
+ [class$="rect-6"],
+ [class$="rect-7"],
+ [class$="mask-1"] {
+ fill: ${({ theme }) => theme.white};
+ }
+ [class$="stop-1"],
+ [class$="stop-4"],
+ [class$="stop-6"] {
+ stop-color: ${({ theme }) => theme.secondaryPurple};
+ }
+ [class$="stop-2"],
+ [class$="stop-3"],
+ [class$="stop-5"] {
+ stop-color: ${({ theme }) => theme.primaryBlue};
+ }
+ [class$="path-3"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+ [class$="path-5"],
+ [class$="path-6"],
+ [class$="path-9"],
+ [class$="path-13"],
+ [class$="path-14"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+ [class$="path-7"],
+ [class$="path-8"],
+ [class$="path-10"],
+ [class$="path-11"],
+ [class$="path-12"] {
+ fill: ${({ theme }) => theme.secondaryText};
+ }
+`;
+
+const JurorRewards: React.FC = () => ;
+
+export default JurorRewards;
diff --git a/web/src/components/Popup/MiniGuides/Staking/Notifications.tsx b/web/src/components/Popup/MiniGuides/Staking/Notifications.tsx
new file mode 100644
index 000000000..7b49e91dc
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Staking/Notifications.tsx
@@ -0,0 +1,48 @@
+import React from "react";
+import styled from "styled-components";
+import NotificationsSvg from "tsx:assets/svgs/mini-guides/staking/notifications.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledNotificationsSvg = styled(NotificationsSvg)`
+ [class$="rect-1"],
+ [class$="rect-5"],
+ [class$="rect-6"],
+ [class$="path-5"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="rect-2"],
+ [class$="line-1"],
+ [class$="rect-3"],
+ [class$="rect-5"],
+ [class$="rect-6"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-4"],
+ [class$="rect-7"],
+ [class$="path-7"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="line-2"] {
+ stroke: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="path-1"] {
+ fill: ${({ theme }) => theme.secondaryText};
+ }
+
+ [class$="path-2"],
+ [class$="path-3"],
+ [class$="path-4"],
+ [class$="path-6"],
+ [class$="path-8"],
+ [class$="path-9"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+`;
+
+const Notifications: React.FC = () => ;
+
+export default Notifications;
diff --git a/web/src/components/Popup/MiniGuides/Staking/StakingSection.tsx b/web/src/components/Popup/MiniGuides/Staking/StakingSection.tsx
new file mode 100644
index 000000000..9bf708aac
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Staking/StakingSection.tsx
@@ -0,0 +1,51 @@
+import React from "react";
+import styled from "styled-components";
+import StakingSectionSvg from "tsx:assets/svgs/mini-guides/staking/staking-section.svg";
+import { StyledImage } from "../PageContentsTemplate";
+
+const StyledStakingSectionSvg = styled(StakingSectionSvg)`
+ [class$="rect-1"],
+ [class$="path-2"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="rect-2"],
+ [class$="rect-3"] {
+ stroke: ${({ theme }) => theme.stroke};
+ }
+
+ [class$="rect-3"] {
+ fill: ${({ theme }) => theme.whiteBackground};
+ }
+
+ [class$="rect-4"] {
+ fill: ${({ theme }) => theme.primaryBlue};
+ }
+
+ [class$="rect-5"],
+ [class$="rect-6"] {
+ fill: ${({ theme }) => theme.white};
+ }
+
+ [class$="path-1"],
+ [class$="path-6"],
+ [class$="path-9"] {
+ fill: ${({ theme }) => theme.secondaryText};
+ }
+
+ [class$="path-3"],
+ [class$="path-4"],
+ [class$="path-7"],
+ [class$="path-10"] {
+ fill: ${({ theme }) => theme.primaryText};
+ }
+
+ [class$="path-5"],
+ [class$="path-8"] {
+ fill: ${({ theme }) => theme.secondaryPurple};
+ }
+`;
+
+const StakingSection: React.FC = () => ;
+
+export default StakingSection;
diff --git a/web/src/components/Popup/MiniGuides/Staking/index.tsx b/web/src/components/Popup/MiniGuides/Staking/index.tsx
new file mode 100644
index 000000000..8bca34e2f
--- /dev/null
+++ b/web/src/components/Popup/MiniGuides/Staking/index.tsx
@@ -0,0 +1,65 @@
+import React from "react";
+import CourtHeader from "./CourtHeader";
+import JurorRewards from "./JurorRewards";
+import Notifications from "./Notifications";
+import StakingSection from "./StakingSection";
+import PageContentsTemplate from "../PageContentsTemplate";
+
+const leftPageContents = [
+ {
+ title: "Joining a court",
+ paragraphs: [
+ "Candidates self-select to serve as jurors by staking PNK tokens. The probability of being drawn as a juror" +
+ " for a specific dispute is proportional to the amount of tokens a juror stakes." +
+ " Check the ‘Juror Odds’ for an estimation of your chances to be drawn. Note that staking PNK in a court" +
+ " automatically stakes in its parent courts" +
+ " (Branch). You can stake in a maximum of 4 court branches.",
+ ],
+ },
+ {
+ title: "Jury selection",
+ paragraphs: [
+ "The final selection of jurors is done randomly. When a juror is selected to arbitrate a case part" +
+ " of his stake (PNK) is locked until the case is resolved. Jurors whose vote is coherent with the final" +
+ " jury decision have their locked stake released. Jurors whose vote is not coherent with the final jury" +
+ " decision, lose their locked stake. The locked stake of incoherent jurors is redistributed as incentives" +
+ " for the coherent jurors.",
+ ],
+ },
+ {
+ title: "Juror Rewards",
+ paragraphs: [
+ "Users have an economic interest in serving as jurors in Kleros: collecting the Juror Rewards in exchange" +
+ " for their work. Each juror who is coherent with the final ruling receive the Juror Rewards composed of" +
+ " arbitration fees (ETH) + PNK redistribution between jurors.",
+ ],
+ },
+ {
+ title: "Subscribe to Notifications",
+ paragraphs: [
+ "After staking your PNK to join a court, we advise you to subscribe to receive email notifications." +
+ " This guarantees you will be notified when drawn to arbitrate a case and won't miss the deadlines.",
+ ],
+ },
+];
+
+const rightPageComponents = [CourtHeader, StakingSection, JurorRewards, Notifications];
+
+interface IStaking {
+ toggleMiniGuide: () => void;
+}
+
+const Staking: React.FC = ({ toggleMiniGuide }) => {
+ return (
+
+ );
+};
+
+export default Staking;
diff --git a/web/src/components/Popup/index.tsx b/web/src/components/Popup/index.tsx
index 6fb3977d3..0b1edfe64 100644
--- a/web/src/components/Popup/index.tsx
+++ b/web/src/components/Popup/index.tsx
@@ -52,7 +52,7 @@ const Container = styled.div`
flex-direction: column;
align-items: center;
justify-content: center;
- width: 82vw;
+ width: 86vw;
max-width: 600px;
border-radius: 3px;
border: 1px solid ${({ theme }) => theme.stroke};
diff --git a/web/src/components/StyledSkeleton.tsx b/web/src/components/StyledSkeleton.tsx
index 0d695e0dc..913a350a6 100644
--- a/web/src/components/StyledSkeleton.tsx
+++ b/web/src/components/StyledSkeleton.tsx
@@ -31,6 +31,11 @@ const StyledSkeletonDisputeListItem = styled(Skeleton)`
height: 62px;
`;
+const StyledSkeletonEvidenceCard = styled(Skeleton)`
+ height: 146px;
+ width: 76vw;
+`;
+
export const SkeletonDisputeCard = () => (
@@ -38,3 +43,5 @@ export const SkeletonDisputeCard = () => (
);
export const SkeletonDisputeListItem = () => ;
+
+export const SkeletonEvidenceCard = () => ;
diff --git a/web/src/hooks/queries/useJurorStakeDetailsQuery.ts b/web/src/hooks/queries/useJurorStakeDetailsQuery.ts
new file mode 100644
index 000000000..7de7effb5
--- /dev/null
+++ b/web/src/hooks/queries/useJurorStakeDetailsQuery.ts
@@ -0,0 +1,28 @@
+import { graphql } from "src/graphql";
+import { JurorStakeDetailsQuery } from "src/graphql/graphql";
+import { useQuery } from "@tanstack/react-query";
+import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper";
+export type { JurorStakeDetailsQuery };
+
+const jurorStakeDetailsQuery = graphql(`
+ query JurorStakeDetails($userId: String) {
+ jurorTokensPerCourts(where: { juror: $userId }) {
+ court {
+ id
+ name
+ }
+ staked
+ locked
+ }
+ }
+`);
+
+export const useJurorStakeDetailsQuery = (userId?: string) => {
+ const isEnabled = userId !== undefined;
+
+ return useQuery({
+ queryKey: ["refetchOnBlock", `jurorStakeDetails${userId}`],
+ enabled: isEnabled,
+ queryFn: async () => await graphqlQueryFnHelper(jurorStakeDetailsQuery, { userId }),
+ });
+};
diff --git a/web/src/layout/Header/navbar/DappList.tsx b/web/src/layout/Header/navbar/DappList.tsx
index 143ee4312..9aca796d5 100644
--- a/web/src/layout/Header/navbar/DappList.tsx
+++ b/web/src/layout/Header/navbar/DappList.tsx
@@ -33,7 +33,7 @@ const Container = styled.div`
flex-direction: column;
align-items: center;
- width: 82%;
+ width: 86vw;
max-width: 480px;
min-width: 300px;
border-radius: 3px;
diff --git a/web/src/layout/Header/navbar/Menu/Help.tsx b/web/src/layout/Header/navbar/Menu/Help.tsx
index 320ec29c7..93a9445c2 100644
--- a/web/src/layout/Header/navbar/Menu/Help.tsx
+++ b/web/src/layout/Header/navbar/Menu/Help.tsx
@@ -1,6 +1,7 @@
import React, { useRef } from "react";
import styled, { css } from "styled-components";
import { landscapeStyle } from "styles/landscapeStyle";
+import { useToggle } from "react-use";
import { useFocusOutside } from "hooks/useFocusOutside";
import Book from "svgs/icons/book-open.svg";
import Guide from "svgs/icons/book.svg";
@@ -9,6 +10,8 @@ import ETH from "svgs/icons/eth.svg";
import Faq from "svgs/menu-icons/help.svg";
import Telegram from "svgs/socialmedia/telegram.svg";
import { IHelp } from "..";
+import Debug from "../Debug";
+import Onboarding from "components/Popup/MiniGuides/Onboarding";
const Container = styled.div`
display: flex;
@@ -16,7 +19,7 @@ const Container = styled.div`
position: absolute;
max-height: 80vh;
overflow-y: auto;
- width: 82%;
+ width: 86vw;
max-width: 444px;
top: 5%;
left: 50%;
@@ -32,7 +35,7 @@ const Container = styled.div`
${landscapeStyle(
() => css`
margin-top: 64px;
- width: 240px;
+ width: 260px;
top: 0;
right: 0;
left: auto;
@@ -69,7 +72,6 @@ const ITEMS = [
{
text: "Onboarding",
Icon: Book,
- url: "",
},
{
text: "Get Help",
@@ -99,20 +101,31 @@ const ITEMS = [
];
const Help: React.FC = ({ toggleIsHelpOpen }) => {
+ const [isOnboardingMiniGuidesOpen, toggleIsOnboardingMiniGuidesOpen] = useToggle(false);
+
const containerRef = useRef(null);
useFocusOutside(containerRef, () => {
- toggleIsHelpOpen();
+ if (!isOnboardingMiniGuidesOpen) toggleIsHelpOpen();
});
return (
-
- {ITEMS.map((item) => (
-
-
- {item.text}
-
- ))}
-
+ <>
+
+ {ITEMS.map((item, index) => (
+ toggleIsOnboardingMiniGuidesOpen() : undefined}
+ >
+
+ {item.text}
+
+ ))}
+
+
+ {isOnboardingMiniGuidesOpen && }
+ >
);
};
export default Help;
diff --git a/web/src/layout/Header/navbar/Menu/Settings/index.tsx b/web/src/layout/Header/navbar/Menu/Settings/index.tsx
index 0e21a1e8d..0466aead9 100644
--- a/web/src/layout/Header/navbar/Menu/Settings/index.tsx
+++ b/web/src/layout/Header/navbar/Menu/Settings/index.tsx
@@ -45,7 +45,7 @@ const StyledSettingsText = styled.div`
const StyledTabs = styled(Tabs)`
padding: 0 calc(8px + (32 - 8) * ((100vw - 300px) / (1250 - 300)));
- width: 82vw;
+ width: 86vw;
max-width: 660px;
${landscapeStyle(
diff --git a/web/src/layout/Header/navbar/index.tsx b/web/src/layout/Header/navbar/index.tsx
index 5faf54147..fc7205d52 100644
--- a/web/src/layout/Header/navbar/index.tsx
+++ b/web/src/layout/Header/navbar/index.tsx
@@ -11,7 +11,6 @@ import LightButton from "components/LightButton";
import { Overlay } from "components/Overlay";
import KlerosSolutionsIcon from "svgs/menu-icons/kleros-solutions.svg";
import Menu from "./Menu";
-import Debug from "./Debug";
import Help from "./Menu/Help";
import Settings from "./Menu/Settings";
import { DisconnectWalletButton } from "./Menu/Settings/General";
@@ -97,7 +96,6 @@ const NavBar: React.FC = () => {
-
{(isDappListOpen || isHelpOpen || isSettingsOpen) && (
diff --git a/web/src/pages/Cases/CaseDetails/Appeal/AppealHistory.tsx b/web/src/pages/Cases/CaseDetails/Appeal/AppealHistory.tsx
index 3902fe9f4..591b5aaae 100644
--- a/web/src/pages/Cases/CaseDetails/Appeal/AppealHistory.tsx
+++ b/web/src/pages/Cases/CaseDetails/Appeal/AppealHistory.tsx
@@ -1,7 +1,10 @@
import React from "react";
import styled from "styled-components";
-import { useOptionsContext, useFundingContext } from "hooks/useClassicAppealContext";
import OptionCard from "./OptionCard";
+import HowItWorks from "components/HowItWorks";
+import Appeal from "components/Popup/MiniGuides/Appeal";
+import { AppealHeader, StyledTitle } from ".";
+import { useOptionsContext, useFundingContext } from "hooks/useClassicAppealContext";
const OptionsContainer = styled.div`
display: flex;
@@ -10,13 +13,25 @@ const OptionsContainer = styled.div`
margin-top: 12px;
`;
-const AppealHistory: React.FC = () => {
+interface IAppealHistory {
+ isAppealMiniGuideOpen: boolean;
+ toggleAppealMiniGuide: () => void;
+}
+
+const AppealHistory: React.FC = ({ isAppealMiniGuideOpen, toggleAppealMiniGuide }) => {
const options = useOptionsContext();
const { winningChoice, paidFees, fundedChoices } = useFundingContext();
return (
-
Appeal Results - Last Round
+
+ Appeal Results - Last Round
+
+
{options ? (
options.map((option, index) => {
diff --git a/web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx b/web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx
index c5b0a67b2..06f1495f4 100644
--- a/web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx
+++ b/web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx
@@ -3,10 +3,18 @@ import { useOptionsContext, useSelectedOptionContext } from "hooks/useClassicApp
import Options from "./Options";
import Fund from "./Fund";
import Popup, { PopupType } from "components/Popup";
+import Appeal from "components/Popup/MiniGuides/Appeal";
+import HowItWorks from "components/HowItWorks";
import AppealIcon from "svgs/icons/appeal.svg";
import { isUndefined } from "utils/index";
+import { AppealHeader, StyledTitle } from "..";
-const Classic: React.FC = () => {
+interface IClassic {
+ isAppealMiniGuideOpen: boolean;
+ toggleAppealMiniGuide: () => void;
+}
+
+const Classic: React.FC = ({ isAppealMiniGuideOpen, toggleAppealMiniGuide }) => {
const [isPopupOpen, setIsPopupOpen] = useState(false);
const [amount, setAmount] = useState("");
const { selectedOption } = useSelectedOptionContext();
@@ -25,7 +33,14 @@ const Classic: React.FC = () => {
amount={amount}
/>
)}
- Appeal crowdfunding
+
+ Appeal crowdfunding
+
+
diff --git a/web/src/pages/Cases/CaseDetails/Appeal/index.tsx b/web/src/pages/Cases/CaseDetails/Appeal/index.tsx
index cdf1629e8..16a7afa49 100644
--- a/web/src/pages/Cases/CaseDetails/Appeal/index.tsx
+++ b/web/src/pages/Cases/CaseDetails/Appeal/index.tsx
@@ -1,5 +1,7 @@
import React from "react";
-import styled from "styled-components";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { useToggle } from "react-use";
import Classic from "./Classic";
import { Periods } from "consts/periods";
import AppealHistory from "./AppealHistory";
@@ -9,10 +11,37 @@ const Container = styled.div`
padding: calc(16px + (32 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
`;
+export const AppealHeader = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 24px;
+ gap: 12px;
+
+ ${landscapeStyle(
+ () => css`
+ flex-direction: row;
+ `
+ )}
+`;
+
+export const StyledTitle = styled.h1`
+ margin: 0;
+`;
+
const Appeal: React.FC<{ currentPeriodIndex: number }> = ({ currentPeriodIndex }) => {
+ const [isAppealMiniGuideOpen, toggleAppealMiniGuide] = useToggle(false);
+
return (
- {Periods.appeal === currentPeriodIndex ? : }
+
+ {Periods.appeal === currentPeriodIndex ? (
+
+ ) : (
+
+ )}
+
);
};
diff --git a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx
index ea06d40da..2e5cd12b1 100644
--- a/web/src/pages/Cases/CaseDetails/Evidence/index.tsx
+++ b/web/src/pages/Cases/CaseDetails/Evidence/index.tsx
@@ -6,6 +6,7 @@ import { Button, Searchbar } from "@kleros/ui-components-library";
import { useEvidenceGroup } from "queries/useEvidenceGroup";
import { useEvidences } from "queries/useEvidences";
import SubmitEvidenceModal from "./SubmitEvidenceModal";
+import { SkeletonEvidenceCard } from "components/StyledSkeleton";
import EvidenceCard from "components/EvidenceCard";
import { EnsureChain } from "components/EnsureChain";
import { isUndefined } from "utils/index";
@@ -24,6 +25,12 @@ const StyledButton = styled(Button)`
align-self: flex-end;
`;
+const StyledLabel = styled.label`
+ display: flex;
+ margin-top: 16px;
+ font-size: 16px;
+`;
+
const Evidence: React.FC<{ arbitrable?: `0x${string}` }> = ({ arbitrable }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const { id } = useParams();
@@ -33,25 +40,27 @@ const Evidence: React.FC<{ arbitrable?: `0x${string}` }> = ({ arbitrable }) => {
return (
- <>
- {!isUndefined(evidenceGroup) && (
- setIsModalOpen(false)} {...{ evidenceGroup }} />
- )}
-
-
- setIsModalOpen(true)}
- />
-
- {data &&
- data.evidences.map(({ key, evidence, sender }, i) => (
-
- ))}
- >
+ {!isUndefined(evidenceGroup) && (
+ setIsModalOpen(false)} {...{ evidenceGroup }} />
+ )}
+
+
+ setIsModalOpen(true)}
+ />
+
+ {data ? (
+ data.evidences.map(({ key, evidence, sender }, i) => (
+
+ ))
+ ) : (
+
+ )}
+ {data && data.evidences.length === 0 ? There is no evidence submitted yet : null}
);
};
diff --git a/web/src/pages/Courts/CourtDetails/index.tsx b/web/src/pages/Courts/CourtDetails/index.tsx
index bd6de5dcf..f7b3c34f8 100644
--- a/web/src/pages/Courts/CourtDetails/index.tsx
+++ b/web/src/pages/Courts/CourtDetails/index.tsx
@@ -1,5 +1,7 @@
import React, { useState } from "react";
-import styled from "styled-components";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { useToggle } from "react-use";
import { useParams } from "react-router-dom";
import { useAccount, useNetwork, useWalletClient, usePublicClient } from "wagmi";
import { Card, Breadcrumb, Button } from "@kleros/ui-components-library";
@@ -15,14 +17,45 @@ import LatestCases from "components/LatestCases";
import Stats from "./Stats";
import Description from "./Description";
import StakePanel from "./StakePanel";
+import HowItWorks from "components/HowItWorks";
+import Staking from "components/Popup/MiniGuides/Staking";
import { usePnkFaucetWithdrewAlready, prepareWritePnkFaucet, usePnkBalanceOf } from "hooks/contracts/generated";
const Container = styled.div``;
+const CourtHeader = styled.h1`
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ gap: 24px;
+ flex-wrap: wrap;
+`;
+
+const CourtInfo = styled.div`
+ display: flex:
+ flex-direction: column;
+ gap: 16px;
+
+ ${landscapeStyle(
+ () => css`
+ gap: 32px;
+ `
+ )};
+`;
+
const ButtonContainer = styled.div`
display: flex;
flex-wrap: wrap;
- justify-content: space-between;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 16px;
+
+ ${landscapeStyle(
+ () => css`
+ align-items: flex-end;
+ gap: 32px;
+ `
+ )};
`;
const StyledCard = styled(Card)`
@@ -34,13 +67,13 @@ const StyledCard = styled(Card)`
`;
const StyledBreadcrumb = styled(Breadcrumb)`
- margin-bottom: 12px;
display: flex;
- align-items: flex-start;
+ margin-top: 12px;
+ align-items: center;
`;
const StyledBreadcrumbSkeleton = styled.div`
- margin-bottom: 12px;
+ margin-top: 12px;
`;
const CourtDetails: React.FC = () => {
@@ -55,6 +88,7 @@ const CourtDetails: React.FC = () => {
args: [address ?? "0x00"],
watch: true,
});
+ const [isStakingMiniGuideOpen, toggleStakingMiniGuide] = useToggle(false);
const faucetAddress = usePNKFaucetAddress();
const { data: balance } = usePnkBalanceOf({
@@ -89,25 +123,34 @@ const CourtDetails: React.FC = () => {
return (
- {policy ? policy.name : }
-
- {items.length > 1 ? (
-
- ) : (
-
-
-
- )}
- {chain?.id === DEFAULT_CHAIN && !claimed && (
-
+
diff --git a/web/src/pages/Dashboard/Courts/CourtCard.tsx b/web/src/pages/Dashboard/Courts/CourtCard.tsx
deleted file mode 100644
index c199929e7..000000000
--- a/web/src/pages/Dashboard/Courts/CourtCard.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from "react";
-import styled from "styled-components";
-import { useAccount } from "wagmi";
-import { formatUnits } from "viem";
-import { Card as _Card, Breadcrumb } from "@kleros/ui-components-library";
-import WithHelpTooltip from "../WithHelpTooltip";
-import { isUndefined } from "utils/index";
-import { useKlerosCoreGetJurorBalance } from "hooks/contracts/generated";
-
-const Card = styled(_Card)`
- height: auto;
- width: 100%;
- padding: 12px 24px;
- border-left: 5px solid ${({ theme }) => theme.secondaryPurple};
-`;
-
-const ValueContainer = styled.div`
- width: 100%;
- display: flex;
- align-items: center;
- justify-content: space-between;
-`;
-
-const StyledBreadcrumb = styled(Breadcrumb)`
- margin-bottom: 12px;
-`;
-
-const tooltipMsg =
- "When a juror is selected to arbitrate a case, part of their stake (PNK) is " +
- "locked until the case is resolved. Jurors whose vote is coherent with the " +
- "final jury decision have their locked stake released. Jurors whose vote " +
- "is not coherent with the final jury decision, lose their locked stake. " +
- "The locked stake of incoherent jurors is redistributed as incentives for " +
- "the coherent jurors.";
-
-interface ICourtCard {
- id: string;
- name: string;
-}
-
-const CourtCard: React.FC = ({ id, name }) => {
- const { address } = useAccount();
- const { data: jurorBalance } = useKlerosCoreGetJurorBalance({
- enabled: !isUndefined(address),
- args: [address!, BigInt(id)],
- watch: true,
- });
-
- const stake = jurorBalance?.[0] ?? BigInt(0);
- const lockedStake = jurorBalance?.[1] ?? BigInt(0);
- const formatedStake = formatUnits(stake, 18);
- const formatedLockedStake = formatUnits(lockedStake, 18);
-
- return stake > 0 || lockedStake > 0 ? (
-
-
-
-
- {`${formatedStake} PNK`}
-
-
-
-
-
- {`${formatedLockedStake} PNK`}
-
-
- ) : null;
-};
-
-export default CourtCard;
diff --git a/web/src/pages/Dashboard/Courts/CourtCard/CourtBranch.tsx b/web/src/pages/Dashboard/Courts/CourtCard/CourtBranch.tsx
new file mode 100644
index 000000000..7c5d2dbb2
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/CourtCard/CourtBranch.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { Breadcrumb } from "@kleros/ui-components-library";
+
+const Container = styled.div`
+ width: 100%;
+ justify-content: flex-start;
+
+ small {
+ height: 100%;
+ }
+
+ ${landscapeStyle(
+ () =>
+ css`
+ width: auto;
+ `
+ )}
+`;
+
+const StyledBreadcrumb = styled(Breadcrumb)`
+ display: flex;
+ align-items: center;
+ height: 100%;
+`;
+
+interface ICourtBranch {
+ name: string;
+}
+
+const CourtBranch: React.FC = ({ name }) => {
+ return (
+
+
+
+ );
+};
+export default CourtBranch;
diff --git a/web/src/pages/Dashboard/Courts/CourtCard/DesktopCard.tsx b/web/src/pages/Dashboard/Courts/CourtCard/DesktopCard.tsx
new file mode 100644
index 000000000..cb6414b8c
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/CourtCard/DesktopCard.tsx
@@ -0,0 +1,58 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { Card as _Card } from "@kleros/ui-components-library";
+import CourtBranch from "./CourtBranch";
+import Stake from "./Stake";
+import LockedStake from "./LockedStake";
+
+const Container = styled(_Card)`
+ display: none;
+
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ height: auto;
+ width: 100%;
+ padding: 21.5px 32px 21.5px 27px;
+ border-left: 5px solid ${({ theme }) => theme.secondaryPurple};
+ flex-wrap: wrap;
+ gap: 20px;
+
+ ${({ theme }) => (theme.name === "light" ? `box-shadow: 0px 2px 3px 0px ${theme.stroke};` : "")}
+
+ ${landscapeStyle(
+ () =>
+ css`
+ display: flex;
+ `
+ )}
+`;
+
+const StakesContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ gap: 32px;
+`;
+
+interface IDesktopCard {
+ name: string;
+ stake: bigint;
+ lockedStake: bigint;
+}
+
+const DesktopCard: React.FC = ({ name, stake, lockedStake }) => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default DesktopCard;
diff --git a/web/src/pages/Dashboard/Courts/CourtCard/LockedStake.tsx b/web/src/pages/Dashboard/Courts/CourtCard/LockedStake.tsx
new file mode 100644
index 000000000..ef0458566
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/CourtCard/LockedStake.tsx
@@ -0,0 +1,29 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { formatUnits } from "viem";
+
+const StyledLabel = styled.label`
+ display: flex;
+ justify-content: flex-end;
+ color: ${({ theme }) => theme.primaryText};
+ font-size: 16px;
+ text-align: right;
+
+ ${landscapeStyle(
+ () => css`
+ min-width: 107px;
+ `
+ )}
+`;
+
+interface ILockedStake {
+ lockedStake: bigint;
+}
+
+const LockedStake: React.FC = ({ lockedStake }) => {
+ const formattedLockedStake = formatUnits(lockedStake, 18);
+
+ return {`${formattedLockedStake} PNK`};
+};
+export default LockedStake;
diff --git a/web/src/pages/Dashboard/Courts/CourtCard/MobileCard.tsx b/web/src/pages/Dashboard/Courts/CourtCard/MobileCard.tsx
new file mode 100644
index 000000000..f113b5964
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/CourtCard/MobileCard.tsx
@@ -0,0 +1,79 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { Card as _Card } from "@kleros/ui-components-library";
+import CourtBranch from "./CourtBranch";
+import HeaderStake from "../Header/Stake";
+import HeaderLockedStake from "../Header/LockedStake";
+import Stake from "./Stake";
+import LockedStake from "./LockedStake";
+
+const Container = styled(_Card)`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ height: auto;
+ width: 100%;
+ padding: 24px;
+ border-left: 5px solid ${({ theme }) => theme.secondaryPurple};
+ flex-wrap: wrap;
+ gap: 18px;
+
+ ${({ theme }) => (theme.name === "light" ? `box-shadow: 0px 2px 3px 0px ${theme.stroke};` : "")}
+
+ ${landscapeStyle(
+ () =>
+ css`
+ display: none;
+ `
+ )}
+`;
+
+const BottomSide = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+`;
+
+const HeaderStakeAndStake = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ gap: 5px;
+ justify-content: flex-start;
+`;
+
+const HeaderLockedStakeAndLockedStake = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ gap: 5px;
+ justify-content: flex-end;
+`;
+
+interface IMobileCard {
+ name: string;
+ stake: bigint;
+ lockedStake: bigint;
+}
+
+const MobileCard: React.FC = ({ name, stake, lockedStake }) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default MobileCard;
diff --git a/web/src/pages/Dashboard/Courts/CourtCard/Stake.tsx b/web/src/pages/Dashboard/Courts/CourtCard/Stake.tsx
new file mode 100644
index 000000000..29052973d
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/CourtCard/Stake.tsx
@@ -0,0 +1,28 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { formatUnits } from "viem";
+
+const StyledLabel = styled.label`
+ display: flex;
+ font-weight: 600;
+ color: ${({ theme }) => theme.primaryText};
+ font-size: 16px;
+
+ ${landscapeStyle(
+ () => css`
+ justify-content: flex-end;
+ `
+ )}
+`;
+
+interface IStake {
+ stake: bigint;
+}
+
+const Stake: React.FC = ({ stake }) => {
+ const formattedStake = formatUnits(stake, 18);
+
+ return {`${formattedStake} PNK`};
+};
+export default Stake;
diff --git a/web/src/pages/Dashboard/Courts/CourtCard/index.tsx b/web/src/pages/Dashboard/Courts/CourtCard/index.tsx
new file mode 100644
index 000000000..d707c9c1d
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/CourtCard/index.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+import DesktopCard from "./DesktopCard";
+import MobileCard from "./MobileCard";
+
+interface ICourtCard {
+ name: string;
+ stake: bigint;
+ lockedStake: bigint;
+}
+
+const CourtCard: React.FC = ({ name, stake, lockedStake }) => {
+ const allProps = { name, stake, lockedStake };
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default CourtCard;
diff --git a/web/src/pages/Dashboard/Courts/Header/CourtBranch.tsx b/web/src/pages/Dashboard/Courts/Header/CourtBranch.tsx
new file mode 100644
index 000000000..b3666599c
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/Header/CourtBranch.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+const CourtBranch: React.FC = () => {
+ return ;
+};
+
+export default CourtBranch;
diff --git a/web/src/pages/Dashboard/Courts/Header/LockedStake.tsx b/web/src/pages/Dashboard/Courts/Header/LockedStake.tsx
new file mode 100644
index 000000000..55619d278
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/Header/LockedStake.tsx
@@ -0,0 +1,49 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import WithHelpTooltip from "pages/Dashboard/WithHelpTooltip";
+
+const Container = styled.div`
+ display: flex;
+ align-items: center;
+ width: 100%;
+ justify-content: flex-end;
+
+ ${landscapeStyle(
+ () =>
+ css`
+ width: 110px;
+ `
+ )}
+`;
+
+const StyledLockedStakeLabel = styled.label`
+ display: flex;
+ font-size: 12px !important;
+
+ ${landscapeStyle(
+ () =>
+ css`
+ font-size: 14px !important;
+ `
+ )}
+`;
+
+const lockedStakeTooltipMsg =
+ "When a juror is selected to arbitrate a case, part of their stake (PNK) is " +
+ "locked until the case is resolved. Jurors whose vote is coherent with the " +
+ "final jury decision have their locked stake released. Jurors whose vote " +
+ "is not coherent with the final jury decision, lose their locked stake. " +
+ "The locked stake of incoherent jurors is redistributed as incentives for " +
+ "the coherent jurors.";
+
+const LockedStake: React.FC = () => {
+ return (
+
+
+ Locked Stake
+
+
+ );
+};
+export default LockedStake;
diff --git a/web/src/pages/Dashboard/Courts/Header/Stake.tsx b/web/src/pages/Dashboard/Courts/Header/Stake.tsx
new file mode 100644
index 000000000..5897407a2
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/Header/Stake.tsx
@@ -0,0 +1,20 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+
+const StyledLabel = styled.label`
+ display: flex;
+ font-size: 12px !important;
+
+ ${landscapeStyle(
+ () =>
+ css`
+ font-size: 14px !important;
+ `
+ )}
+`;
+
+const Stake: React.FC = () => {
+ return Stake;
+};
+export default Stake;
diff --git a/web/src/pages/Dashboard/Courts/Header/index.tsx b/web/src/pages/Dashboard/Courts/Header/index.tsx
new file mode 100644
index 000000000..e58e3e14a
--- /dev/null
+++ b/web/src/pages/Dashboard/Courts/Header/index.tsx
@@ -0,0 +1,62 @@
+import React from "react";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import CourtBranch from "./CourtBranch";
+import Stake from "./Stake";
+import LockedStake from "./LockedStake";
+
+const Container = styled.div`
+ display: none;
+
+ ${landscapeStyle(
+ () =>
+ css`
+ display: flex;
+ flex-direction: column;
+ `
+ )}
+`;
+
+const CourtBranchAndStakesContainer = styled.div`
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ height: 100%;
+ background-color: transparent;
+ padding: 24px;
+ flex-wrap: wrap;
+ padding: 23.15px 32px;
+`;
+
+const StakesContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ gap: 32px;
+`;
+
+const Divider = styled.hr`
+ display: flex;
+ border: none;
+ height: 1px;
+ background-color: ${({ theme }) => theme.stroke};
+ margin: 0;
+`;
+
+const Header: React.FC = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Header;
diff --git a/web/src/pages/Dashboard/Courts/index.tsx b/web/src/pages/Dashboard/Courts/index.tsx
index f82427133..186eb34bb 100644
--- a/web/src/pages/Dashboard/Courts/index.tsx
+++ b/web/src/pages/Dashboard/Courts/index.tsx
@@ -1,10 +1,11 @@
import React from "react";
-import styled from "styled-components";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
import { useAccount } from "wagmi";
-import { useFragment as getFragment } from "src/graphql";
-import { useUserQuery, userFragment } from "queries/useUser";
-import { isUndefined } from "utils/index";
+import Skeleton from "react-loading-skeleton";
import CourtCard from "./CourtCard";
+import Header from "./Header";
+import { useJurorStakeDetailsQuery } from "queries/useJurorStakeDetailsQuery";
const Container = styled.div`
margin-top: 64px;
@@ -14,29 +15,46 @@ const Container = styled.div`
}
`;
-const CourtsContainer = styled.div`
+const CourtCardsContainer = styled.div`
display: flex;
flex-direction: column;
- gap: 8px;
+ gap: 12px;
z-index: 0;
+
+ ${landscapeStyle(
+ () => css`
+ gap: 16px;
+ `
+ )}
+`;
+
+const StyledLabel = styled.label`
+ font-size: 16px;
`;
const Courts: React.FC = () => {
const { address } = useAccount();
- const { data } = useUserQuery(address?.toLowerCase() as `0x${string}`);
- const user = getFragment(userFragment, data?.user);
+ const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(address?.toLowerCase() as `0x${string}`);
+ const stakedCourts = stakeData?.jurorTokensPerCourts?.filter(({ staked, locked }) => staked > 0 || locked > 0);
+ const isStaked = stakedCourts && stakedCourts.length > 0;
return (
My Courts
- {!isUndefined(data) ?
: null}
-
- {!isUndefined(data)
- ? user?.tokens?.map(({ court: { id, name } }) => {
- return ;
- })
- : null}
-
+ {isLoading ? : null}
+ {!isStaked && !isLoading ? You are not staked in any court : null}
+ {isStaked && !isLoading ? (
+ <>
+
+
+ {stakeData?.jurorTokensPerCourts
+ ?.filter(({ staked, locked }) => staked > 0 || locked > 0)
+ .map(({ court: { id, name }, staked, locked }) => (
+
+ ))}
+
+ >
+ ) : null}
);
};
diff --git a/web/src/pages/Dashboard/JurorInfo/Header.tsx b/web/src/pages/Dashboard/JurorInfo/Header.tsx
index a3eb04a09..4e401131c 100644
--- a/web/src/pages/Dashboard/JurorInfo/Header.tsx
+++ b/web/src/pages/Dashboard/JurorInfo/Header.tsx
@@ -1,22 +1,41 @@
import React from "react";
-import styled from "styled-components";
+import styled, { css } from "styled-components";
+import { landscapeStyle } from "styles/landscapeStyle";
+import { useToggle } from "react-use";
import XIcon from "svgs/socialmedia/x.svg";
+import HowItWorks from "components/HowItWorks";
+import JurorLevels from "components/Popup/MiniGuides/JurorLevels";
const Container = styled.div`
display: flex;
- flex-direction: row;
+ flex-direction: column;
align-items: center;
justify-content: space-between;
+
+ ${landscapeStyle(
+ () => css`
+ flex-direction: row;
+ `
+ )}
`;
const StyledTitle = styled.h1`
margin-bottom: calc(16px + (48 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
`;
-const XLinkContainer = styled.div`
+const LinksContainer = styled.div`
display: flex;
color: ${({ theme }) => theme.primaryBlue};
margin-bottom: calc(16px + (48 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
+ align-items: center;
+ gap: 24px;
+ flex-wrap: wrap;
+
+ ${landscapeStyle(
+ () => css`
+ gap: 32px;
+ `
+ )}
`;
const StyledXIcon = styled(XIcon)`
@@ -44,6 +63,8 @@ interface IHeader {
}
const Header: React.FC = ({ levelTitle, levelNumber, totalCoherent, totalResolvedDisputes }) => {
+ const [isJurorLevelsMiniGuideOpen, toggleJurorLevelsMiniGuide] = useToggle(false);
+
const coherencePercentage = parseFloat(((totalCoherent / Math.max(totalResolvedDisputes, 1)) * 100).toFixed(2));
const courtUrl = window.location.origin;
const xPostText = `Hey I've been busy as a Juror on the Kleros court, check out my score: \n\nLevel: ${levelNumber} (${levelTitle})\nCoherence Percentage: ${coherencePercentage}%\nCoherent Votes: ${totalCoherent}/${totalResolvedDisputes}\n\nBe a juror with me! ➡️ ${courtUrl}`;
@@ -52,11 +73,18 @@ const Header: React.FC = ({ levelTitle, levelNumber, totalCoherent, tot
return (
Juror Dashboard
-
-
- Share your juror score
-
-
+
+
+ {totalResolvedDisputes > 0 ? (
+
+ Share your juror score
+
+ ) : null}
+
);
};
diff --git a/web/src/pages/Dashboard/WithHelpTooltip.tsx b/web/src/pages/Dashboard/WithHelpTooltip.tsx
index 3a4fddb39..395b0ea66 100644
--- a/web/src/pages/Dashboard/WithHelpTooltip.tsx
+++ b/web/src/pages/Dashboard/WithHelpTooltip.tsx
@@ -10,10 +10,12 @@ const Container = styled.div`
`;
const HelpIcon = styled(_HelpIcon)`
+ display: flex;
+ align-items: center;
height: 12px;
width: 12px;
fill: ${({ theme }) => theme.secondaryText};
- margin: 4px 4px 6px 8px;
+ margin: 0 0 0 8px;
${landscapeStyle(
() => css`
diff --git a/web/src/pages/Home/TopJurors/Header/Rewards.tsx b/web/src/pages/Home/TopJurors/Header/Rewards.tsx
index 02b062e87..6f55edfae 100644
--- a/web/src/pages/Home/TopJurors/Header/Rewards.tsx
+++ b/web/src/pages/Home/TopJurors/Header/Rewards.tsx
@@ -5,12 +5,13 @@ import WithHelpTooltip from "pages/Dashboard/WithHelpTooltip";
const Container = styled.div`
display: flex;
+ color: ${({ theme }) => theme.secondaryText};
+ gap: 0px;
+
font-size: 12px !important;
&::before {
content: "Rewards";
}
- color: ${({ theme }) => theme.secondaryText};
- align-items: center;
${landscapeStyle(
() =>
diff --git a/web/src/pages/Home/TopJurors/Header/index.tsx b/web/src/pages/Home/TopJurors/Header/index.tsx
index 005681e04..5a061ad3c 100644
--- a/web/src/pages/Home/TopJurors/Header/index.tsx
+++ b/web/src/pages/Home/TopJurors/Header/index.tsx
@@ -1,11 +1,13 @@
import React from "react";
import styled, { css } from "styled-components";
import { landscapeStyle } from "styles/landscapeStyle";
+import { useToggle } from "react-use";
import Rank from "./Rank";
import JurorTitle from "./JurorTitle";
import Rewards from "./Rewards";
import Coherency from "./Coherency";
-import HowItWorks from "./HowItWorks";
+import HowItWorks from "components/HowItWorks";
+import JurorLevels from "components/Popup/MiniGuides/JurorLevels";
const Container = styled.div`
display: flex;
@@ -52,6 +54,8 @@ const StyledLabel = styled.label`
`;
const Header: React.FC = () => {
+ const [isJurorLevelsMiniGuideOpen, toggleJurorLevelsMiniGuide] = useToggle(false);
+
return (
Ranking
@@ -61,7 +65,11 @@ const Header: React.FC = () => {
- toggleIsLevelMiniGuidesOpen()} />
+
);
};
diff --git a/web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx b/web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx
index 910c5595b..479006212 100644
--- a/web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx
+++ b/web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx
@@ -19,7 +19,7 @@ const Container = styled.div`
border 1px solid ${({ theme }) => theme.stroke};
border-top: none;
align-items: center;
- gap: 20px;
+ gap: 18px;
${landscapeStyle(
() => css`
@@ -47,6 +47,7 @@ const HeaderRewardsAndRewards = styled.div`
display: flex;
flex-direction: column;
width: 100%;
+ gap: 5px;
`;
const BottomSide = styled.div`
@@ -60,6 +61,7 @@ const HeaderCoherencyAndCoherency = styled.div`
display: flex;
flex-direction: column;
align-items: flex-end;
+ gap: 3px;
svg {
margin-right: 0;