Skip to content

Commit

Permalink
Merge pull request #37 from oasisprotocol/ml/wrap-fee-modal
Browse files Browse the repository at this point in the history
Add fee retain modal warning
  • Loading branch information
lubej authored Feb 12, 2024
2 parents c919158 + 1045003 commit 12af0be
Show file tree
Hide file tree
Showing 21 changed files with 1,071 additions and 31 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"lint-docs": "markdownlint --ignore '**/node_modules/**' '**/*.md'",
"lint-git": "node ./internals/scripts/gitlint.js",
"prettify": "prettier src --write",
"preview": "vite preview"
"preview": "vite preview",
"test": "vitest"
},
"dependencies": {
"@fontsource-variable/figtree": "^5.0.19",
Expand All @@ -41,6 +42,7 @@
"markdownlint-cli": "^0.37.0",
"prettier": "3.1.0",
"typescript": "^5.0.2",
"vite": "^4.4.5"
"vite": "^4.4.5",
"vitest": "^1.2.2"
}
}
1 change: 1 addition & 0 deletions src/components/Button/index.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.button {
width: auto;
border-radius: 46px;
border: 1px solid transparent;
box-shadow: none;
Expand Down
20 changes: 18 additions & 2 deletions src/components/Input/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
justify-content: space-between;
width: 100%;
border-radius: 46px;
border: 1px solid var(--white);
background: var(--white);
padding: 1.125rem 2.8125rem;
}

.inputGroupDisabled {
cursor: not-allowed;

* {
cursor: not-allowed;
}
}

.inputGroup > label {
color: var(--gray-extra-dark);
font-size: 18px;
Expand Down Expand Up @@ -40,6 +46,16 @@
opacity: 0.75;
}

.inputGroupLight {
border: 1px solid var(--white);
background: var(--white);
}

.inputGroupDark {
border: 2px solid var(--gray-medium-dark);
background: var(--white);
}

