Skip to content

feat: create transaction #7

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

Draft
wants to merge 42 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 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
5f8c828
feat: add new contract ABIs for arbitrable address and token lists, a…
kyrers Jun 22, 2025
a413314
feat: add New page for creating transactions
kyrers Jun 22, 2025
a791e08
feat: implement form for creating transactions
kyrers Jun 22, 2025
9d7230e
feat: add hook for fetching tokens from registry
kyrers Jun 22, 2025
e8f347d
feat: enhance CreateTransactionWizard UI and improve responsiveness
kyrers Jun 23, 2025
ea2f1b3
feat: add validation to transaction form fields
kyrers Jun 24, 2025
52049e2
feat: add NewTransaction context and provider for new transaction inp…
kyrers Jun 24, 2025
621db5d
refactor: remove unused contract ABIs and related code for fetching t…
kyrers Jun 27, 2025
c967995
feat: add predefined escrow tokens data
kyrers Jun 27, 2025
e32a78e
feat: implement TokenSelector component for improved token selection …
kyrers Jun 28, 2025
658061f
feat: add Alchemy API integration for fetching token metadata
kyrers Jun 29, 2025
6f9f367
feat: add custom ERC20 token, by address, for escrow
kyrers Jun 29, 2025
daa5a5c
refactor: reset user-added tokens on chain change
kyrers Jun 29, 2025
23896f9
feat: display escrow type in transaction details
kyrers Jun 30, 2025
5d1b378
refactor: improved transaction details display
kyrers Jun 30, 2025
39f13e0
feat: improved transaction deadline date management
kyrers Jun 30, 2025
928b0ab
refactor: Improved responsiveness and component usability
kyrers Jun 30, 2025
bdb7d4c
feat: make ETH the default token for new escrow transactions
kyrers Jul 1, 2025
60b356b
feat: improve transaction wizard, namely file upload and amount fields
kyrers Jul 1, 2025
e5fe37c
feat: add Preview step to transaction wizard
kyrers Jul 1, 2025
c5f5d2b
fix: update token selection logic to use token address instead of name
kyrers Jul 1, 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
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
VITE_REOWN_PROJECT_ID=YOUR_PROJECT_ID
VITE_ALCHEMY_API_KEY=YOUR_ALCHEMY_API_KEY
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
```
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
"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",
"alchemy-sdk": "^3.6.1",
"react": "^18.2.55",
"react-dom": "^18.2.55",
"react-router": "^7.6.0",
Expand All @@ -26,6 +28,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
9 changes: 8 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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";
import New from "pages/New/New";

function App() {
return (
Expand All @@ -10,6 +12,11 @@ function App() {
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route
path="/transaction/:contractAddress/:id"
element={<Transaction />}
/>
<Route path="/new" element={<New />} />
</Route>
</Routes>
</BrowserRouter>
Expand Down
Binary file added src/assets/aave.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/arb.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/crypto-transaction.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/dai.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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.
Binary file added src/assets/escrow-type.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/eth.png
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.
Binary file added src/assets/general-service.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/gno.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/pnk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/pol.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/uni.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/unknowntoken.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/usdc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/usdt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/weth.png
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/components/Common/Containers/DefaultPageContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from "styled-components";

export const DefaultPageContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
gap: 16px;
align-items: center;
overflow-y: auto;
padding: 8px 16px;
`;
5 changes: 5 additions & 0 deletions src/components/Common/Dividers/DefaultDivider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import styled from "styled-components";

export const DefaultDivider = styled.hr`
border-bottom: 1px solid ${({ theme }) => theme.colors.primaryBlue};
`;
16 changes: 16 additions & 0 deletions src/components/Common/Form/StyledDisplaySmall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import styled from "styled-components";
import { DisplaySmall } from "@kleros/ui-components-library";

export const StyledDisplaySmall = styled(DisplaySmall)`
overflow: hidden;
white-space: nowrap;
height: fit-content;

label {
font-weight: bold;
}

div {
margin-top: 0;
}
`;
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};
`;
62 changes: 62 additions & 0 deletions src/components/CreateTransactionWizard/CreateTransactionWizard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Steps } from "@kleros/ui-components-library";
import { useState } from "react";
import styled from "styled-components";
import Template from "./Template/Template";
import Details from "./Details/Details";
import Terms from "./Terms/Terms";
import Preview from "./Preview/Preview";

const STEPS = [
{ title: "Escrow type" },
{ title: "Details" },
{ title: "Terms" },
{ title: "Preview" },
];

const Container = styled.div`
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;

