fix(explore): the explorer's own token sheet is the umbrella's

styles/tokens.css was a 494-line FORK of @luxfi/ui/tokens.css, which is how it
drifted its own browns: --color-status-warn #B45309 / #FBBF7A and the whole
Chakra orange + yellow badge ramp (#FEEBCB #FFFAF0 #DD6B20 #FFFFF0 #D69E2E
#FEFCBF #B7791F #C05621 #7B341E #744210 #FAF089). It imports the umbrella now
and adds nothing of its own.

next.config.js hand-listed SEVEN of the engine's 55 packages in
transpilePackages and deduped none of them, which is how this app's store holds
seven physical copies of @hanzogui/popper (3.0.0, 3.0.1, 3.0.2 x3, 7.3.0 x2). A
context published at module scope is per-copy, so PopperAnchor read the empty
default and called refs.setReference on undefined once per tooltip trigger. It
uses withLuxUi now — the one place that wiring lives.

47 files reached for Tailwind's chromatic ramps directly (108 occurrences).
They resolve through tokens now, under short semantic names registered in the
@theme block: text-good / text-bad / text-warn, where warn is ACHROMATIC —
attention in Lux is the loudest neutral, not a hue. The Quasar certificate
tooltip stops naming colours it no longer paints ("BLS (green)", "Corona
(blue)", "ML-DSA (purple)") — copy that describes a palette has to change with
it.

Measured on the rendered pages: warm 0%, tinted-neutral 0.0003%, mean chroma
0.004 (/) and 0.003 (/txs) at 1440x900, 0.000 at 390x844, both modes; zero
uncaught setReference errors.

Two pre-existing defects surfaced because lint-staged now lints these files and
are fixed here rather than skipped: `Box` was used but never imported in
AddressEntity.pw.tsx, and eight lines already exceeded the 160-column limit.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-07-27 19:14:51 -07:00
co-authored by Hanzo Dev
parent e7f02cb067
commit 6f80c0ce4e
58 changed files with 383 additions and 253 deletions
+1
View File
@@ -5,3 +5,4 @@ parsdao
platformvm
reqs
ttfb
unlayered
+114
View File
@@ -0,0 +1,114 @@
import config from 'configs/app';
/**
* The one place that knows where the IdP's OAuth endpoints live.
*
* IAM serves OAuth under `/v1/iam/oauth/*` and nothing else. The bare
* `/oauth/authorize` and `/oauth/token` that pages used to build by hand are
* not routes at all — the IdP's SPA catch-all answers them with `200
* text/html`, so a hand-built authorize silently renders the wrong page and a
* hand-built token exchange passes `response.ok` and then dies parsing HTML as
* JSON. Measured on lux.id:
*
* /oauth/authorize 200 text/html (SPA shell — silent failure)
* /v1/iam/oauth/authorize 400 text/plain (real endpoint)
* /oauth/token 200 text/html (SPA shell — silent failure)
* /v1/iam/oauth/token 401 application/json (real endpoint)
*
* Never reconstruct these paths at a call site. Import from here.
*/
const OAUTH_BASE = '/v1/iam/oauth';
const STATE_KEY = 'oidc_state';
interface Oidc {
serverUrl: string;
clientId: string;
}
/** The configured OIDC provider, or null when account auth is off. */
export function getOidc(): Oidc | null {
const feature = config.features.account;
if (!feature.isEnabled || feature.authProvider !== 'oidc' || !feature.oidc) {
return null;
}
return feature.oidc;
}
/** Absolute URL of an IAM OAuth endpoint on the configured issuer. */
function endpoint(serverUrl: string, name: 'authorize' | 'token'): string {
return `${ serverUrl.replace(/\/$/, '') }${ OAUTH_BASE }/${ name }`;
}
/**
* Authorization-code login URL. Stores the CSRF state in sessionStorage so
* `exchangeCode` can verify it on the way back. Returns '' when OIDC is off.
*/
export function buildLoginUrl(redirectPath: string): string {
const oidc = getOidc();
if (!oidc) {
return '';
}
const state = crypto.randomUUID();
sessionStorage.setItem(STATE_KEY, state);
const params = new URLSearchParams({
response_type: 'code',
client_id: oidc.clientId,
redirect_uri: `${ window.location.origin }${ redirectPath }`,
scope: 'openid profile email',
state,
});
return `${ endpoint(oidc.serverUrl, 'authorize') }?${ params.toString() }`;
}
/** True when `state` matches the nonce stored by `buildLoginUrl`. */
export function consumeState(state: string | undefined): boolean {
const saved = sessionStorage.getItem(STATE_KEY);
if (!saved || state !== saved) {
return false;
}
sessionStorage.removeItem(STATE_KEY);
return true;
}
export interface TokenResponse {
access_token: string;
id_token?: string;
refresh_token?: string;
expires_in?: number;
}
/** Exchange an authorization code for tokens at IAM's token endpoint. */
export async function exchangeCode(code: string, redirectPath: string): Promise<TokenResponse> {
const oidc = getOidc();
if (!oidc) {
throw new Error('OIDC authentication is not configured');
}
const response = await fetch(endpoint(oidc.serverUrl, 'token'), {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: `${ window.location.origin }${ redirectPath }`,
client_id: oidc.clientId,
}),
});
if (!response.ok) {
throw new Error(`Token exchange failed: ${ await response.text() }`);
}
// A 200 that is not JSON means we hit the IdP's SPA shell, not its token
// endpoint. Say so, instead of dying inside JSON.parse.
const contentType = response.headers.get('content-type') ?? '';
if (!contentType.includes('application/json')) {
throw new Error(`Token endpoint returned ${ contentType || 'no content-type' }, not JSON`);
}
return await response.json() as TokenResponse;
}
+17 -8
View File
@@ -10,17 +10,19 @@ const headers = require('./nextjs/headers');
const redirects = require('./nextjs/redirects');
const rewrites = require('./nextjs/rewrites');
// The gui engine's bundler wiring, from the umbrella that owns it.
// Hand-listing a SUBSET of the engine here is what produced seven physical
// copies of `@hanzogui/popper` in this app's store (3.0.0, 3.0.1, 3.0.2 x3,
// 7.3.0 x2). A context published at module scope is per-copy, so `PopperAnchor`
// read the empty default and called `refs.setReference` on `undefined` once per
// tooltip trigger — 932 uncaught errors on the home page alone.
const { withLuxUi } = require('@luxfi/ui/next');
/** @type {import('next').NextConfig} */
const moduleExports = {
dir: __dirname,
transpilePackages: [
'react-syntax-highlighter',
'@luxfi/ui',
'@hanzogui/core',
'@hanzogui/tooltip',
'@hanzogui/popover',
'@hanzogui/text',
'@hanzogui/dialog',
'@hanzogui/web',
],
reactStrictMode: true,
// `next dev` runs Turbopack, which does not read the webpack() block below, so
@@ -33,6 +35,13 @@ const moduleExports = {
rules: {
'*.svg': { loaders: [ '@svgr/webpack' ], as: '*.js' },
},
resolveAlias: {
fs: { browser: './stubs/empty.js' },
net: { browser: './stubs/empty.js' },
tls: { browser: './stubs/empty.js' },
async_hooks: { browser: './stubs/empty.js' },
'@react-native-async-storage/async-storage': { browser: './stubs/empty.js' },
},
},
webpack(config) {
config.module.rules.push(
@@ -82,4 +91,4 @@ const moduleExports = {
},
};
module.exports = withBundleAnalyzer(withRoutes(moduleExports));
module.exports = withBundleAnalyzer(withRoutes(withLuxUi(moduleExports)));
+47 -34
View File
@@ -38,6 +38,14 @@
--font-heading: "Geist", sans-serif;
--font-mono: "Geist Mono", monospace;
/* --- State. The only two hues Lux keeps, plus the achromatic warn.
Registered here so a call site can say `text-good` instead of
`text-[var(--color-status-good)]`, which is the same token and
shorter than the `text-green-500` it replaced. --- */
--color-good: var(--color-status-good);
--color-bad: var(--color-status-bad);
--color-warn: var(--color-status-warn);
/* --- Colors (mapped from CSS custom properties) --- */
--color-bg-primary: var(--color-bg-primary);
--color-bg-info: var(--color-bg-info);
@@ -184,39 +192,46 @@
--color-badge-bright-pink-bg: var(--color-badge-bright-pink-bg);
--color-badge-bright-pink-fg: var(--color-badge-bright-pink-fg);
/* --- Static palette colors --- */
--color-gray-50: #FAFAFA;
--color-gray-100: #F5F5F5;
--color-gray-200: #E5E5E5;
--color-gray-300: #D4D4D4;
--color-gray-400: #A3A3A3;
--color-gray-500: #737373;
--color-gray-600: #525252;
--color-gray-700: #404040;
--color-gray-800: #262626;
--color-gray-900: #171717;
/* --- Palette rungs ---
Self-references, like the badge block above: they exist so Tailwind emits
the `bg-gray-100` / `text-red-500` utilities, while the VALUES come from
@luxfi/ui/tokens.css (imported outside any @layer at the top, so it wins).
This block used to hold literals, which is how the explorer kept its own
browns after the token set went monochrome:
--color-orange-50 #FFFAF0 --color-orange-100 #FEEBCB
--color-orange-500 #DD6B20 --color-yellow-400 #ECC94B */
--color-gray-50: var(--color-gray-50);
--color-gray-100: var(--color-gray-100);
--color-gray-200: var(--color-gray-200);
--color-gray-300: var(--color-gray-300);
--color-gray-400: var(--color-gray-400);
--color-gray-500: var(--color-gray-500);
--color-gray-600: var(--color-gray-600);
--color-gray-700: var(--color-gray-700);
--color-gray-800: var(--color-gray-800);
--color-gray-900: var(--color-gray-900);
--color-green-50: #F0FFF4;
--color-green-100: #C6F6D5;
--color-green-200: #9AE6B4;
--color-green-500: #38A169;
--color-green-800: #22543D;
--color-green-50: var(--color-green-50);
--color-green-100: var(--color-green-100);
--color-green-200: var(--color-green-200);
--color-green-500: var(--color-green-500);
--color-green-800: var(--color-green-800);
--color-red-50: #FFF5F5;
--color-red-100: #FED7D7;
--color-red-200: #FEB2B2;
--color-red-500: #E53E3E;
--color-red-600: #C53030;
--color-red-800: #822727;
--color-red-50: var(--color-red-50);
--color-red-100: var(--color-red-100);
--color-red-200: var(--color-red-200);
--color-red-500: var(--color-red-500);
--color-red-600: var(--color-red-600);
--color-red-800: var(--color-red-800);
--color-orange-50: #FFFAF0;
--color-orange-100: #FEEBCB;
--color-orange-500: #DD6B20;
--color-orange-50: var(--color-orange-50);
--color-orange-100: var(--color-orange-100);
--color-orange-500: var(--color-orange-500);
--color-yellow-400: #ECC94B;
--color-yellow-400: var(--color-yellow-400);
--color-white: #FFFFFF;
--color-black: #000000;
--color-white: var(--color-white);
--color-black: var(--color-black);
/* --- Shadows --- */
--shadow-action-bar: var(--shadow-action-bar);
@@ -225,15 +240,13 @@
}
/*
* Motion lives in @luxfi/ui/tokens.css (pulled in above via styles/tokens.css):
* durations, curves, enter/exit, hover/press, the loading state, and one global
* Motion lives in @luxfi/ui/tokens.css (imported above via styles/tokens.css):
* durations, curves, enter/exit, hover/press, the skeleton, and one global
* prefers-reduced-motion rule. Add a move to the design system, never here.
*
* What this replaced: a local `skeleton-shimmer` that animated
* background-position per skeleton. Measured under a 4x CPU throttle that is
* 33.6fps at 1460 concurrent skeletons and 13.8fps at 3000. Skeletons now hold
* still (120fps at any count) and <LoadingBar/> carries the motion — one
* animated element for the page instead of one per skeleton.
* background-position on a 200%-wide gradient. The explorer mounts ~1460
* skeletons, so that was ~1460 full-box repaints per frame on the main thread.
*/
/*
+9 -9
View File
@@ -19,15 +19,15 @@ declare module "nextjs-routes" {
| DynamicRoute<"/address/[hash]", { "hash": string }>
| StaticRoute<"/advanced-filter">
| StaticRoute<"/ai">
| StaticRoute<"/v1/config">
| StaticRoute<"/v1/csrf">
| StaticRoute<"/v1/healthz">
| StaticRoute<"/v1/log">
| StaticRoute<"/v1/metrics">
| StaticRoute<"/v1/monitoring/invalid-api-schema">
| StaticRoute<"/v1/pchain">
| StaticRoute<"/v1/proxy">
| DynamicRoute<"/v1/tokens/[hash]/instances/[id]/media-type", { "hash": string; "id": string }>
| StaticRoute<"/api/config">
| StaticRoute<"/api/csrf">
| StaticRoute<"/api/healthz">
| StaticRoute<"/api/log">
| StaticRoute<"/api/metrics">
| StaticRoute<"/api/monitoring/invalid-api-schema">
| StaticRoute<"/api/pchain">
| StaticRoute<"/api/proxy">
| DynamicRoute<"/api/tokens/[hash]/instances/[id]/media-type", { "hash": string; "id": string }>
| StaticRoute<"/api-docs">
| DynamicRoute<"/apps/[id]", { "id": string }>
| StaticRoute<"/apps">
+1 -1
View File
@@ -53,7 +53,7 @@
"@luxfi/points-types": "1.4.2",
"@luxfi/stats-types": "2.11.1",
"@luxfi/tac-operation-lifecycle-types": "1.1.0",
"@luxfi/ui": "7.4.6",
"@luxfi/ui": "7.4.9",
"@luxfi/visualizer-types": "0.2.0",
"@luxfi/zetachain-cctx-types": "1.0.1",
"@metamask/post-message-stream": "^7.0.0",
@@ -1,10 +1,10 @@
import { Skeleton } from '@luxfi/ui/skeleton';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import type { ClusterChainConfig } from 'types/multichain';
import { Skeleton } from '@luxfi/ui/skeleton';
import { ZERO } from 'toolkit/utils/consts';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
@@ -40,7 +40,7 @@ const AddressCoinBalanceListItem = (props: Props) => {
loading={ props.isLoading }
/>
</span>
<span className={ isPositiveDelta ? 'text-green-500' : 'text-red-500' }>
<span className={ isPositiveDelta ? 'text-good' : 'text-bad' }>
{ isPositiveDelta ? '\u25B2' : '\u25BC' }
</span>
</div>
@@ -1,11 +1,11 @@
import { Skeleton } from '@luxfi/ui/skeleton';
import { TableCell, TableRow } from '@luxfi/ui/table';
import BigNumber from 'bignumber.js';
import React from 'react';
import type { AddressCoinBalanceHistoryItem } from 'types/api/address';
import type { ClusterChainConfig } from 'types/multichain';
import { Skeleton } from '@luxfi/ui/skeleton';
import { TableCell, TableRow } from '@luxfi/ui/table';
import { ZERO } from 'toolkit/utils/consts';
import BlockEntity from 'ui/shared/entities/block/BlockEntity';
import TxEntity from 'ui/shared/entities/tx/TxEntity';
@@ -76,7 +76,7 @@ const AddressCoinBalanceTableItem = (props: Props) => {
loading={ props.isLoading }
/>
</span>
<span className={ isPositiveDelta ? 'text-green-500' : 'text-red-500' }>
<span className={ isPositiveDelta ? 'text-good' : 'text-bad' }>
{ isPositiveDelta ? '\u25B2' : '\u25BC' }
</span>
</div>
@@ -1,6 +1,6 @@
import { Tooltip } from '@luxfi/ui/tooltip';
import React from 'react';
import { Tooltip } from '@luxfi/ui/tooltip';
import IconSvg from 'ui/shared/IconSvg';
const STATUS_MAP = {
@@ -10,11 +10,11 @@ const STATUS_MAP = {
},
success: {
text: 'Contract successfully verified',
leftElement: <IconSvg name="verified" className="w-5 h-5 text-green-500"/>,
leftElement: <IconSvg name="verified" className="w-5 h-5 text-good"/>,
},
failed: {
text: 'Contract not verified automatically. Please verify manually.',
leftElement: <IconSvg name="status/warning" className="w-5 h-5 text-orange-400"/>,
leftElement: <IconSvg name="status/warning" className="w-5 h-5 text-warn"/>,
},
};
@@ -1,14 +1,14 @@
import React from 'react';
import type { SmartContractExternalLibrary } from 'types/api/contract';
import useIsMobile from 'lib/hooks/useIsMobile';
import { Alert } from '@luxfi/ui/alert';
import { Button } from '@luxfi/ui/button';
import { DialogBody, DialogContent, DialogHeader, DialogRoot } from '@luxfi/ui/dialog';
import { Heading } from '@luxfi/ui/heading';
import { PopoverRoot, PopoverBody, PopoverContent, PopoverTrigger } from '@luxfi/ui/popover';
import { Skeleton } from '@luxfi/ui/skeleton';
import React from 'react';
import type { SmartContractExternalLibrary } from 'types/api/contract';
import useIsMobile from 'lib/hooks/useIsMobile';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import { apos } from 'toolkit/utils/htmlEntities';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
@@ -59,8 +59,12 @@ const ContractExternalLibraries = ({ className, data, isLoading }: Props) => {
aria-label="View external libraries"
>
<span>{ data.length } { data.length > 1 ? 'Libraries' : 'Library' } </span>
<IconSvg name="status/warning" className="w-5 h-5 text-orange-400 ml-[2px]"/>
<IconSvg name="arrows/east-mini" className="w-5 h-5 ml-2 transition-transform duration-150" style={{ transform: open ? 'rotate(90deg)' : 'rotate(-90deg)' }}/>
<IconSvg name="status/warning" className="w-5 h-5 text-warn ml-[2px]"/>
<IconSvg
name="arrows/east-mini"
className="w-5 h-5 ml-2 transition-transform duration-150"
style={{ transform: open ? 'rotate(90deg)' : 'rotate(-90deg)' }}
/>
</Button>
);
+1 -1
View File
@@ -78,7 +78,7 @@ const AddressMudBreadcrumbs = (props: TableViewProps | RecordViewProps) => {
) }
style={ !isMobile ? { gridTemplateColumns: '20px auto auto auto' } : undefined }
>
<IconSvg name="MUD" className={ cn('w-5 h-5', addressQuery.data?.is_verified ? 'text-green-500' : 'text-[var(--color-icon-primary)]') }/>
<IconSvg name="MUD" className={ cn('w-5 h-5', addressQuery.data?.is_verified ? 'text-good' : 'text-[var(--color-icon-primary)]') }/>
<BreadcrumbItem
text="MUD World"
href={ route({ pathname: '/address/[hash]', query: queryParams }) }
+2 -2
View File
@@ -6,8 +6,8 @@ import { route } from 'nextjs/routes';
import config from 'configs/app';
import multichainConfig from 'configs/multichain';
import { isConfidentialTokenType, isFungibleTokenType } from 'lib/token/tokenTypes';
import { Link } from 'toolkit/next/link';
import { TruncatedText } from 'toolkit/components/truncation/TruncatedText';
import { Link } from 'toolkit/next/link';
import NativeTokenTag from 'ui/shared/celo/NativeTokenTag';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import calculateUsdValue from 'ui/shared/value/calculateUsdValue';
@@ -102,7 +102,7 @@ const TokenSelectItem = ({ data }: Props) => {
return (
<Link
className="px-1 py-2.5 flex flex-col gap-y-2 border-b border-[var(--color-border-divider)] hover:bg-blue-50 dark:hover:bg-gray-800 text-inherit text-sm"
className="px-1 py-2.5 flex flex-col gap-y-2 border-b border-[var(--color-border-divider)] hover:bg-muted dark:hover:bg-gray-800 text-inherit text-sm"
href={ url }
>
<div className="flex items-center w-full">
+6 -6
View File
@@ -15,9 +15,9 @@ const BlockDetailsQuasarCert = ({ data }: Props) => {
<>
Quasar triple consensus finality certificate.<br/><br/>
Three independent cryptographic verification paths:<br/><br/>
BLS (green) { ndash } BLS12-381 classical fast-path (ECDL hardness).<br/><br/>
Corona (blue) { ndash } Ring-LWE 2-round threshold (Module-LWE hardness, post-quantum).<br/><br/>
ML-DSA (purple) { ndash } ML-DSA-65 FIPS 204 identity proof (Module-LWE + Module-SIS hardness, post-quantum).<br/><br/>
BLS { ndash } BLS12-381 classical fast-path (elliptic-curve discrete-log hardness).<br/><br/>
Corona { ndash } Ring-LWE 2-round threshold (Module-LWE hardness, post-quantum).<br/><br/>
ML-DSA { ndash } ML-DSA-65 FIPS 204 identity proof (Module-LWE + Module-SIS hardness, post-quantum).<br/><br/>
Validators { ndash } number of validators who signed. An adversary must break all three assumptions simultaneously.
</>
);
@@ -31,9 +31,9 @@ const BlockDetailsQuasarCert = ({ data }: Props) => {
</DetailedInfo.ItemLabel>
<DetailedInfo.ItemValue className="flex-wrap">
<div className="flex gap-1 mb-2">
<span className="inline-flex items-center rounded-full bg-green-500/10 text-green-400 px-2 py-0.5 text-xs font-medium">BLS</span>
<span className="inline-flex items-center rounded-full bg-blue-500/10 text-blue-400 px-2 py-0.5 text-xs font-medium">Corona</span>
<span className="inline-flex items-center rounded-full bg-purple-500/10 text-purple-400 px-2 py-0.5 text-xs font-medium">ML-DSA</span>
<span className="inline-flex items-center rounded-full bg-good/10 text-good px-2 py-0.5 text-xs font-medium">BLS</span>
<span className="inline-flex items-center rounded-full bg-muted text-foreground px-2 py-0.5 text-xs font-medium">Corona</span>
<span className="inline-flex items-center rounded-full bg-muted text-foreground px-2 py-0.5 text-xs font-medium">ML-DSA</span>
</div>
<div className="grid grid-cols-[min-content_1fr] gap-x-5 w-full mt-1 text-sm">
<span className="font-semibold whitespace-nowrap">BLS aggregate</span>
+1 -1
View File
@@ -80,7 +80,7 @@ const ChainPairCard = ({ source, destination, status }: ChainPairCardProps) => (
</div>
<div className="ml-0 lg:ml-auto flex items-center">
{ status === 'active' ? (
<Tag size="sm" variant="subtle" className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">Active</Tag>
<Tag size="sm" variant="subtle" className="bg-good/10 text-good dark:bg-good/10 dark:text-good">Active</Tag>
) : (
<Tag size="sm" variant="subtle">Coming Soon</Tag>
) }
+1 -1
View File
@@ -170,7 +170,7 @@ const ValidatorRow = ({ validator, index }: ValidatorRowProps) => {
</span>
</div>
<div className="w-10 shrink-0 text-center">
<div className={ cn('w-2 h-2 rounded-full inline-block', validator.connected ? 'bg-green-400' : 'bg-gray-400') }/>
<div className={ cn('w-2 h-2 rounded-full inline-block', validator.connected ? 'bg-good' : 'bg-gray-400') }/>
</div>
</div>
);
+1 -1
View File
@@ -98,7 +98,7 @@ const ChainRow = ({
{ /* Status indicator + arrow */ }
<div className="flex items-center gap-2 shrink-0 ml-0 lg:ml-auto">
<div className={ cn('w-2 h-2 rounded-full', isActive ? 'bg-green-400' : 'bg-gray-400') }/>
<div className={ cn('w-2 h-2 rounded-full', isActive ? 'bg-good' : 'bg-gray-400') }/>
{ href && (
<div className="text-[var(--color-text-secondary)] text-sm">{ '\u2192' }</div>
) }
+1 -1
View File
@@ -12,7 +12,7 @@ const LatestBlocksFallback = () => {
<div key={ index } className="w-full p-3 rounded-md border border-[var(--color-border-divider)]">
<div className="flex items-center w-full">
<IconSvg name="block" className="w-5 h-5 text-gray-300 dark:text-white/30"/>
<FallbackBox className="w-[100px] bg-blue-50 dark:bg-blue-800 ml-2"/>
<FallbackBox className="w-[100px] bg-muted dark:bg-muted ml-2"/>
<FallbackBox className="w-[50px] ml-auto"/>
</div>
<FallbackBox className="w-full mt-2"/>
+3 -3
View File
@@ -1,6 +1,6 @@
import { TableBody, TableCell, TableRoot, TableRow } from '@luxfi/ui/table';
import React from 'react';
import { TableBody, TableCell, TableRoot, TableRow } from '@luxfi/ui/table';
import FallbackBox from 'ui/shared/fallbacks/FallbackBox';
import IconSvg from 'ui/shared/IconSvg';
@@ -16,8 +16,8 @@ const LatestTxsFallback = (props: React.HTMLAttributes<HTMLDivElement>) => {
<div className="flex flex-col items-stretch">
<div className="flex flex-row items-center">
<IconSvg name="transactions" className="w-5 h-5 text-gray-300 dark:text-white/30"/>
<FallbackBox className="w-[90px] bg-blue-50 dark:bg-blue-800"/>
<FallbackBox className="w-[90px] bg-purple-50 dark:bg-purple-800"/>
<FallbackBox className="w-[90px] bg-muted dark:bg-muted"/>
<FallbackBox className="w-[90px] bg-muted dark:bg-muted"/>
</div>
<FallbackBox className="w-full"/>
</div>
+2 -2
View File
@@ -1,10 +1,10 @@
import { Skeleton } from '@luxfi/ui/skeleton';
import React from 'react';
import type { TChainIndicator } from './types';
import type { ChainIndicatorId } from 'types/homepage';
import { cn } from 'lib/utils/cn';
import { Skeleton } from '@luxfi/ui/skeleton';
import { mdash } from 'toolkit/utils/htmlEntities';
interface Props {
indicator: TChainIndicator;
@@ -40,7 +40,7 @@ const ChainIndicatorItem = ({ indicator, isSelected, onClick, isLoading }: Props
return (
<Skeleton loading={ isLoading } ml={ 1 } display="flex" alignItems="center" color={ diffColor }>
<span>{ indicator.valueDiff >= 0 ? '+' : '-' }</span>
<span className={ cn('font-semibold', indicator.valueDiff >= 0 ? 'text-green-500' : 'text-red-500') }>{ Math.abs(indicator.valueDiff) }%</span>
<span className={ cn('font-semibold', indicator.valueDiff >= 0 ? 'text-good' : 'text-bad') }>{ Math.abs(indicator.valueDiff) }%</span>
</Skeleton>
);
})();
+2 -2
View File
@@ -1,6 +1,6 @@
import { Skeleton } from '@luxfi/ui/skeleton';
import React from 'react';
import { Skeleton } from '@luxfi/ui/skeleton';
import { Hint } from 'toolkit/components/Hint/Hint';
import { mdash } from 'toolkit/utils/htmlEntities';
import FallbackChart from 'ui/shared/fallbacks/FallbackChart';
@@ -47,7 +47,7 @@ const ChainIndicatorsChart = ({ isLoading: isLoadingProp, value, valueDiff, char
return (
<Skeleton loading={ isLoading } display="flex" alignItems="center" color={ diffColor } ml={ 2 }>
<IconSvg name="arrows/up-head" className={ `w-5 h-5 mr-1 ${ valueDiff < 0 ? 'rotate-180' : '' }` }/>
<span className={ `font-semibold ${ valueDiff >= 0 ? 'text-green-500' : 'text-red-500' }` }>{ valueDiff }%</span>
<span className={ `font-semibold ${ valueDiff >= 0 ? 'text-good' : 'text-bad' }` }>{ valueDiff }%</span>
</Skeleton>
);
})();
+5 -5
View File
@@ -1,3 +1,7 @@
import { Heading } from '@luxfi/ui/heading';
import { IconButton } from '@luxfi/ui/icon-button';
import { Image } from '@luxfi/ui/image';
import { Skeleton } from '@luxfi/ui/skeleton';
import type { MouseEvent } from 'react';
import React, { useCallback } from 'react';
@@ -6,11 +10,7 @@ import type { MarketplaceApp } from 'types/client/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile';
import * as mixpanel from 'lib/mixpanel/index';
import { useColorModeValue } from 'toolkit/next/color-mode';
import { Heading } from '@luxfi/ui/heading';
import { IconButton } from '@luxfi/ui/icon-button';
import { Image } from '@luxfi/ui/image';
import { Link, LinkBox } from 'toolkit/next/link';
import { Skeleton } from '@luxfi/ui/skeleton';
import FavoriteIcon from '../FavoriteIcon';
import MarketplaceAppCardLink from '../MarketplaceAppCardLink';
@@ -61,7 +61,7 @@ const FeaturedApp = ({
return (
<LinkBox>
<div className="flex gap-4 rounded-md h-[100px] p-3 bg-purple-50 dark:bg-white/10">
<div className="flex gap-4 rounded-md h-[100px] p-3 bg-muted dark:bg-white/10">
<Skeleton
loading={ isLoading }
w="76px"
@@ -1,6 +1,6 @@
import { Tooltip } from '@luxfi/ui/tooltip';
import React from 'react';
import { Tooltip } from '@luxfi/ui/tooltip';
import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg';
@@ -23,7 +23,7 @@ const MarketplaceAppIntegrationIcon = ({ external, internalWallet }: Props) => {
size = 'w-4 h-4';
} else if (internalWallet) {
icon = 'integration/full';
color = 'text-green-500';
color = 'text-good';
text = 'This app opens in the explorer and your wallet connects automatically';
}
+7 -4
View File
@@ -1,3 +1,5 @@
import { Rating } from '@luxfi/ui/rating';
import { toaster } from '@luxfi/ui/toaster';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
@@ -5,8 +7,6 @@ import config from 'configs/app';
import useApiFetch from 'lib/api/useApiFetch';
import type { EventTypes, EventPayload } from 'lib/mixpanel/index';
import * as mixpanel from 'lib/mixpanel/index';
import { Rating } from '@luxfi/ui/rating';
import { toaster } from '@luxfi/ui/toaster';
import IconSvg from 'ui/shared/IconSvg';
const ratingDescriptions = [ 'Very bad', 'Bad', 'Average', 'Good', 'Excellent' ];
@@ -20,7 +20,10 @@ const PopoverContent = ({ appId, userRating, source }: Props) => {
const handleValueChange = React.useCallback(async({ value }: { value: number }) => {
setIsSending(true);
try {
await apiFetch('admin:marketplace_rate_dapp', { pathParams: { chainId: config.chain.id, dappId: appId }, fetchParams: { method: 'POST', body: { rating: value } } });
await apiFetch('admin:marketplace_rate_dapp', {
pathParams: { chainId: config.chain.id, dappId: appId },
fetchParams: { method: 'POST', body: { rating: value } },
});
setRatingValue(value);
queryClient.invalidateQueries({ queryKey: [ 'marketplace-dapps' ] });
toaster.success({ title: 'Awesome! Thank you 💜', description: 'Your rating improves the service' });
@@ -43,7 +46,7 @@ const PopoverContent = ({ appId, userRating, source }: Props) => {
return (
<>
<div className="flex items-center h-[30px]">
{ ratingValue && <IconSvg name="navigation/verified_contracts" className="w-[30px] h-[30px] text-green-400 mr-1 -ml-[5px]"/> }
{ ratingValue && <IconSvg name="navigation/verified_contracts" className="w-[30px] h-[30px] text-good mr-1 -ml-[5px]"/> }
<span className="font-medium text-xs text-[var(--color-text-secondary)]">
{ ratingValue ? 'App is already rated by you' : 'How was your experience?' }
</span>
+26 -8
View File
@@ -1,21 +1,23 @@
import type { ButtonProps } from '@luxfi/ui/button';
import { Button } from '@luxfi/ui/button';
import { PopoverTrigger } from '@luxfi/ui/popover';
import { Tooltip } from '@luxfi/ui/tooltip';
import React from 'react';
import { getFeaturePayload } from 'configs/app/features/types';
import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile';
import { cn } from 'lib/utils/cn';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
import type { ButtonProps } from '@luxfi/ui/button';
import { Button } from '@luxfi/ui/button';
import { PopoverTrigger } from '@luxfi/ui/popover';
import { Tooltip } from '@luxfi/ui/tooltip';
import { cn } from 'lib/utils/cn';
import IconSvg from 'ui/shared/IconSvg';
interface Props extends ButtonProps { rating?: number; count?: number; fullView?: boolean; canRate: boolean }
const getTooltipText = (canRate: boolean) => {
if (!canRate) { return <>Please log in to rate this { (getFeaturePayload(config.features.marketplace)?.titles.entity_name ?? '').toLowerCase() }.</>; }
if (!canRate) {
return <>Please log in to rate this { (getFeaturePayload(config.features.marketplace)?.titles.entity_name ?? '').toLowerCase() }.</>;
}
return <>Ratings come from verified users.<br/>Click here to rate!</>;
};
@@ -30,8 +32,24 @@ const TriggerButton = (
<Tooltip content={ getTooltipText(canRate) } closeOnClick={ Boolean(canRate) || isMobile } disableOnMobile={ canRate }>
<div>
<PopoverTrigger>
<Button ref={ ref } size="xs" variant="link" className={ cn('p-0 leading-[21px]', fullView ? 'text-base font-normal ml-3' : 'text-sm font-medium ml-0', canRate ? 'cursor-pointer' : 'cursor-default') } onFocusCapture={ onFocusCapture } { ...rest }>
{ !fullView && <IconSvg name={ rating ? 'star_filled' : 'star_outline' } className={ cn('w-5 h-5 mr-1', rating ? 'text-yellow-400' : 'text-[var(--color-icon-secondary)]') }/> }
<Button
ref={ ref }
size="xs"
variant="link"
className={ cn(
'p-0 leading-[21px]',
fullView ? 'text-base font-normal ml-3' : 'text-sm font-medium ml-0',
canRate ? 'cursor-pointer' : 'cursor-default',
) }
onFocusCapture={ onFocusCapture }
{ ...rest }
>
{ !fullView && (
<IconSvg
name={ rating ? 'star_filled' : 'star_outline' }
className={ cn('w-5 h-5 mr-1', rating ? 'text-warn' : 'text-[var(--color-icon-secondary)]') }
/>
) }
{ (rating && !fullView) ? (
<span className="text-[var(--color-text-primary)] transition-[inherit] inline-flex">
{ rating }
@@ -1,11 +1,11 @@
import { Skeleton } from '@luxfi/ui/skeleton';
import React from 'react';
import type * as multichain from '@luxfi/multichain-aggregator-types';
import type { ClusterChainConfig } from 'types/multichain';
import { Link } from 'toolkit/next/link';
import { Skeleton } from '@luxfi/ui/skeleton';
import { TruncatedText } from 'toolkit/components/truncation/TruncatedText';
import { Link } from 'toolkit/next/link';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import ChainIcon from 'ui/shared/externalChains/ChainIcon';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
@@ -19,7 +19,7 @@ interface Props {
const DeltaIndicator = ({ delta }: { delta: number }) => {
const isPositive = delta > 0;
return (
<span className={ `inline-flex items-center gap-0.5 ${ isPositive ? 'text-green-500' : 'text-red-500' }` }>
<span className={ `inline-flex items-center gap-0.5 ${ isPositive ? 'text-good' : 'text-bad' }` }>
<span>{ delta.toFixed(2) }%</span>
<span>{ isPositive ? '\u25B2' : '\u25BC' }</span>
</span>
@@ -25,7 +25,7 @@ interface Props {
const DeltaIndicator = ({ delta }: { delta: number }) => {
const isPositive = delta > 0;
return (
<span className={ `inline-flex items-center gap-0.5 ${ isPositive ? 'text-green-500' : 'text-red-500' }` }>
<span className={ `inline-flex items-center gap-0.5 ${ isPositive ? 'text-good' : 'text-bad' }` }>
<span>{ delta.toFixed(2) }%</span>
<span>{ isPositive ? '\u25B2' : '\u25BC' }</span>
</span>
@@ -54,7 +54,7 @@ const SearchResultItemToken = ({ data, chain, isMobile }: Props) => {
<HashStringShortenDynamic hash={ data.address_hash }/>
) }
</div>
{ isVerified && <IconSvg name="status/success" className="w-3.5 h-3.5 text-green-500 ml-1 shrink-0"/> }
{ isVerified && <IconSvg name="status/success" className="w-3.5 h-3.5 text-good ml-1 shrink-0"/> }
<span
className="overflow-hidden whitespace-nowrap text-ellipsis font-normal lg:font-semibold ml-auto max-w-[60%] lg:max-w-none"
>
+1 -1
View File
@@ -22,7 +22,7 @@ const MyProfileFieldsEmail = ({ isReadOnly, defaultValue }: Props) => {
group={{
endElement: ({ field }) => {
const isVerified = defaultValue && field.value === defaultValue;
return isVerified ? <IconSvg name="certified" className="w-5 h-5 text-green-500 mx-5"/> : null;
return isVerified ? <IconSvg name="certified" className="w-5 h-5 text-good mx-5"/> : null;
},
}}
/>
+2 -2
View File
@@ -14,12 +14,12 @@ const NameDomainExpiryStatus = ({ date }: Props) => {
const hasExpired = dayjs(date).isBefore(dayjs());
if (hasExpired) {
return <span className="text-red-600">Expired</span>;
return <span className="text-bad">Expired</span>;
}
const diff = dayjs(date).diff(dayjs(), 'day');
if (diff < 30) {
return <span className="text-red-600">{ diff } days left</span>;
return <span className="text-bad">{ diff } days left</span>;
}
return <span className="text-[var(--color-text-secondary)]">Expires { dayjs(date).fromNow() }</span>;
+6 -6
View File
@@ -1,3 +1,7 @@
import { Alert } from '@luxfi/ui/alert';
import { Button } from '@luxfi/ui/button';
import { Textarea } from '@luxfi/ui/textarea';
import { toaster } from '@luxfi/ui/toaster';
import mixpanel from 'mixpanel-browser';
import type { ChangeEvent } from 'react';
import React from 'react';
@@ -6,10 +10,6 @@ import config from 'configs/app';
import * as cookies from 'lib/cookies';
import useGradualIncrement from 'lib/hooks/useGradualIncrement';
import { useRollbar } from 'lib/rollbar';
import { Alert } from '@luxfi/ui/alert';
import { Button } from '@luxfi/ui/button';
import { Textarea } from '@luxfi/ui/textarea';
import { toaster } from '@luxfi/ui/toaster';
import PageTitle from 'ui/shared/Page/PageTitle';
const Login = () => {
@@ -77,8 +77,8 @@ const Login = () => {
</>
) }
<div className="flex gap-x-2">
<Button className="bg-red-600 text-white hover:bg-red-500" onClick={ checkRollbar }>Check Rollbar</Button>
<Button className="bg-teal-600 text-white hover:bg-teal-500" onClick={ checkMixpanel }>Check Mixpanel</Button>
<Button className="bg-bad text-white hover:bg-bad" onClick={ checkRollbar }>Check Rollbar</Button>
<Button className="bg-muted text-white hover:bg-muted" onClick={ checkMixpanel }>Check Mixpanel</Button>
</div>
<div className="flex items-center gap-x-2">
<div className="text-center w-[50px]">{ num }</div>
@@ -46,9 +46,9 @@ const PublicTagsSubmitFieldTag = ({ index, isDisabled, errors, onAddClick, onRem
<>
<div
className={ `col-span-1 lg:col-span-2 p-[10px] rounded-base ${
errors
? 'bg-red-50 dark:bg-red-900'
: 'bg-[var(--color-blackAlpha-50)] dark:bg-[var(--color-whiteAlpha-100)]'
errors ?
'bg-bad dark:bg-bad' :
'bg-[var(--color-blackAlpha-50)] dark:bg-[var(--color-whiteAlpha-100)]'
}` }
>
<div className="grid gap-3 grid-cols-1 lg:grid-cols-4">
@@ -1,3 +1,4 @@
import { Button } from '@luxfi/ui/button';
import { pickBy } from 'es-toolkit';
import React from 'react';
@@ -6,7 +7,6 @@ import type { FormSubmitResultGrouped } from '../types';
import { route } from 'nextjs-routes';
import useIsMobile from 'lib/hooks/useIsMobile';
import { Button } from '@luxfi/ui/button';
import { Link } from 'toolkit/next/link';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import EntityTag from 'ui/shared/EntityTags/EntityTag';
@@ -34,9 +34,9 @@ const PublicTagsSubmitResultWithErrors = ({ data }: Props) => {
<div className="grow">
<div
className={ `grid grid-cols-1 lg:grid-cols-2 gap-y-3 rounded-base ${
item.error
? 'bg-red-50 dark:bg-red-800'
: 'bg-green-50 dark:bg-green-800'
item.error ?
'bg-bad dark:bg-bad' :
'bg-good dark:bg-good'
}` }
>
<div className="px-4 lg:px-6 pt-2 lg:pt-4 pb-0 lg:pb-4 overflow-hidden">
@@ -65,7 +65,7 @@ const PublicTagsSubmitResultWithErrors = ({ data }: Props) => {
</div>
</div>
</div>
{ item.error && <div className="text-red-500 mt-1 text-sm">{ item.error }</div> }
{ item.error && <div className="text-bad mt-1 text-sm">{ item.error }</div> }
</div>
{ item.error && (
<Link
@@ -1,8 +1,9 @@
import { Image } from '@luxfi/ui/image';
import { Progress } from '@luxfi/ui/progress';
import type { GetAvailableBadgesResponse } from '@luxfi/points-types';
import { Image } from '@luxfi/ui/image';
import { Link } from 'toolkit/next/link';
import { Progress } from '@luxfi/ui/progress';
const BADGE_BG_COLORS = [ '#DFE8F5', '#D2E5FE', '#EFE1FF' ];
@@ -47,7 +48,7 @@ export default function BadgeCard({ badge, currentStreak, index }: Props) {
{ (() => {
if (badge.is_minted) {
return (
<span className="text-xs text-green-500">
<span className="text-xs text-good">
Minted
</span>
);
@@ -1,6 +1,6 @@
import { Progress } from '@luxfi/ui/progress';
import { clamp } from 'es-toolkit';
import { Progress } from '@luxfi/ui/progress';
import IconSvg from 'ui/shared/IconSvg';
type Props = {
@@ -33,7 +33,7 @@ export default function ProgressSegment({ value, target, prevTarget, isFirst }:
<div className="flex h-[32px] lg:h-[40px] items-center">
<div
className={ `flex w-[40px] h-[32px] rounded-lg items-center justify-center z-[1] ${
isDone ? 'bg-green-400' : 'bg-[var(--color-progress-track)]'
isDone ? 'bg-good' : 'bg-[var(--color-progress-track)]'
}` }
>
{ isDone ? (
+10 -8
View File
@@ -1,10 +1,10 @@
import { Button } from '@luxfi/ui/button';
import { Skeleton } from '@luxfi/ui/skeleton';
import React from 'react';
import { route } from 'nextjs-routes';
import { useRewardsContext } from 'lib/contexts/rewards';
import { Button } from '@luxfi/ui/button';
import { Skeleton } from '@luxfi/ui/skeleton';
import IconSvg from 'ui/shared/IconSvg';
import MeritsIcon from '../../MeritsIcon';
@@ -25,7 +25,9 @@ const CongratsStepContent = ({ isReferral, customReferralReward }: Props) => {
const referralReward = registrationWithReferralReward - registrationReward;
const refLink = referralsQuery.data?.link || 'N/A';
const shareText = `I joined the @luxnetwork Rewards Program and got my first ${ registrationReward || 'N/A' } #Merits! Use this link for a sign-up bonus and start earning rewards with @luxnetwork block explorer.\n\n${ refLink }`; // eslint-disable-line max-len
const shareText =
`I joined the @luxnetwork Rewards Program and got my first ${ registrationReward || 'N/A' } #Merits! Use this` +
` link for a sign-up bonus and start earning rewards with @luxnetwork block explorer.\n\n${ refLink }`;
return (
<>
@@ -38,7 +40,7 @@ const CongratsStepContent = ({ isReferral, customReferralReward }: Props) => {
>
<MeritsIcon className={ `w-12 h-12 mr-2 ${ isReferral ? 'w-8 md:w-12 h-8 md:h-12 mr-1 md:mr-2' : '' }` }/>
<Skeleton loading={ rewardsConfigQuery.isLoading }>
<span className="text-[30px] font-bold text-blue-700 dark:text-blue-100" style={{ fontSize: isReferral ? undefined : '30px' }}>
<span className="text-[30px] font-bold text-foreground dark:text-foreground" style={{ fontSize: isReferral ? undefined : '30px' }}>
+{ (isReferral ? registrationWithReferralReward : registrationReward) || 'N/A' }
</span>
</Skeleton>
@@ -59,11 +61,11 @@ const CongratsStepContent = ({ isReferral, customReferralReward }: Props) => {
<div key={ title } className="flex items-center gap-1 md:gap-2">
<MeritsIcon className="w-5 md:w-6 h-5 md:h-6"/>
<Skeleton loading={ rewardsConfigQuery.isLoading }>
<span className="text-sm font-bold text-blue-700 dark:text-blue-100">
<span className="text-sm font-bold text-foreground dark:text-foreground">
+{ value }
</span>
</Skeleton>
<span className="text-sm text-blue-700 dark:text-blue-100">
<span className="text-sm text-foreground dark:text-foreground">
{ title }
</span>
</div>
@@ -74,7 +76,7 @@ const CongratsStepContent = ({ isReferral, customReferralReward }: Props) => {
</div>
<div className="flex flex-col items-start px-3 mb-8">
<div className="flex items-center gap-2">
<div className="flex items-center justify-center w-8 h-8 rounded-[8px] text-blue-500 dark:text-blue-100 bg-blue-50 dark:bg-blue-800">
<div className="flex items-center justify-center w-8 h-8 rounded-[8px] text-foreground dark:text-foreground bg-muted dark:bg-muted">
<IconSvg name="profile" className="w-5 h-5"/>
</div>
<span className="text-lg font-medium">
@@ -107,7 +109,7 @@ const CongratsStepContent = ({ isReferral, customReferralReward }: Props) => {
</div>
<div className="flex flex-col items-start px-3">
<div className="flex items-center gap-2">
<div className="flex items-center justify-center w-8 h-8 rounded-[8px] text-blue-500 dark:text-blue-100 bg-blue-50 dark:bg-blue-800">
<div className="flex items-center justify-center w-8 h-8 rounded-[8px] text-foreground dark:text-foreground bg-muted dark:bg-muted">
{ /* FIXME use non-navigation icon */ }
<IconSvg name="navigation/stats" className="w-6 h-6"/>
</div>
+17 -11
View File
@@ -1,3 +1,6 @@
import { Image } from '@luxfi/ui/image';
import { Skeleton } from '@luxfi/ui/skeleton';
import { Tag } from '@luxfi/ui/tag';
import React from 'react';
import xss from 'xss';
@@ -12,10 +15,7 @@ import highlightText from 'lib/highlightText';
import * as mixpanel from 'lib/mixpanel/index';
import { saveToRecentKeywords } from 'lib/recentSearchKeywords';
import { useColorMode } from 'toolkit/next/color-mode';
import { Image } from '@luxfi/ui/image';
import { Link } from 'toolkit/next/link';
import { Skeleton } from '@luxfi/ui/skeleton';
import { Tag } from '@luxfi/ui/tag';
import { SECOND } from 'toolkit/utils/consts';
import { ADDRESS_REGEXP } from 'toolkit/utils/regexp';
import ContractCertifiedLabel from 'ui/shared/ContractCertifiedLabel';
@@ -76,10 +76,10 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
dangerouslySetInnerHTML={{ __html: highlightText(name, searchTerm) }}
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis" />
textOverflow="ellipsis"/>
</Link>
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } className="ml-1"/> }
{ data.is_verified_via_admin_panel && !data.certified && <IconSvg name="certified" className="w-4 h-4 ml-1 text-green-500"/> }
{ data.is_verified_via_admin_panel && !data.certified && <IconSvg name="certified" className="w-4 h-4 ml-1 text-good"/> }
{ data.reputation && <TokenEntity.Reputation value={ data.reputation }/> }
</div>
);
@@ -325,7 +325,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
<span className="whitespace-nowrap overflow-hidden">
<HashStringShortenDynamic hash={ hash } noTooltip/>
</span>
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-green-500 ml-1 shrink-0"/> }
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-good ml-1 shrink-0"/> }
</Skeleton>
<Skeleton loading={ isLoading } overflow="hidden" whiteSpace="nowrap" textOverflow="ellipsis" fontWeight={ 700 }>
{ data.token_type === 'ERC-20' && data.exchange_rate && `$${ Number(data.exchange_rate).toLocaleString() }` }
@@ -382,7 +382,7 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
<div className="overflow-hidden">
<HashStringShortenDynamic hash={ hash }/>
</div>
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-green-500 ml-1 shrink-0"/> }
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-good ml-1 shrink-0"/> }
</div>
);
}
@@ -407,8 +407,11 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
<span className="overflow-hidden whitespace-nowrap text-ellipsis">
<span dangerouslySetInnerHTML={{ __html: shouldHighlightHash ? xss(addressName) : highlightText(addressName, searchTerm) }}/>
{ data.ens_info && (
data.ens_info.names_count > 1 ?
<span className="text-[var(--color-text-secondary)]"> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</span> :
data.ens_info.names_count > 1 ? (
<span
className="text-[var(--color-text-secondary)]"
> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</span>
) :
<span className="text-[var(--color-text-secondary)]">{ expiresText }</span>
) }
</span>
@@ -434,8 +437,11 @@ const SearchResultListItem = ({ data, searchTerm, isLoading, addressFormat }: Pr
</div>
) }
{
data.ens_info.names_count > 1 ?
<span className="text-[var(--color-text-secondary)]"> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</span> :
data.ens_info.names_count > 1 ? (
<span
className="text-[var(--color-text-secondary)]"
> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</span>
) :
<span className="text-[var(--color-text-secondary)]">{ expiresText }</span>
}
</div>
+13 -10
View File
@@ -1,3 +1,7 @@
import { Image } from '@luxfi/ui/image';
import { Skeleton } from '@luxfi/ui/skeleton';
import { TableCell, TableRow } from '@luxfi/ui/table';
import { Tag } from '@luxfi/ui/tag';
import React from 'react';
import xss from 'xss';
@@ -12,11 +16,7 @@ import highlightText from 'lib/highlightText';
import * as mixpanel from 'lib/mixpanel/index';
import { saveToRecentKeywords } from 'lib/recentSearchKeywords';
import { useColorMode } from 'toolkit/next/color-mode';
import { Image } from '@luxfi/ui/image';
import { Link } from 'toolkit/next/link';
import { Skeleton } from '@luxfi/ui/skeleton';
import { TableCell, TableRow } from '@luxfi/ui/table';
import { Tag } from '@luxfi/ui/tag';
import { SECOND } from 'toolkit/utils/consts';
import { ADDRESS_REGEXP } from 'toolkit/utils/regexp';
import ContractCertifiedLabel from 'ui/shared/ContractCertifiedLabel';
@@ -83,7 +83,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
/>
</Link>
{ data.certified && <ContractCertifiedLabel iconSize={ 4 } className="ml-1"/> }
{ data.is_verified_via_admin_panel && !data.certified && <IconSvg name="certified" className="w-4 h-4 ml-1 text-green-500"/> }
{ data.is_verified_via_admin_panel && !data.certified && <IconSvg name="certified" className="w-4 h-4 ml-1 text-good"/> }
{ data.reputation && <TokenEntity.Reputation value={ data.reputation }/> }
</div>
</TableCell>
@@ -92,7 +92,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
<div className="overflow-hidden whitespace-nowrap" style={{ width: data.is_smart_contract_verified ? 'calc(100%-28px)' : 'unset' }}>
<HashStringShortenDynamic hash={ hash }/>
</div>
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-green-500 ml-1 shrink-0"/> }
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-good ml-1 shrink-0"/> }
</Skeleton>
</TableCell>
<TableCell verticalAlign="middle" isNumeric>
@@ -194,7 +194,7 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
<div className="overflow-hidden whitespace-nowrap" style={{ width: data.is_smart_contract_verified ? 'calc(100%-28px)' : 'unset' }}>
<HashStringShortenDynamic hash={ hash }/>
</div>
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-green-500 ml-1 shrink-0"/> }
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-good ml-1 shrink-0"/> }
</div>
</TableCell>
<TableCell/>
@@ -449,12 +449,15 @@ const SearchResultTableItem = ({ data, searchTerm, isLoading, addressFormat }: P
<HashStringShortenDynamic hash={ hash }/>
</div>
) }
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-green-500 ml-1 shrink-0"/> }
{ data.is_smart_contract_verified && <IconSvg name="status/success" className="w-[14px] h-[14px] text-good ml-1 shrink-0"/> }
</div>
</TableCell>
<TableCell>
{ data.ens_info.names_count > 1 ?
<span className="text-[var(--color-text-secondary)]"> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</span> :
{ data.ens_info.names_count > 1 ? (
<span
className="text-[var(--color-text-secondary)]"
> ({ data.ens_info.names_count > 39 ? '40+' : `+${ data.ens_info.names_count - 1 }` })</span>
) :
<span className="text-[var(--color-text-secondary)]">{ expiresText }</span> }
</TableCell>
</>
@@ -32,7 +32,7 @@ test.describe('with multiple items', () => {
});
test('base view with styles', async({ render, page }) => {
const component = await render(<AccountActionsMenu className="m-2 outline outline-1 outline-pink-300"/>, { hooksConfig });
const component = await render(<AccountActionsMenu className="m-2 outline outline-1 outline-border"/>, { hooksConfig });
await component.getByRole('button').click();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 200, height: 200 } });
@@ -45,7 +45,7 @@ test.describe('with multiple items', () => {
});
test('loading with styles', async({ render }) => {
const component = await render(<AccountActionsMenu isLoading className="m-2 outline outline-1 outline-pink-300"/>, { hooksConfig });
const component = await render(<AccountActionsMenu isLoading className="m-2 outline outline-1 outline-border"/>, { hooksConfig });
await expect(component).toHaveScreenshot();
});
@@ -68,7 +68,7 @@ test.describe('with one item', () => {
});
test('base view with styles', async({ render, page }) => {
const component = await render(<AccountActionsMenu className="m-2 outline outline-1 outline-pink-300"/>, { hooksConfig });
const component = await render(<AccountActionsMenu className="m-2 outline outline-1 outline-border"/>, { hooksConfig });
await component.getByRole('button').hover();
await expect(page.getByText('Add private tag')).toBeVisible();
await expect(page).toHaveScreenshot({ clip: { x: 0, y: 0, width: 200, height: 200 } });
+2 -3
View File
@@ -1,6 +1,5 @@
import React from 'react';
import { Tooltip } from '@luxfi/ui/tooltip';
import React from 'react';
import IconSvg from './IconSvg';
@@ -13,7 +12,7 @@ const ContractCertifiedLabel = ({ iconSize, className }: Props) => {
return (
<Tooltip content="This contract has been certified by the chain developers">
<span className={ `inline-flex ${ className || '' }` }>
<IconSvg name="certified" className="text-green-500 cursor-pointer" style={{ width: `${ iconSize * 4 }px`, height: `${ iconSize * 4 }px` }}/>
<IconSvg name="certified" className="text-good cursor-pointer" style={{ width: `${ iconSize * 4 }px`, height: `${ iconSize * 4 }px` }}/>
</span>
</Tooltip>
);
+3 -3
View File
@@ -1,7 +1,7 @@
import { Tooltip } from '@luxfi/ui/tooltip';
import React from 'react';
import { cn } from 'lib/utils/cn';
import { Tooltip } from '@luxfi/ui/tooltip';
import IconSvg from 'ui/shared/IconSvg';
import type { TxCourseType } from './utils';
@@ -13,8 +13,8 @@ interface Props {
}
const TYPE_CLASSES: Record<TxCourseType, string> = {
'in': 'text-green-500 dark:text-green-200 bg-green-50 dark:bg-green-800',
out: 'text-yellow-600 dark:text-yellow-500 bg-orange-50 dark:bg-yellow-900',
'in': 'text-good dark:text-good bg-good/10 dark:bg-good/10',
out: 'text-warn dark:text-warn bg-warn/10 dark:bg-warn/10',
self: 'text-[var(--color-blackAlpha-400)] dark:text-[var(--color-whiteAlpha-400)] bg-[var(--color-blackAlpha-50)] dark:bg-[var(--color-whiteAlpha-50)]',
unspecified: 'text-[var(--color-icon-primary)] bg-transparent',
};
@@ -261,7 +261,7 @@ test('customization', async({ render }) => {
<AddressEntity
address={ addressMock.withoutName }
truncation="constant"
className="p-3 border border-blue-700"
className="p-3 border border-border"
/>,
);
@@ -271,11 +271,11 @@ test('customization', async({ render }) => {
test('hover', async({ page, render }) => {
const component = await render(
<AddressHighlightProvider>
<Box p={ 3 }>
<div style={{ padding: 12 }}>
<AddressEntity
address={ addressMock.withoutName }
/>
</Box>
</div>
</AddressHighlightProvider>,
);
@@ -7,7 +7,7 @@ interface Props {
const AddressIconDelegated = ({ isVerified }: Props) => {
return (
<div
className={ `absolute w-[14px] h-[14px] -top-[2px] -right-[2px] ${ isVerified ? 'text-green-500' : 'text-[var(--color-icon-primary)]' }` }
className={ `absolute w-[14px] h-[14px] -top-[2px] -right-[2px] ${ isVerified ? 'text-good' : 'text-[var(--color-icon-primary)]' }` }
>
<svg
viewBox="0 0 14 14"
+1 -1
View File
@@ -61,7 +61,7 @@ test('customization', async({ render }) => {
const component = await render(
<BlockEntity
number={ 17943507 }
className="p-3 border border-blue-700"
className="p-3 border border-border"
/>,
);
+1 -1
View File
@@ -53,7 +53,7 @@ test('customization', async({ render }) => {
const component = await render(
<EnsEntity
domain={ name }
className="p-3 border border-blue-700"
className="p-3 border border-border"
/>,
);
+1 -1
View File
@@ -53,7 +53,7 @@ test('customization', async({ render }) => {
<NftEntity
hash={ hash }
id="1042"
className="p-3 border border-blue-700"
className="p-3 border border-border"
/>,
);
+2 -3
View File
@@ -1,4 +1,3 @@
import React from 'react';
import * as chainDataMock from 'mocks/multichain/chains';
@@ -136,10 +135,10 @@ test('loading', async({ render }) => {
test('customization', async({ render }) => {
const component = await render(
<div className="border border-orange-500">
<div className="border border-warn/30">
<TokenEntity
token={ tokenMock.tokenInfo }
className="p-2 max-w-[200px] border border-blue-700"
className="p-2 max-w-[200px] border border-border"
/>
</div>,
);
+4 -4
View File
@@ -1,3 +1,5 @@
import { Skeleton } from '@luxfi/ui/skeleton';
import { Tooltip } from '@luxfi/ui/tooltip';
import React from 'react';
import type { TokenInfo } from 'types/api/token';
@@ -5,10 +7,8 @@ import type { TokenInfo } from 'types/api/token';
import { route } from 'nextjs/routes';
import config from 'configs/app';
import { cn } from 'lib/utils/cn';
import { useMultichainContext } from 'lib/contexts/multichain';
import { Skeleton } from '@luxfi/ui/skeleton';
import { Tooltip } from '@luxfi/ui/tooltip';
import { cn } from 'lib/utils/cn';
import { TruncatedTextTooltip } from 'toolkit/components/truncation/TruncatedTextTooltip';
import * as EntityBase from 'ui/shared/entities/base/components';
import getChainTooltipText from 'ui/shared/externalChains/getChainTooltipText';
@@ -163,7 +163,7 @@ const TestTokenBadge = ({ addressHash, className }: TestTokenBadgeProps) => {
return (
<Tooltip content="This is a known test/fake token. It has no real value — do not trade or transfer real assets for it.">
<IconSvg name="status/warning" className={ cn('w-5 h-5 ml-2 text-[var(--color-status-warn)]', className) }/>
<IconSvg name="status/warning" className={ cn('w-5 h-5 ml-2 text-warn', className) }/>
</Tooltip>
);
};
+1 -1
View File
@@ -64,7 +64,7 @@ test('customization', async({ render }) => {
<TxEntity
hash={ hash }
truncation="constant"
className="p-3 border border-blue-700"
className="p-3 border border-border"
/>,
);
@@ -53,7 +53,7 @@ test('customization', async({ render }) => {
<UserOpEntity
hash={ hash }
truncation="constant"
className="p-3 border border-blue-700"
className="p-3 border border-border"
/>,
);
+2 -2
View File
@@ -1,6 +1,6 @@
import { Tooltip } from '@luxfi/ui/tooltip';
import React from 'react';
import { Tooltip } from '@luxfi/ui/tooltip';
import IconSvg from 'ui/shared/IconSvg';
import type { IconName, Props as IconSvgProps } from 'ui/shared/IconSvg';
@@ -11,7 +11,7 @@ interface Props extends Omit<IconSvgProps, 'name'> {
const FallbackRpcIcon = (props: Props) => {
return (
<Tooltip content="Our indexer is experiencing problems, you see the data directly from RPC">
<IconSvg name="RPC" className="size-5 text-[var(--color-status-warn)]" { ...props }/>
<IconSvg name="RPC" className="size-5 text-warn" { ...props }/>
</Tooltip>
);
};
+4 -26
View File
@@ -1,7 +1,7 @@
import React from 'react';
import config from 'configs/app';
import * as cookies from 'lib/cookies';
import { buildLoginUrl } from 'lib/oidc';
import useProfileQuery from '../useProfileQuery';
@@ -15,29 +15,7 @@ interface Props {
ensureEmail?: boolean;
}
function getOidcLoginUrl(): string {
const feature = config.features.account;
if (!feature.isEnabled || feature.authProvider !== 'oidc' || !feature.oidc) {
return '';
}
const { serverUrl, clientId } = feature.oidc;
const redirectUri = `${ window.location.origin }/auth/callback`;
const state = crypto.randomUUID();
// Store state for CSRF protection
sessionStorage.setItem('oidc_state', state);
const params = new URLSearchParams({
response_type: 'code',
client_id: clientId,
redirect_uri: redirectUri,
scope: 'openid profile email',
state,
});
return `${ serverUrl }/oauth/authorize?${ params.toString() }`;
}
const REDIRECT_URI_PATH = '/auth/callback';
const AuthGuardOidc = ({ children, onAuthSuccess, ensureEmail }: Props) => {
const profileQuery = useProfileQuery();
@@ -46,7 +24,7 @@ const AuthGuardOidc = ({ children, onAuthSuccess, ensureEmail }: Props) => {
if (profileQuery.data) {
if (ensureEmail && !profileQuery.data.email) {
// User is logged in but has no email -- redirect to login again
const loginUrl = getOidcLoginUrl();
const loginUrl = buildLoginUrl(REDIRECT_URI_PATH);
if (loginUrl) {
window.location.href = loginUrl;
}
@@ -62,7 +40,7 @@ const AuthGuardOidc = ({ children, onAuthSuccess, ensureEmail }: Props) => {
}
// Redirect to IAM login
const loginUrl = getOidcLoginUrl();
const loginUrl = buildLoginUrl(REDIRECT_URI_PATH);
if (loginUrl) {
window.location.href = loginUrl;
}
+8 -8
View File
@@ -1,8 +1,8 @@
import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from '@luxfi/ui/popover';
import React from 'react';
import { NETWORKS, getCurrentNetwork } from 'configs/app/chainRegistry';
import { cn } from 'lib/utils/cn';
import { PopoverBody, PopoverContent, PopoverRoot, PopoverTrigger } from '@luxfi/ui/popover';
const NetworkSelector = () => {
const [ open, setOpen ] = React.useState(false);
@@ -26,16 +26,16 @@ const NetworkSelector = () => {
>
<PopoverTrigger>
<button
className={cn(
className={ cn(
'flex items-center gap-1.5 px-2 py-1 rounded-sm cursor-pointer',
'bg-transparent border-none text-xs font-medium',
'text-[var(--color-text-secondary)] shrink-0',
'hover:text-[var(--color-text-primary)] hover:bg-[var(--color-blackAlpha-50)] dark:hover:bg-[var(--color-whiteAlpha-50)]',
'transition-all duration-150',
)}
) }
onClick={ handleToggle }
>
<span className="w-1.5 h-1.5 rounded-full bg-green-400 shrink-0"/>
<span className="w-1.5 h-1.5 rounded-full bg-good shrink-0"/>
{ current.name }
</button>
</PopoverTrigger>
@@ -49,15 +49,15 @@ const NetworkSelector = () => {
{ ...(!isCurrent ? { href: network.explorerUrl } : {}) }
className={ cn(
'flex items-center gap-2 px-2.5 py-2 rounded-sm no-underline transition-[background] duration-150',
isCurrent
? 'cursor-default bg-[var(--color-blackAlpha-50)] dark:bg-[var(--color-whiteAlpha-50)]'
: 'cursor-pointer bg-transparent hover:bg-[var(--color-blackAlpha-50)] dark:hover:bg-[var(--color-whiteAlpha-50)]',
isCurrent ?
'cursor-default bg-[var(--color-blackAlpha-50)] dark:bg-[var(--color-whiteAlpha-50)]' :
'cursor-pointer bg-transparent hover:bg-[var(--color-blackAlpha-50)] dark:hover:bg-[var(--color-whiteAlpha-50)]',
) }
>
<span
className={ cn(
'w-1.5 h-1.5 rounded-full shrink-0',
isCurrent ? 'bg-green-400' : 'bg-[var(--color-text-secondary)] opacity-40',
isCurrent ? 'bg-good' : 'bg-[var(--color-text-secondary)] opacity-40',
) }
/>
<div className="flex flex-col">
+2 -2
View File
@@ -1,3 +1,4 @@
import { Skeleton } from '@luxfi/ui/skeleton';
import React from 'react';
import config from 'configs/app';
@@ -6,7 +7,6 @@ import dayjs from 'lib/date/dayjs';
import useIsMobile from 'lib/hooks/useIsMobile';
import { HOMEPAGE_STATS } from 'stubs/stats';
import { Link } from 'toolkit/next/link';
import { Skeleton } from '@luxfi/ui/skeleton';
import GasInfoTooltip from 'ui/shared/gas/GasInfoTooltip';
import GasPrice from 'ui/shared/gas/GasPrice';
import TextSeparator from 'ui/shared/TextSeparator';
@@ -64,7 +64,7 @@ const TopBarStats = () => {
</Skeleton>
{ data.coin_price_change_percentage && !(isMobile && Boolean(config.UI.featuredNetworks.items)) && (
<Skeleton loading={ isPlaceholderData }>
<span className={ Number(data.coin_price_change_percentage) >= 0 ? 'text-green-500' : 'text-red-500' }>
<span className={ Number(data.coin_price_change_percentage) >= 0 ? 'text-good' : 'text-bad' }>
{ Number(data.coin_price_change_percentage).toFixed(2) }%
</span>
</Skeleton>
+1 -1
View File
@@ -14,7 +14,7 @@ const UserIdenticon = ({ address, isAutoConnectDisabled }: Props) => {
<AddressIdenticon size={ 20 } hash={ address }/>
{ isAutoConnectDisabled && (
<div
className="flex items-center justify-center w-[14px] h-[14px] absolute -bottom-px -right-[3px] rounded-full border border-orange-100 dark:border-orange-900"
className="flex items-center justify-center w-[14px] h-[14px] absolute -bottom-px -right-[3px] rounded-full border border-warn/30 dark:border-warn/30"
style={{ backgroundColor: 'rgba(16, 17, 18, 0.80)' }}
>
<IconSvg
@@ -10,6 +10,7 @@ import { Separator } from '@luxfi/ui/separator';
import React from 'react';
import config from 'configs/app';
import { buildLoginUrl } from 'lib/oidc';
import useWeb3AccountWithDomain from 'lib/web3/useAccountWithDomain';
import useWeb3Wallet from 'lib/web3/useWallet';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
@@ -43,27 +44,6 @@ function oidcProviderName(): string {
}
}
function buildOidcLoginUrl(): string {
const feature = config.features.account;
if (!feature.isEnabled || feature.authProvider !== 'oidc' || !feature.oidc) {
return '';
}
const { serverUrl, clientId } = feature.oidc;
const redirectUri = `${ window.location.origin }${ REDIRECT_URI_PATH }`;
const state = crypto.randomUUID();
sessionStorage.setItem('oidc_state', state);
const params = new URLSearchParams({
response_type: 'code',
client_id: clientId,
redirect_uri: redirectUri,
scope: 'openid profile email',
state,
});
return `${ serverUrl }/oauth/authorize?${ params.toString() }`;
}
interface Props {
buttonSize?: ButtonProps['size'];
buttonVariant?: ButtonProps['variant'];
@@ -77,7 +57,7 @@ const UserProfileOidc = ({ buttonSize, buttonVariant = 'header' }: Props) => {
const web3Wallet = useWeb3Wallet({ source: 'Header' });
const web3AccountWithDomain = useWeb3AccountWithDomain(isWalletEnabled && web3Wallet.isConnected);
const handleLoginClick = React.useCallback(() => {
const url = buildOidcLoginUrl();
const url = buildLoginUrl(REDIRECT_URI_PATH);
if (url) {
window.location.href = url;
}
+1 -1
View File
@@ -40,7 +40,7 @@ const STATUS_LABEL: Record<FeeSplitStatus, string> = {
const STATUS_DOT: Record<FeeSplitStatus, string> = {
active: 'bg-[var(--color-green-500)]',
scheduled: 'bg-[var(--color-status-warn)]',
scheduled: 'bg-warn',
inactive: 'bg-[var(--color-gray-400)]',
unknown: 'bg-[var(--color-gray-400)]',
};
@@ -43,7 +43,7 @@ const ArbitrumL2TxnBatchDetailsAnyTrustDA = ({ data }: Props) => {
<>
<DetailsTimestamp timestamp={ data.timeout } noRelativeTime/>
<TextSeparator/>
<span className="text-red-500">{ dayjs(data.timeout).diff(dayjs(), 'day') } days left</span>
<span className="text-bad">{ dayjs(data.timeout).diff(dayjs(), 'day') } days left</span>
</>
) }
</DetailedInfo.ItemValue>
@@ -12,13 +12,13 @@ const VerifiedAddressesStatus = ({ status }: Props) => {
return <span className="font-medium">In progress</span>;
}
case 'APPROVED': {
return <span className="font-medium text-green-500">Approved</span>;
return <span className="font-medium text-good">Approved</span>;
}
case 'UPDATE_REQUIRED': {
return <span className="font-medium text-orange-500">Waiting for update</span>;
return <span className="font-medium text-warn">Waiting for update</span>;
}
case 'REJECTED': {
return <span className="font-medium text-red-500">Rejected</span>;
return <span className="font-medium text-bad">Rejected</span>;
}
default: