From 36c9199e42f46e0966331d2c2163d8f680fa4a16 Mon Sep 17 00:00:00 2001 From: reallybeard <89934888+reallybeard@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:15:02 -0600 Subject: [PATCH 1/3] add the basic ui --- package.json | 3 +- src/App.css | 41 ----- src/App.tsx | 46 +++-- src/components/AssetSelection.tsx | 16 ++ src/components/SelectPair.tsx | 39 ++++ src/components/Status/Status.tsx | 166 ++++++++++++++++++ .../Status/components/StatusStepper.tsx | 71 ++++++++ src/components/TradeInput.tsx | 104 +++++++++++ src/hooks/useCopyToClipboard.tsx | 29 +++ src/index.css | 68 ------- src/lib/const.ts | 2 + src/theme/components/Input.tsx | 6 +- src/theme/components/Stepper.tsx | 76 ++++++++ src/theme/theme.tsx | 12 ++ yarn.lock | 20 +++ 15 files changed, 563 insertions(+), 136 deletions(-) create mode 100644 src/components/AssetSelection.tsx create mode 100644 src/components/SelectPair.tsx create mode 100644 src/components/Status/Status.tsx create mode 100644 src/components/Status/components/StatusStepper.tsx create mode 100644 src/components/TradeInput.tsx create mode 100644 src/hooks/useCopyToClipboard.tsx create mode 100644 src/lib/const.ts create mode 100644 src/theme/components/Stepper.tsx diff --git a/package.json b/package.json index 2777db3..b12470a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "framer-motion": "^10.17.9", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-icons": "^4.12.0" + "react-icons": "^4.12.0", + "react-router-dom": "^6.21.1" }, "devDependencies": { "@types/inquirer": "^9.0.7", diff --git a/src/App.css b/src/App.css index b9d355d..8b13789 100644 --- a/src/App.css +++ b/src/App.css @@ -1,42 +1 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.tsx b/src/App.tsx index a847042..29ce22c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,33 @@ import './App.css' -import { useState } from 'react' +import { Center } from '@chakra-ui/react' +import { createBrowserRouter, RouterProvider } from 'react-router-dom' +import { SelectPair } from 'components/SelectPair' +import { Status } from 'components/Status/Status' +import { TradeInput } from 'components/TradeInput' -import reactLogo from './assets/react.svg' - -import viteLogo from '/vite.svg' +const router = createBrowserRouter([ + { + path: '/', + element: , + }, + { + path: '/input', + element: , + }, + { + path: '/status', + element: , + }, +]) function App() { - const [count, setCount] = useState(0) - console.log(import.meta.env.VITE_FOO) return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

Click on the Vite and React logos to learn more

- +
+ +
) } diff --git a/src/components/AssetSelection.tsx b/src/components/AssetSelection.tsx new file mode 100644 index 0000000..c99f143 --- /dev/null +++ b/src/components/AssetSelection.tsx @@ -0,0 +1,16 @@ +import { Avatar, Button, Text } from '@chakra-ui/react' + +type AssetSelectionProps = { + onClick: () => void + label: string +} + +export const AssetSelection: React.FC = ({ label, onClick }) => { + return ( + + ) +} diff --git a/src/components/SelectPair.tsx b/src/components/SelectPair.tsx new file mode 100644 index 0000000..1f5a25b --- /dev/null +++ b/src/components/SelectPair.tsx @@ -0,0 +1,39 @@ +import { Button, Card, CardBody, Flex, Heading, IconButton } from '@chakra-ui/react' +import { useCallback, useMemo } from 'react' +import { FaArrowRightArrowLeft } from 'react-icons/fa6' +import { useNavigate } from 'react-router-dom' + +import { AssetSelection } from './AssetSelection' + +export const SelectPair = () => { + const navigate = useNavigate() + const switchIcon = useMemo(() => , []) + const handleSubmit = useCallback(() => { + navigate('/input') + }, [navigate]) + + const handleFromAssetClick = useCallback(() => { + console.info('asset click') + }, []) + const handleToAssetClick = useCallback(() => { + console.info('to asset click') + }, []) + + return ( + + + + Choose which assets to trade + + + + + + + + + + ) +} diff --git a/src/components/Status/Status.tsx b/src/components/Status/Status.tsx new file mode 100644 index 0000000..6e0ee17 --- /dev/null +++ b/src/components/Status/Status.tsx @@ -0,0 +1,166 @@ +import { + Avatar, + Card, + CardBody, + CardFooter, + CardHeader, + Center, + Divider, + Flex, + IconButton, + Input, + InputGroup, + InputRightElement, + Stack, + Tag, + Text, + useSteps, +} from '@chakra-ui/react' +import { useCallback, useMemo } from 'react' +import { FaArrowDown, FaArrowRightArrowLeft, FaCheck, FaRegCopy } from 'react-icons/fa6' +import { useCopyToClipboard } from 'hooks/useCopyToClipboard' +import { BTCImage, ETHImage } from 'lib/const' + +import type { StepProps } from './components/StatusStepper' +import { StatusStepper } from './components/StatusStepper' + +const steps: StepProps[] = [ + { + title: 'Awaiting Deposit', + icon: FaArrowDown, + }, + { + title: 'Awaiting Exchange', + icon: FaArrowRightArrowLeft, + }, + { + title: 'Trade Complete', + icon: FaCheck, + }, +] + +export const Status = () => { + const { activeStep } = useSteps({ + index: 0, + count: steps.length, + }) + const CopyIcon = useMemo(() => , []) + const CheckIcon = useMemo(() => , []) + const { copyToClipboard: copyToAddress, isCopied: isToAddressCopied } = useCopyToClipboard({ + timeout: 3000, + }) + const { copyToClipboard: copyDepositAddress, isCopied: isDepositAddressCopied } = + useCopyToClipboard({ timeout: 3000 }) + + const handleCopyToAddress = useCallback(() => { + copyToAddress('1234') + }, [copyToAddress]) + + const handleCopyDepositAddress = useCallback(() => { + copyDepositAddress('1234') + }, [copyDepositAddress]) + + return ( + + + TX ID + 0x124567 + + + +
+ + Time remaining 06:23 + + + + + Send + + + 0.002 BTC + + + + To + + + + + + + + + + You will receive + + + 0.000158162 ETH + + + + + + + Order Details + + + + + Deposit + + 0.002 BTC + + + bc1q8n6t65jpm6k048ejvwgfa69xp5laqr2sexx7gl + + + + + + + + Receive + + 0.00158162 ETH + + + 0x1234484844949494949 + + + + + Estimated Rate + 1 BTC = 12.90126 ETH + + + + ) +} diff --git a/src/components/Status/components/StatusStepper.tsx b/src/components/Status/components/StatusStepper.tsx new file mode 100644 index 0000000..4d954b4 --- /dev/null +++ b/src/components/Status/components/StatusStepper.tsx @@ -0,0 +1,71 @@ +import { + Center, + CircularProgress, + Flex, + Progress, + Step, + Stepper, + StepStatus, + Text, +} from '@chakra-ui/react' +import { useMemo } from 'react' +import type { IconType } from 'react-icons' +import { FaCheck } from 'react-icons/fa6' + +export type StepProps = { + title: string + icon: IconType +} + +type StatusStepperProps = { + steps: StepProps[] + activeStep: number +} + +export const StatusStepper: React.FC = ({ steps, activeStep }) => { + const CheckIcon = useMemo(() => , []) + const LoadingIcon = useMemo( + () => , + [], + ) + + const max = steps.length - 1 + const progressPercent = (activeStep / max) * 100 + + const renderSteps = useMemo(() => { + return steps.map((step, index) => { + return ( + +
+ +
+ {step.title} +
+ ) + }) + }, [CheckIcon, LoadingIcon, steps]) + + return ( + + + {renderSteps} + + + + + + ) +} diff --git a/src/components/TradeInput.tsx b/src/components/TradeInput.tsx new file mode 100644 index 0000000..062cca4 --- /dev/null +++ b/src/components/TradeInput.tsx @@ -0,0 +1,104 @@ +import { + Avatar, + Button, + Card, + CardBody, + CardFooter, + CardHeader, + Flex, + HStack, + IconButton, + Input, + StackDivider, + Stat, + StatLabel, + StatNumber, + Text, +} from '@chakra-ui/react' +import { useCallback, useMemo } from 'react' +import { FaArrowRightArrowLeft } from 'react-icons/fa6' +import { useNavigate } from 'react-router-dom' +import { BTCImage, ETHImage } from 'lib/const' + +export const TradeInput = () => { + const navigate = useNavigate() + const Divider = useMemo(() => , []) + const FromAssetIcon = useMemo(() => , []) + const ToAssetIcon = useMemo(() => , []) + const SwitchIcon = useMemo(() => , []) + const handleSubmit = useCallback(() => { + navigate('/status') + }, [navigate]) + + const handleFromAssetClick = useCallback(() => { + console.info('asset click') + }, []) + const handleToAssetClick = useCallback(() => { + console.info('to asset click') + }, []) + + return ( + + + + Your rate + 1 BTC = 12.90126 ETH + + + + Deposit This + 0.002 BTC + + + To Get This + 0.0248 ETH + + + Miner Fee + $10 + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/src/hooks/useCopyToClipboard.tsx b/src/hooks/useCopyToClipboard.tsx new file mode 100644 index 0000000..47ba325 --- /dev/null +++ b/src/hooks/useCopyToClipboard.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' + +export interface useCopyToClipboardProps { + timeout?: number +} + +export function useCopyToClipboard({ timeout = 2000 }: useCopyToClipboardProps) { + const [isCopied, setIsCopied] = React.useState(false) + + const copyToClipboard = (value: string) => { + if (typeof window === 'undefined' || !navigator.clipboard?.writeText) { + return + } + + if (!value) { + return + } + + void navigator.clipboard.writeText(value).then(() => { + setIsCopied(true) + + setTimeout(() => { + setIsCopied(false) + }, timeout) + }) + } + + return { isCopied, copyToClipboard } +} diff --git a/src/index.css b/src/index.css index 6119ad9..e69de29 100644 --- a/src/index.css +++ b/src/index.css @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/lib/const.ts b/src/lib/const.ts new file mode 100644 index 0000000..9b56614 --- /dev/null +++ b/src/lib/const.ts @@ -0,0 +1,2 @@ +export const BTCImage = 'https://assets.coincap.io/assets/icons/256/btc.png' +export const ETHImage = 'https://assets.coincap.io/assets/icons/256/eth.png' diff --git a/src/theme/components/Input.tsx b/src/theme/components/Input.tsx index 50c5113..bb095fa 100644 --- a/src/theme/components/Input.tsx +++ b/src/theme/components/Input.tsx @@ -6,7 +6,7 @@ export const InputStyle = { baseStyle: () => ({ field: { _placeholder: { - color: 'text.subtle', + color: 'text.subtlest', }, }, }), @@ -66,5 +66,7 @@ export const InputStyle = { }), }, // The default `size` or `variant` values - defaultProps: {}, + defaultProps: { + variant: 'filled', + }, } diff --git a/src/theme/components/Stepper.tsx b/src/theme/components/Stepper.tsx new file mode 100644 index 0000000..1bdeac3 --- /dev/null +++ b/src/theme/components/Stepper.tsx @@ -0,0 +1,76 @@ +import { keyframes } from '@chakra-ui/react' + +const throb = keyframes({ + '0%': { + boxShadow: '0 0 0 0px rgba(55, 97, 249, 1)', + }, + '100%': { + boxShadow: '0 0 0 15px rgba(55, 97, 249, 0)', + }, +}) + +const baseStyle = { + // select the indicator part + indicator: { + '&[data-status=active]': { + bg: 'background.surface.raised.base', + borderColor: 'blue.500', + }, + // add throbbing to active steps that are not current executing (to get user attention) + '&[data-status=active]:not(.step-pending)': { + animation: `${throb} 1s infinite cubic-bezier(0.87, 0, 0.13, 1)`, + }, + '&[data-status=incomplete]': { + bg: 'background.surface.raised.base', + borderColor: 'border.base', + }, + '&[data-status=complete]': { + bg: 'background.success', + }, + }, + separator: { + bg: 'border.base', + '&[data-status=complete]': { + bg: 'blue.500', + }, + '&[data-status=active]': { + bg: 'border.base', + }, + }, +} + +const variants = { + vert: { + step: { + flexDirection: 'column', + flex: '1 0 0 !important', + '&[data-status=incomplete]': { + opacity: 0.2, + }, + '&[data-status=complete]': { + color: 'text.success', + }, + }, + }, + error: { + indicator: { + '&[data-status=active]': { + bg: 'background.surface.raised.base', + borderColor: 'red.500', + }, + '&[data-status=incomplete]': { + bg: 'background.surface.raised.base', + borderColor: 'border.base', + }, + '&[data-status=complete]': { + bg: 'background.error', + }, + }, + }, + // other variants if needed +} + +export const stepperTheme = { + baseStyle, + variants, +} diff --git a/src/theme/theme.tsx b/src/theme/theme.tsx index 1ee3643..e56c7c8 100644 --- a/src/theme/theme.tsx +++ b/src/theme/theme.tsx @@ -3,6 +3,10 @@ import { extendTheme } from '@chakra-ui/react' import { mode } from '@chakra-ui/theme-tools' import { colors } from './colors' +import { ButtonStyle as Button } from './components/Button' +import { CardStyle as Card } from './components/Card' +import { InputStyle as Input } from './components/Input' +import { stepperTheme as Stepper } from './components/Stepper' import { semanticTokens } from './semanticTokens' export const breakpoints = { @@ -21,6 +25,8 @@ const styles = { backgroundSize: 'cover', fontFeatureSettings: `'zero' on`, overflowX: 'hidden', + textRendering: 'optimizeLegibility', + fontSmoothing: 'antialiased', }, html: { scrollBehavior: 'smooth', @@ -88,6 +94,12 @@ export const theme = extendTheme({ body: 'Inter, system-ui, sans-serif', heading: 'Work Sans, system-ui, sans-serif', }, + components: { + Button, + Card, + Input, + Stepper, + }, colors, sizes: { container: { diff --git a/yarn.lock b/yarn.lock index 5ed2691..aead116 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2308,6 +2308,11 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== +"@remix-run/router@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.14.1.tgz#6d2dd03d52e604279c38911afc1079d58c50a755" + integrity sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow== + "@rollup/rollup-android-arm-eabi@4.9.4": version "4.9.4" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.4.tgz#b1094962742c1a0349587040bc06185e2a667c9b" @@ -5106,6 +5111,21 @@ react-remove-scroll@^2.5.6: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" +react-router-dom@^6.21.1: + version "6.21.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.21.1.tgz#58b459d2fe1841388c95bb068f85128c45e27349" + integrity sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA== + dependencies: + "@remix-run/router" "1.14.1" + react-router "6.21.1" + +react-router@6.21.1: + version "6.21.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.21.1.tgz#8db7ee8d7cfc36513c9a66b44e0897208c33be34" + integrity sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA== + dependencies: + "@remix-run/router" "1.14.1" + react-style-singleton@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" From f81c38b3d61f6d81bf546241bc61a114c2567e34 Mon Sep 17 00:00:00 2001 From: reallybeard <89934888+reallybeard@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:19:20 -0600 Subject: [PATCH 2/3] hook up more parts --- src/components/AssetSelection.tsx | 13 +++-- src/components/SelectPair.tsx | 15 +++++- src/components/Status/Status.tsx | 83 ++++++++++++++++--------------- 3 files changed, 66 insertions(+), 45 deletions(-) diff --git a/src/components/AssetSelection.tsx b/src/components/AssetSelection.tsx index c99f143..e702171 100644 --- a/src/components/AssetSelection.tsx +++ b/src/components/AssetSelection.tsx @@ -3,14 +3,21 @@ import { Avatar, Button, Text } from '@chakra-ui/react' type AssetSelectionProps = { onClick: () => void label: string + assetIcon: string + assetName: string } -export const AssetSelection: React.FC = ({ label, onClick }) => { +export const AssetSelection: React.FC = ({ + label, + onClick, + assetIcon, + assetName, +}) => { return ( ) } diff --git a/src/components/SelectPair.tsx b/src/components/SelectPair.tsx index 1f5a25b..193c9e8 100644 --- a/src/components/SelectPair.tsx +++ b/src/components/SelectPair.tsx @@ -2,6 +2,7 @@ import { Button, Card, CardBody, Flex, Heading, IconButton } from '@chakra-ui/re import { useCallback, useMemo } from 'react' import { FaArrowRightArrowLeft } from 'react-icons/fa6' import { useNavigate } from 'react-router-dom' +import { BTCImage, ETHImage } from 'lib/const' import { AssetSelection } from './AssetSelection' @@ -26,9 +27,19 @@ export const SelectPair = () => { Choose which assets to trade - + - +