Skip to content

Commit 60495b2

Browse files
authored
Merge pull request #68 from kleros/feat/file-attachment-display
Feat/file attachment display
2 parents 9a56ba0 + 5473436 commit 60495b2

File tree

16 files changed

+774
-14
lines changed

16 files changed

+774
-14
lines changed

web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"typescript": "^5.3.3"
7171
},
7272
"dependencies": {
73+
"@cyntler/react-doc-viewer": "^1.16.3",
7374
"@filebase/client": "^0.0.5",
7475
"@kleros/ui-components-library": "^2.12.0",
7576
"@middy/core": "^5.3.5",

web/src/app.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Layout from "layout/index";
1313
import NewTransaction from "./pages/NewTransaction";
1414
import MyTransactions from "./pages/MyTransactions";
1515
import { NewTransactionProvider } from "./context/NewTransactionContext";
16+
import AttachmentDisplay from "./pages/AttachmentDisplay";
1617

1718
const App: React.FC = () => {
1819
return (
@@ -28,6 +29,7 @@ const App: React.FC = () => {
2829
<Route index element={<Navigate to="new-transaction" replace />} />
2930
<Route path="new-transaction/*" element={<NewTransaction />} />
3031
<Route path="transactions/*" element={<MyTransactions />} />
32+
<Route path="attachment/*" element={<AttachmentDisplay />} />
3133
<Route path="*" element={<h1>404 not found</h1>} />
3234
</Route>
3335
</SentryRoutes>
Lines changed: 10 additions & 0 deletions
Loading

web/src/assets/svgs/icons/new-tab.svg

Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 17 additions & 0 deletions
Loading
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react";
2+
import styled from "styled-components";
3+
4+
import { type DocRenderer } from "@cyntler/react-doc-viewer";
5+
import ReactMarkdown from "react-markdown";
6+
7+
const Container = styled.div`
8+
padding: 16px;
9+
`;
10+
11+
const StyledMarkdown = styled(ReactMarkdown)`
12+
background-color: ${({ theme }) => theme.whiteBackground};
13+
a {
14+
font-size: 16px;
15+
}
16+
code {
17+
color: ${({ theme }) => theme.secondaryText};
18+
}
19+
`;
20+
21+
const MarkdownRenderer: DocRenderer = ({ mainState: { currentDocument } }) => {
22+
if (!currentDocument) return null;
23+
const base64String = (currentDocument.fileData as string).split(",")[1];
24+
25+
// Decode the base64 string
26+
const decodedData = atob(base64String);
27+
28+
return (
29+
<Container id="md-renderer">
30+
<StyledMarkdown>{decodedData}</StyledMarkdown>
31+
</Container>
32+
);
33+
};
34+
35+
MarkdownRenderer.fileTypes = ["md", "text/plain"];
36+
MarkdownRenderer.weight = 1;
37+
38+
export default MarkdownRenderer;
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from "react";
2+
import styled from "styled-components";
3+
4+
import DocViewer, { DocViewerRenderers } from "@cyntler/react-doc-viewer";
5+
6+
import "@cyntler/react-doc-viewer/dist/index.css";
7+
8+
import MarkdownRenderer from "./Viewers/MarkdownViewer";
9+
import { customScrollbar } from "styles/customScrollbar";
10+
11+
const Wrapper = styled.div`
12+
background-color: ${({ theme }) => theme.whiteBackground};
13+
border-radius: 3px;
14+
box-shadow: 0px 2px 3px 0px rgba(0, 0, 0, 0.06);
15+
max-height: 1050px;
16+
overflow: scroll;
17+
18+
${customScrollbar}
19+
`;
20+
21+
const StyledDocViewer = styled(DocViewer)`
22+
background-color: ${({ theme }) => theme.whiteBackground} !important;
23+
`;
24+
25+
/**
26+
* @description this viewer supports loading multiple files, it can load urls, local files, etc
27+
* @param url The url of the file to be displayed
28+
* @returns renders the file
29+
*/
30+
const FileViewer: React.FC<{ url: string }> = ({ url }) => {
31+
const docs = [{ uri: url }];
32+
return (
33+
<Wrapper className="file-viewer-wrapper">
34+
<StyledDocViewer
35+
documents={docs}
36+
pluginRenderers={[...DocViewerRenderers, MarkdownRenderer]}
37+
config={{
38+
header: {
39+
disableHeader: true,
40+
disableFileName: true,
41+
},
42+
pdfZoom: {
43+
defaultZoom: 0.8,
44+
zoomJump: 0.1,
45+
},
46+
pdfVerticalScrollByDefault: true, // false as default
47+
}}
48+
/>
49+
</Wrapper>
50+
);
51+
};
52+
53+
export default FileViewer;

web/src/components/Loader.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React from "react";
2+
import styled, { type CSSProperties, keyframes } from "styled-components";
3+
4+
import KlerosIcon from "svgs/icons/kleros.svg";
5+
6+
type Width = CSSProperties["width"];
7+
type Height = CSSProperties["height"];
8+
9+
const breathing = keyframes`
10+
0% {
11+
transform: scale(1);
12+
}
13+
14+
50% {
15+
transform: scale(1.3);
16+
}
17+
18+
100% {
19+
transform: scale(1);
20+
}
21+
`;
22+
23+
const StyledKlerosIcon = styled(KlerosIcon)`
24+
path {
25+
fill: ${({ theme }) => theme.klerosUIComponentsStroke};
26+
}
27+
animation: ${breathing} 2s ease-out infinite normal;
28+
`;
29+
30+
const Container = styled.div<{ width?: Width; height?: Height }>`
31+
width: ${({ width }) => width ?? "100%"};
32+
height: ${({ height }) => height ?? "100%"};
33+
`;
34+
35+
interface ILoader {
36+
width?: Width;
37+
height?: Height;
38+
className?: string;
39+
}
40+
41+
const Loader: React.FC<ILoader> = ({ width, height, className }) => {
42+
return (
43+
<Container {...{ width, height, className }}>
44+
<StyledKlerosIcon />
45+
</Container>
46+
);
47+
};
48+
49+
export default Loader;

web/src/components/PreviewCard/Terms/AttachedFile.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import React from "react";
2+
import { Link } from "react-router-dom";
23
import styled from "styled-components";
3-
import { responsiveSize } from "styles/responsiveSize";
44
import AttachmentIcon from "svgs/icons/attachment.svg";
5+
import { responsiveSize } from "styles/responsiveSize";
56
import { getIpfsUrl } from "utils/getIpfsUrl";
67

7-
const StyledA = styled.a`
8+
const StyledA = styled(Link)`
89
display: flex;
910
gap: ${responsiveSize(5, 6)};
11+
1012
> svg {
1113
width: 16px;
1214
fill: ${({ theme }) => theme.primaryBlue};
@@ -18,10 +20,10 @@ interface IAttachedFile {
1820
}
1921

2022
const AttachedFile: React.FC<IAttachedFile> = ({ extraDescriptionUri }) => {
21-
const href = extraDescriptionUri && getIpfsUrl(extraDescriptionUri);
23+
const uri = extraDescriptionUri && getIpfsUrl(extraDescriptionUri);
2224

2325
return extraDescriptionUri ? (
24-
<StyledA href={href} target="_blank" rel="noreferrer">
26+
<StyledA to={`/attachment/?url=${uri}`}>
2527
<AttachmentIcon />
2628
View Attached File
2729
</StyledA>

web/src/layout/Header/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import DesktopHeader from "./DesktopHeader";
55

66
const Container = styled.div`
77
position: sticky;
8-
z-index: 1;
8+
z-index: 10;
99
top: 0;
1010
width: 100%;
1111
height: 64px;
@@ -33,4 +33,4 @@ const Header: React.FC = () => {
3333
);
3434
};
3535

36-
export default Header;
36+
export default Header;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from "react";
2+
import styled from "styled-components";
3+
4+
import { useNavigate } from "react-router-dom";
5+
6+
import { Button } from "@kleros/ui-components-library";
7+
8+
import Arrow from "svgs/icons/arrow-left.svg";
9+
import PaperClip from "svgs/icons/paperclip.svg";
10+
11+
import { responsiveSize } from "styles/responsiveSize";
12+
13+
const Container = styled.div`
14+
width: 100%;
15+
display: flex;
16+
justify-content: space-between;
17+
align-items: center;
18+
margin-bottom: 38px;
19+
`;
20+
21+
const TitleContainer = styled.div`
22+
display: flex;
23+
flex-direction: row;
24+
align-items: center;
25+
gap: 8px;
26+
`;
27+
28+
const Title = styled.h1`
29+
margin: 0px;
30+
font-size: ${responsiveSize(16, 24)};
31+
`;
32+
33+
const StyledPaperClip = styled(PaperClip)`
34+
width: ${responsiveSize(16, 24)};
35+
height: ${responsiveSize(16, 24)};
36+
path {
37+
fill: ${({ theme }) => theme.primaryPurple};
38+
}
39+
`;
40+
41+
const StyledButton = styled(Button)`
42+
background-color: transparent;
43+
padding: 0;
44+
.button-text {
45+
color: ${({ theme }) => theme.primaryBlue};
46+
font-weight: 400;
47+
}
48+
.button-svg {
49+
path {
50+
fill: ${({ theme }) => theme.primaryBlue};
51+
}
52+
}
53+
:focus,
54+
:hover {
55+
background-color: transparent;
56+
}
57+
`;
58+
59+
const Header: React.FC = () => {
60+
const navigate = useNavigate();
61+
62+
return (
63+
<Container>
64+
<TitleContainer>
65+
<StyledPaperClip />
66+
<Title>Attachment File</Title>{" "}
67+
</TitleContainer>
68+
<StyledButton text="Return" Icon={Arrow} onClick={() => navigate(-1)} />
69+
</Container>
70+
);
71+
};
72+
73+
export default Header;

0 commit comments

Comments
 (0)