mirror of
https://github.com/kremalicious/asi-calculator.git
synced 2024-12-22 17:33:18 +01:00
loading ui
This commit is contained in:
parent
e2c21cf497
commit
4850c19417
@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from 'next'
|
import type { Metadata } from 'next'
|
||||||
import { Hanken_Grotesk } from 'next/font/google'
|
import { Hanken_Grotesk } from 'next/font/google'
|
||||||
import '@/styles/globals.css'
|
import '@/styles/globals.css'
|
||||||
|
import '@/styles/loading-ui.css'
|
||||||
|
|
||||||
const hankenGrotesk = Hanken_Grotesk({
|
const hankenGrotesk = Hanken_Grotesk({
|
||||||
subsets: ['latin'],
|
subsets: ['latin'],
|
||||||
|
@ -6,34 +6,48 @@ import { usePrices } from '@/hooks'
|
|||||||
import { Label } from '@/components/Label'
|
import { Label } from '@/components/Label'
|
||||||
|
|
||||||
export function CalculationBase() {
|
export function CalculationBase() {
|
||||||
const { prices, isValidating } = usePrices()
|
const { prices, isValidating, isLoading } = usePrices()
|
||||||
|
|
||||||
|
const feedbackClasses = isLoading
|
||||||
|
? 'isLoading'
|
||||||
|
: isValidating
|
||||||
|
? 'isValidating'
|
||||||
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className={styles.calculationBase}>
|
<ul className={styles.calculationBase}>
|
||||||
<li>
|
<li>
|
||||||
<p>1 ASI</p>
|
<p>1 ASI</p>
|
||||||
<p className={isValidating ? 'isValidating' : ''}>= ${prices.asi}</p>
|
<p>
|
||||||
|
= <span className={feedbackClasses}>${prices.asi}</span>
|
||||||
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>
|
<p>
|
||||||
1 Fet = {ratioFetToAsi} ASI
|
1 Fet = {ratioFetToAsi} ASI
|
||||||
<Label>fixed</Label>
|
<Label>fixed</Label>
|
||||||
</p>
|
</p>
|
||||||
<p className={isValidating ? 'isValidating' : ''}>= ${prices.fet}</p>
|
<p>
|
||||||
|
= <span className={feedbackClasses}>${prices.fet}</span>
|
||||||
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>
|
<p>
|
||||||
1 OCEAN = {ratioOceanToAsi} ASI
|
1 OCEAN = {ratioOceanToAsi} ASI
|
||||||
<Label>fixed</Label>
|
<Label>fixed</Label>
|
||||||
</p>
|
</p>
|
||||||
<p className={isValidating ? 'isValidating' : ''}>= ${prices.ocean}</p>
|
<p>
|
||||||
|
= <span className={feedbackClasses}>${prices.ocean}</span>
|
||||||
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>
|
<p>
|
||||||
1 AGIX = {ratioAgixToAsi} ASI
|
1 AGIX = {ratioAgixToAsi} ASI
|
||||||
<Label>fixed</Label>
|
<Label>fixed</Label>
|
||||||
</p>
|
</p>
|
||||||
<p className={isValidating ? 'isValidating' : ''}>= ${prices.agix}</p>
|
<p>
|
||||||
|
= <span className={feedbackClasses}>${prices.agix}</span>
|
||||||
|
</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
)
|
)
|
||||||
|
@ -3,4 +3,5 @@
|
|||||||
border: 1px solid rgba(var(--foreground-rgb), 0.15);
|
border: 1px solid rgba(var(--foreground-rgb), 0.15);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
margin: 0 0.25rem;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
width: 60px;
|
width: 60px;
|
||||||
padding: 0 0.2rem;
|
padding: 0 0.2rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
border-right: 1px solid rgba(var(--foreground-rgb), 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.input:hover {
|
.input:hover {
|
||||||
|
@ -1,7 +1,26 @@
|
|||||||
.select {
|
.select {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
all: unset;
|
all: unset;
|
||||||
padding: 0 0.75rem 0 0;
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select:hover:not(.select[disabled]) {
|
||||||
|
background-color: rgba(var(--background-rgb), 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select:focus-within {
|
||||||
|
outline: none;
|
||||||
|
background-color: rgba(var(--background-rgb), 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.select:hover:not(.select[disabled]) {
|
||||||
|
background-color: rgba(var(--foreground-rgb), 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.select:focus-within {
|
||||||
|
background-color: rgba(var(--foreground-rgb), 0.2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectWrapper {
|
.selectWrapper {
|
||||||
@ -10,7 +29,8 @@
|
|||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.175rem;
|
right: 0.1rem;
|
||||||
|
width: 1em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
.resultLine {
|
.resultLine {
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
grid-template-columns: 24px 2fr 3fr;
|
grid-template-columns: 24px 40% 40%;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -11,6 +11,7 @@ type Props = {
|
|||||||
amountFiat: number
|
amountFiat: number
|
||||||
amountOriginalFiat?: number
|
amountOriginalFiat?: number
|
||||||
isValidating: boolean
|
isValidating: boolean
|
||||||
|
isLoading: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Result({
|
export function Result({
|
||||||
@ -19,36 +20,48 @@ export function Result({
|
|||||||
amountAsi,
|
amountAsi,
|
||||||
amountFiat,
|
amountFiat,
|
||||||
amountOriginalFiat,
|
amountOriginalFiat,
|
||||||
isValidating
|
isValidating,
|
||||||
|
isLoading
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const feedbackClasses = isLoading
|
||||||
|
? 'isLoading'
|
||||||
|
: isValidating
|
||||||
|
? 'isValidating'
|
||||||
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.result}>
|
<div className={styles.result}>
|
||||||
<div className={styles.resultLine}>
|
<div className={styles.resultLine}>
|
||||||
<TokenLogo token={token} />
|
<TokenLogo token={token} />
|
||||||
|
|
||||||
<span className={isValidating ? 'isValidating' : ''}>
|
<p>
|
||||||
|
<span className={feedbackClasses}>
|
||||||
{formatNumber(amount || 0, token?.symbol || '')}
|
{formatNumber(amount || 0, token?.symbol || '')}
|
||||||
</span>
|
</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
{amountOriginalFiat ? (
|
{amountOriginalFiat ? (
|
||||||
<span className={styles.fiat}>
|
<p>
|
||||||
|
<span className={`${styles.fiat} ${feedbackClasses}`}>
|
||||||
{formatNumber(amountOriginalFiat || 0, 'USD')}
|
{formatNumber(amountOriginalFiat || 0, 'USD')}
|
||||||
</span>
|
</span>
|
||||||
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.resultLine}>
|
<div className={styles.resultLine}>
|
||||||
<ArrowRightIcon className={styles.iconArrow} />
|
<ArrowRightIcon className={styles.iconArrow} />
|
||||||
<strong
|
|
||||||
title={`${amountAsi}`}
|
<p>
|
||||||
className={isValidating ? 'isValidating' : ''}
|
<strong title={`${amountAsi}`} className={feedbackClasses}>
|
||||||
>
|
|
||||||
{formatNumber(amountAsi || 0, 'ASI')}
|
{formatNumber(amountAsi || 0, 'ASI')}
|
||||||
</strong>
|
</strong>
|
||||||
<strong
|
</p>
|
||||||
className={`${styles.fiat} ${isValidating ? 'isValidating' : ''}`}
|
<p>
|
||||||
>
|
<strong className={`${styles.fiat} ${feedbackClasses}`}>
|
||||||
{formatNumber(amountFiat || 0, 'USD')}
|
{formatNumber(amountFiat || 0, 'USD')}
|
||||||
</strong>
|
</strong>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,7 @@ import { FormAmount } from '@/components/FormAmount'
|
|||||||
import { getTokenBySymbol } from '@/utils'
|
import { getTokenBySymbol } from '@/utils'
|
||||||
|
|
||||||
export function Buy() {
|
export function Buy() {
|
||||||
const { prices, isValidating } = usePrices()
|
const { prices, isValidating, isLoading } = usePrices()
|
||||||
const [amount, setAmount] = useState(100)
|
const [amount, setAmount] = useState(100)
|
||||||
const [debouncedAmount] = useDebounce(amount, 500)
|
const [debouncedAmount] = useDebounce(amount, 500)
|
||||||
|
|
||||||
@ -33,6 +33,7 @@ export function Buy() {
|
|||||||
: 0
|
: 0
|
||||||
}
|
}
|
||||||
isValidating={isValidating}
|
isValidating={isValidating}
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
<Result
|
<Result
|
||||||
token={getTokenBySymbol('AGIX')}
|
token={getTokenBySymbol('AGIX')}
|
||||||
@ -46,6 +47,7 @@ export function Buy() {
|
|||||||
: 0
|
: 0
|
||||||
}
|
}
|
||||||
isValidating={isValidating}
|
isValidating={isValidating}
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
<Result
|
<Result
|
||||||
token={getTokenBySymbol('FET')}
|
token={getTokenBySymbol('FET')}
|
||||||
@ -59,6 +61,7 @@ export function Buy() {
|
|||||||
: 0
|
: 0
|
||||||
}
|
}
|
||||||
isValidating={isValidating}
|
isValidating={isValidating}
|
||||||
|
isLoading={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -5,6 +5,10 @@ import { fetcher, getTokenAddressBySymbol, getTokenBySymbol } from '@/utils'
|
|||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { TokenSymbol } from '@/types'
|
import { TokenSymbol } from '@/types'
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
keepPreviousData: true // so loading UI can kick in properly
|
||||||
|
}
|
||||||
|
|
||||||
export function SwapResults({
|
export function SwapResults({
|
||||||
tokenSymbol,
|
tokenSymbol,
|
||||||
amount
|
amount
|
||||||
@ -12,30 +16,49 @@ export function SwapResults({
|
|||||||
tokenSymbol: TokenSymbol
|
tokenSymbol: TokenSymbol
|
||||||
amount: number
|
amount: number
|
||||||
}) {
|
}) {
|
||||||
const { prices, isValidating: isValidatingPrices } = usePrices()
|
const {
|
||||||
|
prices,
|
||||||
|
isValidating: isValidatingPrices,
|
||||||
|
isLoading: isLoadingPrices
|
||||||
|
} = usePrices()
|
||||||
|
|
||||||
// -> AGIX
|
// -> AGIX
|
||||||
const { data: dataSwapToAgix, isValidating: isValidatingToAgix } = useSWR(
|
const {
|
||||||
|
data: dataSwapToAgix,
|
||||||
|
isValidating: isValidatingToAgix,
|
||||||
|
isLoading: isLoadingToAgix
|
||||||
|
} = useSWR(
|
||||||
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
||||||
tokenSymbol
|
tokenSymbol
|
||||||
)}&tokenOut=${getTokenAddressBySymbol('AGIX')}&amountIn=${amount}`,
|
)}&tokenOut=${getTokenAddressBySymbol('AGIX')}&amountIn=${amount}`,
|
||||||
fetcher
|
fetcher,
|
||||||
|
options
|
||||||
)
|
)
|
||||||
|
|
||||||
// -> FET
|
// -> FET
|
||||||
const { data: dataSwapToFet, isValidating: isValidatingToFet } = useSWR(
|
const {
|
||||||
|
data: dataSwapToFet,
|
||||||
|
isValidating: isValidatingToFet,
|
||||||
|
isLoading: isLoadingToFet
|
||||||
|
} = useSWR(
|
||||||
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
||||||
tokenSymbol
|
tokenSymbol
|
||||||
)}&tokenOut=${getTokenAddressBySymbol('FET')}&amountIn=${amount}`,
|
)}&tokenOut=${getTokenAddressBySymbol('FET')}&amountIn=${amount}`,
|
||||||
fetcher
|
fetcher,
|
||||||
|
options
|
||||||
)
|
)
|
||||||
|
|
||||||
// -> OCEAN
|
// -> OCEAN
|
||||||
const { data: dataSwapToOcean, isValidating: isValidatingToOcean } = useSWR(
|
const {
|
||||||
|
data: dataSwapToOcean,
|
||||||
|
isValidating: isValidatingToOcean,
|
||||||
|
isLoading: isLoadingToOcean
|
||||||
|
} = useSWR(
|
||||||
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
|
||||||
tokenSymbol
|
tokenSymbol
|
||||||
)}&tokenOut=${getTokenAddressBySymbol('OCEAN')}&amountIn=${amount}`,
|
)}&tokenOut=${getTokenAddressBySymbol('OCEAN')}&amountIn=${amount}`,
|
||||||
fetcher
|
fetcher,
|
||||||
|
options
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -66,6 +89,7 @@ export function SwapResults({
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
isValidating={isValidatingToOcean || isValidatingPrices}
|
isValidating={isValidatingToOcean || isValidatingPrices}
|
||||||
|
isLoading={isLoadingToOcean || isLoadingPrices}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Result
|
<Result
|
||||||
@ -90,6 +114,7 @@ export function SwapResults({
|
|||||||
prices.agix
|
prices.agix
|
||||||
}
|
}
|
||||||
isValidating={isValidatingToAgix || isValidatingPrices}
|
isValidating={isValidatingToAgix || isValidatingPrices}
|
||||||
|
isLoading={isLoadingToAgix || isLoadingPrices}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Result
|
<Result
|
||||||
@ -110,6 +135,7 @@ export function SwapResults({
|
|||||||
prices.asi
|
prices.asi
|
||||||
}
|
}
|
||||||
isValidating={isValidatingToFet || isValidatingPrices}
|
isValidating={isValidatingToFet || isValidatingPrices}
|
||||||
|
isLoading={isLoadingToFet || isLoadingPrices}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
.isValidating {
|
|
||||||
animation: flicker 2s infinite ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes flicker {
|
|
||||||
0% {
|
|
||||||
opacity: 0.1;
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0.1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -40,5 +40,3 @@ ul {
|
|||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@import './_animations.css';
|
|
||||||
|
53
styles/loading-ui.css
Normal file
53
styles/loading-ui.css
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
.isLoading {
|
||||||
|
background-color: rgba(0, 0, 0, 0) !important;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
rgba(var(--foreground-rgb), 0) 0,
|
||||||
|
rgba(var(--foreground-rgb), 0.1) 50%,
|
||||||
|
rgba(var(--foreground-rgb), 0) 100%
|
||||||
|
) !important;
|
||||||
|
background-size: 800% 800% !important;
|
||||||
|
color: rgba(0, 0, 0, 0) !important;
|
||||||
|
border-color: rgba(0, 0, 0, 0) !important;
|
||||||
|
border-radius: var(--border-radius) !important;
|
||||||
|
user-select: none;
|
||||||
|
cursor: wait;
|
||||||
|
animation: loading 3s infinite ease-out !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isLoading * {
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.isLoading:empty::after,
|
||||||
|
.isLoading *:empty::after {
|
||||||
|
content: '\00a0';
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loading {
|
||||||
|
0% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-position: 100% 50%;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: 0% 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.isValidating {
|
||||||
|
animation: flicker 2s infinite ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes flicker {
|
||||||
|
0% {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user