loading ui

This commit is contained in:
Matthias Kretschmann 2024-04-01 01:15:51 +01:00
parent e2c21cf497
commit 4850c19417
Signed by: m
GPG Key ID: 606EEEF3C479A91F
12 changed files with 166 additions and 51 deletions

View File

@ -1,6 +1,7 @@
import type { Metadata } from 'next'
import { Hanken_Grotesk } from 'next/font/google'
import '@/styles/globals.css'
import '@/styles/loading-ui.css'
const hankenGrotesk = Hanken_Grotesk({
subsets: ['latin'],

View File

@ -6,34 +6,48 @@ import { usePrices } from '@/hooks'
import { Label } from '@/components/Label'
export function CalculationBase() {
const { prices, isValidating } = usePrices()
const { prices, isValidating, isLoading } = usePrices()
const feedbackClasses = isLoading
? 'isLoading'
: isValidating
? 'isValidating'
: ''
return (
<ul className={styles.calculationBase}>
<li>
<p>1 ASI</p>
<p className={isValidating ? 'isValidating' : ''}>= ${prices.asi}</p>
<p>
= <span className={feedbackClasses}>${prices.asi}</span>
</p>
</li>
<li>
<p>
1 Fet = {ratioFetToAsi} ASI
<Label>fixed</Label>
</p>
<p className={isValidating ? 'isValidating' : ''}>= ${prices.fet}</p>
<p>
= <span className={feedbackClasses}>${prices.fet}</span>
</p>
</li>
<li>
<p>
1 OCEAN = {ratioOceanToAsi} ASI
<Label>fixed</Label>
</p>
<p className={isValidating ? 'isValidating' : ''}>= ${prices.ocean}</p>
<p>
= <span className={feedbackClasses}>${prices.ocean}</span>
</p>
</li>
<li>
<p>
1 AGIX = {ratioAgixToAsi} ASI
<Label>fixed</Label>
</p>
<p className={isValidating ? 'isValidating' : ''}>= ${prices.agix}</p>
<p>
= <span className={feedbackClasses}>${prices.agix}</span>
</p>
</li>
</ul>
)

View File

@ -3,4 +3,5 @@
border: 1px solid rgba(var(--foreground-rgb), 0.15);
border-radius: var(--border-radius);
overflow: hidden;
margin: 0 0.25rem;
}

View File

@ -3,6 +3,7 @@
width: 60px;
padding: 0 0.2rem;
text-align: center;
border-right: 1px solid rgba(var(--foreground-rgb), 0.15);
}
.input:hover {

View File

@ -1,7 +1,26 @@
.select {
display: inline-block;
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 {
@ -10,7 +29,8 @@
.icon {
position: absolute;
right: 0.175rem;
right: 0.1rem;
width: 1em;
height: 100%;
z-index: -1;
}

View File

@ -11,7 +11,7 @@
.resultLine {
display: inline-grid;
grid-template-columns: 24px 2fr 3fr;
grid-template-columns: 24px 40% 40%;
gap: 0.5rem;
align-items: center;
width: 100%;

View File

@ -11,6 +11,7 @@ type Props = {
amountFiat: number
amountOriginalFiat?: number
isValidating: boolean
isLoading: boolean
}
export function Result({
@ -19,36 +20,48 @@ export function Result({
amountAsi,
amountFiat,
amountOriginalFiat,
isValidating
isValidating,
isLoading
}: Props) {
const feedbackClasses = isLoading
? 'isLoading'
: isValidating
? 'isValidating'
: ''
return (
<div className={styles.result}>
<div className={styles.resultLine}>
<TokenLogo token={token} />
<span className={isValidating ? 'isValidating' : ''}>
{formatNumber(amount || 0, token?.symbol || '')}
</span>
<p>
<span className={feedbackClasses}>
{formatNumber(amount || 0, token?.symbol || '')}
</span>
</p>
{amountOriginalFiat ? (
<span className={styles.fiat}>
{formatNumber(amountOriginalFiat || 0, 'USD')}
</span>
<p>
<span className={`${styles.fiat} ${feedbackClasses}`}>
{formatNumber(amountOriginalFiat || 0, 'USD')}
</span>
</p>
) : null}
</div>
<div className={styles.resultLine}>
<ArrowRightIcon className={styles.iconArrow} />
<strong
title={`${amountAsi}`}
className={isValidating ? 'isValidating' : ''}
>
{formatNumber(amountAsi || 0, 'ASI')}
</strong>
<strong
className={`${styles.fiat} ${isValidating ? 'isValidating' : ''}`}
>
{formatNumber(amountFiat || 0, 'USD')}
</strong>
<p>
<strong title={`${amountAsi}`} className={feedbackClasses}>
{formatNumber(amountAsi || 0, 'ASI')}
</strong>
</p>
<p>
<strong className={`${styles.fiat} ${feedbackClasses}`}>
{formatNumber(amountFiat || 0, 'USD')}
</strong>
</p>
</div>
</div>
)

View File

@ -10,7 +10,7 @@ import { FormAmount } from '@/components/FormAmount'
import { getTokenBySymbol } from '@/utils'
export function Buy() {
const { prices, isValidating } = usePrices()
const { prices, isValidating, isLoading } = usePrices()
const [amount, setAmount] = useState(100)
const [debouncedAmount] = useDebounce(amount, 500)
@ -33,6 +33,7 @@ export function Buy() {
: 0
}
isValidating={isValidating}
isLoading={isLoading}
/>
<Result
token={getTokenBySymbol('AGIX')}
@ -46,6 +47,7 @@ export function Buy() {
: 0
}
isValidating={isValidating}
isLoading={isLoading}
/>
<Result
token={getTokenBySymbol('FET')}
@ -59,6 +61,7 @@ export function Buy() {
: 0
}
isValidating={isValidating}
isLoading={isLoading}
/>
</div>
)

View File

@ -5,6 +5,10 @@ import { fetcher, getTokenAddressBySymbol, getTokenBySymbol } from '@/utils'
import useSWR from 'swr'
import { TokenSymbol } from '@/types'
const options = {
keepPreviousData: true // so loading UI can kick in properly
}
export function SwapResults({
tokenSymbol,
amount
@ -12,30 +16,49 @@ export function SwapResults({
tokenSymbol: TokenSymbol
amount: number
}) {
const { prices, isValidating: isValidatingPrices } = usePrices()
const {
prices,
isValidating: isValidatingPrices,
isLoading: isLoadingPrices
} = usePrices()
// -> AGIX
const { data: dataSwapToAgix, isValidating: isValidatingToAgix } = useSWR(
const {
data: dataSwapToAgix,
isValidating: isValidatingToAgix,
isLoading: isLoadingToAgix
} = useSWR(
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
tokenSymbol
)}&tokenOut=${getTokenAddressBySymbol('AGIX')}&amountIn=${amount}`,
fetcher
fetcher,
options
)
// -> FET
const { data: dataSwapToFet, isValidating: isValidatingToFet } = useSWR(
const {
data: dataSwapToFet,
isValidating: isValidatingToFet,
isLoading: isLoadingToFet
} = useSWR(
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
tokenSymbol
)}&tokenOut=${getTokenAddressBySymbol('FET')}&amountIn=${amount}`,
fetcher
fetcher,
options
)
// -> OCEAN
const { data: dataSwapToOcean, isValidating: isValidatingToOcean } = useSWR(
const {
data: dataSwapToOcean,
isValidating: isValidatingToOcean,
isLoading: isLoadingToOcean
} = useSWR(
`/api/quote/?tokenIn=${getTokenAddressBySymbol(
tokenSymbol
)}&tokenOut=${getTokenAddressBySymbol('OCEAN')}&amountIn=${amount}`,
fetcher
fetcher,
options
)
return (
@ -66,6 +89,7 @@ export function SwapResults({
: undefined
}
isValidating={isValidatingToOcean || isValidatingPrices}
isLoading={isLoadingToOcean || isLoadingPrices}
/>
<Result
@ -90,6 +114,7 @@ export function SwapResults({
prices.agix
}
isValidating={isValidatingToAgix || isValidatingPrices}
isLoading={isLoadingToAgix || isLoadingPrices}
/>
<Result
@ -110,6 +135,7 @@ export function SwapResults({
prices.asi
}
isValidating={isValidatingToFet || isValidatingPrices}
isLoading={isLoadingToFet || isLoadingPrices}
/>
</>
)

View File

@ -1,15 +0,0 @@
.isValidating {
animation: flicker 2s infinite ease-out;
}
@keyframes flicker {
0% {
opacity: 0.1;
}
50% {
opacity: 1;
}
100% {
opacity: 0.1;
}
}

View File

@ -40,5 +40,3 @@ ul {
color-scheme: dark;
}
}
@import './_animations.css';

53
styles/loading-ui.css Normal file
View 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;
}
}