@media (max-height: ${({ theme }) => theme.breakpoints.md}) {
height: unset;
}
`;

const StyledSteps = styled(Steps)`
position: absolute;
left: 4%;
top: 50%;
transform: translateY(-50%);
height: 200px;

@media (max-width: ${({ theme }) => theme.breakpoints.lg}) {
display: none;
}
`;

export default function CreateTransactionWizard() {
const [currentStep, setCurrentStep] = useState<number>(0);

const next = () => {
setCurrentStep(currentStep + 1);
};

const back = () => {
setCurrentStep(currentStep - 1);
};

return (
<Container>
<StyledSteps items={STEPS} currentItemIndex={currentStep} />

{currentStep === 0 && <Template next={next} />}
{currentStep === 1 && <Details next={next} back={back} />}
{currentStep === 2 && <Terms next={next} back={back} />}
{currentStep === 3 && <Preview back={back} />}
</Container>
);
}
114 changes: 114 additions & 0 deletions src/components/CreateTransactionWizard/Details/Details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Button, NumberField, TextField } from "@kleros/ui-components-library";
import { useNewTransactionContext } from "context/newTransaction/useNewTransactionContext";
import styled from "styled-components";
import { validateAddress } from "utils/common";
import {
ButtonContainer,
mobileResponsive,
StyledForm,
} from "../StyledForm/StyledForm";
import TokenSelector from "./TokenSelector/TokenSelector";

const StyledTextField = styled(TextField)`
width: 500px;

${mobileResponsive}
`;

const AmountAndTokenContainer = styled.div`
display: flex;
width: 500px;
gap: 16px;

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

const StyledNumberField = styled(NumberField)`
width: 242px;

${mobileResponsive}
`;

interface Props {
next: () => void;
back: () => void;
}

export default function Details({ next, back }: Props) {
const {
title,
setTitle,
receiverAddress,
setReceiverAddress,
amount,
setAmount,
} = useNewTransactionContext();

const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
next();
};

return (
<StyledForm onSubmit={handleSubmit}>
<StyledTextField
value={title}
onChange={(value) => setTitle(value)}
isRequired
label="Title"
name="title"
placeholder="Title of the escrow transaction"
type="text"
validate={(value) => (value.length > 0 ? true : "Title is required")}
showFieldError
/>

<StyledTextField
value={receiverAddress}
onChange={(value) => setReceiverAddress(value)}
isRequired
label="Receiver"
name="receiver"
placeholder="ETH address of the funds receiver"
type="text"
maxLength={42}
autoComplete="off"
validate={(value) =>
validateAddress(value) ? true : "Invalid ETH address"
}
showFieldError
/>

<AmountAndTokenContainer>
<StyledNumberField
value={amount}
onChange={(value) => setAmount(value)}
isRequired
label="Amount"
name="amount"
placeholder="Amount"
validate={(value) =>
value > 0 ? true : "Amount must be greater than 0"
}
showFieldError
formatOptions={{
//Prevent automatic rounding of very small amounts
minimumFractionDigits: 0,
maximumFractionDigits: 18,
}}
/>

<TokenSelector />
</AmountAndTokenContainer>

<ButtonContainer>
<Button small text="Back" onPress={back} />
<Button small text="Next" type="submit" />
</ButtonContainer>
</StyledForm>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useState } from "react";
import TokenSelectorModal from "./TokenSelectorModal/TokenSelectorModal";
import { useNewTransactionContext } from "context/newTransaction/useNewTransactionContext";
import { getEscrowTokens } from "config/tokens";
import { useAccount } from "wagmi";
import TokenSelectorButton from "./TokenSelectorButton/TokenSelectorButton";
import type { EscrowToken } from "model/EscrowToken";

export default function TokenSelector() {
const { token, setToken, userAddedTokens, setUserAddedTokens } =
useNewTransactionContext();
const { chain } = useAccount();
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const escrowTokens = getEscrowTokens(chain?.id ?? 1);
const allTokens = [...userAddedTokens, ...escrowTokens];

const handleSelectToken = (token: EscrowToken) => {
setToken(token);
setIsModalOpen(false);
};

const handleAddToken = (token: EscrowToken) => {
setUserAddedTokens([...userAddedTokens, token]);
};

return (
<>
<TokenSelectorButton
token={
allTokens.find((item) => item.address === token.address) ??
allTokens[0]
}
onClick={() => setIsModalOpen(true)}
/>

<TokenSelectorModal
isOpen={isModalOpen}
onClose={() => setIsModalOpen(false)}
handleSelectToken={handleSelectToken}
handleAddToken={handleAddToken}
escrowTokens={allTokens}
/>
</>
);
}
Loading