Skip to content

Commit 8637c5e

Browse files
committed
feat(web): evidence-search
1 parent 2d23ed7 commit 8637c5e

File tree

5 files changed

+167
-63
lines changed

5 files changed

+167
-63
lines changed
Lines changed: 10 additions & 0 deletions
Loading

web/src/components/EvidenceCard.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,6 @@ const BottomShade = styled.div`
6262
}
6363
`;
6464

65-
const StyledA = styled.a`
66-
display: flex;
67-
margin-left: auto;
68-
gap: ${responsiveSize(5, 6)};
69-
${landscapeStyle(
70-
() => css`
71-
> svg {
72-
width: 16px;
73-
fill: ${({ theme }) => theme.primaryBlue};
74-
}
75-
`
76-
)}
77-
`;
78-
7965
const AccountContainer = styled.div`
8066
display: flex;
8167
flex-direction: row;

web/src/hooks/queries/useEvidences.ts

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,60 @@ import { useQuery } from "@tanstack/react-query";
33
import { useGraphqlBatcher } from "context/GraphqlBatcher";
44

55
import { graphql } from "src/graphql";
6-
import { EvidencesQuery } from "src/graphql/graphql";
6+
import { EvidenceDetailsFragment, EvidencesQuery } from "src/graphql/graphql";
77
export type { EvidencesQuery };
88

9+
export const evidenceFragment = graphql(`
10+
fragment EvidenceDetails on ClassicEvidence {
11+
id
12+
evidence
13+
sender {
14+
id
15+
}
16+
timestamp
17+
name
18+
description
19+
fileURI
20+
fileTypeExtension
21+
evidenceIndex
22+
}
23+
`);
24+
925
const evidencesQuery = graphql(`
1026
query Evidences($evidenceGroupID: String) {
11-
evidences(where: { evidenceGroup: $evidenceGroupID }, orderBy: timestamp, orderDirection: desc) {
12-
id
13-
evidence
14-
sender {
15-
id
16-
}
17-
timestamp
18-
name
19-
description
20-
fileURI
21-
fileTypeExtension
27+
evidences(where: { evidenceGroup: $evidenceGroupID }, orderBy: timestamp, orderDirection: asc) {
28+
...EvidenceDetails
2229
}
2330
}
2431
`);
2532

26-
export const useEvidences = (evidenceGroup?: string) => {
33+
const evidenceSearchQuery = graphql(`
34+
query EvidenceSearch($keywords: String!, $evidenceGroupID: String) {
35+
evidenceSearch(text: $keywords, where: { evidenceGroup: $evidenceGroupID }) {
36+
...EvidenceDetails
37+
}
38+
}
39+
`);
40+
41+
export const useEvidences = (evidenceGroup?: string, keywords?: string) => {
2742
const isEnabled = evidenceGroup !== undefined;
2843
const { graphqlBatcher } = useGraphqlBatcher();
2944

30-
return useQuery<EvidencesQuery>({
31-
queryKey: ["refetchOnBlock", `evidencesQuery${evidenceGroup}`],
45+
const document = keywords ? evidenceSearchQuery : evidencesQuery;
46+
return useQuery<{ evidences: EvidenceDetailsFragment[] }>({
47+
queryKey: [
48+
"refetchOnBlock",
49+
keywords ? `evidenceSearchQuery${evidenceGroup}-${keywords}` : `evidencesQuery${evidenceGroup}`,
50+
],
3251
enabled: isEnabled,
33-
queryFn: async () =>
34-
await graphqlBatcher.fetch({
52+
queryFn: async () => {
53+
const result = await graphqlBatcher.fetch({
3554
id: crypto.randomUUID(),
36-
document: evidencesQuery,
37-
variables: { evidenceGroupID: evidenceGroup?.toString() },
38-
}),
55+
document: document,
56+
variables: { evidenceGroupID: evidenceGroup?.toString(), keywords: keywords },
57+
});
58+
59+
return keywords ? { evidences: [...result.evidenceSearch] } : result;
60+
},
3961
});
4062
};
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React, { useState } from "react";
2+
import styled from "styled-components";
3+
4+
import { useAccount } from "wagmi";
5+
6+
import { Button, Searchbar } from "@kleros/ui-components-library";
7+
8+
import { isUndefined } from "src/utils";
9+
10+
import { responsiveSize } from "styles/responsiveSize";
11+
12+
import { EnsureChain } from "components/EnsureChain";
13+
14+
import SubmitEvidenceModal from "./SubmitEvidenceModal";
15+
16+
const SearchContainer = styled.div`
17+
width: 100%;
18+
display: flex;
19+
flex-wrap: wrap;
20+
align-items: center;
21+
gap: ${responsiveSize(16, 28)};
22+
`;
23+
24+
const StyledSearchBar = styled(Searchbar)`
25+
min-width: 220px;
26+
flex: 1;
27+
`;
28+
29+
const StyledButton = styled(Button)`
30+
align-self: flex-end;
31+
`;
32+
33+
interface IEvidenceSearch {
34+
search?: string;
35+
setSearch: (search: string) => void;
36+
evidenceGroup?: bigint;
37+
}
38+
39+
const EvidenceSearch: React.FC<IEvidenceSearch> = ({ search, setSearch, evidenceGroup }) => {
40+
const [isModalOpen, setIsModalOpen] = useState(false);
41+
const { address } = useAccount();
42+
43+
return (
44+
<>
45+
{!isUndefined(evidenceGroup) && (
46+
<SubmitEvidenceModal isOpen={isModalOpen} close={() => setIsModalOpen(false)} {...{ evidenceGroup }} />
47+
)}
48+
49+
<SearchContainer>
50+
<StyledSearchBar
51+
placeholder="Search evidence by number, word, or submitter."
52+
onChange={(e) => setSearch(e.target.value)}
53+
value={search}
54+
/>
55+
56+
<EnsureChain>
57+
<StyledButton
58+
text="Submit Evidence"
59+
disabled={typeof address === "undefined" || isModalOpen}
60+
isLoading={isModalOpen}
61+
onClick={() => setIsModalOpen(true)}
62+
/>
63+
</EnsureChain>
64+
</SearchContainer>
65+
</>
66+
);
67+
};
68+
69+
export default EvidenceSearch;

web/src/pages/Cases/CaseDetails/Evidence/index.tsx

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
1-
import React, { useState } from "react";
1+
import React, { useCallback, useRef, useState } from "react";
22
import styled from "styled-components";
33

44
import { useParams } from "react-router-dom";
5-
import { useAccount } from "wagmi";
5+
import { useDebounce } from "react-use";
66

7-
import { Button, Searchbar } from "@kleros/ui-components-library";
7+
import { Button } from "@kleros/ui-components-library";
88

9-
import { isUndefined } from "utils/index";
9+
import DownArrow from "svgs/icons/arrow-down.svg";
1010

1111
import { useEvidenceGroup } from "queries/useEvidenceGroup";
1212
import { useEvidences } from "queries/useEvidences";
1313

1414
import { responsiveSize } from "styles/responsiveSize";
1515

16-
import { EnsureChain } from "components/EnsureChain";
1716
import EvidenceCard from "components/EvidenceCard";
1817
import { SkeletonEvidenceCard } from "components/StyledSkeleton";
1918

20-
import SubmitEvidenceModal from "./SubmitEvidenceModal";
19+
import EvidenceSearch from "./EvidenceSearch";
2120

2221
const Container = styled.div`
2322
width: 100%;
@@ -29,43 +28,61 @@ const Container = styled.div`
2928
padding: ${responsiveSize(16, 32)};
3029
`;
3130

32-
const StyledButton = styled(Button)`
33-
align-self: flex-end;
34-
`;
35-
3631
const StyledLabel = styled.label`
3732
display: flex;
3833
margin-top: 16px;
3934
font-size: 16px;
4035
`;
4136

37+
const ScrollButton = styled(Button)`
38+
align-self: flex-end;
39+
background-color: transparent;
40+
padding: 0;
41+
flex-direction: row-reverse;
42+
margin: 0 0 18px;
43+
gap: 8px;
44+
.button-text {
45+
color: ${({ theme }) => theme.primaryBlue};
46+
font-weight: 400;
47+
}
48+
.button-svg {
49+
margin: 0;
50+
}
51+
:focus,
52+
:hover {
53+
background-color: transparent;
54+
}
55+
`;
56+
4257
const Evidence: React.FC<{ arbitrable?: `0x${string}` }> = ({ arbitrable }) => {
43-
const [isModalOpen, setIsModalOpen] = useState(false);
4458
const { id } = useParams();
4559
const { data: evidenceGroup } = useEvidenceGroup(id, arbitrable);
46-
const { data } = useEvidences(evidenceGroup?.toString());
47-
const { address } = useAccount();
60+
const ref = useRef<HTMLDivElement>(null);
61+
const [search, setSearch] = useState<string>();
62+
const [debouncedSearch, setDebouncedSearch] = useState<string>();
63+
64+
const { data } = useEvidences(evidenceGroup?.toString(), debouncedSearch);
65+
66+
useDebounce(() => setDebouncedSearch(search), 500, [search]);
67+
68+
const scrollToLatest = useCallback(() => {
69+
if (!ref.current) return;
70+
const latestEvidence = ref.current.lastElementChild;
71+
72+
if (!latestEvidence) return;
73+
74+
latestEvidence.scrollIntoView({ behavior: "smooth" });
75+
}, [ref]);
4876

4977
return (
50-
<Container>
51-
{!isUndefined(evidenceGroup) && (
52-
<SubmitEvidenceModal isOpen={isModalOpen} close={() => setIsModalOpen(false)} {...{ evidenceGroup }} />
53-
)}
54-
<Searchbar />
55-
<EnsureChain>
56-
<StyledButton
57-
small
58-
text="Submit Evidence"
59-
disabled={typeof address === "undefined" || isModalOpen}
60-
isLoading={isModalOpen}
61-
onClick={() => setIsModalOpen(true)}
62-
/>
63-
</EnsureChain>
78+
<Container ref={ref}>
79+
<EvidenceSearch {...{ search, setSearch, evidenceGroup }} />
80+
<ScrollButton small Icon={DownArrow} text="Scroll to latest" onClick={scrollToLatest} />
6481
{data ? (
65-
data.evidences.map(({ evidence, sender, timestamp, name, description, fileURI }, i) => (
82+
data.evidences.map(({ evidence, sender, timestamp, name, description, fileURI, evidenceIndex }) => (
6683
<EvidenceCard
6784
key={timestamp}
68-
index={i + 1}
85+
index={parseInt(evidenceIndex)}
6986
sender={sender?.id}
7087
{...{ evidence, timestamp, name, description, fileURI }}
7188
/>

0 commit comments

Comments
 (0)