Skip to content

Commit

Permalink
feat: new swap page (keep-starknet-strange#102)
Browse files Browse the repository at this point in the history
* feat: new swap page

* feat: Account selection modal

---------

Co-authored-by: Chqrles <[email protected]>
  • Loading branch information
ugur-eren and 0xChqrles authored Oct 3, 2024
1 parent b22c538 commit 313ef40
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 66 deletions.
3 changes: 2 additions & 1 deletion client/src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export const PrimaryButton = styled.button`
font-weight: 485;
&:disabled {
background-color: ${({ theme }) => theme.surface};
background-color: ${({ theme }) => theme.bg3};
color: ${({ theme }) => theme.neutral2};
cursor: default;
}
`
Expand Down
24 changes: 24 additions & 0 deletions client/src/components/ChipButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ThemedText } from 'src/theme/components'
import styled from 'styled-components'

const StyledChipButton = styled(ThemedText.BodySecondary)<{ active?: boolean }>`
background-color: ${({ theme }) => theme.bg3};
color: ${({ theme, active }) => (active ? theme.neutral1 : theme.neutral2)};
border: none;
border-radius: 99px;
padding: 8px 16px;
cursor: pointer;
transition: background-color 0.2s ease;
&:hover {
background-color: ${({ theme }) => theme.bg2};
}
`

interface ChipButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
active?: boolean
}

export function ChipButton({ active, ...props }: ChipButtonProps) {
return <StyledChipButton as="button" active={active} {...props} />
}
8 changes: 6 additions & 2 deletions client/src/components/CurrencyButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Currency } from 'src/constants/currencies'
import { ThemedText } from 'src/theme/components'
import { ChevronDown } from 'src/theme/components/icons'
import styled from 'styled-components'

import { Row } from '../Flex'
Expand All @@ -20,15 +21,18 @@ const CurrencyCard = styled(Row)`
`

interface CurrencyButtonProps {
className?: string
selectedCurrency: Currency
}

export function CurrencyButton({ selectedCurrency }: CurrencyButtonProps) {
export function CurrencyButton({ className, selectedCurrency }: CurrencyButtonProps) {
return (
<CurrencyCard as="button" gap={4}>
<CurrencyCard as="button" gap={4} className={className}>
<img src={selectedCurrency.img} alt={selectedCurrency.name} />

<ThemedText.BodyPrimary fontWeight={500}>{selectedCurrency.name}</ThemedText.BodyPrimary>

<ChevronDown width={14} height={14} />
</CurrencyCard>
)
}
18 changes: 11 additions & 7 deletions client/src/components/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@ interface CurrencyInputProps {
}

const StyledInput = styled.input`
box-sizing: border-box;
width: 100%;
padding: 2px;
border: none;
background-color: transparent;
color: ${({ theme }) => theme.neutral1};
border: none;
border-radius: 4px;
font-size: 26px;
width: 100%;
box-sizing: border-box;
color: white;
font-family: 'Inter';
font-size: 72px;
font-weight: 600;
text-align: center;
outline: none;
&:focus {
outline: none;
&::placeholder {
color: ${({ theme }) => theme.neutral2};
}
`

Expand Down
68 changes: 68 additions & 0 deletions client/src/components/SelectAccountModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useCloseModal, useSelectAccountModal } from 'src/hooks/useModal'
import { ThemedText } from 'src/theme/components'
import { RevolutLogo, VenmoLogo } from 'src/theme/components/icons'
import styled from 'styled-components'

import { PrimaryButton } from '../Button'
import { Column, Row } from '../Flex'
import Content from '../Modal/Content'
import Overlay from '../Modal/Overlay'
import Portal from '../Portal'

const AccountButtons = styled(Column)`
width: 100%;
`

const StyledAccountModal = styled(Row)`
width: 100%;
justify-content: space-between;
padding: 16px;
background-color: ${({ theme }) => theme.bg3};
color: ${({ theme }) => theme.neutral1};
border: none;
border-radius: 12px;
cursor: pointer;
`

interface AccountButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
Logo: React.FC<React.SVGProps<SVGSVGElement>>
name: string
currencies: string[]
}

