Skip to content

feat: display connected address transactions #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ea6950e
feat: fetch connected address transactions
kyrers May 29, 2025
ac2dd39
chore: fix path aliases and some imports
kyrers May 30, 2025
a169df6
feat: fetch transaction block timestamp so we can display the creatio…
kyrers May 30, 2025
34b9cad
feat: order by last interaction
kyrers May 30, 2025
79d7921
feat: initial transaction card display
kyrers May 30, 2025
9e2437c
feat: styled transaction cards
kyrers Jun 2, 2025
5e8d63b
feat: implement transactions loading & no transactions UI state
kyrers Jun 3, 2025
14eff35
chore: remove .yarn/install-state.gz from git tracking
kyrers Jun 3, 2025
c0a68db
fix: improve transaction data fetching for cards
kyrers Jun 5, 2025
3ef6ad5
fix: ui adjustments for smaller screens
kyrers Jun 5, 2025
5d6c18f
feat: transaction details page initial UI
kyrers Jun 6, 2025
c08192d
feat: improved transaction details fetching and display
kyrers Jun 7, 2025
3f47b6e
feat: download and display transaction evidence
kyrers Jun 8, 2025
969449b
fix: correctly typed items for the CustomTimeline
kyrers Jun 9, 2025
a56dc03
chore: update kleros/ui-component-library to latest version
kyrers Jun 9, 2025
88ba52a
fix: minor code improvements
kyrers Jun 9, 2025
12c7305
fix: shorten queryFn bodies for fetching transaction info
kyrers Jun 12, 2025
5071329
feat: add wagmi/cli generated hooks
kyrers Jun 13, 2025
1afc53f
docs: update README
kyrers Jun 13, 2025
49c891e
feat: filter transactions by title or address
kyrers Jun 17, 2025
cde4460
fix: solve searchbar label warning
kyrers Jun 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
VITE_REOWN_PROJECT_ID=YOUR_PROJECT_ID
VITE_ETHEREUM_MAINNET_RPC=YOUR_MAINNET_RPC_URL
VITE_ETHREUM_SEPOLIA_RPC=YOUR_SEPOLIA_RPC_URL
VITE_ETHEREUM_SEPOLIA_RPC=YOUR_SEPOLIA_RPC_URL
VITE_IPFS_UPLOAD_URL=YOUR_IPFS_UPLOAD_URL
VITE_IPFS_GATEWAY_URL=YOUR_IPFS_GATEWAY_URL
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ dist-ssr

# Environment variables
.env

# Yarn
.yarn/install-state.gz
Binary file removed .yarn/install-state.gz
Binary file not shown.
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
# Escrow V1 UI

Local development:
For local development:

1. Install dependencies:

```bash
yarn install
```

2. Generate hooks using `wagmi/cli`:

```bash
yarn generate
```

3. Run:

```bash
yarn install && yarn dev
```
yarn dev
```
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
"preview": "vite preview",
"generate": "wagmi generate"
},
"dependencies": {
"@kleros/ui-components-library": "^3.3.4",
"@kleros/ui-components-library": "^3.4.5",
"@reown/appkit": "^1.7.6",
"@reown/appkit-adapter-wagmi": "^1.7.6",
"@tanstack/react-query": "^5.76.1",
Expand All @@ -26,6 +27,7 @@
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^4.4.1",
"@wagmi/cli": "^2.3.1",
"eslint": "^9.25.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
Expand Down
7 changes: 6 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { BrowserRouter, Routes, Route } from "react-router";
import { Providers } from "./providers";
import Layout from "layout/Layout";
import Home from "components/Home/Home";
import Home from "pages/Home/Home";
import Transaction from "pages/Transaction/Transaction";

function App() {
return (
Expand All @@ -10,6 +11,10 @@ function App() {
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route
path="/transaction/:contractAddress/:id"
element={<Transaction />}
/>
</Route>
</Routes>
</BrowserRouter>
Expand Down
3 changes: 3 additions & 0 deletions src/assets/doc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/assets/etherscan.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/components/Common/Skeleton/BaseSkeleton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import styled from "styled-components";

export const BaseSkeleton = styled.div`
animation: ${({ theme }) => theme.animations.loading};
background-color: ${({ theme }) => theme.colors.tintPurple};
`;
2 changes: 1 addition & 1 deletion src/components/Footer/KlerosLinks/KlerosLinks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IconButton } from "components/common/IconButton";
import { IconButton } from "components/Common/Buttons/IconButton";
import styled from "styled-components";
import Discord from "assets/discord.svg?react";
import Blog from "assets/blog.svg?react";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function ConnectButton() {
isDisabled={isOpen}
small
text={"Connect Wallet"}
onClick={async () => open({ view: "Connect" })}
onPress={async () => open({ view: "Connect" })}
/>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Button, Tag } from "@kleros/ui-components-library";
import { useState, useRef, useEffect } from "react";
import styled from "styled-components";
import { addressToShortString } from "utils/common";
import { useAccount, useDisconnect } from "wagmi";

const Container = styled.div`
Expand All @@ -10,6 +11,9 @@ const Container = styled.div`
const CustomTag = styled(Tag)`
font-weight: bold;
background-color: transparent;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;

