Skip to content

Commit

Permalink
Merge pull request #25 from Agoric/pc/ui-add-prettier
Browse files Browse the repository at this point in the history
feat: ui improvements
  • Loading branch information
0xpatrickdev authored Feb 5, 2024
2 parents 1f74994 + ed60c87 commit 91e461d
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 221 deletions.
57 changes: 52 additions & 5 deletions ui/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,61 @@
border-radius: 10%;
}

.want {
border-collapse: collapse;
.coin {
width: 2em;
margin: 10px;
}

.want td {
border: 1px solid;
.trade {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: #171717;
border-radius: 25px;
margin-bottom: 15px;
}

.item-col {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 15px 25px 15px;
margin: 5px;
}

.row-center {
display: flex;
flex-direction: row;
align-items: center;
}

input {
border: none;
background: #242424;
text-align: center;
padding: 5px 10px;
border-radius: 15px;
font-size: 1.2rem;
width: 75px;
}

@media (prefers-color-scheme: light) {
.trade {
background: #fafafa;
border: 1px solid #e5e5e5;
}
input {
background: #e5e5e5;
}
}

.error {
background-color: red;
background-color: #E11D48;
color: #fff;
}

/* increment/decrement arrows always visible */
input[type=number]::-webkit-inner-spin-button {
opacity: 1
}
229 changes: 17 additions & 212 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import { FormEvent, useEffect, useState } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import agoricLogo from '/agoric.svg';
import scrollIcon from './assets/scroll.png';
import mapIcon from './assets/map.png';
import potionIcon from './assets/potionBlue.png';
import { useEffect } from 'react';

import './App.css';
import {
makeAgoricChainStorageWatcher,
Expand All @@ -16,16 +11,12 @@ import {
suggestChain,
} from '@agoric/web-components';
import { subscribeLatest } from '@agoric/notifier';
import { stringifyAmountValue } from '@agoric/ui-components';
import { makeCopyBag } from '@agoric/store';
import { Logos } from './components/Logos';
import { Inventory } from './components/Inventory';
import { Trade } from './components/Trade';

const { entries, fromEntries, keys, values } = Object;
const sum = (xs: bigint[]) => xs.reduce((acc, next) => acc + next, 0n);

const terms = {
price: 250000n,
maxItems: 3n,
};
const { entries, fromEntries } = Object;

type Wallet = Awaited<ReturnType<typeof makeAgoricWalletConnection>>;

Expand All @@ -36,23 +27,6 @@ const ENDPOINTS = {

const watcher = makeAgoricChainStorageWatcher(ENDPOINTS.API, 'agoriclocal');

interface CopyBag<T = string> {
payload: Array<[T, bigint]>;
}

interface Purse {
brand: unknown;
brandPetname: string;
currentAmount: {
brand: unknown;
value: bigint | CopyBag;
};
displayInfo: {
decimalPlaces: number;
assetKind: unknown;
};
}

interface AppState {
wallet?: Wallet;
offerUpInstance?: unknown;
Expand Down Expand Up @@ -127,20 +101,6 @@ const makeOffer = (giveValue: bigint, wantChoices: Record<string, bigint>) => {
);
};

const nameToIcon = {
scroll: scrollIcon,
map: mapIcon,
potion: potionIcon,
} as const;
type ItemName = keyof typeof nameToIcon;
type ItemChoices = Partial<Record<ItemName, bigint>>;

const parseValue = (numeral: string, purse: Purse): bigint => {
const { decimalPlaces } = purse.displayInfo;
const num = Number(numeral) * 10 ** decimalPlaces;
return BigInt(num);
};

function App() {
useEffect(() => {
setup();
Expand All @@ -167,179 +127,24 @@ function App() {
});
};

const Logos = () => (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
<a href="https://agoric.com/develop" target="_blank">
<img src={agoricLogo} className="logo agoric" alt="Agoric logo" />
</a>
</div>
</>
);

const Inventory = () =>
wallet &&
istPurse && (
<div className="card">
<h3>My Wallet</h3>
<div>
<div>
<small>
<code>{wallet.address}</code>
</small>
</div>

<div style={{ textAlign: 'left' }}>
<div>
<b>IST: </b>
{stringifyAmountValue(
istPurse.currentAmount,
istPurse.displayInfo.assetKind,
istPurse.displayInfo.decimalPlaces,
)}
</div>
<div>
<b>Items:</b>
{itemsPurse ? (
<ul style={{ marginTop: 0, textAlign: 'left' }}>
{(itemsPurse.currentAmount.value as CopyBag).payload.map(
([name, number]) => (
<li key={name}>
{String(number)} {name}
</li>
),
)}
</ul>
) : (
'None'
)}
</div>
</div>
</div>
</div>
);

// XXX giveValue, choices state should be scoped to Trade component.
const [giveValue, setGiveValue] = useState(terms.price);
const renderGiveValue = (purse: Purse) => (
<input
type="number"
min="0"
value={stringifyAmountValue(
{ ...purse.currentAmount, value: giveValue },
purse.displayInfo.assetKind,
purse.displayInfo.decimalPlaces,
)}
onChange={ev => setGiveValue(parseValue(ev?.target?.value, purse))}
className={giveValue >= terms.price ? 'ok' : 'error'}
step="0.01"
/>
);

const [choices, setChoices] = useState<ItemChoices>({ map: 1n, scroll: 2n });
const changeChoice = (ev: FormEvent) => {
if (!ev.target) return;
const elt = ev.target as HTMLInputElement;
const title = elt.title as ItemName;
if (!title) return;
const qty = BigInt(elt.value);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { [title]: _old, ...rest }: ItemChoices = choices;
const newChoices = qty > 0 ? { ...rest, [title]: qty } : rest;
setChoices(newChoices);
};

const WantItems = () => (
<>
<thead>
<tr>
<th colSpan={keys(nameToIcon).length}>Want: Choose up to 3 items</th>
</tr>
</thead>
<tbody className="want">
<tr>
{entries(nameToIcon).map(([title, icon]) => (
<td key={title}>
<img className="piece" src={icon} title={title} />
</td>
))}
</tr>

<tr>
{keys(nameToIcon).map(title => (
<td key={title}>
<input
title={title}
type="number"
min="0"
max="3"
value={Number(choices[title as ItemName])}
step="1"
onChange={changeChoice}
className={
sum(values(choices)) <= terms.maxItems ? 'ok' : 'error'
}
/>
<br />
{title}
</td>
))}
</tr>
</tbody>
</>
);

// TODO: don't wait for connect wallet to show Give.
// IST displayInfo is available in vbankAsset or boardAux
const Trade = () => (
<>
<table className="want">
<WantItems />
{istPurse && (
<>
<thead>
<tr>
<th colSpan={keys(nameToIcon).length}>
Give: Offer at least 0.25 IST
</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>{renderGiveValue(istPurse)}</td>
<td>IST</td>
</tr>
</tbody>
</>
)}
</table>
<div>
{wallet && (
<button onClick={() => makeOffer(giveValue, choices)}>
Make an Offer
</button>
)}
</div>
</>
);

return (
<>
<Logos />
<h1>Items Listed on Offer Up</h1>

<div className="card">
<Trade />
<Trade
makeOffer={makeOffer}
istPurse={istPurse as Purse}
walletConnected={!!wallet}
/>
<hr />
{wallet ? (
<Inventory />
{wallet && istPurse ? (
<Inventory
address={wallet.address}
istPurse={istPurse}
itemsPurse={itemsPurse as Purse}
/>
) : (
<button onClick={tryConnectWallet}>Connect Wallet</button>
)}
Expand Down
19 changes: 19 additions & 0 deletions ui/src/assets/IST.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions ui/src/components/Inventory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { stringifyAmountValue } from '@agoric/ui-components';

type InventoryProps = {
address: string;
istPurse: Purse;
itemsPurse: Purse;
};

const Inventory = ({ address, istPurse, itemsPurse }: InventoryProps) => (
<div className="card">
<h3>My Wallet</h3>
<div>
<div>
<small>
<code>{address}</code>
</small>
</div>

<div style={{ textAlign: 'left' }}>
<div>
<b>IST: </b>
{stringifyAmountValue(
istPurse.currentAmount,
istPurse.displayInfo.assetKind,
istPurse.displayInfo.decimalPlaces,
)}
</div>
<div>
<b>Items:</b>
{itemsPurse ? (
<ul style={{ marginTop: 0, textAlign: 'left' }}>
{(itemsPurse.currentAmount.value as CopyBag).payload.map(
([name, number]) => (
<li key={name}>
{String(number)} {name}
</li>
),
)}
</ul>
) : (
'None'
)}
</div>
</div>
</div>
</div>
);

export { Inventory };
Loading

0 comments on commit 91e461d

Please sign in to comment.