const AccountButton = ({ Logo, name, currencies, ...props }: AccountButtonProps) => {
return (
<StyledAccountModal as="button" {...props}>
<Row gap={16}>
<Logo width={22} height={22} />

<ThemedText.HeadlineSmall>{name}</ThemedText.HeadlineSmall>
</Row>

<ThemedText.BodySecondary>{currencies.join(', ')}</ThemedText.BodySecondary>
</StyledAccountModal>
)
}

export default function SelectAccountModal() {
// modal
const [isOpen] = useSelectAccountModal()
const close = useCloseModal()

if (!isOpen) return null

return (
<Portal>
<Content title="Select account" close={close}>
<AccountButtons gap={16}>
<AccountButton Logo={RevolutLogo} name="Revolut" currencies={['EUR', 'USD']} />
<AccountButton Logo={VenmoLogo} name="Venmo" currencies={['USD']} />
</AccountButtons>

<PrimaryButton>Register new account</PrimaryButton>
</Content>

<Overlay onClick={close} />
</Portal>
)
}
2 changes: 2 additions & 0 deletions client/src/constants/currencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ export const FIAT_CURRENCIES = {
EUR: {
img: EURLogo,
name: 'EUR',
symbol: '€',
},
}

export const TOKEN_CURRENCIES = {
USDC: {
img: USDCLogo,
name: 'USDC',
symbol: 'USDC',
},
}
2 changes: 2 additions & 0 deletions client/src/hooks/useModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ export const useWalletConnectModal = () => useModal(ModalType.WALLET_CONNECT)
export const useWalletOverviewModal = () => useModal(ModalType.WALLET_OVERVIEW)

export const useProofGenerationModal = () => useModal(ModalType.PROOF_GENERATION)

export const useSelectAccountModal = () => useModal(ModalType.SELECT_ACCOUNT)
142 changes: 86 additions & 56 deletions client/src/pages/Swap.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,88 @@
import { ChangeEvent, useState } from 'react'
import { PrimaryButton } from 'src/components/Button'
import { ChipButton } from 'src/components/ChipButton'
import { CurrencyButton } from 'src/components/CurrencyButton'
import { Column, Row } from 'src/components/Flex'
import { CurrencyInput } from 'src/components/Input'
import SelectAccountModal from 'src/components/SelectAccountModal'
import { FIAT_CURRENCIES, TOKEN_CURRENCIES } from 'src/constants/currencies'
import { useSelectAccountModal } from 'src/hooks/useModal'
import { ThemedText } from 'src/theme/components'
import { ArrowDown } from 'src/theme/components/icons'
import { ChevronDown } from 'src/theme/components/icons'
import { styled } from 'styled-components'

const Content = styled(Column)`
max-width: 460px;
width: 100%;
align-items: normal;
gap: 24px;
margin: 0 auto;
margin-top: 120px;
`

