Test page UI layout
This commit is contained in:
@@ -1,18 +1,14 @@
|
||||
import toast from "react-hot-toast";
|
||||
import { useSnapshot } from "valtio";
|
||||
import { ArrowSquareOut, Copy, Wallet, X } from "phosphor-react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, FC } from "react";
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
import Button from "./Button";
|
||||
import { addFaucetAccount, deployHook, importAccount } from "../state/actions";
|
||||
import state from "../state";
|
||||
import Box from "./Box";
|
||||
import Container from "./Container";
|
||||
import Heading from "./Heading";
|
||||
import Stack from "./Stack";
|
||||
import Text from "./Text";
|
||||
import Flex from "./Flex";
|
||||
import { Container, Heading, Stack, Text, Flex } from ".";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -40,13 +36,11 @@ const AccountDialog = ({
|
||||
}) => {
|
||||
const snap = useSnapshot(state);
|
||||
const [showSecret, setShowSecret] = useState(false);
|
||||
const activeAccount = snap.accounts.find(
|
||||
(account) => account.address === activeAccountAddress
|
||||
);
|
||||
const activeAccount = snap.accounts.find(account => account.address === activeAccountAddress);
|
||||
return (
|
||||
<Dialog
|
||||
open={Boolean(activeAccountAddress)}
|
||||
onOpenChange={(open) => {
|
||||
onOpenChange={open => {
|
||||
setShowSecret(false);
|
||||
!open && setActiveAccountAddress(null);
|
||||
}}
|
||||
@@ -141,7 +135,7 @@ const AccountDialog = ({
|
||||
}}
|
||||
ghost
|
||||
size="xs"
|
||||
onClick={() => setShowSecret((curr) => !curr)}
|
||||
onClick={() => setShowSecret(curr => !curr)}
|
||||
>
|
||||
{showSecret ? "Hide" : "Show"}
|
||||
</Button>
|
||||
@@ -187,11 +181,7 @@ const AccountDialog = ({
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
ghost
|
||||
css={{ color: "$green11 !important", mt: "$3" }}
|
||||
>
|
||||
<Button size="sm" ghost css={{ color: "$green11 !important", mt: "$3" }}>
|
||||
<ArrowSquareOut size="15px" />
|
||||
</Button>
|
||||
</a>
|
||||
@@ -207,10 +197,8 @@ const AccountDialog = ({
|
||||
>
|
||||
{activeAccount && activeAccount?.hooks?.length > 0
|
||||
? activeAccount?.hooks
|
||||
.map((i) => {
|
||||
return `${i?.substring(0, 6)}...${i?.substring(
|
||||
i.length - 4
|
||||
)}`;
|
||||
.map(i => {
|
||||
return `${i?.substring(0, 6)}...${i?.substring(i.length - 4)}`;
|
||||
})
|
||||
.join(", ")
|
||||
: "–"}
|
||||
@@ -229,15 +217,19 @@ const AccountDialog = ({
|
||||
);
|
||||
};
|
||||
|
||||
const Accounts = () => {
|
||||
interface AccountProps {
|
||||
card?: boolean;
|
||||
hideDeployBtn?: boolean;
|
||||
showHookStats?: boolean;
|
||||
}
|
||||
|
||||
const Accounts: FC<AccountProps> = props => {
|
||||
const snap = useSnapshot(state);
|
||||
const [activeAccountAddress, setActiveAccountAddress] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [activeAccountAddress, setActiveAccountAddress] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
const fetchAccInfo = async () => {
|
||||
if (snap.clientStatus === "online") {
|
||||
const requests = snap.accounts.map((acc) =>
|
||||
const requests = snap.accounts.map(acc =>
|
||||
snap.client?.send({
|
||||
id: acc.address,
|
||||
command: "account_info",
|
||||
@@ -249,15 +241,13 @@ const Accounts = () => {
|
||||
const address = res?.account_data?.Account as string;
|
||||
const balance = res?.account_data?.Balance as string;
|
||||
const sequence = res?.account_data?.Sequence as number;
|
||||
const accountToUpdate = state.accounts.find(
|
||||
(acc) => acc.address === address
|
||||
);
|
||||
const accountToUpdate = state.accounts.find(acc => acc.address === address);
|
||||
if (accountToUpdate) {
|
||||
accountToUpdate.xrp = balance;
|
||||
accountToUpdate.sequence = sequence;
|
||||
}
|
||||
});
|
||||
const objectRequests = snap.accounts.map((acc) => {
|
||||
const objectRequests = snap.accounts.map(acc => {
|
||||
return snap.client?.send({
|
||||
id: `${acc.address}-hooks`,
|
||||
command: "account_objects",
|
||||
@@ -267,9 +257,7 @@ const Accounts = () => {
|
||||
const objectResponses = await Promise.all(objectRequests);
|
||||
objectResponses.forEach((res: any) => {
|
||||
const address = res?.account as string;
|
||||
const accountToUpdate = state.accounts.find(
|
||||
(acc) => acc.address === address
|
||||
);
|
||||
const accountToUpdate = state.accounts.find(acc => acc.address === address);
|
||||
if (accountToUpdate) {
|
||||
accountToUpdate.hooks = res.account_objects
|
||||
.filter((ac: any) => ac?.LedgerEntryType === "Hook")
|
||||
@@ -299,16 +287,20 @@ const Accounts = () => {
|
||||
as="div"
|
||||
css={{
|
||||
display: "flex",
|
||||
borderTop: "1px solid $mauve6",
|
||||
background: "$mauve1",
|
||||
backgroundColor: props.card ? "$deep" : "$mauve1",
|
||||
position: "relative",
|
||||
width: "50%",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
flexShrink: 0,
|
||||
borderTop: "1px solid $mauve6",
|
||||
borderRight: "1px solid $mauve6",
|
||||
borderLeft: "1px solid $mauve6",
|
||||
borderBottom: "1px solid $mauve6",
|
||||
borderRadius: props.card ? "$md" : undefined,
|
||||
}}
|
||||
>
|
||||
<Container css={{ px: 0, flexShrink: 1 }}>
|
||||
<Flex css={{ py: "$3" }}>
|
||||
<Container css={{ p: 0, flexShrink: 1, height: "100%" }}>
|
||||
<Flex css={{ py: "$3", borderBottom: props.card ? "1px solid $mauve6" : undefined }}>
|
||||
<Heading
|
||||
as="h3"
|
||||
css={{
|
||||
@@ -326,79 +318,94 @@ const Accounts = () => {
|
||||
<Wallet size="15px" /> <Text css={{ lineHeight: 1 }}>Accounts</Text>
|
||||
</Heading>
|
||||
<Flex css={{ ml: "auto", gap: "$3", marginRight: "$3" }}>
|
||||
<Button ghost size="xs" onClick={() => addFaucetAccount(true)}>
|
||||
<Button ghost size="sm" onClick={() => addFaucetAccount(true)}>
|
||||
Create
|
||||
</Button>
|
||||
<ImportAccountDialog />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
as="div"
|
||||
<Stack
|
||||
css={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
height: "160px",
|
||||
fontSize: "13px",
|
||||
wordWrap: "break-word",
|
||||
fontWeight: "$body",
|
||||
fontFamily: "$monospace",
|
||||
gap: 0,
|
||||
height: "calc(100% - 52px)",
|
||||
flexWrap: "nowrap",
|
||||
overflowY: "auto",
|
||||
wordWrap: "break-word",
|
||||
}}
|
||||
>
|
||||
<Stack css={{ flexDirection: "column", gap: 0 }}>
|
||||
{snap.accounts.map((account) => (
|
||||
<Flex
|
||||
key={account.address + account.name}
|
||||
onClick={() => setActiveAccountAddress(account.address)}
|
||||
css={{
|
||||
gap: "$3",
|
||||
p: "$2 $3",
|
||||
justifyContent: "center",
|
||||
cursor: "pointer",
|
||||
"@hover": {
|
||||
"&:hover": {
|
||||
background: "$mauve3",
|
||||
},
|
||||
{snap.accounts.map(account => (
|
||||
<Flex
|
||||
column
|
||||
key={account.address + account.name}
|
||||
onClick={() => setActiveAccountAddress(account.address)}
|
||||
css={{
|
||||
px: "$3",
|
||||
py: props.card ? "$3" : "$2",
|
||||
cursor: "pointer",
|
||||
borderBottom: props.card ? "1px solid $mauve6" : undefined,
|
||||
"@hover": {
|
||||
"&:hover": {
|
||||
background: "$mauve3",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
row
|
||||
css={{
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Text>{account.name} </Text>
|
||||
<Text css={{ color: "$mauve9" }}>
|
||||
{account.address} (
|
||||
{Dinero({
|
||||
amount: Number(account?.xrp || "0"),
|
||||
precision: 6,
|
||||
})
|
||||
.toUnit()
|
||||
.toLocaleString(undefined, {
|
||||
style: "currency",
|
||||
currency: "XRP",
|
||||
currencyDisplay: "name",
|
||||
})}
|
||||
)
|
||||
</Text>
|
||||
<Button
|
||||
css={{ ml: "auto" }}
|
||||
size="xs"
|
||||
uppercase
|
||||
isLoading={account.isLoading}
|
||||
disabled={
|
||||
account.isLoading ||
|
||||
!snap.files.filter((file) => file.compiledWatContent).length
|
||||
}
|
||||
variant="secondary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deployHook(account);
|
||||
}}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
<Box>
|
||||
<Text>{account.name} </Text>
|
||||
<Text css={{ color: "$mauve9" }}>
|
||||
{account.address} (
|
||||
{Dinero({
|
||||
amount: Number(account?.xrp || "0"),
|
||||
precision: 6,
|
||||
})
|
||||
.toUnit()
|
||||
.toLocaleString(undefined, {
|
||||
style: "currency",
|
||||
currency: "XRP",
|
||||
currencyDisplay: "name",
|
||||
})}
|
||||
)
|
||||
</Text>
|
||||
</Box>
|
||||
{!props.hideDeployBtn && (
|
||||
<Button
|
||||
css={{ ml: "auto" }}
|
||||
size="xs"
|
||||
uppercase
|
||||
isLoading={account.isLoading}
|
||||
disabled={
|
||||
account.isLoading ||
|
||||
!snap.files.filter(file => file.compiledWatContent).length
|
||||
}
|
||||
variant="secondary"
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
deployHook(account);
|
||||
}}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Box>
|
||||
{props.showHookStats && (
|
||||
<Text muted small css={{ mt: "$2" }}>
|
||||
X hooks installed
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
))}
|
||||
</Stack>
|
||||
</Container>
|
||||
<AccountDialog
|
||||
activeAccountAddress={activeAccountAddress}
|
||||
@@ -413,7 +420,7 @@ const ImportAccountDialog = () => {
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button ghost size="xs">
|
||||
<Button ghost size="sm">
|
||||
Import
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
@@ -425,7 +432,7 @@ const ImportAccountDialog = () => {
|
||||
name="secret"
|
||||
type="password"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onChange={e => setValue(e.target.value)}
|
||||
/>
|
||||
</DialogDescription>
|
||||
|
||||
|
||||
@@ -3,6 +3,23 @@ import Box from "./Box";
|
||||
|
||||
const Flex = styled(Box, {
|
||||
display: "flex",
|
||||
variants: {
|
||||
row: {
|
||||
true: {
|
||||
flexDirection: "row",
|
||||
},
|
||||
},
|
||||
column: {
|
||||
true: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
},
|
||||
fluid: {
|
||||
true: {
|
||||
width: '100%'
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default Flex;
|
||||
|
||||
@@ -110,20 +110,22 @@ export const Input = styled("input", {
|
||||
backgroundColor: "transparent",
|
||||
},
|
||||
},
|
||||
deep: {
|
||||
backgroundColor: "$deep",
|
||||
boxShadow: "none",
|
||||
},
|
||||
},
|
||||
state: {
|
||||
invalid: {
|
||||
boxShadow: "inset 0 0 0 1px $colors$red7",
|
||||
"&:focus": {
|
||||
boxShadow:
|
||||
"inset 0px 0px 0px 1px $colors$red8, 0px 0px 0px 1px $colors$red8",
|
||||
boxShadow: "inset 0px 0px 0px 1px $colors$red8, 0px 0px 0px 1px $colors$red8",
|
||||
},
|
||||
},
|
||||
valid: {
|
||||
boxShadow: "inset 0 0 0 1px $colors$green7",
|
||||
"&:focus": {
|
||||
boxShadow:
|
||||
"inset 0px 0px 0px 1px $colors$green8, 0px 0px 0px 1px $colors$green8",
|
||||
boxShadow: "inset 0px 0px 0px 1px $colors$green8, 0px 0px 0px 1px $colors$green8",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
55
components/Select.tsx
Normal file
55
components/Select.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import { FC } from "react";
|
||||
import { gray, grayDark } from "@radix-ui/colors";
|
||||
import { useTheme } from "next-themes";
|
||||
import { styled } from '../stitches.config';
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { Props } from "react-select";
|
||||
const SelectInput = dynamic(() => import("react-select"), { ssr: false });
|
||||
|
||||
const Select: FC<Props> = props => {
|
||||
const { theme } = useTheme();
|
||||
const isDark = theme === "dark";
|
||||
const colors: any = {
|
||||
// primary: pink.pink9,
|
||||
primary: isDark ? grayDark.gray4 : gray.gray4,
|
||||
secondary: isDark ? grayDark.gray8 : gray.gray8,
|
||||
background: isDark ? "rgb(10, 10, 10)" : "rgb(245, 245, 245)",
|
||||
searchText: isDark ? grayDark.gray12 : gray.gray12,
|
||||
placeholder: isDark ? grayDark.gray11 : gray.gray11,
|
||||
};
|
||||
colors.outline = colors.background;
|
||||
colors.selected = colors.secondary;
|
||||
return (
|
||||
<SelectInput
|
||||
theme={theme => ({
|
||||
...theme,
|
||||
spacing: {
|
||||
...theme.spacing,
|
||||
controlHeight: 30
|
||||
},
|
||||
colors: {
|
||||
primary: colors.selected,
|
||||
primary25: colors.primary,
|
||||
primary50: colors.primary,
|
||||
primary75: colors.primary,
|
||||
danger: colors.primary,
|
||||
dangerLight: colors.primary,
|
||||
neutral0: colors.background,
|
||||
neutral5: colors.primary,
|
||||
neutral10: colors.primary,
|
||||
neutral20: colors.outline,
|
||||
neutral30: colors.primary,
|
||||
neutral40: colors.primary,
|
||||
neutral50: colors.placeholder,
|
||||
neutral60: colors.primary,
|
||||
neutral70: colors.primary,
|
||||
neutral80: colors.searchText,
|
||||
neutral90: colors.primary,
|
||||
},
|
||||
})}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default styled(Select, {});
|
||||
70
components/Tabs.tsx
Normal file
70
components/Tabs.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import type { ReactNode, ReactElement } from "react";
|
||||
import { Button, Stack } from ".";
|
||||
|
||||
interface TabProps {
|
||||
header?: string;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
activeIndex?: number;
|
||||
activeHeader?: string;
|
||||
headless?: boolean;
|
||||
children: ReactElement<TabProps>[];
|
||||
}
|
||||
|
||||
export const Tab = (props: TabProps) => null;
|
||||
|
||||
export const Tabs = ({ children, activeIndex, activeHeader, headless }: Props) => {
|
||||
const [active, setActive] = useState(activeIndex || 0);
|
||||
const tabProps: TabProps[] = children.map(elem => elem.props);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeIndex) setActive(activeIndex);
|
||||
}, [activeIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeHeader) {
|
||||
const idx = tabProps.findIndex(tab => tab.header === activeHeader);
|
||||
setActive(idx);
|
||||
}
|
||||
}, [activeHeader, tabProps]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!headless && (
|
||||
<Stack
|
||||
css={{
|
||||
gap: "$3",
|
||||
flex: 1,
|
||||
flexWrap: "nowrap",
|
||||
marginBottom: "-1px",
|
||||
}}
|
||||
>
|
||||
{tabProps.map((prop, idx) => (
|
||||
<Button
|
||||
key={prop.header}
|
||||
role="tab"
|
||||
tabIndex={idx}
|
||||
onClick={() => setActive(idx)}
|
||||
onKeyPress={() => setActive(idx)}
|
||||
outline={active !== idx}
|
||||
size="sm"
|
||||
css={{
|
||||
"&:hover": {
|
||||
span: {
|
||||
visibility: "visible",
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{prop.header || idx}
|
||||
</Button>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
{tabProps[active].children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,18 @@ const Text = styled("span", {
|
||||
fontFamily: "$body",
|
||||
lineHeight: "$body",
|
||||
color: "$text",
|
||||
variants: {
|
||||
small: {
|
||||
true: {
|
||||
fontSize: '$xs'
|
||||
}
|
||||
},
|
||||
muted: {
|
||||
true: {
|
||||
color: '$mauve9'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default Text;
|
||||
|
||||
15
components/index.tsx
Normal file
15
components/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export { default as Flex } from './Flex'
|
||||
export { default as Container } from './Container'
|
||||
export { default as Heading } from './Heading'
|
||||
export { default as Stack } from './Stack'
|
||||
export { default as Text } from './Text'
|
||||
export { default as Input } from './Input'
|
||||
export { default as Select } from './Select'
|
||||
export * from './Tabs'
|
||||
export * from './AlertDialog'
|
||||
export { default as Box } from './Box'
|
||||
export { default as Button } from './Button'
|
||||
export { default as ButtonGroup } from './ButtonGroup'
|
||||
export { default as DeployFooter } from './DeployFooter'
|
||||
export * from './Dialog'
|
||||
export * from './DropdownMenu'
|
||||
Reference in New Issue
Block a user