Make banner color dynamic based on message state

Add spinner for message pending
Improve footer icons
Move ContentFrame to layout dir
pull/1/head
J M Rossy 2 years ago
parent 1c2b574e32
commit 849a388745
  1. 3
      src/components/animation/Spinner.module.css
  2. 6
      src/components/animation/Spinner.tsx
  3. 31
      src/components/layout/BackgroundBanner.tsx
  4. 32
      src/components/layout/ContentFrame.tsx
  5. 15
      src/components/nav/ContentFrame.tsx
  6. 27
      src/features/search/MessageDetails.tsx
  7. 19
      src/features/search/placeholderMessages.ts
  8. 4
      src/images/icons/book.svg
  9. 5
      src/images/icons/briefcase.svg
  10. 4
      src/images/icons/info-circle.svg
  11. 2
      src/pages/index.tsx
  12. 2
      src/pages/message/[messageId].tsx

@ -20,6 +20,9 @@
border-radius: 20%; border-radius: 20%;
background: #010101; background: #010101;
} }
.spinner.white div:after {
background: #ffffff;
}
.spinner div:nth-child(1) { .spinner div:nth-child(1) {
transform: rotate(0deg); transform: rotate(0deg);
animation-delay: -1.1s; animation-delay: -1.1s;

@ -3,9 +3,11 @@ import { memo } from 'react';
import styles from './Spinner.module.css'; import styles from './Spinner.module.css';
// From https://loading.io/css/ // From https://loading.io/css/
function _Spinner({ classes }: { classes?: string }) { function _Spinner({ white, classes }: { white?: boolean; classes?: string }) {
return ( return (
<div className={`${styles.spinner} ${classes || ''}`}> <div
className={`${styles.spinner} ${white && styles.white} ${classes || ''}`}
>
<div></div> <div></div>
<div></div> <div></div>
<div></div> <div></div>

@ -0,0 +1,31 @@
import { createContext, useContext, useMemo, useState } from 'react';
export const BannerColorContext = createContext<{
bannerClassName: string;
setBannerClassName?: (name: string) => void;
}>({ bannerClassName: '', setBannerClassName: undefined });
export function useBackgroundBannerState() {
// State for managing banner class, to be used as context value
const [bannerClassName, setBannerClassName] = useState('');
const bannerState = useMemo(
() => ({ bannerClassName, setBannerClassName }),
[bannerClassName, setBannerClassName],
);
return bannerState;
}
export function useBackgroundBanner() {
return useContext(BannerColorContext);
}
export function BackgroundBanner() {
const { bannerClassName } = useBackgroundBanner();
return (
<div
className={`absolute -top-5 -left-4 -right-4 h-36 rounded z-10 transition-all duration-500 ${
bannerClassName || 'bg-green-600'
}`}
></div>
);
}

@ -0,0 +1,32 @@
import { PropsWithChildren } from 'react';
import {
BackgroundBanner,
BannerColorContext,
useBackgroundBannerState,
} from './BackgroundBanner';
export function ContentFrame(props: PropsWithChildren) {
// Provide context so children can change banner color
const bannerState = useBackgroundBannerState();
return (
<div className="flex flex-col justify-center items-center min-h-full">
<div
style={styles.container}
className="relative overflow-visible mt-7 mb-8"
>
<BannerColorContext.Provider value={bannerState}>
<BackgroundBanner />
<div className="relative z-20">{props.children}</div>
</BannerColorContext.Provider>
</div>
</div>
);
}
const styles = {
container: {
width: 'min(900px,96vw)',
},
};

@ -1,15 +0,0 @@
import { PropsWithChildren } from 'react';
export function ContentFrame(props: PropsWithChildren) {
return (
<div className="flex flex-col justify-center items-center min-h-full">
<div
style={{ width: 'min(900px,96vw)' }}
className="relative overflow-visible mt-7 mb-8"
>
<div className="absolute -top-5 -left-4 -right-4 h-36 bg-green-600 rounded z-10"></div>
<div className="relative z-20">{props.children}</div>
</div>
</div>
);
}

@ -1,5 +1,5 @@
import Image from 'next/future/image'; import Image from 'next/future/image';
import { useCallback, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useQuery } from 'urql'; import { useQuery } from 'urql';
import { Spinner } from '../../components/animation/Spinner'; import { Spinner } from '../../components/animation/Spinner';
@ -7,6 +7,7 @@ import { CopyButton } from '../../components/buttons/CopyButton';
import { ChainIcon } from '../../components/icons/ChainIcon'; import { ChainIcon } from '../../components/icons/ChainIcon';
import { ChainToChain } from '../../components/icons/ChainToChain'; import { ChainToChain } from '../../components/icons/ChainToChain';
import { HelpIcon } from '../../components/icons/HelpIcon'; import { HelpIcon } from '../../components/icons/HelpIcon';
import { useBackgroundBanner } from '../../components/layout/BackgroundBanner';
import { Card } from '../../components/layout/Card'; import { Card } from '../../components/layout/Card';
import CheckmarkIcon from '../../images/icons/checkmark-circle.svg'; import CheckmarkIcon from '../../images/icons/checkmark-circle.svg';
import XCircleIcon from '../../images/icons/x-circle.svg'; import XCircleIcon from '../../images/icons/x-circle.svg';
@ -43,6 +44,18 @@ export function MessageDetails({ messageId }: { messageId: string }) {
destinationTransaction, destinationTransaction,
} = message; } = message;
const { bannerClassName, setBannerClassName } = useBackgroundBanner();
useEffect(() => {
if (!setBannerClassName) return;
if (error || message.status === MessageStatus.Failing) {
setBannerClassName('bg-red-600');
} else if (bannerClassName) {
setBannerClassName('');
}
// TODO toast on error or surface some other way
}, [error, message, bannerClassName, setBannerClassName]);
const reExecutor = useCallback(() => { const reExecutor = useCallback(() => {
if (status !== MessageStatus.Delivered) { if (status !== MessageStatus.Delivered) {
reexecuteQuery({ requestPolicy: 'network-only' }); reexecuteQuery({ requestPolicy: 'network-only' });
@ -50,15 +63,23 @@ export function MessageDetails({ messageId }: { messageId: string }) {
}, [reexecuteQuery, status]); }, [reexecuteQuery, status]);
useInterval(reExecutor, AUTO_REFRESH_DELAY); useInterval(reExecutor, AUTO_REFRESH_DELAY);
// TODO handle message not found and error cases // TODO handle message not found
// TODO hide chain logos in circles while loading // TODO hide chain logos in circles while loading
// TODO show spinner while message is fetching, not just found and pending
return ( return (
<> <>
<div className="flex items-center justify-between px-1 -mt-1"> <div className="flex items-center justify-between px-1 -mt-1">
<h2 className="text-white text-lg">Message</h2> <h2 className="text-white text-lg">Message</h2>
{status === MessageStatus.Pending && ( {status === MessageStatus.Pending && (
<div className="text-white text-lg">Status: Pending</div> <div className="flex items-center">
<div className="text-white text-lg mr-3">Status: Pending</div>
<div className="w-7 h-7 overflow-hidden flex items-center justify-center">
<div className="scale-[35%]">
<Spinner white={true} />
</div>
</div>
</div>
)} )}
{status === MessageStatus.Delivered && ( {status === MessageStatus.Delivered && (
<div className="flex items-center"> <div className="flex items-center">

@ -19,13 +19,16 @@ export const TX_ZERO: PartialTransactionReceipt = {
blockNumber: 123456789, blockNumber: 123456789,
}; };
const BODY_ZERO =
'0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
export const PLACEHOLDER_MESSAGES: Message[] = [ export const PLACEHOLDER_MESSAGES: Message[] = [
{ {
id: '1', id: '1',
status: MessageStatus.Pending, status: MessageStatus.Pending,
sender: constants.AddressZero, sender: constants.AddressZero,
recipient: constants.AddressZero, recipient: constants.AddressZero,
body: constants.AddressZero + constants.AddressZero + constants.AddressZero, body: BODY_ZERO,
originChainId: chain.mainnet.id, originChainId: chain.mainnet.id,
destinationChainId: chain.arbitrum.id, destinationChainId: chain.arbitrum.id,
originTransaction: TX_ZERO, originTransaction: TX_ZERO,
@ -38,7 +41,7 @@ export const PLACEHOLDER_MESSAGES: Message[] = [
status: MessageStatus.Pending, status: MessageStatus.Pending,
sender: constants.AddressZero, sender: constants.AddressZero,
recipient: constants.AddressZero, recipient: constants.AddressZero,
body: constants.AddressZero + constants.AddressZero + constants.AddressZero, body: BODY_ZERO,
originChainId: chain.polygon.id, originChainId: chain.polygon.id,
destinationChainId: chain.optimism.id, destinationChainId: chain.optimism.id,
originTransaction: TX_ZERO, originTransaction: TX_ZERO,
@ -51,7 +54,7 @@ export const PLACEHOLDER_MESSAGES: Message[] = [
status: MessageStatus.Pending, status: MessageStatus.Pending,
sender: constants.AddressZero, sender: constants.AddressZero,
recipient: constants.AddressZero, recipient: constants.AddressZero,
body: constants.AddressZero + constants.AddressZero + constants.AddressZero, body: BODY_ZERO,
originChainId: avalancheChain.id, originChainId: avalancheChain.id,
destinationChainId: celoMainnetChain.id, destinationChainId: celoMainnetChain.id,
originTransaction: TX_ZERO, originTransaction: TX_ZERO,
@ -64,7 +67,7 @@ export const PLACEHOLDER_MESSAGES: Message[] = [
status: MessageStatus.Pending, status: MessageStatus.Pending,
sender: constants.AddressZero, sender: constants.AddressZero,
recipient: constants.AddressZero, recipient: constants.AddressZero,
body: constants.AddressZero + constants.AddressZero + constants.AddressZero, body: BODY_ZERO,
originChainId: bscChain.id, originChainId: bscChain.id,
destinationChainId: chain.mainnet.id, destinationChainId: chain.mainnet.id,
originTransaction: TX_ZERO, originTransaction: TX_ZERO,
@ -77,7 +80,7 @@ export const PLACEHOLDER_MESSAGES: Message[] = [
status: MessageStatus.Pending, status: MessageStatus.Pending,
sender: constants.AddressZero, sender: constants.AddressZero,
recipient: constants.AddressZero, recipient: constants.AddressZero,
body: constants.AddressZero + constants.AddressZero + constants.AddressZero, body: BODY_ZERO,
originChainId: chain.mainnet.id, originChainId: chain.mainnet.id,
destinationChainId: chain.goerli.id, destinationChainId: chain.goerli.id,
originTransaction: TX_ZERO, originTransaction: TX_ZERO,
@ -90,7 +93,7 @@ export const PLACEHOLDER_MESSAGES: Message[] = [
status: MessageStatus.Pending, status: MessageStatus.Pending,
sender: constants.AddressZero, sender: constants.AddressZero,
recipient: constants.AddressZero, recipient: constants.AddressZero,
body: constants.AddressZero + constants.AddressZero + constants.AddressZero, body: BODY_ZERO,
originChainId: chain.mainnet.id, originChainId: chain.mainnet.id,
destinationChainId: celoMainnetChain.id, destinationChainId: celoMainnetChain.id,
originTransaction: TX_ZERO, originTransaction: TX_ZERO,
@ -103,7 +106,7 @@ export const PLACEHOLDER_MESSAGES: Message[] = [
status: MessageStatus.Pending, status: MessageStatus.Pending,
sender: constants.AddressZero, sender: constants.AddressZero,
recipient: constants.AddressZero, recipient: constants.AddressZero,
body: constants.AddressZero + constants.AddressZero + constants.AddressZero, body: BODY_ZERO,
originChainId: chain.optimism.id, originChainId: chain.optimism.id,
destinationChainId: avalancheChain.id, destinationChainId: avalancheChain.id,
originTransaction: TX_ZERO, originTransaction: TX_ZERO,
@ -116,7 +119,7 @@ export const PLACEHOLDER_MESSAGES: Message[] = [
status: MessageStatus.Pending, status: MessageStatus.Pending,
sender: constants.AddressZero, sender: constants.AddressZero,
recipient: constants.AddressZero, recipient: constants.AddressZero,
body: constants.AddressZero + constants.AddressZero + constants.AddressZero, body: BODY_ZERO,
originChainId: bscChain.id, originChainId: bscChain.id,
destinationChainId: avalancheChain.id, destinationChainId: avalancheChain.id,
originTransaction: TX_ZERO, originTransaction: TX_ZERO,

@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="2 8 44 32"><path d="M28 19.3v-2.4q1.65-.7 3.375-1.05Q33.1 15.5 35 15.5q1.3 0 2.55.2 1.25.2 2.45.5v2.2q-1.2-.45-2.425-.675Q36.35 17.5 35 17.5q-1.9 0-3.65.475T28 19.3Zm0 11v-2.45q1.65-.7 3.375-1.025Q33.1 26.5 35 26.5q1.3 0 2.55.2 1.25.2 2.45.5v2.2q-1.2-.45-2.425-.675Q36.35 28.5 35 28.5q-1.9 0-3.65.45T28 30.3Zm0-5.5v-2.4q1.65-.7 3.375-1.05Q33.1 21 35 21q1.3 0 2.55.2 1.25.2 2.45.5v2.2q-1.2-.45-2.425-.675Q36.35 23 35 23q-1.9 0-3.65.475T28 24.8ZM12.4 33q2.7 0 5.225.625 2.525.625 4.975 1.875V14.15q-2.25-1.5-4.875-2.325Q15.1 11 12.4 11q-1.9 0-3.725.475Q6.85 11.95 5 12.65v21.7q1.55-.7 3.525-1.025Q10.5 33 12.4 33Zm13.2 2.5q2.5-1.25 4.9-1.875Q32.9 33 35.6 33q1.9 0 3.925.3t3.475.8V12.65q-1.7-.85-3.6-1.25-1.9-.4-3.8-.4-2.7 0-5.225.825-2.525.825-4.775 2.325ZM24.1 40q-2.55-1.9-5.55-2.925T12.4 36.05q-1.85 0-3.6.45t-3.5 1.1q-1.15.55-2.225-.15Q2 36.75 2 35.45V12.3q0-.75.35-1.375T3.4 9.95q2.1-1 4.375-1.475Q10.05 8 12.4 8q3.15 0 6.125.85t5.575 2.6q2.55-1.75 5.475-2.6Q32.5 8 35.6 8q2.35 0 4.6.475 2.25.475 4.35 1.475.7.35 1.075.975T46 12.3v23.15q0 1.4-1.125 2.125-1.125.725-2.225.025-1.7-.7-3.45-1.125-1.75-.425-3.6-.425-3.15 0-6.05 1.05T24.1 40ZM13.8 23.55Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-book-half" viewBox="0 0 16 16">
<path d="M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 649 B

@ -1 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="4 4 40 38"><path d="M7 42q-1.2 0-2.1-.9Q4 40.2 4 39V15q0-1.2.9-2.1.9-.9 2.1-.9h9V7q0-1.2.9-2.1.9-.9 2.1-.9h10q1.2 0 2.1.9.9.9.9 2.1v5h9q1.2 0 2.1.9.9.9.9 2.1v24q0 1.2-.9 2.1-.9.9-2.1.9Zm0-3h34V15H7v24Zm12-27h10V7H19ZM7 39V15v24Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-briefcase-fill" viewBox="0 0 16 16">
<path d="M6.5 1A1.5 1.5 0 0 0 5 2.5V3H1.5A1.5 1.5 0 0 0 0 4.5v1.384l7.614 2.03a1.5 1.5 0 0 0 .772 0L16 5.884V4.5A1.5 1.5 0 0 0 14.5 3H11v-.5A1.5 1.5 0 0 0 9.5 1h-3zm0 1h3a.5.5 0 0 1 .5.5V3H6v-.5a.5.5 0 0 1 .5-.5z"/>
<path d="M0 12.5A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5V6.85L8.129 8.947a.5.5 0 0 1-.258 0L0 6.85v5.65z"/>
</svg>

Before

Width:  |  Height:  |  Size: 315 B

After

Width:  |  Height:  |  Size: 473 B

@ -1 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" viewBox="4 4 40 40"><path d="M22.65 34h3V22h-3ZM24 18.3q.7 0 1.175-.45.475-.45.475-1.15t-.475-1.2Q24.7 15 24 15q-.7 0-1.175.5-.475.5-.475 1.2t.475 1.15q.475.45 1.175.45ZM24 44q-4.1 0-7.75-1.575-3.65-1.575-6.375-4.3-2.725-2.725-4.3-6.375Q4 28.1 4 23.95q0-4.1 1.575-7.75 1.575-3.65 4.3-6.35 2.725-2.7 6.375-4.275Q19.9 4 24.05 4q4.1 0 7.75 1.575 3.65 1.575 6.35 4.275 2.7 2.7 4.275 6.35Q44 19.85 44 24q0 4.1-1.575 7.75-1.575 3.65-4.275 6.375t-6.35 4.3Q28.15 44 24 44Zm.05-3q7.05 0 12-4.975T41 23.95q0-7.05-4.95-12T24 7q-7.05 0-12.025 4.95Q7 16.9 7 24q0 7.05 4.975 12.025Q16.95 41 24.05 41ZM24 24Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-info-circle-fill" viewBox="0 0 16 16">
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" fill="#000"/>
</svg>

Before

Width:  |  Height:  |  Size: 671 B

After

Width:  |  Height:  |  Size: 441 B

@ -1,6 +1,6 @@
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import { ContentFrame } from '../components/nav/ContentFrame'; import { ContentFrame } from '../components/layout/ContentFrame';
import { MessageSearch } from '../features/search/MessageSearch'; import { MessageSearch } from '../features/search/MessageSearch';
const Home: NextPage = () => { const Home: NextPage = () => {

@ -2,7 +2,7 @@ import type { NextPage } from 'next';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { ContentFrame } from '../../components/nav/ContentFrame'; import { ContentFrame } from '../../components/layout/ContentFrame';
import { MessageDetails } from '../../features/search/MessageDetails'; import { MessageDetails } from '../../features/search/MessageDetails';
import { logger } from '../../utils/logger'; import { logger } from '../../utils/logger';

Loading…
Cancel
Save