const SwapCard = styled(Row)`
const SwapCard = styled(Column)`
width: 100%;
background-color: ${({ theme }) => theme.bg3};
border-radius: 12px;
padding: 12px 16px;
background-color: ${({ theme }) => theme.bg3};
border-top-left-radius: 12px;
border-top-right-radius: 12px;
`

const FiatCurrenyCard = styled(Row)`
width: 100%;
justify-content: space-between;
`

const SwapCardContent = styled(Column)`
flex: 1;
align-items: flex-start;
input {
width: 100%;
padding-top: 12px;
padding-bottom: 24px;
font-size: 42px;
font-weight: 600;
color: ${({ theme }) => theme.neutral1};
&::placeholder {
color: ${({ theme }) => theme.neutral2};
}
width: 100%;
padding: 34px 0 42px 0;
`

const TokenCurrencyButton = styled(CurrencyButton)`
gap: 8px;
background-color: transparent;
border: none;
margin: 8px 0 12px 0;
img {
width: 18px;
height: 18px;
}
`

const SwitchButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
const PresetAmountButton = styled(ChipButton)`
background-color: ${({ theme }) => theme.bg1};
color: ${({ theme }) => theme.neutral1};
border: 1px solid ${({ theme }) => theme.border};
padding: 6px 12px;
`

const RampCard = styled(Row)`
justify-content: space-between;
width: 100%;
padding: 12px 16px;
background-color: ${({ theme }) => theme.bg3};
border: 4px solid ${({ theme }) => theme.bg1};
border-radius: 6px;
cursor: pointer;
height: 32px;
width: 32px;
box-sizing: content-box;
margin: -18px 0;
z-index: 1;
padding: 0;
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
`

const AccountButton = styled(PrimaryButton)`
width: auto;
align-items: center;
gap: 4px;
padding: 8px;
`

export default function SwapPage() {
const [rampMode, setRampMode] = useState<'on' | 'off'>('on')
const [fiatCurrency, setFiatCurrency] = useState(FIAT_CURRENCIES['EUR'])
const [tokenCurrency, setTokenCurrency] = useState(TOKEN_CURRENCIES['USDC'])

const [inputSendValue, setInputSendValue] = useState('')
const [inputReceiveValue, setInputReceiveValue] = useState('')

const [, toggleSelectAccountModal] = useSelectAccountModal()

const handleReceiveChange = (event: ChangeEvent<HTMLInputElement>) => {
const inputValue = event.target.value
const numericValue = inputValue.replace(/[^0-9]/g, '')
Expand All @@ -77,43 +95,55 @@ export default function SwapPage() {
setInputSendValue(numericValue)
}

const handleChangeClick = () => {
setRampMode((state) => (state == 'off' ? 'on' : 'off'))
setInputSendValue(inputReceiveValue)
setInputReceiveValue(inputSendValue)
}

return (
<Content>
<ThemedText.HeadlineLarge>Swap</ThemedText.HeadlineLarge>
<Content gap={24}>
<Row gap={16}>
<ChipButton active>Buy</ChipButton>
<ChipButton>Sell</ChipButton>
</Row>

<Column gap={12}>
<Column>
<Column gap={2}>
<SwapCard as="label">
<SwapCardContent>
<ThemedText.Subtitle fontSize={12}>Send</ThemedText.Subtitle>
<CurrencyInput placeholder="0.0" value={inputSendValue} onChange={handleSendChange} />
</SwapCardContent>
<FiatCurrenyCard>
<ThemedText.Subtitle fontSize={14} color="neutral1">
You&apos;re buying
</ThemedText.Subtitle>

<CurrencyButton selectedCurrency={rampMode === 'on' ? FIAT_CURRENCIES['EUR'] : TOKEN_CURRENCIES['USDC']} />
</SwapCard>

<SwitchButton onClick={handleChangeClick}>
<ArrowDown width={18} height={18} />
</SwitchButton>
<CurrencyButton selectedCurrency={fiatCurrency} />
</FiatCurrenyCard>

<SwapCard as="label">
<SwapCardContent>
<ThemedText.Subtitle>Receive</ThemedText.Subtitle>
<CurrencyInput placeholder="0.0" value={inputReceiveValue} onChange={handleReceiveChange} />
<CurrencyInput
placeholder={`0${fiatCurrency.symbol}`}
value={inputSendValue}
onChange={handleSendChange}
/>

<TokenCurrencyButton selectedCurrency={tokenCurrency} />

<Row gap={8}>
<PresetAmountButton>100{fiatCurrency.symbol}</PresetAmountButton>
<PresetAmountButton>300{fiatCurrency.symbol}</PresetAmountButton>
<PresetAmountButton>1000{fiatCurrency.symbol}</PresetAmountButton>
</Row>
</SwapCardContent>

<CurrencyButton selectedCurrency={rampMode === 'off' ? FIAT_CURRENCIES['EUR'] : TOKEN_CURRENCIES['USDC']} />
</SwapCard>

<RampCard>
<ThemedText.BodyPrimary>From</ThemedText.BodyPrimary>

<AccountButton onClick={toggleSelectAccountModal}>
<span>Select account</span>
<ChevronDown width={14} height={14} />
</AccountButton>
</RampCard>
</Column>

<PrimaryButton>Swap</PrimaryButton>
<PrimaryButton disabled>Enter amount</PrimaryButton>
</Column>

<SelectAccountModal />
</Content>
)
}
1 change: 1 addition & 0 deletions client/src/state/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export enum ModalType {
WALLET_CONNECT,
WALLET_OVERVIEW,
PROOF_GENERATION,
SELECT_ACCOUNT,
}

export type ApplicationSlice = State & Actions
Expand Down
Loading

0 comments on commit 313ef40

Please sign in to comment.