diff --git a/.changelog/2302.feature.md b/.changelog/2302.feature.md new file mode 100644 index 0000000000..e30d901c68 --- /dev/null +++ b/.changelog/2302.feature.md @@ -0,0 +1 @@ +Implement new homepage diff --git a/package.json b/package.json index 4c148702a6..b1eac46468 100644 --- a/package.json +++ b/package.json @@ -70,11 +70,8 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-i18next": "14.1.3", - "react-quick-pinch-zoom": "5.1.0", "react-router-dom": "6.30.0", "recharts": "2.15.1", - "swiper": "11.2.5", - "use-resize-observer": "9.1.0", "viem": "^2.31.4" }, "devDependencies": { diff --git a/src/app/components/CustomIcons/Navigate.tsx b/src/app/components/CustomIcons/Navigate.tsx deleted file mode 100644 index 5f7e3453a9..0000000000 --- a/src/app/components/CustomIcons/Navigate.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { FC } from 'react' -import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon' -import { useTheme } from '@mui/material/styles' - -export const NavigateIcon: FC = ({ secondary, ...restProps }) => { - const theme = useTheme() - const secondaryColor = secondary ?? theme.palette.layout.helpScreenIconColor - - return ( - - - - - - - - - - ) -} diff --git a/src/app/components/CustomIcons/Pinch.tsx b/src/app/components/CustomIcons/Pinch.tsx deleted file mode 100644 index 927c1d60e0..0000000000 --- a/src/app/components/CustomIcons/Pinch.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { FC } from 'react' -import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon' -import { useTheme } from '@mui/material/styles' - -export const PinchIcon: FC = ({ secondary, ...restProps }) => { - const theme = useTheme() - const secondaryColor = secondary ?? theme.palette.layout.helpScreenIconColor - - return ( - - - - - - - ) -} diff --git a/src/app/components/CustomIcons/Tap.tsx b/src/app/components/CustomIcons/Tap.tsx deleted file mode 100644 index bf460dfca9..0000000000 --- a/src/app/components/CustomIcons/Tap.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { FC } from 'react' -import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon' -import { useTheme } from '@mui/material/styles' - -export const TapIcon: FC = ({ secondary, ...restProps }) => { - const theme = useTheme() - const secondaryColor = secondary ?? theme.palette.layout.helpScreenIconColor - - return ( - - - - - - - - ) -} diff --git a/src/app/components/LayerPicker/index.tsx b/src/app/components/LayerPicker/index.tsx index e9f8e9c4b0..32464b256f 100644 --- a/src/app/components/LayerPicker/index.tsx +++ b/src/app/components/LayerPicker/index.tsx @@ -31,7 +31,7 @@ export const LayerPicker: FC = ({ onClose, onConfirm, open, is <> {/* Match MUI md breakpoint during migration */} - + diff --git a/src/app/components/PageLayout/Header.tsx b/src/app/components/PageLayout/Header.tsx index f508c17958..81e4bd3b8c 100644 --- a/src/app/components/PageLayout/Header.tsx +++ b/src/app/components/PageLayout/Header.tsx @@ -7,8 +7,13 @@ import { useScopeParam } from '../../hooks/useScopeParam' import { useScreenSize } from '../../hooks/useScreensize' import { isScopeSelectorNeeded } from '../../utils/route-utils' import { useTranslation } from 'react-i18next' +import { cn } from '@oasisprotocol/ui-library/src/lib/utils' -export const Header: FC = () => { +type HeaderProps = { + sticky?: boolean +} + +export const Header: FC = ({ sticky = true }) => { const { t } = useTranslation() const { isMobile } = useScreenSize() const { isDesktop } = useScreenSize() @@ -17,11 +22,18 @@ export const Header: FC = () => { const scrolled = useScrolled() return ( -
-
-
+
+
+
- +
{withScopeSelector && ( diff --git a/src/app/components/Select/index.tsx b/src/app/components/Select/index.tsx deleted file mode 100644 index 2e0cbce0e3..0000000000 --- a/src/app/components/Select/index.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import { Select as SelectUnstyled, SelectProps, selectClasses, SelectRootSlotProps } from '@mui/base/Select' -import { Option, optionClasses } from '@mui/base/Option' -import { styled } from '@mui/material/styles' -import React, { - ForwardedRef, - forwardRef, - memo, - ReactElement, - useCallback, - useId, - RefAttributes, - MouseEvent, - KeyboardEvent, - FocusEvent, - ReactNode, -} from 'react' -import Typography from '@mui/material/Typography' -import Button from '@mui/material/Button' -import ExpandLessIcon from '@mui/icons-material/ExpandLess' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { COLORS } from '../../../styles/theme/colors' -import { useTranslation } from 'react-i18next' -import { WithOptionalOwnerState } from '@mui/base/utils' -import { SelectPopupSlotProps, SelectSlots } from '@mui/base/Select/Select.types' -import { PopupProps } from '@mui/base/Unstable_Popup' -import { Theme } from '@mui/material/styles/createTheme' - -// Props that are not supposed to be passed to the DOM -const forbiddenProps = ['light', 'ownerState'] - -// Utility function for filtering out stuff that is now supposed to go to the DOM -const swallowReactProps = { - shouldForwardProp: (prop: string) => !forbiddenProps.includes(prop), -} - -export const StyledSelectButton = styled( - Button, - swallowReactProps, -)<{ light?: boolean }>(({ theme, light }) => ({ - height: '36px', - minWidth: '135px', - padding: `0 ${theme.spacing(4)}`, - borderRadius: '6px', - border: light ? '1px solid' : undefined, - borderColor: light ? COLORS.grayMediumLight : undefined, - color: light ? COLORS.grayDark : COLORS.white, - backgroundColor: light ? `${COLORS.white} !important` : undefined, - textTransform: 'none', - justifyContent: 'space-between', - [`&.${selectClasses.focusVisible}`]: { - backgroundColor: light ? COLORS.white : COLORS.brandExtraDark, - }, - span: { - color: light ? COLORS.grayDark : undefined, - }, -})) - -export const StyledSelectListbox = styled( - 'ul', - swallowReactProps, -)(({ theme, light }: { theme: Theme; light?: boolean }) => ({ - boxSizing: 'border-box', - padding: theme.spacing(0), - margin: theme.spacing(3, 0), - minWidth: '135px', - borderRadius: '6px', - overflow: 'auto', - outline: 0, - background: light ? COLORS.white : theme.palette.tertiary.main, - filter: 'drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25))', - maxHeight: '50vh', -})) - -export const StyledSelectOption = styled( - Option, - swallowReactProps, -)(({ theme, light }: { theme: Theme; light?: boolean }) => ({ - display: 'flex', - alignItems: 'center', - boxSizing: 'border-box', - listStyle: 'none', - height: '36px', - padding: `0 ${theme.spacing(4)}`, - cursor: 'default', - color: light ? COLORS.grayDark : COLORS.white, - [`&:hover:not(.${optionClasses.disabled})`]: { - cursor: 'pointer', - backgroundColor: light ? COLORS.grayMediumLight : undefined, - }, - [`&:hover:not(.${optionClasses.disabled}), - &.${optionClasses.highlighted}`]: { - backgroundColor: light ? COLORS.grayMediumLight : COLORS.brandExtraDark, - }, - [`&.${optionClasses.disabled}`]: { - backgroundColor: COLORS.lavenderGray, - }, - [`&&.${optionClasses.selected}`]: { - opacity: 0.5, - backgroundColor: 'transparent', - }, - transition: 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms', - span: { - color: light ? COLORS.grayDark : undefined, - }, -})) - -const StyledPopup = styled('div')` - z-index: 1; -` - -const TertiaryButton = forwardRef( - ( - { children, ownerState, light, ...restProps }: SelectRootSlotProps & { light: boolean }, - ref: ForwardedRef, - ) => { - const { t } = useTranslation() - - return ( - - {children ? children : t('select.placeholder')} - {ownerState.open ? : } - - ) - }, -) - -const CustomSelect = forwardRef(function CustomSelect( - { - root, - listbox, - popup, - placement, - ...restProps - }: SelectProps & SelectSlots & Partial>, - ref: ForwardedRef, -) { - const { slotProps = {} } = restProps - const slots: SelectProps['slots'] = { - root, - listbox, - popup, - ...restProps.slots, - } - - return ( - - ) -}) as ( - props: SelectProps & - SelectSlots & - Partial> & - RefAttributes, -) => JSX.Element - -export interface SelectOptionBase { - label: string | number - value: string | number -} - -const SelectOption = ({ - value, - label, - light, -}: T & { light: boolean }): ReactElement => ( - - {label} - -) - -interface SelectCmpProps { - label?: ReactNode - options: T[] - /** - * If you want to use this component in uncontrolled mode, you can provide the detauls / initial value here. - */ - defaultValue?: T['value'] - - /** - * You can provide a value if you want to use this component in controlled mode - */ - value?: T['value'] | null - handleChange?: (selectedOption: T['value'] | null) => void - placement?: PopupProps['placement'] - className?: string - root?: React.ElementType - light?: boolean | undefined - Option?: typeof SelectOption | undefined - listbox?: React.ElementType - popup?: React.ComponentType>> -} - -const SelectCmp = ({ - label, - options, - defaultValue, - value, - handleChange, - placement = 'bottom-start', - className, - light = false, - root = TertiaryButton, - listbox = StyledSelectListbox, - popup = StyledPopup, - Option = SelectOption, -}: SelectCmpProps): ReactElement => { - const selectId = useId() - - const onChange = useCallback( - (_: MouseEvent | KeyboardEvent | FocusEvent | null, selectedValue: T['value'] | null) => { - handleChange?.(selectedValue) - }, - [handleChange], - ) - - const withLight = { light } - - return ( -
- {label && } - - id={selectId} - defaultValue={defaultValue} - {...(value === undefined || value === null ? {} : { value })} - onChange={onChange} - root={root} - listbox={listbox} - popup={popup} - placement={placement} - slotProps={{ - root: withLight, - listbox: withLight, - }} - > - {options.map((props: T) => ( -
- ) -} - -export const Select = memo(SelectCmp) as typeof SelectCmp diff --git a/src/app/components/TotalTransactions/index.tsx b/src/app/components/TotalTransactions/index.tsx index 4c401a17a7..0bc004437b 100644 --- a/src/app/components/TotalTransactions/index.tsx +++ b/src/app/components/TotalTransactions/index.tsx @@ -2,16 +2,17 @@ import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import { Card, CardContent, CardHeader, CardTitle } from '@oasisprotocol/ui-library/src/components/cards' import { LineChart } from '../charts/LineChart' -import { useGetLayerStatsTxVolume } from '../../../oasis-nexus/api' +import { Layer, useGetLayerStatsTxVolume, useGetStatsTxVolume } from '../../../oasis-nexus/api' import { chartUseQueryStaleTimeMs, durationToQueryParams } from '../../utils/chart-utils' import { DurationPills } from '../DurationPills' import { ChartDuration, cumulativeSum } from '../../utils/chart-utils' import { SearchScope } from '../../../types/searchScope' import { ErrorBoundary } from '../ErrorBoundary' import { Typography } from '@oasisprotocol/ui-library/src/components/typography' +import { Network } from '../../../types/network' const TotalTransactionsContent: FC<{ - scope: SearchScope + scope: { network: Network; layer?: Layer } chartDuration: ChartDuration }> = ({ scope, chartDuration }) => { const { t } = useTranslation() @@ -20,13 +21,25 @@ const TotalTransactionsContent: FC<{ // We want to start from the beginning of offset as cumulative sum generates a line chart that always goes up offset: 0, } - const dailyVolumeQuery = useGetLayerStatsTxVolume(scope.network, scope.layer, statsParams, { + + const layerVolumeQuery = useGetLayerStatsTxVolume(scope.network, scope.layer!, statsParams, { query: { + enabled: !!scope.layer, keepPreviousData: true, staleTime: chartUseQueryStaleTimeMs, }, }) + const networkVolumeQuery = useGetStatsTxVolume(scope.network, statsParams, { + query: { + enabled: !scope.layer, + keepPreviousData: true, + staleTime: chartUseQueryStaleTimeMs, + }, + }) + + const dailyVolumeQuery = scope.layer ? layerVolumeQuery : networkVolumeQuery + const windows = dailyVolumeQuery.data?.data.windows ? cumulativeSum(dailyVolumeQuery.data?.data.windows.slice().reverse(), 'tx_volume') : undefined @@ -62,10 +75,11 @@ const TotalTransactionsContent: FC<{ ) } -export const TotalTransactions: FC<{ chartContainerHeight?: number; scope: SearchScope }> = ({ - chartContainerHeight = 450, - scope, -}) => { +type TotalTransactionsProps = { + chartContainerHeight?: number +} & ({ scope: SearchScope } | { network: Network }) + +export const TotalTransactions: FC = ({ chartContainerHeight = 450, ...props }) => { const { t } = useTranslation() const [chartDuration, setChartDuration] = useState(ChartDuration.MONTH) return ( @@ -81,7 +95,10 @@ export const TotalTransactions: FC<{ chartContainerHeight?: number; scope: Searc - + diff --git a/src/app/pages/HomePage/Ecosystem.tsx b/src/app/pages/HomePage/Ecosystem.tsx index 73945c7eb4..cd74823bf3 100644 --- a/src/app/pages/HomePage/Ecosystem.tsx +++ b/src/app/pages/HomePage/Ecosystem.tsx @@ -11,10 +11,10 @@ export const Ecosystem: FC = () => { const { t } = useTranslation() return ( -
+
{t('home.ecosystem.title')} -
+
}> diff --git a/src/app/pages/HomePage/EcosystemCard.tsx b/src/app/pages/HomePage/EcosystemCard.tsx index b256b14dae..ad256ab340 100644 --- a/src/app/pages/HomePage/EcosystemCard.tsx +++ b/src/app/pages/HomePage/EcosystemCard.tsx @@ -1,7 +1,8 @@ import { FC, ReactNode } from 'react' -import { Link } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { ArrowRight } from 'lucide-react' +import { Link as RouterLink } from 'react-router-dom' +import { Link } from '@oasisprotocol/ui-library/src/components/link' import { Card, CardContent, @@ -13,6 +14,8 @@ import { Button } from '@oasisprotocol/ui-library/src/components/ui/button' import { Typography } from '@oasisprotocol/ui-library/src/components/typography' import { Badge } from '@oasisprotocol/ui-library/src/components/badge' import { Skeleton } from '@oasisprotocol/ui-library/src/components/ui/skeleton' +import { Tooltip } from '@oasisprotocol/ui-library/src/components/tooltip' +import { Network } from '../../../types/network' import { RouteUtils } from '../../utils/route-utils' type EcosystemCardProps = { @@ -26,6 +29,7 @@ type EcosystemCardProps = { title: ReactNode description: ReactNode legacy?: boolean + network?: Network } export const EcosystemCard: FC = ({ @@ -39,6 +43,7 @@ export const EcosystemCard: FC = ({ title, description, legacy, + network = 'mainnet', }) => { const { t } = useTranslation() @@ -51,23 +56,30 @@ export const EcosystemCard: FC = ({ > - {title} - - {legacy && ( - - {t('common.legacy')} - - )} +
+ + {title} + + {legacy && ( + +
+ + {t('common.legacy')} + +
+
+ )} +
- {!outOfDate && !legacy && !isLoading && ( - - {t('common.online')} - + {!outOfDate && !isLoading && ( + +
+ )} - {outOfDate && !legacy && !isLoading && ( - - {t('common.outdated')} - + {outOfDate && !isLoading && ( + +
+ )} @@ -77,14 +89,39 @@ export const EcosystemCard: FC = ({ {isLoading && } {!isLoading && ( -
+
{t('home.blockNumber')}
- {latestBlock !== undefined && latestBlock !== null ? latestBlock.toLocaleString() : '-'} + {latestBlock !== undefined && latestBlock !== null ? ( + <> + + + {latestBlock.toLocaleString()} + + + + ) : ( + '-' + )}
{t('home.activeNodes')}
- {activeNodes !== undefined && activeNodes !== null ? activeNodes.toLocaleString() : '-'} + {activeNodes !== undefined && activeNodes !== null ? ( + <> + {layer === 'consensus' ? ( + + + {activeNodes.toLocaleString()} + + + ) : ( + // Link to runtime nodes when available + activeNodes.toLocaleString() + )} + + ) : ( + '-' + )}
)} @@ -92,16 +129,16 @@ export const EcosystemCard: FC = ({ {footer || ( <> - - )} diff --git a/src/app/pages/HomePage/EmeraldCard.tsx b/src/app/pages/HomePage/EmeraldCard.tsx index 2cfdb6ec7e..caae384f0f 100644 --- a/src/app/pages/HomePage/EmeraldCard.tsx +++ b/src/app/pages/HomePage/EmeraldCard.tsx @@ -1,12 +1,17 @@ import { FC } from 'react' import { useTranslation } from 'react-i18next' import { useGetRuntimeStatus } from '../../../oasis-nexus/api' +import { useRuntimeFreshness } from '../../components/OfflineBanner/hook' import { EcosystemCard } from './EcosystemCard' import emeraldBg from './images/emerald-bg.svg' export const EmeraldCard: FC = () => { const { t } = useTranslation() const emeraldStatusQuery = useGetRuntimeStatus('mainnet', 'emerald') + const { outOfDate: emeraldOutOfDate } = useRuntimeFreshness({ + network: 'mainnet', + layer: 'emerald', + }) return ( { title={t('common.emerald')} background={emeraldBg} layer="emerald" + outOfDate={emeraldOutOfDate} legacy latestBlock={emeraldStatusQuery?.data?.data?.latest_block} activeNodes={emeraldStatusQuery?.data?.data?.active_nodes} diff --git a/src/app/pages/HomePage/Graph/Graph/graph-utils.ts b/src/app/pages/HomePage/Graph/Graph/graph-utils.ts deleted file mode 100644 index 1ea6da4434..0000000000 --- a/src/app/pages/HomePage/Graph/Graph/graph-utils.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ScaleToOptions } from 'react-quick-pinch-zoom' -import { exhaustedTypeWarning } from '../../../../../types/errors' -import { SelectorArea, UniverseArea } from '../ParaTimeSelector' - -export abstract class GraphUtils { - static getScaleTo( - area: SelectorArea, - { width, height }: { width?: number; height?: number }, - ): ScaleToOptions { - const initialValue = { - scale: 1, - x: 0, - y: 0, - } - - if (!width || !height) { - return initialValue - } - - switch (area) { - case 'emerald': - return { - scale: 2.4, - x: 0.68 * width, - y: 0.1 * height, - } - case 'cipher': - return { - scale: 2.4, - x: 1.2 * width, - y: 0.7 * height, - } - case 'sapphire': - return { - scale: 2.4, - x: 0.31 * width, - y: 1.1 * height, - } - case 'consensus': - return { - scale: 2.4, - x: 0.65 * width, - y: 0.65 * height, - } - case UniverseArea: - return initialValue - default: - exhaustedTypeWarning('Unexpected area', area) - return initialValue - } - } -} diff --git a/src/app/pages/HomePage/Graph/Graph/index.tsx b/src/app/pages/HomePage/Graph/Graph/index.tsx deleted file mode 100644 index 6542bfd850..0000000000 --- a/src/app/pages/HomePage/Graph/Graph/index.tsx +++ /dev/null @@ -1,1174 +0,0 @@ -import { Link, useNavigate } from 'react-router-dom' -import { styled } from '@mui/material/styles' -import { - FC, - forwardRef, - ForwardRefRenderFunction, - memo, - MouseEventHandler, - PropsWithChildren, - useEffect, - useMemo, - useState, -} from 'react' -import { RouteUtils, isLocalnet } from '../../../../utils/route-utils' -import { useScreenSize } from '../../../../hooks/useScreensize' -import { Layer } from '../../../../../oasis-nexus/api' -import { Network } from '../../../../../types/network' -import { COLORS } from '../../../../../styles/theme/testnet/colors' -import { COLORS as LOCALNET_COLORS } from '../../../../../styles/theme/localnet/colors' -import { useTranslation } from 'react-i18next' -import { useConsensusFreshness, useRuntimeFreshness } from '../../../../components/OfflineBanner/hook' -import { RuntimeScope, SearchScope } from '../../../../../types/searchScope' -import { SelectorArea, UniverseArea } from '../ParaTimeSelector' - -interface GraphBaseProps { - disabled?: boolean - transparent?: boolean -} - -interface GraphProps extends GraphBaseProps { - network: Network - scale: number - // TODO: Consider moving this to a state management solution - selectedArea?: SelectorArea - setSelectedArea: (value: SelectorArea) => void - setActiveMobileGraphTooltip: (area: { current: SelectorArea | null }) => void - isZoomedIn: boolean -} - -interface GraphStyledProps extends GraphBaseProps { - selectedArea?: SelectorArea - hoveredLayer: Layer | null -} - -const LayerNotEnabledLabel = ({ network }: { network: Network }) => { - const { t } = useTranslation() - const isLocal = isLocalnet(network) - - return ( - <> - - {isLocal ? t('home.not') : t('home.coming')} - - - {isLocal ? t('home.enabled') : t('home.soon')} - - - ) -} - -const GraphStyled = styled('svg', { - shouldForwardProp: prop => - !(['disabled', 'transparent', 'selectedArea', 'hoveredLayer'] as (keyof GraphStyledProps)[]).includes( - prop as keyof GraphStyledProps, - ), -})(({ theme, disabled, transparent, selectedArea, hoveredLayer }) => ({ - position: 'absolute', - left: '50%', - top: '45%', - width: '80%', - height: '80%', - transform: 'translate(-50%, -50%)', - ...(disabled || transparent - ? { - pointerEvents: 'none', - } - : {}), - ...(transparent - ? { - opacity: 0.25, - } - : {}), - '[id]:not([aria-disabled="true"])': { - cursor: 'pointer', - }, - path: { - ...(selectedArea && selectedArea !== 'consensus' && selectedArea !== UniverseArea - ? { - [`&:not(.${selectedArea})`]: { - opacity: 0.5, - }, - '&.status-icon': { - opacity: 1, - }, - } - : {}), - }, - 'g[id$=circle]': { - 'ellipse:not(:last-child)': { - display: 'inline', - }, - 'ellipse:last-child': { - display: 'none', - }, - ...(selectedArea && selectedArea !== 'consensus' && selectedArea !== UniverseArea - ? { - [`&:not([id=${selectedArea}-circle])`]: { - opacity: 0.5, - }, - } - : {}), - }, - [theme.breakpoints.up('md')]: { - 'g[id$=circle]': { - '&:hover, &:focus-visible': { - 'ellipse:not(.hover-bg)': { - display: 'none', - }, - 'ellipse.hover-bg': { - display: 'inline', - }, - 'circle:not(.no-hide)': { - display: 'none', - }, - }, - }, - [`g[id$=${hoveredLayer}-circle]`]: { - 'ellipse:not(.hover-bg)': { - display: 'none', - }, - 'ellipse.hover-bg': { - display: 'inline', - }, - 'circle:not(.no-hide)': { - display: 'none', - }, - }, - }, - text: { - userSelect: 'none', - pointerEvents: 'none', - }, - 'g.highlight': { - '&:hover, &:focus-visible': { - path: { - strokeWidth: 4, - }, - }, - }, - '&.testnet': { - 'g.highlight': { - '&:hover, &:focus-visible': { - path: { - stroke: COLORS.brandExtraDark, - }, - }, - }, - }, -})) - -const preventDoubleClick: { onDoubleClick: MouseEventHandler } = { - onDoubleClick: e => e.preventDefault(), -} - -const handleHover: ( - layer: Layer, - set: (layer: Layer | null) => void, -) => { - onMouseEnter: MouseEventHandler - onMouseLeave: MouseEventHandler -} = (layer: Layer, set: (layer: Layer | null) => void) => ({ - onMouseEnter: () => { - set(layer) - }, - onMouseLeave: () => { - set(null) - }, -}) - -const GraphLayerStatus: FC< - PropsWithChildren<{ - fillText: string - iconX: number - iconY: number - textX: number - textY: number - outOfDate: boolean | undefined - }> -> = ({ children, fillText, iconX, iconY, textX, textY, outOfDate }) => { - const { t } = useTranslation() - - if (outOfDate === undefined) { - return ( - - {children} - - ) - } - - return ( - <> - - {children} - {!outOfDate && ( - - {t('common.online')} - - )} - {outOfDate && ( - - {t('home.offline')} - - )} - - - {!outOfDate && ( - - )} - {outOfDate && ( - - )} - - - ) -} - -const ConsensusStatus: FC<{ network: Network; statusChange: (outOfDate?: boolean) => void }> = ({ - network, - statusChange, -}) => { - const { outOfDate } = useConsensusFreshness(network) - - useEffect(() => { - statusChange(outOfDate) - return () => { - statusChange(undefined) - } - }, [outOfDate, statusChange]) - - return null -} - -const RuntimeStatus: FC<{ scope: RuntimeScope; statusChange: (outOfDate?: boolean) => void }> = ({ - scope, - statusChange, -}) => { - const { outOfDate } = useRuntimeFreshness(scope) - - useEffect(() => { - statusChange(outOfDate) - return () => { - statusChange(undefined) - } - }, [outOfDate, statusChange]) - - return null -} - -const LayerStatus: FC<{ scope: SearchScope; statusChange: (outOfDate?: boolean) => void }> = ({ - scope: { network, layer }, - statusChange, -}) => - layer === 'consensus' ? ( - - ) : ( - - ) - -const GraphCmp: ForwardRefRenderFunction = ( - { - disabled = false, - transparent = false, - selectedArea, - network, - setSelectedArea, - scale, - setActiveMobileGraphTooltip, - isZoomedIn, - }, - ref, -) => { - const { t } = useTranslation() - const navigate = useNavigate() - const { isMobile } = useScreenSize() - const [hoveredLayer, setHoveredLayer] = useState(null) - const [outOfDateMap, setOutOfDateMap] = useState< - Partial<{ - [key in Layer]: boolean | undefined - }> - >({}) - - useEffect(() => { - setActiveMobileGraphTooltip({ current: null }) - // should only close tooltips on isMobile change - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isMobile]) - - useEffect(() => { - setOutOfDateMap({}) - }, [network]) - - const isLayerDisabled = (Layer: Layer) => { - return !RouteUtils.getAllLayersForNetwork(network).enabled.includes(Layer) - } - - const disabledMap: Partial> = { - emerald: isLayerDisabled('emerald'), - consensus: isLayerDisabled('consensus'), - cipher: isLayerDisabled('cipher'), - sapphire: isLayerDisabled('sapphire'), - } - - const enabledLayers: Layer[] = useMemo(() => RouteUtils.getAllLayersForNetwork(network).enabled, [network]) - - const onSelectArea = (area: SelectorArea) => { - if (isMobile && (isZoomedIn || area === 'consensus')) { - setSelectedArea(area) - setActiveMobileGraphTooltip({ current: area }) - - return - } - - if ( - ((!isMobile && !isZoomedIn) || area === selectedArea) && - area !== UniverseArea && - RouteUtils.getAllLayersForNetwork(network).enabled.includes(area) - ) { - navigate(RouteUtils.getDashboardRoute({ network, layer: area })) - - return - } - - if (!disabledMap[area]) { - setSelectedArea(area) - } - } - - const graphThemes = { - mainnet: { - cipherCircle: 'url(#paint7_radial_6093_287252)', - cipherCircleFilter: 'url(#filter2_dii_6093_287252)', - cipherCircleFill: COLORS.darkBlue, - emeraldCircle: 'url(#paint6_radial_6093_287252)', - emeraldCircleFilter: 'url(#filter0_dii_6093_287252)', - sapphireCircle: 'url(#paint7_radial_6093_287252)', - sapphireCircleFilter: 'url(#filter1_dii_6093_287252)', - consensusCircle: COLORS.brandExtraDark, - line: COLORS.aqua, - text: COLORS.white, - textBackground: COLORS.graphLabel, - textBorder: COLORS.graphLine, - circleBorder: COLORS.brandExtraDark, - hoverBackground: COLORS.aqua, - hoverText: COLORS.brandExtraDark, - }, - testnet: { - cipherCircle: COLORS.testnet, - cipherCircleFilter: 'url(#filter2_di_6093_290291)', - cipherCircleFill: COLORS.testnet, - emeraldCircle: COLORS.testnet, - emeraldCircleFilter: 'url(#filter0_di_6093_290291)', - sapphireCircle: COLORS.testnet, - sapphireCircleFilter: 'url(#filter1_di_6093_290291)', - consensusCircle: COLORS.testnetLight, - line: COLORS.testnet, - text: COLORS.brandExtraDark, - textBackground: COLORS.testnetLight, - textBorder: COLORS.testnet, - circleBorder: COLORS.testnet, - hoverBackground: COLORS.brandExtraDark, - hoverText: COLORS.white, - }, - localnet: { - cipherCircle: LOCALNET_COLORS.localnet, - cipherCircleFilter: 'url(#filter2_di_6093_290291)', - cipherCircleFill: LOCALNET_COLORS.localnet, - emeraldCircle: LOCALNET_COLORS.localnet, - emeraldCircleFilter: 'url(#filter0_di_6093_290291)', - sapphireCircle: LOCALNET_COLORS.localnet, - sapphireCircleFilter: 'url(#filter1_di_6093_290291)', - consensusCircle: LOCALNET_COLORS.localnetLight, - line: LOCALNET_COLORS.localnet, - text: COLORS.brandExtraDark, - textBackground: LOCALNET_COLORS.localnetLight, - textBorder: LOCALNET_COLORS.localnet, - circleBorder: LOCALNET_COLORS.localnet, - hoverBackground: COLORS.brandExtraDark, - hoverText: COLORS.white, - }, - } - - const graphTheme = graphThemes[network] - - const layers = useMemo( - () => - enabledLayers.map(layer => ( - setOutOfDateMap(prevValue => ({ ...prevValue, [layer]: outOfDate }))} - /> - )), - [network, enabledLayers], - ) - - return ( - <> - {isZoomedIn && network && layers} - - - - - - - - {/* Sapphire blocks connector */} - - {/* Sapphire transactions connector */} - - {/* Sapphire ROFL connector */} - - {/* Sapphire tokens connector */} - - - - - - - - {t('home.txns')} - - - - - - - - {t('home.blocks')} - - - - - - - - {t('common.tokens')} - - - - - - - - - {t('home.blocks')} - - - - - - - - {t('home.txns')} - - - - - - - - {t('common.tokens')} - - - - - - - - {t('common.rofl')} - - - - - onSelectArea('emerald')} - filter={graphTheme.emeraldCircleFilter} - {...preventDoubleClick} - {...handleHover('emerald', setHoveredLayer)} - > - {network === 'mainnet' && ( - - )} - - - - onSelectArea('emerald')} - {...preventDoubleClick} - {...handleHover('emerald', setHoveredLayer)} - > - {(isMobile || hoveredLayer !== 'emerald') && ( - - {t('common.emerald')} - - )} - - {!isMobile && hoveredLayer === 'emerald' && !disabledMap.emerald && ( - - {t('common.view')} - - )} - {!isMobile && hoveredLayer === 'emerald' && disabledMap.emerald && ( - - - - )} - - onSelectArea('sapphire')} - filter={graphTheme.sapphireCircleFilter} - {...preventDoubleClick} - {...handleHover('sapphire', setHoveredLayer)} - > - {network === 'mainnet' && ( - - )} - - - - onSelectArea('sapphire')} - {...preventDoubleClick} - {...handleHover('sapphire', setHoveredLayer)} - > - {(isMobile || hoveredLayer !== 'sapphire') && ( - - {t('common.sapphire')} - - )} - {!isMobile && hoveredLayer === 'sapphire' && !disabledMap.sapphire && ( - - {t('common.view')} - - )} - {!isMobile && hoveredLayer === 'sapphire' && disabledMap.sapphire && ( - - - - )} - - onSelectArea('consensus')} - aria-disabled={disabledMap.consensus} - {...handleHover('consensus', setHoveredLayer)} - > - - - - - - onSelectArea('consensus')} - {...preventDoubleClick} - {...handleHover('consensus', setHoveredLayer)} - > - {(isMobile || hoveredLayer !== 'consensus') && ( - - {t('common.consensus')} - - )} - {!isMobile && hoveredLayer === 'consensus' && !disabledMap.consensus && ( - - {t('common.view')} - - )} - {!isMobile && hoveredLayer === 'consensus' && disabledMap.consensus && ( - - - - )} - - onSelectArea('cipher')} - id={`cipher-circle`} - {...preventDoubleClick} - {...handleHover('cipher', setHoveredLayer)} - > - - {network === 'mainnet' && ( - - )} - - - onSelectArea('cipher')} - {...preventDoubleClick} - {...handleHover('cipher', setHoveredLayer)} - > - {(isMobile || hoveredLayer !== 'cipher') && ( - - {t('common.cipher')} - - )} - {!isMobile && hoveredLayer === 'cipher' && !disabledMap.cipher && ( - - {t('common.view')} - - )} - {!isMobile && hoveredLayer === 'cipher' && disabledMap.cipher && ( - - - - )} - - - {network === 'testnet' && ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - )} - - {network === 'mainnet' && ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - )} - - - ) -} - -export const Graph = memo(forwardRef(GraphCmp)) diff --git a/src/app/pages/HomePage/Graph/GraphTooltipMobile/index.tsx b/src/app/pages/HomePage/Graph/GraphTooltipMobile/index.tsx deleted file mode 100644 index a95a711566..0000000000 --- a/src/app/pages/HomePage/Graph/GraphTooltipMobile/index.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import { FC, MouseEvent } from 'react' -import { COLORS } from '../../../../../styles/theme/colors' -import AccessTimeIcon from '@mui/icons-material/AccessTime' -import { Typography } from '@oasisprotocol/ui-library/src/components/typography' -import CheckCircleIcon from '@mui/icons-material/CheckCircle' -import ErrorIcon from '@mui/icons-material/Error' -import { useTranslation } from 'react-i18next' -import { TFunction } from 'i18next' -import { Network } from '../../../../../types/network' -import { useConsensusFreshness, useRuntimeFreshness } from '../../../../components/OfflineBanner/hook' -import { getNetworkIcons } from '../../../../utils/content' -import { useNavigate } from 'react-router-dom' -import { RouteUtils } from '../../../../utils/route-utils' -import { Button } from '@oasisprotocol/ui-library/src/components/ui/button' -import CloseIcon from '@mui/icons-material/Close' -import { zIndexHomePage } from '../../index' -import { SelectorArea, UniverseArea } from '../ParaTimeSelector' -import { cn } from '@oasisprotocol/ui-library/src/lib/utils' - -interface GraphTooltipMobileProps { - network: Network - area: SelectorArea - onClose: (e?: MouseEvent) => void -} - -type TooltipInfo = { - disabled: boolean - failing?: boolean - body: GraphTooltipBodyProps -} - -const layerTooltipBodyCaption = (t: TFunction, enabled: boolean, outOfDate: boolean | undefined) => { - if (!enabled) { - return t('home.tooltip.coming') - } - - return outOfDate ? t('common.outOfDate') : t('common.online') -} - -const useAreaTooltipMap = (network: Network): Partial> => { - const isSapphireEnabled = RouteUtils.getAllLayersForNetwork(network).enabled.includes('sapphire') - const isEmeraldEnabled = RouteUtils.getAllLayersForNetwork(network).enabled.includes('emerald') - const isCipherEnabled = RouteUtils.getAllLayersForNetwork(network).enabled.includes('cipher') - const isConsensusEnabled = RouteUtils.getAllLayersForNetwork(network).enabled.includes('consensus') - - const isEmeraldOutOfDate = useRuntimeFreshness({ network, layer: 'emerald' }).outOfDate - const isSapphireOutOfDate = useRuntimeFreshness({ network, layer: 'sapphire' }).outOfDate - const isConsensusOutOfDate = useConsensusFreshness(network).outOfDate - return { - sapphire: { - disabled: !isSapphireEnabled, - body: { - title: (t: TFunction) => t('common.sapphire'), - caption: (t: TFunction) => layerTooltipBodyCaption(t, isSapphireEnabled, isSapphireOutOfDate), - body: (t: TFunction) => t('home.tooltip.sapphireParaTimeDesc'), - }, - ...(isSapphireEnabled - ? { - failing: isSapphireOutOfDate, - } - : {}), - }, - emerald: { - disabled: !isEmeraldEnabled, - body: { - title: (t: TFunction) => t('common.emerald'), - caption: (t: TFunction) => layerTooltipBodyCaption(t, isEmeraldEnabled, isEmeraldOutOfDate), - body: (t: TFunction) => t('home.tooltip.emeraldParaTimeDesc'), - }, - ...(isEmeraldEnabled - ? { - failing: isEmeraldOutOfDate, - } - : {}), - }, - cipher: { - disabled: !isCipherEnabled, - body: { - title: (t: TFunction) => t('common.cipher'), - caption: (t: TFunction) => layerTooltipBodyCaption(t, isCipherEnabled, false /* TODO */), - body: (t: TFunction) => t('home.tooltip.cipherParaTimeDesc'), - }, - }, - consensus: { - disabled: !isConsensusEnabled, - body: { - title: (t: TFunction) => t('common.consensus'), - caption: (t: TFunction) => layerTooltipBodyCaption(t, isConsensusEnabled, isConsensusOutOfDate), - body: (t: TFunction) => t('home.tooltip.consensusDesc'), - }, - ...(isConsensusEnabled - ? { - failing: isConsensusOutOfDate, - } - : {}), - }, - } -} - -interface GraphTooltipHeaderProps { - disabled: boolean - network: Network - area: SelectorArea -} - -const GraphTooltipHeader: FC = ({ disabled, network, area }) => { - const { t } = useTranslation() - const icons = getNetworkIcons({ className: 'h-9 w-9' }) - - return ( -
- {disabled ? ( - - ) : ( - <> - {icons[network]} - - {area === 'consensus' ? t('home.tooltip.openConsensus') : t('home.tooltip.openParatime')} - - - )} -
- ) -} - -interface GraphTooltipBodyProps { - title: (t: TFunction) => string - caption: (t: TFunction) => string - body: (t: TFunction) => string - disabled?: boolean - failing?: boolean -} - -const GraphTooltipBody: FC = ({ title, caption, body, disabled, failing }) => { - const { t } = useTranslation() - return ( -
-
- {title(t)} - - - {caption(t)} - {!(disabled || failing) && ( - - )} - {failing && } - -
-
- {body(t)} -
-
- ) -} - -export const GraphTooltipMobile: FC = ({ network, area, onClose }) => { - const navigate = useNavigate() - const { t } = useTranslation() - const tooltip = useAreaTooltipMap(network)[area] - if (!tooltip) return - const { body, disabled, failing } = tooltip - - const navigateTo = () => { - if (disabled) { - return - } - if (area === UniverseArea) { - return - } - navigate(RouteUtils.getDashboardRoute({ network, layer: area })) - } - - return ( - <> -
-
- -
- - -
-
- - ) -} diff --git a/src/app/pages/HomePage/Graph/HelpScreen/index.tsx b/src/app/pages/HomePage/Graph/HelpScreen/index.tsx deleted file mode 100644 index 949c2343f9..0000000000 --- a/src/app/pages/HomePage/Graph/HelpScreen/index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { FC, ReactElement, useEffect, useRef, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { TFunction } from 'i18next' -import MobileStepper from '@mui/material/MobileStepper' -import { Typography } from '@oasisprotocol/ui-library/src/components/typography' -import Button from '@mui/material/Button' -import { ParaTimeSelectorStep } from '../types' -import { SlideChangeEvent } from '../../../../../types/swiper' -import { storage } from '../../../../utils/storage' -import { StorageKeys } from '../../../../../types/storage' -import { TapIcon } from '../../../../components/CustomIcons/Tap' -import { PinchIcon } from '../../../../components/CustomIcons/Pinch' -import { NavigateIcon } from '../../../../components/CustomIcons/Navigate' -import { Theme } from '@mui/material/styles/createTheme' - -interface Step { - icon: ReactElement - label: string -} - -const iconSx = (theme: Theme) => ({ - overflow: 'hidden', - margin: `0 auto ${theme.spacing(3)}`, - fontSize: 50, -}) - -const steps = (t: TFunction): Step[] => [ - { - icon: , - label: t('home.helpScreen.navigate'), - }, - { - icon: , - label: t('home.helpScreen.pinch'), - }, - { - icon: , - label: t('home.helpScreen.tap'), - }, -] - -type AvailableSteps = 0 | 1 | 2 - -interface HelpScreenProps { - setParaTimeStep: (value: ParaTimeSelectorStep) => void -} - -const localStore = storage() - -const HelpScreen: FC = ({ setParaTimeStep }) => { - const { t } = useTranslation() - const [activeStep, setActiveStep] = useState(0) - const allSteps = steps(t) - const totalSteps = allSteps.length - const currentStep = allSteps[activeStep] - - const swiperElRef = useRef(null) - - useEffect(() => { - const handleSlideChange = (e: SlideChangeEvent) => { - const [swiper] = e.detail - setActiveStep(swiper.activeIndex as AvailableSteps) - } - - const swiperEl = swiperElRef.current - swiperEl?.addEventListener<'swiperslidechange'>('swiperslidechange', handleSlideChange) - - return () => { - swiperEl?.removeEventListener<'swiperslidechange'>('swiperslidechange', handleSlideChange) - } - }, []) - - const onGetStartedClick = () => { - setParaTimeStep(ParaTimeSelectorStep.Explore) - - localStore.set(StorageKeys.MobileHelpScreenShown, true) - } - - return ( -
-
- - {allSteps.map(({ icon, label }, index) => ( - - {Math.abs(activeStep - index) < totalSteps ? <>{icon} : null} - - ))} - -
- - {currentStep.label} - - {activeStep < 2 && ( - - )} - {activeStep > 1 && ( - - )} -
- ) -} - -export default HelpScreen diff --git a/src/app/pages/HomePage/Graph/NetworkSelector/index.tsx b/src/app/pages/HomePage/Graph/NetworkSelector/index.tsx deleted file mode 100644 index 37ff285679..0000000000 --- a/src/app/pages/HomePage/Graph/NetworkSelector/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import { FC, ForwardedRef, forwardRef, ReactElement } from 'react' -import { useTranslation } from 'react-i18next' -import { Typography } from '@oasisprotocol/ui-library/src/components/typography' -import { styled } from '@mui/material/styles' -import { COLORS } from '../../../../../styles/theme/colors' -import { getNetworkNames, Network } from '../../../../../types/network' -import { RouteUtils } from '../../../../utils/route-utils' -import { - Select, - SelectOptionBase, - StyledSelectButton, - StyledSelectListbox, - StyledSelectOption, -} from '../../../../components/Select' -import { selectClasses, SelectRootSlotProps } from '@mui/base/Select' -import ExpandLessIcon from '@mui/icons-material/ExpandLess' -import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { getNetworkIcons } from '../../../../utils/content' -import { optionClasses } from '@mui/base/Option' -import { useTheme } from '@mui/material/styles' - -interface NetworkOption extends SelectOptionBase { - label: Network - value: Network -} - -const StyledNetworkSelector = styled(Select)(({ theme }) => ({ - position: 'absolute', - bottom: theme.spacing(3), - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(3), - width: '100%', - alignItems: 'center', - [theme.breakpoints.up('sm')]: { - bottom: theme.spacing(5), - }, -})) - -const StyledButton = styled(StyledSelectButton)(({ theme }) => ({ - height: '44px', - minWidth: '200px', - backgroundColor: theme.palette.layout.primaryBackground, - border: `2px solid ${theme.palette.layout.lightBorder}`, - color: theme.palette.layout.contrastMain, - '&:hover': { - backgroundColor: theme.palette.layout.primaryBackground, - }, - [`&.${selectClasses.focusVisible}`]: { - backgroundColor: theme.palette.layout.primaryBackground, - outline: 'none', - border: `2px solid ${theme.palette.layout.main}`, - }, -})) - -const StyledListbox = styled(StyledSelectListbox)(() => ({ - minWidth: '200px', - background: COLORS.white, - color: COLORS.grayDark, -})) - -const StyledOption = styled(StyledSelectOption)(() => ({ - height: '44px', - color: COLORS.grayDark, - svg: { - color: COLORS.grayExtraDark, - }, - [`&:hover:not(.${optionClasses.disabled}), - &.${optionClasses.highlighted}`]: { - backgroundColor: COLORS.grayLight, - }, -})) - -const SelectOption = ({ value }: NetworkOption): ReactElement => { - const { t } = useTranslation() - - const labels = getNetworkNames(t) - const icons = getNetworkIcons() - - return ( - -
- {icons[value]} - {labels[value]} -
-
- ) -} - -const NetworkSelectorButton = forwardRef( - (props: SelectRootSlotProps, ref: ForwardedRef) => { - const { ownerState, ...restProps } = props - const { open, value } = ownerState - const { t } = useTranslation() - const label = getNetworkNames(t) - const icons = getNetworkIcons() - - return ( - -
- {icons[value!]} - {label[value!]} -
- {open ? : } -
- ) - }, -) - -interface NetworkSelectProps { - network: Network - setNetwork: (network: Network | null) => void -} - -export const NetworkSelector: FC = ({ network, setNetwork }) => { - const { t } = useTranslation() - const theme = useTheme() - - const options = RouteUtils.getEnabledNetworks().map(network => ({ - label: network, - value: network, - })) - - return ( - - {t('home.selectedNetwork')} - - } - /> - ) -} diff --git a/src/app/pages/HomePage/Graph/ParaTimeSelector/index.tsx b/src/app/pages/HomePage/Graph/ParaTimeSelector/index.tsx deleted file mode 100644 index fb1cce28c3..0000000000 --- a/src/app/pages/HomePage/Graph/ParaTimeSelector/index.tsx +++ /dev/null @@ -1,271 +0,0 @@ -import { FC, memo, useEffect, useRef, useState } from 'react' -import { styled } from '@mui/material/styles' -import paratimeSelectorGlow from '../images/paratime-selector-glow.svg' -import paratimeSelectorGlobe from '../images/paratime-selector-globe.svg' -import paratimeSelectorGlowTestnet from '../images/paratime-selector-glow-testnet.svg' -import paratimeSelectorGlobeTestnet from '../images/paratime-selector-globe-testnet.svg' -import { Graph } from '../Graph' -import Button from '@mui/material/Button' -import { useTranslation } from 'react-i18next' -import { ParaTimeSelectorStep } from '../types' -import { ParaTimeSelectorUtils } from '../para-time-selector-utils' -import { useScreenSize } from '../../../../hooks/useScreensize' -import QuickPinchZoom, { make3dTransformValue, UpdateAction } from 'react-quick-pinch-zoom' -import ChevronLeftIcon from '@mui/icons-material/ChevronLeft' -import { GraphUtils } from '../Graph/graph-utils' -import useResizeObserver from 'use-resize-observer' -import HelpScreen from '../HelpScreen' -import { NetworkSelector } from '../NetworkSelector' -import { useSearchQueryNetworkParam } from '../../../../hooks/useSearchQueryNetworkParam' -import { storage } from '../../../../utils/storage' -import { StorageKeys } from '../../../../../types/storage' -import { GraphTooltipMobile } from '../GraphTooltipMobile' -import { fixedNetwork } from '../../../../utils/route-utils' -import { cn } from '@oasisprotocol/ui-library/src/lib/utils' - -interface ParaTimeSelectorBaseProps { - disabled: boolean -} - -export const ExploreBtn = styled(Button)(({ theme }) => ({ - fontWeight: 700, - paddingLeft: theme.spacing(4), - paddingRight: theme.spacing(4), - width: 'max-content', - [theme.breakpoints.up('sm')]: { - paddingLeft: theme.spacing(6), - paddingRight: theme.spacing(6), - }, -})) - -export const ZoomOutBtn = styled(Button)(({ theme }) => ({ - color: theme.palette.layout.graphZoomOutText, - position: 'absolute', - top: theme.spacing(5), - left: '50%', - transform: 'translateX(-50%)', - lineHeight: '18px', - textTransform: 'none', - '&&:hover, &&:active': { - color: theme.palette.layout.graphZoomOutText, - textDecoration: 'none', - }, - '&&': { - backgroundColor: 'transparent', - }, -})) - -// border affecting scale (quick-pinch-zoom) -const QuickPinchZoomOuter = styled('div')(({ theme }) => ({ - '> div': { - position: 'absolute', - inset: 0, - borderWidth: 10, - borderStyle: 'solid', - borderRadius: '50%', - borderColor: theme.palette.layout.darkBorder, - }, - [theme.breakpoints.up('sm')]: { - '> div': { - borderWidth: 15, - }, - }, -})) - -const QuickPinchZoomInner = styled('div')(() => ({ - position: 'absolute', - width: '100%', - height: '100%', -})) - -interface ParaTimeSelectorProps extends ParaTimeSelectorBaseProps { - step: ParaTimeSelectorStep - setStep: (value: ParaTimeSelectorStep) => void - showInfoScreen: boolean - graphZoomedIn: boolean - onGraphZoomedIn: (isGraphZoomedIn: boolean) => void -} - -const localStore = storage() - -// This is a special area used to indicate that we should zoom out, -// and see the whole universe. -export const UniverseArea = 'Universe' - -export type SelectorArea = typeof UniverseArea | 'consensus' | 'cipher' | 'emerald' | 'sapphire' - -const ParaTimeSelectorCmp: FC = ({ - disabled, - step, - setStep, - showInfoScreen, - graphZoomedIn, - onGraphZoomedIn, -}) => { - const graphRef = useRef(null) - const quickPinchZoomRef = useRef(null) - const quickPinchZoomInnerRef = useRef(null) - const { isMobile } = useScreenSize() - const { t } = useTranslation() - const exploreBtnTextTranslated = t('home.exploreBtnText') - const { network, setNetwork } = useSearchQueryNetworkParam() - - // Using object here to force side effect trigger when setting to the same area - const [selectedArea, setSelectedArea] = useState<{ current: SelectorArea }>() - const [activeMobileGraphTooltip, setActiveMobileGraphTooltip] = useState<{ current: SelectorArea | null }>({ - current: null, - }) - - const [scale, setScale] = useState(1) - - const { width, height } = useResizeObserver({ - ref: graphRef, - }) - - useEffect(() => { - if (selectedArea) { - quickPinchZoomRef.current?.scaleTo(GraphUtils.getScaleTo(selectedArea.current, { width, height })) - } - }, [selectedArea, width, height]) - - useEffect(() => { - // Switch from mobile -> desktop view while on help screen - if (!isMobile && step === ParaTimeSelectorStep.ShowHelpScreen) { - setStep(ParaTimeSelectorStep.Explore) - } - }, [isMobile, step, setStep]) - - const onExploreClick = () => { - const mobileHelpScreenShown = localStore.get(StorageKeys.MobileHelpScreenShown) - - if (!mobileHelpScreenShown) { - setStep(ParaTimeSelectorStep.ShowHelpScreen) - } else { - setStep(ParaTimeSelectorStep.Explore) - } - } - - const onZoomOutClick = () => { - setSelectedArea({ current: UniverseArea }) - } - - const onPinchZoom = ({ x, y, scale }: UpdateAction) => { - if (step !== ParaTimeSelectorStep.Explore) { - return - } - - const transformValue = make3dTransformValue({ x, y, scale }) - setScale(scale) - quickPinchZoomInnerRef.current?.style.setProperty('transform', transformValue) - } - - const clearSelectedArea = () => { - if (selectedArea?.current) { - setSelectedArea(undefined) - } - } - - useEffect(() => { - // true when scale larger than "safe" zoom, due to viewport - const isZoomedIn = scale > 1.5 - - onGraphZoomedIn(isZoomedIn) - }, [scale, onGraphZoomedIn]) - - return ( - <> -
-
- - - - setSelectedArea({ current: area })} - scale={scale} - setActiveMobileGraphTooltip={setActiveMobileGraphTooltip} - isZoomedIn={graphZoomedIn} - /> - - - - {!isMobile && ( -
- } - onClick={onZoomOutClick} - disabled={disabled} - > - {t('home.zoomOutBtnText')} - -
- )} - {isMobile && ParaTimeSelectorUtils.showExploreBtn(step) && ( - - {t('home.exploreBtnText')} - - )} - {ParaTimeSelectorUtils.showMobileHelpScreen(step, isMobile, showInfoScreen) && ( - - )} -
- {!fixedNetwork && step === ParaTimeSelectorStep.Explore && ( - setNetwork(network ?? 'mainnet')} /> - )} -
- {activeMobileGraphTooltip.current && ( - { - setActiveMobileGraphTooltip({ current: null }) - }} - /> - )} - - ) -} - -export const ParaTimeSelector = memo(ParaTimeSelectorCmp) diff --git a/src/app/pages/HomePage/Graph/images/paratime-selector-globe-testnet.svg b/src/app/pages/HomePage/Graph/images/paratime-selector-globe-testnet.svg deleted file mode 100644 index 4cc3233eb5..0000000000 --- a/src/app/pages/HomePage/Graph/images/paratime-selector-globe-testnet.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/app/pages/HomePage/Graph/images/paratime-selector-globe.svg b/src/app/pages/HomePage/Graph/images/paratime-selector-globe.svg deleted file mode 100644 index 075ea2d82e..0000000000 --- a/src/app/pages/HomePage/Graph/images/paratime-selector-globe.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/app/pages/HomePage/Graph/images/paratime-selector-glow-testnet.svg b/src/app/pages/HomePage/Graph/images/paratime-selector-glow-testnet.svg deleted file mode 100644 index 906aae5f2e..0000000000 --- a/src/app/pages/HomePage/Graph/images/paratime-selector-glow-testnet.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/pages/HomePage/Graph/images/paratime-selector-glow.svg b/src/app/pages/HomePage/Graph/images/paratime-selector-glow.svg deleted file mode 100644 index 978d7d0f81..0000000000 --- a/src/app/pages/HomePage/Graph/images/paratime-selector-glow.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/pages/HomePage/Graph/para-time-selector-utils.ts b/src/app/pages/HomePage/Graph/para-time-selector-utils.ts deleted file mode 100644 index 421f5f64ad..0000000000 --- a/src/app/pages/HomePage/Graph/para-time-selector-utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ParaTimeSelectorStep } from './types' -import { storage } from '../../../utils/storage' -import { StorageKeys } from '../../../../types/storage' - -const localStore = storage() - -export abstract class ParaTimeSelectorUtils { - static getIsGraphTransparent(step: ParaTimeSelectorStep) { - return step !== ParaTimeSelectorStep.Explore - } - - static showExploreBtn(step: ParaTimeSelectorStep) { - return step === ParaTimeSelectorStep.EnableExplore - } - - static showMobileHelpScreen(step: ParaTimeSelectorStep, isMobile: boolean, showInfoScreen: boolean) { - const mobileHelpScreenShown = localStore.get(StorageKeys.MobileHelpScreenShown) - - if (!showInfoScreen && (!isMobile || mobileHelpScreenShown)) { - return false - } - - return step === ParaTimeSelectorStep.ShowHelpScreen - } -} diff --git a/src/app/pages/HomePage/Graph/types.ts b/src/app/pages/HomePage/Graph/types.ts deleted file mode 100644 index a64d514d57..0000000000 --- a/src/app/pages/HomePage/Graph/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -export enum ParaTimeSelectorStep { - /** - * Graph interactions disabled - explore button shown on top of graph - */ - EnableExplore, - /** - * Show help screen - */ - ShowHelpScreen, - /** - * Graph interactions enabled - */ - Explore, -} diff --git a/src/app/pages/HomePage/HomeSearch.tsx b/src/app/pages/HomePage/HomeSearch.tsx new file mode 100644 index 0000000000..5392484540 --- /dev/null +++ b/src/app/pages/HomePage/HomeSearch.tsx @@ -0,0 +1,31 @@ +import { FC } from 'react' +import { Search } from '../../components/Search' +import { useTranslation } from 'react-i18next' +import { useSearchQueryNetworkParam } from '../../hooks/useSearchQueryNetworkParam' +import { useIsApiReachable } from '../../components/OfflineBanner/hook' + +import SearchBg from './images/search-bg.png' +import { Typography } from '@oasisprotocol/ui-library/src/components/typography' + +export const HomeSearch: FC = () => { + const { t } = useTranslation() + const { network } = useSearchQueryNetworkParam() + const isApiReachable = useIsApiReachable(network).reachable + + return ( + + ) +} diff --git a/src/app/pages/HomePage/PontusXCard.tsx b/src/app/pages/HomePage/PontusXCard.tsx index d44c0bd1cf..770d88100c 100644 --- a/src/app/pages/HomePage/PontusXCard.tsx +++ b/src/app/pages/HomePage/PontusXCard.tsx @@ -22,6 +22,7 @@ export const PontusXCard: FC = () => { return ( @@ -32,25 +33,28 @@ export const PontusXCard: FC = () => { {t('common.pontusx')}{' '} - {t('home.ecosystem.by')} - - {t('home.ecosystem.deltaDAODescription')}{' '} - - {t('home.ecosystem.visitSite')} - - - } - > - {t('home.ecosystem.deltaDAO')} - + + {t('home.ecosystem.by')}{' '} + + {t('home.ecosystem.deltaDAODescription')}{' '} + e.stopPropagation()} + > + {t('home.ecosystem.visitSite')} + + + } + > + {t('home.ecosystem.deltaDAO')} + + } @@ -61,17 +65,17 @@ export const PontusXCard: FC = () => { activeNodes={pontusxStatusQuery?.data?.data?.active_nodes} footer={ <> - - + } /> diff --git a/src/app/pages/HomePage/RoflAppsCard.tsx b/src/app/pages/HomePage/RoflAppsCard.tsx new file mode 100644 index 0000000000..f54ebcfb40 --- /dev/null +++ b/src/app/pages/HomePage/RoflAppsCard.tsx @@ -0,0 +1,22 @@ +import { FC } from 'react' +import { useTranslation } from 'react-i18next' +import { LatestRoflAppsContent } from '../ParatimeDashboardPage/LatestRoflApps' +import { Card, CardContent, CardHeader, CardTitle } from '@oasisprotocol/ui-library/src/components/cards' +import { ErrorBoundary } from '../../components/ErrorBoundary' + +export const RoflAppsCard: FC = () => { + const { t } = useTranslation() + + return ( + + + {t('rofl.listTitle')} + + + + + + + + ) +} diff --git a/src/app/pages/HomePage/images/background.svg b/src/app/pages/HomePage/images/background.svg deleted file mode 100644 index d3b89d36b7..0000000000 --- a/src/app/pages/HomePage/images/background.svg +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/app/pages/HomePage/images/search-bg.png b/src/app/pages/HomePage/images/search-bg.png new file mode 100644 index 0000000000..e6df7eb2c3 Binary files /dev/null and b/src/app/pages/HomePage/images/search-bg.png differ diff --git a/src/app/pages/HomePage/index.tsx b/src/app/pages/HomePage/index.tsx index 9589f40426..ccb15d5e38 100644 --- a/src/app/pages/HomePage/index.tsx +++ b/src/app/pages/HomePage/index.tsx @@ -1,110 +1,35 @@ -import { FC, useState } from 'react' -import { Logotype } from '../../components/PageLayout/Logotype' -import background from './images/background.svg' -import { COLORS } from '../../../styles/theme/colors' -import { Search } from '../../components/Search' -import { ParaTimeSelector } from './Graph/ParaTimeSelector' +import { FC } from 'react' import { Footer } from '../../components/PageLayout/Footer' -import { useScreenSize } from '../../hooks/useScreensize' -import { Button } from '@oasisprotocol/ui-library/src/components/ui/button' -import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined' -import { useTranslation } from 'react-i18next' -import { ParaTimeSelectorStep } from './Graph/types' import { BuildBanner } from '../../components/BuildBanner' import { useSearchQueryNetworkParam } from '../../hooks/useSearchQueryNetworkParam' -import { ThemeByScope } from '../../components/ThemeByScope' import { NetworkOfflineBanner } from '../../components/OfflineBanner' -import { useIsApiReachable } from '../../components/OfflineBanner/hook' -import { cn } from '@oasisprotocol/ui-library/src/lib/utils' - -export const zIndexHomePage = { - paraTimeSelector: 1, - searchInput: 2, - logo: 3, - mobileTooltip: 4, -} +import { Social } from '../../components/Social' +import { RecentBlocksCard } from './RecentBlocksCard' +import { Ecosystem } from './Ecosystem' +import { RoflAppsCard } from './RoflAppsCard' +import { HomeSearch } from './HomeSearch' +import { Header } from 'app/components/PageLayout/Header' +import { TotalTransactions } from 'app/components/TotalTransactions' export const HomePage: FC = () => { - const { t } = useTranslation() - const infoAriaLabel = t('home.helpScreen.infoIconAria') - const { isMobile } = useScreenSize() const { network } = useSearchQueryNetworkParam() - const isApiReachable = useIsApiReachable(network).reachable - - const [searchHasFocus, setSearchHasFocus] = useState(false) - const [isGraphZoomedIn, setIsGraphZoomedIn] = useState(false) - const [step, setStep] = useState(() => { - if (isMobile) { - return ParaTimeSelectorStep.EnableExplore - } - - return ParaTimeSelectorStep.Explore - }) - const [showInfoScreen, setShowInfoScreen] = useState(false) - - const onFocusChange = (hasFocus: boolean) => { - setSearchHasFocus(hasFocus) - } - - const onToggleInfoScreenClick = () => { - setStep(ParaTimeSelectorStep.ShowHelpScreen) - setShowInfoScreen(true) - } - - const showInfoScreenBtn = isMobile && step !== ParaTimeSelectorStep.ShowHelpScreen return ( <> -
-
-
- -
-
-
- -
- {showInfoScreenBtn && ( - - )} -
- -
- -
-
-
-
-