@media screen and (min-width: 1000px) {
@media screen and (min-width: 1000px) {
.inputGroup > input {
Expand Down
24 changes: 21 additions & 3 deletions src/components/Input/index.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,52 @@
import classes from './index.module.css'
import { HTMLInputTypeAttribute, PropsWithChildren, useId } from 'react'

type InputGroupVariant = 'light' | 'dark'

const inputGroupVariantMap: Record<InputGroupVariant, string> = {
light: classes.inputGroupLight,
dark: classes.inputGroupDark,
}

interface Props<T> extends PropsWithChildren {
type: HTMLInputTypeAttribute
label: string
variant?: InputGroupVariant
pattern?: string
placeholder?: string
id?: string
disabled?: boolean
inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
className?: string
value: T
valueChange: (value: T) => void
valueChange?: (value: T) => void
}

export const Input = <T extends string | number | readonly string[]>({
type,
label,
variant = 'light',
pattern,
placeholder,
id,
disabled,
inputMode,
className,
value,
valueChange,
}: Props<T>) => {
const uniqueId = useId()
const inputId = id || uniqueId

return (
<div className={classes.inputGroup}>
<div
className={[
classes.inputGroup,
inputGroupVariantMap[variant],
...[disabled ? [classes.inputGroupDisabled] : []],
...[className ? [className] : []],
].join(' ')}
>
<label htmlFor={inputId}>{label}</label>
<input
id={inputId}
Expand All @@ -40,7 +58,7 @@ export const Input = <T extends string | number | readonly string[]>({
placeholder={placeholder}
disabled={disabled}
value={value}
onChange={({ target: { value } }) => valueChange(value as T)}
onChange={({ target: { value } }) => valueChange?.(value as T)}
/>
</div>
)
Expand Down
44 changes: 44 additions & 0 deletions src/components/Modal/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
.modalOverlay {
position: fixed;
display: flex;
justify-content: center;
align-items: center;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(6, 21, 43, 0.9);
}

.modal {
position: relative;
background: #fff;
padding: 1.25rem;
width: 100%;
max-width: 675px;
border-radius: 20px;
}

.modalContent {
padding: 3.5rem 3.5rem 0;
}

.modalCloseButton {
float: right;
border: none;
background: none;
cursor: pointer;
}

@media screen and (max-width: 1000px) {
.modal {
max-width: none;
width: 100%;
height: 100vh;
padding: 1.75rem;
overflow: auto;
}
.modalContent {
padding: 1rem;
}
}
37 changes: 37 additions & 0 deletions src/components/Modal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { MouseEvent, FC, PropsWithChildren } from 'react'
import classes from './index.module.css'
import { TimesIcon } from '../icons/TimesIcon.tsx'

export interface ModalProps {
isOpen: boolean
disableBackdropClick?: boolean
closeModal: (event?: MouseEvent<HTMLElement>) => void
}

export const Modal: FC<PropsWithChildren<ModalProps>> = ({
children,
isOpen,
disableBackdropClick,
closeModal,
}) => {
if (!isOpen) {
return null
}

const handleOverlayClick = () => {
if (!disableBackdropClick) {
closeModal()
}
}

return (
<div className={classes.modalOverlay} onClick={handleOverlayClick}>
<div className={classes.modal}>
<button className={classes.modalCloseButton} onClick={closeModal}>
<TimesIcon />
</button>
<div className={classes.modalContent}>{children}</div>
</div>
</div>
)
}
83 changes: 83 additions & 0 deletions src/components/WrapFeeWarningModal/index.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.wrapFeeWarningModalContent {
display: flex;
flex-direction: column;

h4 {
color: var(--gray-extra-dark);
}

p {
color: var(--gray-extra-dark);

&:first-of-type {
margin-bottom: 1rem;
}

&:last-of-type {
margin-bottom: 3.125rem;
}
}
}

.wrapFeeWarningModalLogo {
align-self: center;
margin-bottom: 3.125rem;
}

.wrapFeeWarningModalInput {
width: 75%;
margin: 0 auto;
}

.wrapFeeWarningModalActions {
display: flex;
flex-direction: column;
gap: 2.125rem;
margin: 2.5rem auto;
text-align: center;
}

.wrapFeeWarningModalButton {
min-width: 250px;
}

.wrapFeeWarningModalButtonText {
font-size: 13px;
}

.wrapFeeWarningModalFullAmount {
background: none;
border: none;
padding: 0;
font: inherit;
outline: inherit;
color: var(--danger);
text-decoration: underline;
cursor: pointer;
}

@media screen and (max-width: 1000px) {
.wrapFeeWarningModalContent {
p {
&:last-of-type {
margin-bottom: 2.625rem;
}
}
}

.wrapFeeWarningModalLogo {
margin-bottom: 2rem;
}

.wrapFeeWarningModalInput {
width: 100%;
}

.wrapFeeWarningModalActions {
display: flex;
flex-direction: column;
gap: 1.625rem;
margin: 1.25rem auto;
text-align: center;
}
}
69 changes: 69 additions & 0 deletions src/components/WrapFeeWarningModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { FC } from 'react'
import { Modal, ModalProps } from '../Modal'
import { LogoIconRound } from '../icons/LogoIconRound.tsx'
import classes from './index.module.css'
import { Input } from '../Input'
import { Button } from '../Button'
import { useWrapForm } from '../../hooks/useWrapForm.ts'
import { WRAP_FEE_DEDUCTION_MULTIPLIER } from '../../constants/config.ts'
import { BigNumber, utils } from 'ethers'
import { NumberUtils } from '../../utils/number.utils.ts'

interface WrapFeeWarningModalProps extends Pick<ModalProps, 'isOpen' | 'closeModal'> {
next: (amount: BigNumber) => void
}

export const WrapFeeWarningModal: FC<WrapFeeWarningModalProps> = ({ isOpen, closeModal, next }) => {
const {
state: { amount, estimatedFee },
} = useWrapForm()
const estimatedFeeDeduction = estimatedFee.mul(WRAP_FEE_DEDUCTION_MULTIPLIER)

const roseAmount = NumberUtils.ensureNonNullBigNumber(amount)
const estimatedAmountWithDeductedFees = roseAmount!.sub(estimatedFeeDeduction)

return (
<Modal isOpen={isOpen} closeModal={closeModal} disableBackdropClick>
<div className={classes.wrapFeeWarningModalContent}>
<div className={classes.wrapFeeWarningModalLogo}>
<LogoIconRound />
</div>

<h4>You have chosen to wrap your entire balance</h4>

<p>
It is recommended to keep a small amount in your wallet at all times to cover future transactions.
</p>
<p>
Choose if you want to wrap the reduced amount and keep &#123;sum of {WRAP_FEE_DEDUCTION_MULTIPLIER}{' '}
x gas fee - e.g. ‘<b>{utils.formatEther(estimatedFeeDeduction)} ROSE</b>’&#125; in your account, or
continue with the full amount.
</p>

<Input<string>
className={classes.wrapFeeWarningModalInput}
variant="dark"
disabled
type="text"
label="wROSE"
placeholder="0"
inputMode="decimal"
value={utils.formatEther(estimatedAmountWithDeductedFees)}
/>

<div className={classes.wrapFeeWarningModalActions}>
<Button
className={classes.wrapFeeWarningModalButton}
onClick={() => next(estimatedAmountWithDeductedFees)}
>
<span className={classes.wrapFeeWarningModalButtonText}>Wrap reduced amount</span>
</Button>

<button className={classes.wrapFeeWarningModalFullAmount} onClick={() => next(amount!)}>
Continue with full amount
</button>
</div>
</div>
</Modal>
)
}
Loading

0 comments on commit 12af0be

Please sign in to comment.