mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
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:
@@ -5,3 +5,4 @@ parsdao
|
||||
platformvm
|
||||
reqs
|
||||
ttfb
|
||||
unlayered
|
||||
|
||||
+114
@@ -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
@@ -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
@@ -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.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
Vendored
+9
-9
@@ -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
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
@@ -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 }) }
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
) }
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
) }
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})();
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -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
@@ -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 ? (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 } });
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user