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 + 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
-
+
-
+