&:hover {
opacity: 0.8;
Expand Down Expand Up @@ -66,9 +70,7 @@ export default function ConnectedMenu() {
<Container ref={containerRef}>
<CustomTag
active
text={`${chain?.name} | ${address?.slice(0, 6)}...${address?.slice(
-4
)}`}
text={`${chain?.name} | ${addressToShortString(address ?? "")}`}
onPress={() => setIsMenuOpen(!isMenuOpen)}
/>
{isMenuOpen && (
Expand Down
11 changes: 10 additions & 1 deletion src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import KlerosEscrowLogo from "assets/kleros.svg?react";
import ConnectWallet from "./ConnectWallet/ConnectWallet";
import ToggleTheme from "./ToggleTheme/ToggleTheme";
import Tutorial from "./Tutorial/Tutorial";
import { Link } from "react-router";

const Container = styled.header`
display: flex;
Expand All @@ -17,6 +18,12 @@ const Container = styled.header`
: theme.colors.primaryPurple};
padding: 8px 16px;
justify-content: space-between;

@media (max-width: ${({ theme }) => theme.breakpoints.xs}) {
height: 120px;
flex-direction: column;
align-items: center;
}
`;

const EscrowLogo = styled(KlerosEscrowLogo)`
Expand All @@ -33,7 +40,9 @@ const OptionsContainer = styled.div`
export default function Header() {
return (
<Container>
<EscrowLogo />
<Link to="/">
<EscrowLogo />
</Link>
<OptionsContainer>
<ConnectWallet />
<ToggleTheme />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Header/ToggleTheme/ToggleTheme.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Moon from "assets/moon.svg?react";
import Sun from "assets/sun.svg?react";
import { IconButton } from "components/common/IconButton";
import { IconButton } from "components/Common/Buttons/IconButton";
import { useThemeContext } from "context/theme/useThemeContext";

export default function ToggleTheme() {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Header/Tutorial/Tutorial.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IconButton } from "components/common/IconButton";
import { IconButton } from "components/Common/Buttons/IconButton";
import InfoCircleFull from "assets/info-circle-full.svg?react";

export default function Tutorial() {
Expand Down
7 changes: 0 additions & 7 deletions src/components/Home/Home.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { useTransactions } from "hooks/useTransactions";
import TransactionCard from "../TransactionCard/TransactionCard";
import styled from "styled-components";
import { BaseSkeleton } from "components/Common/Skeleton/BaseSkeleton";
import { useMemo, useState } from "react";
import { Searchbar } from "@kleros/ui-components-library";

const Container = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
width: 80%;
`;

const StyledSearchbar = styled(Searchbar)`
width: 100%;
`;

const CardContainer = styled.div`
display: grid;
grid-template-columns: repeat(
auto-fill,
minmax(min(100%, max(312px, (100% - 16px * 2)/3)), 1fr)
);
align-items: center;
gap: 16px;
`;

const SkeletonCard = styled(BaseSkeleton)`
border-radius: ${({ theme }) => theme.radius.base};
height: 200px;
`;

export default function DisplayTransactions() {
const { data: transactions, isFetching } = useTransactions();
const [search, setSearch] = useState<string>("");

//Allow users to filter transactions by title or address
const filteredTransactions = useMemo(() => {
if (!search) return transactions;

const searchLower = search.toLowerCase();
return transactions.filter(
(transaction) =>
transaction.metaEvidence.title.toLowerCase().includes(searchLower) ||
transaction.metaEvidence.sender.toLowerCase().includes(searchLower) ||
transaction.metaEvidence.receiver.toLowerCase().includes(searchLower)
);
}, [transactions, search]);

return (
<Container>
<StyledSearchbar
aria-label="Search by title or address"
placeholder="Search by title or address"
value={search}
onChange={(value) => setSearch(value)}
/>

<CardContainer>
{isFetching
? [...Array(9)].map((_, i) => <SkeletonCard key={i} />)
: filteredTransactions.map((transaction) => (
<TransactionCard
key={`${transaction.id}-${transaction.arbitrableAddress}`}
transaction={transaction}
/>
))}
</CardContainer>
</Container>
);
}
102 changes: 102 additions & 0 deletions src/components/Transactions/TransactionCard/TransactionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Card, Tag } from "@kleros/ui-components-library";
import { type TransactionMini } from "model/Transaction";
import { Link } from "react-router";
import styled from "styled-components";
import { addressToShortString } from "utils/common";
import { TransactionStatusTag } from "components/Transactions/TransactionStatusTag/TransactionStatusTag";

const StyledCard = styled(Card)`
display: flex;
flex-direction: column;
height: 200px;
overflow: hidden;

p {
font-size: 14px;
}

@media (max-width: ${({ theme }) => theme.breakpoints.xs}) {
height: 280px;
`;

const CardEdge = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
background-color: ${({ theme }) => theme.colors.tintPurple};
padding: 8px;
height: 40px;

@media (max-width: ${({ theme }) => theme.breakpoints.xs}) {
height: 80px;
flex-direction: column;
gap: 8px;
align-items: center;
}
`;

const CardBody = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px;
flex: 1;
`;

const Title = styled.p`
font-weight: bold;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
`;

const Description = styled.p`
display: -webkit-box;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
`;

const AmountTag = styled(Tag)`
pointer-events: none;
text-transform: capitalize;
font-weight: bold;
`;

interface Props {
transaction: TransactionMini;
}

export default function TransactionCard({ transaction }: Props) {
return (
<Link
to={`/transaction/${transaction.arbitrableAddress}/${transaction.id}`}
>
<StyledCard round hover className="w-[1/3]">
<CardEdge>
<TransactionStatusTag
active
status={transaction.status}
text={transaction.status}
/>
<AmountTag
active
text={`${transaction.userPartyLabel}: ${
transaction.metaEvidence.amount
} ${transaction.metaEvidence.token?.ticker ?? "ETH"}`}
/>
</CardEdge>
<CardBody>
<Title>{transaction.metaEvidence.title}</Title>
<Description>{transaction.metaEvidence.description}</Description>
</CardBody>
<CardEdge>
<Tag text={addressToShortString(transaction.otherPartyAddress)} />
<p>{transaction.createdAt}</p>
</CardEdge>
</StyledCard>
</Link>
);
}
Loading