mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
Initialize the chakra update and ensure the login page does not crash on load.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import { Alert as ChakraAlert } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { CloseButton } from './close-button';
|
||||
|
||||
export interface AlertProps extends Omit<ChakraAlert.RootProps, 'title'> {
|
||||
startElement?: React.ReactNode;
|
||||
endElement?: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
icon?: React.ReactElement;
|
||||
closable?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
||||
function Alert(props, ref) {
|
||||
const {
|
||||
title,
|
||||
children,
|
||||
icon,
|
||||
closable,
|
||||
onClose,
|
||||
startElement,
|
||||
endElement,
|
||||
...rest
|
||||
} = props;
|
||||
return (
|
||||
<ChakraAlert.Root ref={ ref } { ...rest }>
|
||||
{ startElement || <ChakraAlert.Indicator>{ icon }</ChakraAlert.Indicator> }
|
||||
{ children ? (
|
||||
<ChakraAlert.Content>
|
||||
<ChakraAlert.Title>{ title }</ChakraAlert.Title>
|
||||
<ChakraAlert.Description>{ children }</ChakraAlert.Description>
|
||||
</ChakraAlert.Content>
|
||||
) : (
|
||||
<ChakraAlert.Title flex="1">{ title }</ChakraAlert.Title>
|
||||
) }
|
||||
{ endElement }
|
||||
{ closable && (
|
||||
<CloseButton
|
||||
size="sm"
|
||||
pos="relative"
|
||||
top="-2"
|
||||
insetEnd="-2"
|
||||
alignSelf="flex-start"
|
||||
onClick={ onClose }
|
||||
/>
|
||||
) }
|
||||
</ChakraAlert.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,74 @@
|
||||
'use client';
|
||||
|
||||
import type { GroupProps, SlotRecipeProps } from '@chakra-ui/react';
|
||||
import { Avatar as ChakraAvatar, Group } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
type ImageProps = React.ImgHTMLAttributes<HTMLImageElement>;
|
||||
|
||||
export interface AvatarProps extends ChakraAvatar.RootProps {
|
||||
name?: string;
|
||||
src?: string;
|
||||
srcSet?: string;
|
||||
loading?: ImageProps['loading'];
|
||||
icon?: React.ReactElement;
|
||||
fallback?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
|
||||
function Avatar(props, ref) {
|
||||
const { name, src, srcSet, loading, icon, fallback, children, ...rest } =
|
||||
props;
|
||||
return (
|
||||
<ChakraAvatar.Root ref={ ref } { ...rest }>
|
||||
<AvatarFallback name={ name } icon={ icon }>
|
||||
{ fallback }
|
||||
</AvatarFallback>
|
||||
<ChakraAvatar.Image src={ src } srcSet={ srcSet } loading={ loading }/>
|
||||
{ children }
|
||||
</ChakraAvatar.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
interface AvatarFallbackProps extends ChakraAvatar.FallbackProps {
|
||||
name?: string;
|
||||
icon?: React.ReactElement;
|
||||
}
|
||||
|
||||
const AvatarFallback = React.forwardRef<HTMLDivElement, AvatarFallbackProps>(
|
||||
function AvatarFallback(props, ref) {
|
||||
const { name, icon, children, ...rest } = props;
|
||||
return (
|
||||
<ChakraAvatar.Fallback ref={ ref } { ...rest }>
|
||||
{ children }
|
||||
{ name != null && children == null && <>{ getInitials(name) }</> }
|
||||
{ name == null && children == null && (
|
||||
<ChakraAvatar.Icon asChild={ Boolean(icon) }>{ icon }</ChakraAvatar.Icon>
|
||||
) }
|
||||
</ChakraAvatar.Fallback>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
function getInitials(name: string) {
|
||||
const names = name.trim().split(' ');
|
||||
const firstName = names[0] != null ? names[0] : '';
|
||||
const lastName = names.length > 1 ? names[names.length - 1] : '';
|
||||
return firstName && lastName ?
|
||||
`${ firstName.charAt(0) }${ lastName.charAt(0) }` :
|
||||
firstName.charAt(0);
|
||||
}
|
||||
|
||||
interface AvatarGroupProps extends GroupProps, SlotRecipeProps<'avatar'> {}
|
||||
|
||||
export const AvatarGroup = React.forwardRef<HTMLDivElement, AvatarGroupProps>(
|
||||
function AvatarGroup(props, ref) {
|
||||
const { size, variant, borderless, ...rest } = props;
|
||||
return (
|
||||
<ChakraAvatar.PropsProvider value={{ size, variant, borderless }}>
|
||||
<Group gap="0" spaceX="-3" ref={ ref } { ...rest }/>
|
||||
</ChakraAvatar.PropsProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { ButtonProps as ChakraButtonProps } from '@chakra-ui/react';
|
||||
import {
|
||||
AbsoluteCenter,
|
||||
Button as ChakraButton,
|
||||
Span,
|
||||
Spinner,
|
||||
} from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
interface ButtonLoadingProps {
|
||||
loading?: boolean;
|
||||
loadingText?: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface ButtonProps extends ChakraButtonProps, ButtonLoadingProps {}
|
||||
|
||||
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
function Button(props, ref) {
|
||||
const { loading, disabled, loadingText, children, ...rest } = props;
|
||||
return (
|
||||
<ChakraButton disabled={ loading || disabled } ref={ ref } { ...rest }>
|
||||
{ loading && !loadingText ? (
|
||||
<>
|
||||
<AbsoluteCenter display="inline-flex">
|
||||
<Spinner size="inherit" color="inherit"/>
|
||||
</AbsoluteCenter>
|
||||
<Span opacity={ 0 }>{ children }</Span>
|
||||
</>
|
||||
) : loading && loadingText ? (
|
||||
<>
|
||||
<Spinner size="inherit" color="inherit"/>
|
||||
{ loadingText }
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
) }
|
||||
</ChakraButton>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Checkbox as ChakraCheckbox } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface CheckboxProps extends ChakraCheckbox.RootProps {
|
||||
icon?: React.ReactNode;
|
||||
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
|
||||
rootRef?: React.Ref<HTMLLabelElement>;
|
||||
}
|
||||
|
||||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
||||
function Checkbox(props, ref) {
|
||||
const { icon, children, inputProps, rootRef, ...rest } = props;
|
||||
return (
|
||||
<ChakraCheckbox.Root ref={ rootRef } { ...rest }>
|
||||
<ChakraCheckbox.HiddenInput ref={ ref } { ...inputProps }/>
|
||||
<ChakraCheckbox.Control>
|
||||
{ icon || <ChakraCheckbox.Indicator/> }
|
||||
</ChakraCheckbox.Control>
|
||||
{ children != null && (
|
||||
<ChakraCheckbox.Label>{ children }</ChakraCheckbox.Label>
|
||||
) }
|
||||
</ChakraCheckbox.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { ButtonProps } from '@chakra-ui/react';
|
||||
import { IconButton as ChakraIconButton } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
import { LuX } from 'react-icons/lu';
|
||||
|
||||
export type CloseButtonProps = ButtonProps;
|
||||
|
||||
export const CloseButton = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
CloseButtonProps
|
||||
>(function CloseButton(props, ref) {
|
||||
return (
|
||||
<ChakraIconButton variant="ghost" aria-label="Close" ref={ ref } { ...props }>
|
||||
{ props.children ?? <LuX/> }
|
||||
</ChakraIconButton>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
'use client';
|
||||
|
||||
import { ThemeProvider, useTheme } from 'next-themes';
|
||||
import type { ThemeProviderProps } from 'next-themes';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ColorModeProviderProps extends ThemeProviderProps {}
|
||||
|
||||
export type ColorMode = 'light' | 'dark';
|
||||
|
||||
export function ColorModeProvider(props: ColorModeProviderProps) {
|
||||
return (
|
||||
<ThemeProvider attribute="class" scriptProps={{ 'data-cfasync': 'false' }} disableTransitionOnChange { ...props }/>
|
||||
);
|
||||
}
|
||||
|
||||
export function useColorMode() {
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
const toggleColorMode = () => {
|
||||
setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
||||
};
|
||||
return {
|
||||
colorMode: resolvedTheme as ColorMode,
|
||||
setColorMode: setTheme,
|
||||
toggleColorMode,
|
||||
};
|
||||
}
|
||||
|
||||
export function useColorModeValue<T>(light: T, dark: T) {
|
||||
const { colorMode } = useColorMode();
|
||||
return colorMode === 'light' ? light : dark;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Dialog as ChakraDialog, Portal } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { CloseButton } from './close-button';
|
||||
|
||||
interface DialogContentProps extends ChakraDialog.ContentProps {
|
||||
portalled?: boolean;
|
||||
portalRef?: React.RefObject<HTMLElement>;
|
||||
backdrop?: boolean;
|
||||
}
|
||||
|
||||
export const DialogContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
DialogContentProps
|
||||
>(function DialogContent(props, ref) {
|
||||
const {
|
||||
children,
|
||||
portalled = true,
|
||||
portalRef,
|
||||
backdrop = true,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Portal disabled={ !portalled } container={ portalRef }>
|
||||
{ backdrop && <ChakraDialog.Backdrop/> }
|
||||
<ChakraDialog.Positioner>
|
||||
<ChakraDialog.Content ref={ ref } { ...rest } asChild={ false }>
|
||||
{ children }
|
||||
</ChakraDialog.Content>
|
||||
</ChakraDialog.Positioner>
|
||||
</Portal>
|
||||
);
|
||||
});
|
||||
|
||||
export const DialogCloseTrigger = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
ChakraDialog.CloseTriggerProps
|
||||
>(function DialogCloseTrigger(props, ref) {
|
||||
return (
|
||||
<ChakraDialog.CloseTrigger
|
||||
position="absolute"
|
||||
top="2"
|
||||
insetEnd="2"
|
||||
{ ...props }
|
||||
asChild
|
||||
>
|
||||
<CloseButton size="sm" ref={ ref }>
|
||||
{ props.children }
|
||||
</CloseButton>
|
||||
</ChakraDialog.CloseTrigger>
|
||||
);
|
||||
});
|
||||
|
||||
export const DialogRoot = ChakraDialog.Root;
|
||||
export const DialogFooter = ChakraDialog.Footer;
|
||||
export const DialogHeader = ChakraDialog.Header;
|
||||
export const DialogBody = ChakraDialog.Body;
|
||||
export const DialogBackdrop = ChakraDialog.Backdrop;
|
||||
export const DialogTitle = ChakraDialog.Title;
|
||||
export const DialogDescription = ChakraDialog.Description;
|
||||
export const DialogTrigger = ChakraDialog.Trigger;
|
||||
export const DialogActionTrigger = ChakraDialog.ActionTrigger;
|
||||
@@ -0,0 +1,53 @@
|
||||
import { Drawer as ChakraDrawer, Portal } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { CloseButton } from './close-button';
|
||||
|
||||
interface DrawerContentProps extends ChakraDrawer.ContentProps {
|
||||
portalled?: boolean;
|
||||
portalRef?: React.RefObject<HTMLElement>;
|
||||
offset?: ChakraDrawer.ContentProps['padding'];
|
||||
}
|
||||
|
||||
export const DrawerContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
DrawerContentProps
|
||||
>(function DrawerContent(props, ref) {
|
||||
const { children, portalled = true, portalRef, offset, ...rest } = props;
|
||||
return (
|
||||
<Portal disabled={ !portalled } container={ portalRef }>
|
||||
<ChakraDrawer.Positioner padding={ offset }>
|
||||
<ChakraDrawer.Content ref={ ref } { ...rest } asChild={ false }>
|
||||
{ children }
|
||||
</ChakraDrawer.Content>
|
||||
</ChakraDrawer.Positioner>
|
||||
</Portal>
|
||||
);
|
||||
});
|
||||
|
||||
export const DrawerCloseTrigger = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
ChakraDrawer.CloseTriggerProps
|
||||
>(function DrawerCloseTrigger(props, ref) {
|
||||
return (
|
||||
<ChakraDrawer.CloseTrigger
|
||||
position="absolute"
|
||||
top="2"
|
||||
insetEnd="2"
|
||||
{ ...props }
|
||||
asChild
|
||||
>
|
||||
<CloseButton size="sm" ref={ ref }/>
|
||||
</ChakraDrawer.CloseTrigger>
|
||||
);
|
||||
});
|
||||
|
||||
export const DrawerTrigger = ChakraDrawer.Trigger;
|
||||
export const DrawerRoot = ChakraDrawer.Root;
|
||||
export const DrawerFooter = ChakraDrawer.Footer;
|
||||
export const DrawerHeader = ChakraDrawer.Header;
|
||||
export const DrawerBody = ChakraDrawer.Body;
|
||||
export const DrawerBackdrop = ChakraDrawer.Backdrop;
|
||||
export const DrawerDescription = ChakraDrawer.Description;
|
||||
export const DrawerTitle = ChakraDrawer.Title;
|
||||
export const DrawerActionTrigger = ChakraDrawer.ActionTrigger;
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Field as ChakraField } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface FieldProps extends Omit<ChakraField.RootProps, 'label'> {
|
||||
label?: React.ReactNode;
|
||||
helperText?: React.ReactNode;
|
||||
errorText?: React.ReactNode;
|
||||
optionalText?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
||||
function Field(props, ref) {
|
||||
const { label, children, helperText, errorText, optionalText, ...rest } =
|
||||
props;
|
||||
return (
|
||||
<ChakraField.Root ref={ ref } { ...rest }>
|
||||
{ label && (
|
||||
<ChakraField.Label>
|
||||
{ label }
|
||||
<ChakraField.RequiredIndicator fallback={ optionalText }/>
|
||||
</ChakraField.Label>
|
||||
) }
|
||||
{ children }
|
||||
{ helperText && (
|
||||
<ChakraField.HelperText>{ helperText }</ChakraField.HelperText>
|
||||
) }
|
||||
{ errorText && (
|
||||
<ChakraField.ErrorText>{ errorText }</ChakraField.ErrorText>
|
||||
) }
|
||||
</ChakraField.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { BoxProps, InputElementProps } from '@chakra-ui/react';
|
||||
import { Group, InputElement } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface InputGroupProps extends BoxProps {
|
||||
startElementProps?: InputElementProps;
|
||||
endElementProps?: InputElementProps;
|
||||
startElement?: React.ReactNode;
|
||||
endElement?: React.ReactNode;
|
||||
children: React.ReactElement<InputElementProps>;
|
||||
startOffset?: InputElementProps['paddingStart'];
|
||||
endOffset?: InputElementProps['paddingEnd'];
|
||||
}
|
||||
|
||||
export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
|
||||
function InputGroup(props, ref) {
|
||||
const {
|
||||
startElement,
|
||||
startElementProps,
|
||||
endElement,
|
||||
endElementProps,
|
||||
children,
|
||||
startOffset = '6px',
|
||||
endOffset = '6px',
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const child =
|
||||
React.Children.only<React.ReactElement<InputElementProps>>(children);
|
||||
|
||||
return (
|
||||
<Group ref={ ref } { ...rest }>
|
||||
{ startElement && (
|
||||
<InputElement pointerEvents="none" { ...startElementProps }>
|
||||
{ startElement }
|
||||
</InputElement>
|
||||
) }
|
||||
{ React.cloneElement(child, {
|
||||
...(startElement && {
|
||||
ps: `calc(var(--input-height) - ${ startOffset })`,
|
||||
}),
|
||||
...(endElement && { pe: `calc(var(--input-height) - ${ endOffset })` }),
|
||||
...children.props,
|
||||
}) }
|
||||
{ endElement && (
|
||||
<InputElement placement="end" { ...endElementProps }>
|
||||
{ endElement }
|
||||
</InputElement>
|
||||
) }
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Popover as ChakraPopover, Portal } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { CloseButton } from './close-button';
|
||||
|
||||
interface PopoverContentProps extends ChakraPopover.ContentProps {
|
||||
portalled?: boolean;
|
||||
portalRef?: React.RefObject<HTMLElement>;
|
||||
}
|
||||
|
||||
export const PopoverContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
PopoverContentProps
|
||||
>(function PopoverContent(props, ref) {
|
||||
const { portalled = true, portalRef, ...rest } = props;
|
||||
return (
|
||||
<Portal disabled={ !portalled } container={ portalRef }>
|
||||
<ChakraPopover.Positioner>
|
||||
<ChakraPopover.Content ref={ ref } { ...rest }/>
|
||||
</ChakraPopover.Positioner>
|
||||
</Portal>
|
||||
);
|
||||
});
|
||||
|
||||
export const PopoverArrow = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
ChakraPopover.ArrowProps
|
||||
>(function PopoverArrow(props, ref) {
|
||||
return (
|
||||
<ChakraPopover.Arrow { ...props } ref={ ref }>
|
||||
<ChakraPopover.ArrowTip/>
|
||||
</ChakraPopover.Arrow>
|
||||
);
|
||||
});
|
||||
|
||||
export const PopoverCloseTrigger = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
ChakraPopover.CloseTriggerProps
|
||||
>(function PopoverCloseTrigger(props, ref) {
|
||||
return (
|
||||
<ChakraPopover.CloseTrigger
|
||||
position="absolute"
|
||||
top="1"
|
||||
insetEnd="1"
|
||||
{ ...props }
|
||||
asChild
|
||||
ref={ ref }
|
||||
>
|
||||
<CloseButton size="sm"/>
|
||||
</ChakraPopover.CloseTrigger>
|
||||
);
|
||||
});
|
||||
|
||||
export const PopoverTitle = ChakraPopover.Title;
|
||||
export const PopoverDescription = ChakraPopover.Description;
|
||||
export const PopoverFooter = ChakraPopover.Footer;
|
||||
export const PopoverHeader = ChakraPopover.Header;
|
||||
export const PopoverRoot = ChakraPopover.Root;
|
||||
export const PopoverBody = ChakraPopover.Body;
|
||||
export const PopoverTrigger = ChakraPopover.Trigger;
|
||||
@@ -0,0 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
|
||||
import theme from 'chakra/theme/theme';
|
||||
|
||||
import {
|
||||
ColorModeProvider,
|
||||
type ColorModeProviderProps,
|
||||
} from './color-mode';
|
||||
|
||||
export function Provider(props: ColorModeProviderProps) {
|
||||
return (
|
||||
<ChakraProvider value={ theme }>
|
||||
<ColorModeProvider { ...props }/>
|
||||
</ChakraProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { RadioGroup as ChakraRadioGroup } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface RadioProps extends ChakraRadioGroup.ItemProps {
|
||||
rootRef?: React.Ref<HTMLDivElement>;
|
||||
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
|
||||
}
|
||||
|
||||
export const Radio = React.forwardRef<HTMLInputElement, RadioProps>(
|
||||
function Radio(props, ref) {
|
||||
const { children, inputProps, rootRef, ...rest } = props;
|
||||
return (
|
||||
<ChakraRadioGroup.Item ref={ rootRef } { ...rest }>
|
||||
<ChakraRadioGroup.ItemHiddenInput ref={ ref } { ...inputProps }/>
|
||||
<ChakraRadioGroup.ItemIndicator/>
|
||||
{ children && (
|
||||
<ChakraRadioGroup.ItemText>{ children }</ChakraRadioGroup.ItemText>
|
||||
) }
|
||||
</ChakraRadioGroup.Item>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const RadioGroup = ChakraRadioGroup.Root;
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Slider as ChakraSlider, For, HStack } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface SliderProps extends ChakraSlider.RootProps {
|
||||
marks?: Array<number | { value: number; label: React.ReactNode }>;
|
||||
label?: React.ReactNode;
|
||||
showValue?: boolean;
|
||||
}
|
||||
|
||||
export const Slider = React.forwardRef<HTMLDivElement, SliderProps>(
|
||||
function Slider(props, ref) {
|
||||
const { marks: marksProp, label, showValue, ...rest } = props;
|
||||
const value = props.defaultValue ?? props.value;
|
||||
|
||||
const marks = marksProp?.map((mark) => {
|
||||
if (typeof mark === 'number') return { value: mark, label: undefined };
|
||||
return mark;
|
||||
});
|
||||
|
||||
const hasMarkLabel = Boolean(marks?.some((mark) => mark.label));
|
||||
|
||||
return (
|
||||
<ChakraSlider.Root ref={ ref } thumbAlignment="center" { ...rest }>
|
||||
{ label && !showValue && (
|
||||
<ChakraSlider.Label>{ label }</ChakraSlider.Label>
|
||||
) }
|
||||
{ label && showValue && (
|
||||
<HStack justify="space-between">
|
||||
<ChakraSlider.Label>{ label }</ChakraSlider.Label>
|
||||
<ChakraSlider.ValueText/>
|
||||
</HStack>
|
||||
) }
|
||||
<ChakraSlider.Control data-has-mark-label={ hasMarkLabel || undefined }>
|
||||
<ChakraSlider.Track>
|
||||
<ChakraSlider.Range/>
|
||||
</ChakraSlider.Track>
|
||||
<SliderThumbs value={ value }/>
|
||||
<SliderMarks marks={ marks }/>
|
||||
</ChakraSlider.Control>
|
||||
</ChakraSlider.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
function SliderThumbs(props: { value?: Array<number> }) {
|
||||
const { value } = props;
|
||||
return (
|
||||
<For each={ value }>
|
||||
{ (_, index) => (
|
||||
<ChakraSlider.Thumb key={ index } index={ index }>
|
||||
<ChakraSlider.HiddenInput/>
|
||||
</ChakraSlider.Thumb>
|
||||
) }
|
||||
</For>
|
||||
);
|
||||
}
|
||||
|
||||
interface SliderMarksProps {
|
||||
marks?: Array<number | { value: number; label: React.ReactNode }>;
|
||||
}
|
||||
|
||||
const SliderMarks = React.forwardRef<HTMLDivElement, SliderMarksProps>(
|
||||
function SliderMarks(props, ref) {
|
||||
const { marks } = props;
|
||||
if (!marks?.length) return null;
|
||||
|
||||
return (
|
||||
<ChakraSlider.MarkerGroup ref={ ref }>
|
||||
{ marks.map((mark, index) => {
|
||||
const value = typeof mark === 'number' ? mark : mark.value;
|
||||
const label = typeof mark === 'number' ? undefined : mark.label;
|
||||
return (
|
||||
<ChakraSlider.Marker key={ index } value={ value }>
|
||||
<ChakraSlider.MarkerIndicator/>
|
||||
{ label }
|
||||
</ChakraSlider.Marker>
|
||||
);
|
||||
}) }
|
||||
</ChakraSlider.MarkerGroup>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Toaster as ChakraToaster,
|
||||
Portal,
|
||||
Spinner,
|
||||
Stack,
|
||||
Toast,
|
||||
createToaster,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
import { SECOND } from 'lib/consts';
|
||||
|
||||
export const toaster = createToaster({
|
||||
placement: 'top-end',
|
||||
pauseOnPageIdle: true,
|
||||
duration: 5 * SECOND,
|
||||
});
|
||||
|
||||
export const Toaster = () => {
|
||||
return (
|
||||
<Portal>
|
||||
<ChakraToaster toaster={ toaster } insetInline={{ mdDown: '4' }}>
|
||||
{ (toast) => (
|
||||
<Toast.Root width={{ md: 'sm' }}>
|
||||
{ toast.type === 'loading' ? (
|
||||
<Spinner size="sm" color="blue.solid"/>
|
||||
) : (
|
||||
<Toast.Indicator/>
|
||||
) }
|
||||
<Stack gap="1" flex="1" maxWidth="100%">
|
||||
{ toast.title && <Toast.Title>{ toast.title }</Toast.Title> }
|
||||
{ toast.description && (
|
||||
<Toast.Description>{ toast.description }</Toast.Description>
|
||||
) }
|
||||
</Stack>
|
||||
{ toast.action && (
|
||||
<Toast.ActionTrigger>{ toast.action.label }</Toast.ActionTrigger>
|
||||
) }
|
||||
{ toast.meta?.closable && <Toast.CloseTrigger/> }
|
||||
</Toast.Root>
|
||||
) }
|
||||
</ChakraToaster>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Tooltip as ChakraTooltip, Portal } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
export interface TooltipProps extends ChakraTooltip.RootProps {
|
||||
showArrow?: boolean;
|
||||
portalled?: boolean;
|
||||
portalRef?: React.RefObject<HTMLElement>;
|
||||
content: React.ReactNode;
|
||||
contentProps?: ChakraTooltip.ContentProps;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
||||
function Tooltip(props, ref) {
|
||||
const {
|
||||
showArrow,
|
||||
children,
|
||||
disabled,
|
||||
portalled,
|
||||
content,
|
||||
contentProps,
|
||||
portalRef,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
if (disabled) return children;
|
||||
|
||||
return (
|
||||
<ChakraTooltip.Root { ...rest }>
|
||||
<ChakraTooltip.Trigger asChild>{ children }</ChakraTooltip.Trigger>
|
||||
<Portal disabled={ !portalled } container={ portalRef }>
|
||||
<ChakraTooltip.Positioner>
|
||||
<ChakraTooltip.Content ref={ ref } { ...contentProps }>
|
||||
{ showArrow && (
|
||||
<ChakraTooltip.Arrow>
|
||||
<ChakraTooltip.ArrowTip/>
|
||||
</ChakraTooltip.Arrow>
|
||||
) }
|
||||
{ content }
|
||||
</ChakraTooltip.Content>
|
||||
</ChakraTooltip.Positioner>
|
||||
</Portal>
|
||||
</ChakraTooltip.Root>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types';
|
||||
|
||||
export const radii: TokenDefinition['radii'] = {
|
||||
none: { value: '0' },
|
||||
sm: { value: '4px' },
|
||||
base: { value: '8px' },
|
||||
md: { value: '12px' },
|
||||
lg: { value: '16px' },
|
||||
xl: { value: '24px' },
|
||||
full: { value: '9999px' },
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
const breakpoints = {
|
||||
// maybe we need them in future
|
||||
sm: '415px',
|
||||
// md: '768px',
|
||||
lg: '1000px',
|
||||
xl: '1440px',
|
||||
'2xl': '1920px',
|
||||
// these breakpoint are needed just to make others work
|
||||
'3xl': '3000px',
|
||||
};
|
||||
|
||||
export default breakpoints;
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types';
|
||||
|
||||
const colors: TokenDefinition['colors'] = {
|
||||
green: {
|
||||
'100': { value: '#C6F6D5' },
|
||||
'400': { value: '#48BB78' },
|
||||
'500': { value: '#38A169' },
|
||||
'600': { value: '#25855A' },
|
||||
},
|
||||
blue: {
|
||||
'50': { value: '#EBF8FF' },
|
||||
'100': { value: '#BEE3F8' },
|
||||
'200': { value: '#90CDF4' },
|
||||
'300': { value: '#63B3ED' },
|
||||
'400': { value: '#4299E1' },
|
||||
'500': { value: '#3182CE' },
|
||||
'600': { value: '#2B6CB0' },
|
||||
'700': { value: '#2C5282' },
|
||||
'800': { value: '#2A4365' },
|
||||
'900': { value: '#1A365D' },
|
||||
},
|
||||
red: {
|
||||
'500': { value: '#E53E3E' },
|
||||
'100': { value: '#FED7D7' },
|
||||
},
|
||||
orange: {
|
||||
'100': { value: '#FEEBCB' },
|
||||
},
|
||||
gray: {
|
||||
'50': { value: '#F7FAFC' },
|
||||
'100': { value: '#EDF2F7' },
|
||||
'200': { value: '#E2E8F0' },
|
||||
'300': { value: '#CBD5E0' },
|
||||
'400': { value: '#A0AEC0' },
|
||||
'500': { value: '#718096' },
|
||||
'600': { value: '#4A5568' },
|
||||
'700': { value: '#2D3748' },
|
||||
'800': { value: '#1A202C' },
|
||||
'900': { value: '#171923' },
|
||||
},
|
||||
black: { value: '#101112' },
|
||||
white: { value: '#ffffff' },
|
||||
blackAlpha: {
|
||||
'50': { value: 'RGBA(16, 17, 18, 0.04)' },
|
||||
'100': { value: 'RGBA(16, 17, 18, 0.06)' },
|
||||
'200': { value: 'RGBA(16, 17, 18, 0.08)' },
|
||||
'300': { value: 'RGBA(16, 17, 18, 0.16)' },
|
||||
'400': { value: 'RGBA(16, 17, 18, 0.24)' },
|
||||
'500': { value: 'RGBA(16, 17, 18, 0.36)' },
|
||||
'600': { value: 'RGBA(16, 17, 18, 0.48)' },
|
||||
'700': { value: 'RGBA(16, 17, 18, 0.64)' },
|
||||
'800': { value: 'RGBA(16, 17, 18, 0.80)' },
|
||||
'900': { value: 'RGBA(16, 17, 18, 0.92)' },
|
||||
},
|
||||
github: { value: '#171923' },
|
||||
telegram: { value: '#2775CA' },
|
||||
linkedin: { value: '#1564BA' },
|
||||
discord: { value: '#9747FF' },
|
||||
slack: { value: '#1BA27A' },
|
||||
twitter: { value: '#000000' },
|
||||
opensea: { value: '#2081E2' },
|
||||
facebook: { value: '#4460A0' },
|
||||
medium: { value: '#231F20' },
|
||||
reddit: { value: '#FF4500' },
|
||||
celo: { value: '#FCFF52' },
|
||||
};
|
||||
|
||||
export default colors;
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types';
|
||||
|
||||
const durations: TokenDefinition['durations'] = {
|
||||
'ultra-fast': { value: '50ms' },
|
||||
faster: { value: '100ms' },
|
||||
fast: { value: '150ms' },
|
||||
normal: { value: '200ms' },
|
||||
slow: { value: '300ms' },
|
||||
slower: { value: '400ms' },
|
||||
'ultra-slow': { value: '500ms' },
|
||||
};
|
||||
|
||||
export default durations;
|
||||
@@ -0,0 +1,40 @@
|
||||
import { getCSSVar } from '@chakra-ui/styled-system';
|
||||
import { mode } from '@chakra-ui/theme-tools';
|
||||
import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
|
||||
|
||||
const scrollbar = (props: StyleFunctionProps) => {
|
||||
const bgColor = mode('blackAlpha.300', 'whiteAlpha.300')(props);
|
||||
const resizerUrl = mode('url(/static/resizer_light.png)', 'url(/static/resizer_dark.png)')(props);
|
||||
|
||||
return {
|
||||
'body *::-webkit-scrollbar': {
|
||||
width: '20px',
|
||||
},
|
||||
'body *::-webkit-scrollbar-track': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
'body *::-webkit-scrollbar-thumb': {
|
||||
backgroundColor: bgColor,
|
||||
borderRadius: '20px',
|
||||
border: `8px solid rgba(0,0,0,0)`,
|
||||
backgroundClip: 'content-box',
|
||||
minHeight: '32px',
|
||||
},
|
||||
'body *::-webkit-scrollbar-button': {
|
||||
display: 'none',
|
||||
},
|
||||
'body *::-webkit-scrollbar-corner': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
'body *::-webkit-resizer': {
|
||||
backgroundImage: resizerUrl,
|
||||
backgroundSize: '20px',
|
||||
},
|
||||
'body *': {
|
||||
scrollbarWidth: 'thin',
|
||||
scrollbarColor: `${ getCSSVar(props.theme, 'colors', bgColor) } transparent`,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default scrollbar;
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { SemanticTokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types';
|
||||
|
||||
const semanticTokens: SemanticTokenDefinition = {
|
||||
colors: {
|
||||
divider: {
|
||||
DEFAULT: { value: '{colors.blackAlpha.200}' },
|
||||
_dark: { value: '{colors.whiteAlpha.200}' },
|
||||
},
|
||||
text: {
|
||||
DEFAULT: { value: '{colors.blackAlpha.800}' },
|
||||
_dark: { value: '{colors.whiteAlpha.800}' },
|
||||
},
|
||||
text_secondary: {
|
||||
DEFAULT: { value: '{colors.gray.500}' },
|
||||
_dark: { value: '{colors.gray.400}' },
|
||||
},
|
||||
link: {
|
||||
DEFAULT: { value: '{colors.blue.600}' },
|
||||
_dark: { value: '{colors.blue.300}' },
|
||||
},
|
||||
link_hovered: {
|
||||
DEFAULT: { value: '{colors.blue.400}' },
|
||||
},
|
||||
icon_link_external: {
|
||||
DEFAULT: { value: '{colors.gray.300}' },
|
||||
_dark: { value: '{colors.gray.500}' },
|
||||
},
|
||||
icon_info: {
|
||||
DEFAULT: { value: '{colors.gray.400}' },
|
||||
_dark: { value: '{colors.gray.500}' },
|
||||
},
|
||||
error: {
|
||||
DEFAULT: { value: '{colors.red.500}' },
|
||||
_dark: { value: '{colors.red.500}' },
|
||||
},
|
||||
dialog_bg: {
|
||||
DEFAULT: { value: '{colors.white}' },
|
||||
_dark: { value: '{colors.gray.900}' },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default semanticTokens;
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types';
|
||||
|
||||
const shadows: TokenDefinition['shadows'] = {
|
||||
action_bar: { value: '0 4px 4px -4px rgb(0 0 0 / 10%), 0 2px 4px -4px rgb(0 0 0 / 6%)' },
|
||||
};
|
||||
|
||||
export default shadows;
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { ThemingConfig, TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
export const BODY_TYPEFACE = config.UI.fonts.body?.name ?? 'Inter';
|
||||
export const HEADING_TYPEFACE = config.UI.fonts.heading?.name ?? 'Poppins';
|
||||
|
||||
export const fonts: TokenDefinition['fonts'] = {
|
||||
heading: { value: `${ HEADING_TYPEFACE }, sans-serif` },
|
||||
body: { value: `${ BODY_TYPEFACE }, sans-serif` },
|
||||
};
|
||||
|
||||
export const textStyles: ThemingConfig['textStyles'] = {
|
||||
h2: {
|
||||
fontSize: '32px',
|
||||
fontWeight: '500',
|
||||
lineHeight: '40px',
|
||||
fontFamily: 'heading',
|
||||
},
|
||||
h3: {
|
||||
fontSize: '24px',
|
||||
fontWeight: '500',
|
||||
lineHeight: '32px',
|
||||
fontFamily: 'heading',
|
||||
},
|
||||
h4: {
|
||||
fontSize: 'md',
|
||||
fontWeight: '500',
|
||||
lineHeight: '24px',
|
||||
fontFamily: 'heading',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { TokenDefinition } from '@chakra-ui/react/dist/types/styled-system/types';
|
||||
|
||||
export const zIndex: TokenDefinition['zIndex'] = {
|
||||
hide: { value: -1 },
|
||||
auto: { value: 'auto' },
|
||||
base: { value: 0 },
|
||||
docked: { value: 10 },
|
||||
dropdown: { value: 1000 },
|
||||
sticky: { value: 1100 },
|
||||
sticky1: { value: 1101 },
|
||||
sticky2: { value: 1102 },
|
||||
banner: { value: 1200 },
|
||||
overlay: { value: 1300 },
|
||||
modal: { value: 1400 },
|
||||
popover: { value: 1500 },
|
||||
tooltip: { value: 1550 }, // otherwise tooltips will not be visible in modals
|
||||
skipLink: { value: 1600 },
|
||||
toast: { value: 1700 },
|
||||
};
|
||||
|
||||
export default zIndex;
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { StyleFunctionProps, SystemStyleObject } from '@chakra-ui/theme-tools';
|
||||
import { mode } from '@chakra-ui/theme-tools';
|
||||
|
||||
import scrollbar from './foundations/scrollbar';
|
||||
import addressEntity from './globals/address-entity';
|
||||
import recaptcha from './globals/recaptcha';
|
||||
// import getDefaultTransitionProps from './utils/getDefaultTransitionProps';
|
||||
|
||||
const globalCss: Record<string, SystemStyleObject> = {
|
||||
body: {
|
||||
// bg: mode('white', 'black')(props),
|
||||
// ...getDefaultTransitionProps(),
|
||||
'-webkit-tap-highlight-color': 'transparent',
|
||||
'font-variant-ligatures': 'no-contextual',
|
||||
},
|
||||
mark: {
|
||||
// bgColor: mode('green.100', 'green.800')(props),
|
||||
color: 'inherit',
|
||||
},
|
||||
'svg *::selection': {
|
||||
color: 'none',
|
||||
background: 'none',
|
||||
},
|
||||
form: {
|
||||
w: '100%',
|
||||
},
|
||||
// ...scrollbar(props),
|
||||
// ...addressEntity(props),
|
||||
...recaptcha(),
|
||||
};
|
||||
|
||||
export default globalCss;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { mode } from '@chakra-ui/theme-tools';
|
||||
import type { StyleFunctionProps } from '@chakra-ui/theme-tools';
|
||||
|
||||
const styles = (props: StyleFunctionProps) => {
|
||||
return {
|
||||
'.address-entity': {
|
||||
'&.address-entity_highlighted': {
|
||||
_before: {
|
||||
content: `" "`,
|
||||
position: 'absolute',
|
||||
py: 1,
|
||||
pl: 1,
|
||||
pr: 0,
|
||||
top: '-5px',
|
||||
left: '-5px',
|
||||
width: `100%`,
|
||||
height: '100%',
|
||||
borderRadius: 'base',
|
||||
borderColor: mode('blue.200', 'blue.600')(props),
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'dashed',
|
||||
bgColor: mode('blue.50', 'blue.900')(props),
|
||||
zIndex: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
'.address-entity_no-copy': {
|
||||
'&.address-entity_highlighted': {
|
||||
_before: {
|
||||
pr: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default styles;
|
||||
@@ -0,0 +1,22 @@
|
||||
const styles = () => {
|
||||
return {
|
||||
'.grecaptcha-badge': {
|
||||
visibility: 'hidden',
|
||||
},
|
||||
'div:has(div):has(iframe[title="recaptcha challenge expires in two minutes"])': {
|
||||
'&::after': {
|
||||
content: `" "`,
|
||||
display: 'block',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
zIndex: 100000,
|
||||
bgColor: 'blackAlpha.300',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default styles;
|
||||
@@ -0,0 +1,34 @@
|
||||
import { createSystem, defaultConfig, defineConfig } from '@chakra-ui/react';
|
||||
|
||||
// import components from './components/index';
|
||||
// import config from './config';
|
||||
import * as borders from './foundations/borders';
|
||||
import breakpoints from './foundations/breakpoints';
|
||||
import colors from './foundations/colors';
|
||||
import durations from './foundations/durations';
|
||||
import semanticTokens from './foundations/semanticTokens';
|
||||
import shadows from './foundations/shadows';
|
||||
import { fonts, textStyles } from './foundations/typography';
|
||||
import zIndex from './foundations/zIndex';
|
||||
import globalCss from './globalCss';
|
||||
|
||||
const customConfig = defineConfig({
|
||||
globalCss,
|
||||
theme: {
|
||||
breakpoints,
|
||||
semanticTokens,
|
||||
textStyles,
|
||||
tokens: {
|
||||
...borders,
|
||||
colors,
|
||||
durations,
|
||||
fonts,
|
||||
shadows,
|
||||
zIndex,
|
||||
},
|
||||
},
|
||||
// components,
|
||||
// config,
|
||||
});
|
||||
|
||||
export default createSystem(defaultConfig, customConfig);
|
||||
@@ -283,6 +283,7 @@ export default tseslint.config(
|
||||
'/types/',
|
||||
[ '/^nextjs/' ],
|
||||
[
|
||||
'/^chakra/',
|
||||
'/^configs/',
|
||||
'/^data/',
|
||||
'/^deploy/',
|
||||
|
||||
+20
-16
@@ -1,6 +1,6 @@
|
||||
import { useBoolean } from '@chakra-ui/react';
|
||||
import type { UseQueryResult } from '@tanstack/react-query';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useToggle } from '@uidotdev/usehooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { createContext, useContext, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useSignMessage } from 'wagmi';
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
RewardsConfigResponse,
|
||||
} from 'types/api/rewards';
|
||||
|
||||
import { toaster } from 'chakra/components/toaster';
|
||||
import config from 'configs/app';
|
||||
import type { ResourceError } from 'lib/api/resources';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
@@ -22,7 +23,6 @@ import * as cookies from 'lib/cookies';
|
||||
import decodeJWT from 'lib/decodeJWT';
|
||||
import getErrorMessage from 'lib/errors/getErrorMessage';
|
||||
import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
import removeQueryParam from 'lib/router/removeQueryParam';
|
||||
import useAccount from 'lib/web3/useAccount';
|
||||
@@ -114,13 +114,12 @@ export function RewardsContextProvider({ children }: Props) {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const apiFetch = useApiFetch();
|
||||
const toast = useToast();
|
||||
const { address } = useAccount();
|
||||
const { signMessageAsync } = useSignMessage();
|
||||
const profileQuery = useProfileQuery();
|
||||
|
||||
const [ isLoginModalOpen, setIsLoginModalOpen ] = useBoolean(false);
|
||||
const [ isInitialized, setIsInitialized ] = useBoolean(false);
|
||||
const [ isLoginModalOpen, setIsLoginModalOpen ] = useToggle(false);
|
||||
const [ isInitialized, setIsInitialized ] = useToggle(false);
|
||||
const [ apiToken, setApiToken ] = React.useState<string | undefined>();
|
||||
|
||||
// Initialize state with the API token from cookies
|
||||
@@ -131,7 +130,7 @@ export function RewardsContextProvider({ children }: Props) {
|
||||
if (registeredAddress === profileQuery.data?.address_hash) {
|
||||
setApiToken(token);
|
||||
}
|
||||
setIsInitialized.on();
|
||||
setIsInitialized(true);
|
||||
}
|
||||
}, [ setIsInitialized, profileQuery ]);
|
||||
|
||||
@@ -187,22 +186,18 @@ export function RewardsContextProvider({ children }: Props) {
|
||||
cookies.set(cookies.NAMES.REWARDS_REFERRAL_CODE, refCode);
|
||||
removeQueryParam(router, 'ref');
|
||||
if (!apiToken) {
|
||||
setIsLoginModalOpen.on();
|
||||
setIsLoginModalOpen(true);
|
||||
}
|
||||
}
|
||||
}, [ router, apiToken, isInitialized, setIsLoginModalOpen ]);
|
||||
|
||||
const errorToast = useCallback((error: unknown) => {
|
||||
const apiError = getErrorObjPayload<{ message: string }>(error);
|
||||
toast({
|
||||
position: 'top-right',
|
||||
toaster.error({
|
||||
title: 'Error',
|
||||
description: apiError?.message || getErrorMessage(error) || 'Something went wrong. Try again later.',
|
||||
status: 'error',
|
||||
variant: 'subtle',
|
||||
isClosable: true,
|
||||
});
|
||||
}, [ toast ]);
|
||||
}, [ ]);
|
||||
|
||||
// Login to the rewards program
|
||||
const login = useCallback(async(refCode: string) => {
|
||||
@@ -254,6 +249,14 @@ export function RewardsContextProvider({ children }: Props) {
|
||||
}
|
||||
}, [ apiFetch, errorToast, fetchParams ]);
|
||||
|
||||
const openLoginModal = React.useCallback(() => {
|
||||
setIsLoginModalOpen(true);
|
||||
}, [ setIsLoginModalOpen ]);
|
||||
|
||||
const closeLoginModal = React.useCallback(() => {
|
||||
setIsLoginModalOpen(false);
|
||||
}, [ setIsLoginModalOpen ]);
|
||||
|
||||
const value = useMemo(() => {
|
||||
if (!feature.isEnabled) {
|
||||
return initialState;
|
||||
@@ -267,14 +270,15 @@ export function RewardsContextProvider({ children }: Props) {
|
||||
apiToken,
|
||||
isInitialized,
|
||||
isLoginModalOpen,
|
||||
openLoginModal: setIsLoginModalOpen.on,
|
||||
closeLoginModal: setIsLoginModalOpen.off,
|
||||
openLoginModal,
|
||||
closeLoginModal,
|
||||
login,
|
||||
claim,
|
||||
};
|
||||
}, [
|
||||
isLoginModalOpen, setIsLoginModalOpen, balancesQuery, dailyRewardQuery, checkUserQuery,
|
||||
balancesQuery, dailyRewardQuery, checkUserQuery,
|
||||
apiToken, login, claim, referralsQuery, rewardsConfigQuery, isInitialized,
|
||||
isLoginModalOpen, openLoginModal, closeLoginModal,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { ColorMode } from '@chakra-ui/react';
|
||||
import { useColorMode } from '@chakra-ui/react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useRouter } from 'next/router';
|
||||
import React from 'react';
|
||||
|
||||
import type { ColorMode } from 'chakra/components/color-mode';
|
||||
import { useColorMode } from 'chakra/components/color-mode';
|
||||
import config from 'configs/app';
|
||||
import * as cookies from 'lib/cookies';
|
||||
import getQueryParamString from 'lib/router/getQueryParamString';
|
||||
|
||||
@@ -70,8 +70,7 @@ export function app(): CspDev.DirectiveDescriptor {
|
||||
config.app.isDev ? KEY_WORDS.UNSAFE_EVAL : '',
|
||||
|
||||
// hash of ColorModeScript: system + dark
|
||||
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
|
||||
'\'sha256-9A7qFFHmxdWjZMQmfzYD2XWaNHLu1ZmQB0Ds4Go764k=\'',
|
||||
'\'sha256-yYJq8IP5/WhJj6zxyTmujEqBFs/MufRufp2QKJFU76M=\'',
|
||||
|
||||
// CapybaraRunner
|
||||
'\'sha256-5+YTmTcBwCYdJ8Jetbr6kyjGp0Ry/H7ptpoun6CrSwQ=\'',
|
||||
|
||||
+8
-6
@@ -40,11 +40,10 @@
|
||||
"@blockscout/bens-types": "1.4.1",
|
||||
"@blockscout/stats-types": "2.0.0",
|
||||
"@blockscout/visualizer-types": "0.2.0",
|
||||
"@chakra-ui/react": "2.7.1",
|
||||
"@chakra-ui/react": "3.2.5",
|
||||
"@chakra-ui/theme-tools": "^2.0.18",
|
||||
"@cloudnouns/kit": "1.1.6",
|
||||
"@emotion/react": "^11.10.4",
|
||||
"@emotion/styled": "^11.10.4",
|
||||
"@emotion/react": "11.14.0",
|
||||
"@growthbook/growthbook-react": "0.21.0",
|
||||
"@helia/verified-fetch": "2.0.1",
|
||||
"@hypelab/sdk-react": "^1.0.0",
|
||||
@@ -60,15 +59,16 @@
|
||||
"@opentelemetry/sdk-node": "0.49.1",
|
||||
"@opentelemetry/sdk-trace-node": "1.22.0",
|
||||
"@opentelemetry/semantic-conventions": "1.22.0",
|
||||
"@rollbar/react": "0.12.0-beta",
|
||||
"@scure/base": "1.1.9",
|
||||
"@reown/appkit": "1.6.0",
|
||||
"@reown/appkit-adapter-wagmi": "1.6.0",
|
||||
"@rollbar/react": "0.12.0-beta",
|
||||
"@scure/base": "1.1.9",
|
||||
"@slise/embed-react": "^2.2.0",
|
||||
"@tanstack/react-query": "5.55.4",
|
||||
"@tanstack/react-query-devtools": "5.55.4",
|
||||
"@types/papaparse": "^5.3.5",
|
||||
"@types/react-scroll": "^1.8.4",
|
||||
"@uidotdev/usehooks": "2.4.1",
|
||||
"airtable": "^0.12.2",
|
||||
"bignumber.js": "^9.1.0",
|
||||
"blo": "^1.1.1",
|
||||
@@ -79,7 +79,6 @@
|
||||
"dayjs": "^1.11.5",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"focus-visible": "^5.2.0",
|
||||
"framer-motion": "^6.5.1",
|
||||
"getit-sdk": "^1.0.4",
|
||||
"gradient-avatar": "git+https://github.com/blockscout/gradient-avatar.git",
|
||||
"graphiql": "^2.2.0",
|
||||
@@ -91,6 +90,7 @@
|
||||
"mixpanel-browser": "^2.47.0",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"next": "15.0.3",
|
||||
"next-themes": "0.4.4",
|
||||
"nextjs-routes": "^1.0.8",
|
||||
"node-fetch": "^3.2.9",
|
||||
"papaparse": "^5.3.2",
|
||||
@@ -105,6 +105,7 @@
|
||||
"react-dom": "18.3.1",
|
||||
"react-google-recaptcha": "3.1.0",
|
||||
"react-hook-form": "7.52.1",
|
||||
"react-icons": "5.4.0",
|
||||
"react-identicons": "^1.2.5",
|
||||
"react-intersection-observer": "^9.5.2",
|
||||
"react-jazzicon": "^1.0.4",
|
||||
@@ -119,6 +120,7 @@
|
||||
"xss": "^1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chakra-ui/cli": "3.2.5",
|
||||
"@eslint/compat": "1.2.2",
|
||||
"@eslint/js": "9.14.0",
|
||||
"@next/eslint-plugin-next": "15.0.3",
|
||||
|
||||
+6
-4
@@ -1,4 +1,4 @@
|
||||
import { type ChakraProps } from '@chakra-ui/react';
|
||||
import type { HTMLChakraProps } from '@chakra-ui/react';
|
||||
import { GrowthBookProvider } from '@growthbook/growthbook-react';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
@@ -7,10 +7,11 @@ import React from 'react';
|
||||
|
||||
import type { NextPageWithLayout } from 'nextjs/types';
|
||||
|
||||
import { Provider as ChakraProvider } from 'chakra/components/provider';
|
||||
import { Toaster } from 'chakra/components/toaster';
|
||||
import config from 'configs/app';
|
||||
import useQueryClientConfig from 'lib/api/useQueryClientConfig';
|
||||
import { AppContextProvider } from 'lib/contexts/app';
|
||||
import { ChakraProvider } from 'lib/contexts/chakra';
|
||||
import { MarketplaceContextProvider } from 'lib/contexts/marketplace';
|
||||
import { RewardsContextProvider } from 'lib/contexts/rewards';
|
||||
import { ScrollDirectionProvider } from 'lib/contexts/scrollDirection';
|
||||
@@ -34,7 +35,7 @@ type AppPropsWithLayout = AppProps & {
|
||||
Component: NextPageWithLayout;
|
||||
};
|
||||
|
||||
const ERROR_SCREEN_STYLES: ChakraProps = {
|
||||
const ERROR_SCREEN_STYLES: HTMLChakraProps<'div'> = {
|
||||
h: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -56,7 +57,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
const getLayout = Component.getLayout ?? ((page) => <Layout>{ page }</Layout>);
|
||||
|
||||
return (
|
||||
<ChakraProvider cookies={ pageProps.cookies }>
|
||||
<ChakraProvider>
|
||||
<RollbarProvider config={ rollbarConfig }>
|
||||
<AppErrorBoundary
|
||||
{ ...ERROR_SCREEN_STYLES }
|
||||
@@ -72,6 +73,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
<MarketplaceContextProvider>
|
||||
<SettingsContextProvider>
|
||||
{ getLayout(<Component { ...pageProps }/>) }
|
||||
<Toaster/>
|
||||
{ config.features.rewards.isEnabled && <RewardsLoginModal/> }
|
||||
</SettingsContextProvider>
|
||||
</MarketplaceContextProvider>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ColorModeScript } from '@chakra-ui/react';
|
||||
import type { DocumentContext } from 'next/document';
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
||||
import React from 'react';
|
||||
@@ -7,7 +6,6 @@ import logRequestFromBot from 'nextjs/utils/logRequestFromBot';
|
||||
import * as serverTiming from 'nextjs/utils/serverTiming';
|
||||
|
||||
import config from 'configs/app';
|
||||
import theme from 'theme/theme';
|
||||
import * as svgSprite from 'ui/shared/IconSvg';
|
||||
|
||||
class MyDocument extends Document {
|
||||
@@ -57,7 +55,6 @@ class MyDocument extends Document {
|
||||
<link rel="preload" as="image" href={ svgSprite.href }/>
|
||||
</Head>
|
||||
<body>
|
||||
<ColorModeScript initialColorMode={ theme.config.initialColorMode }/>
|
||||
<Main/>
|
||||
<NextScript/>
|
||||
</body>
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
|
||||
+16
-17
@@ -1,19 +1,18 @@
|
||||
import { VStack, Textarea, Button, Alert, AlertTitle, AlertDescription, Code, Flex, Box } from '@chakra-ui/react';
|
||||
import { VStack, Textarea, Button, Alert, Code, Flex, Box } from '@chakra-ui/react';
|
||||
import mixpanel from 'mixpanel-browser';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { toaster } from 'chakra/components/toaster';
|
||||
import config from 'configs/app';
|
||||
import * as cookies from 'lib/cookies';
|
||||
import useFeatureValue from 'lib/growthbook/useFeatureValue';
|
||||
import useGradualIncrement from 'lib/hooks/useGradualIncrement';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import { useRollbar } from 'lib/rollbar';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
|
||||
const Login = () => {
|
||||
const rollbar = useRollbar();
|
||||
const toast = useToast();
|
||||
const [ num, setNum ] = useGradualIncrement(0);
|
||||
const testFeature = useFeatureValue('test_value', 'fallback');
|
||||
|
||||
@@ -41,18 +40,18 @@ const Login = () => {
|
||||
const handleSetTokenClick = React.useCallback(() => {
|
||||
cookies.set(cookies.NAMES.API_TOKEN, token);
|
||||
setToken('');
|
||||
toast({
|
||||
position: 'top-right',
|
||||
toaster.create({
|
||||
placement: 'top-end',
|
||||
title: 'Success 🥳',
|
||||
description: 'Successfully set cookie',
|
||||
status: 'success',
|
||||
variant: 'subtle',
|
||||
isClosable: true,
|
||||
onCloseComplete: () => {
|
||||
setFormVisibility(false);
|
||||
type: 'success',
|
||||
onStatusChange: (details) => {
|
||||
if (details.status === 'unmounted') {
|
||||
setFormVisibility(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
}, [ toast, token ]);
|
||||
}, [ token ]);
|
||||
|
||||
const handleNumIncrement = React.useCallback(() => {
|
||||
for (let index = 0; index < 5; index++) {
|
||||
@@ -65,16 +64,16 @@ const Login = () => {
|
||||
<PageTitle title="Login page 😂"/>
|
||||
{ isFormVisible && (
|
||||
<>
|
||||
<Alert status="error" flexDirection="column" alignItems="flex-start">
|
||||
<AlertTitle fontSize="md">
|
||||
<Alert.Root status="error" flexDirection="column" alignItems="flex-start">
|
||||
<Alert.Title fontSize="md">
|
||||
!!! Temporary solution for authentication on localhost !!!
|
||||
</AlertTitle>
|
||||
<AlertDescription mt={ 3 }>
|
||||
</Alert.Title>
|
||||
<Alert.Description mt={ 3 }>
|
||||
To Sign in go to production instance first, sign in there, copy obtained API token from cookie
|
||||
<Code ml={ 1 }>{ cookies.NAMES.API_TOKEN }</Code> and paste it in the form below. After submitting the form you should be successfully
|
||||
authenticated in current environment
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</Alert.Description>
|
||||
</Alert.Root>
|
||||
<Textarea value={ token } onChange={ handleTokenChange } placeholder="API token"/>
|
||||
<Button onClick={ handleSetTokenClick }>Set cookie</Button>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { chakra, Flex } from '@chakra-ui/react';
|
||||
import type { GroupBase, SelectComponentsConfig, SingleValueProps } from 'chakra-react-select';
|
||||
import { chakraComponents } from 'chakra-react-select';
|
||||
import _capitalize from 'lodash/capitalize';
|
||||
import React from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
@@ -9,6 +7,8 @@ import type { FormFields } from '../types';
|
||||
import type { PublicTagType } from 'types/api/addressMetadata';
|
||||
import type { Option } from 'ui/shared/forms/inputs/select/types';
|
||||
|
||||
import { chakraComponents } from 'chakra-react-select';
|
||||
import type { GroupBase, SelectComponentsConfig, SingleValueProps } from 'chakra-react-select';
|
||||
import FormFieldFancySelect from 'ui/shared/forms/fields/FormFieldFancySelect';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Box, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { useColorModeValue } from 'chakra/components/color-mode';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Button, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { toaster } from 'chakra/components/toaster';
|
||||
import config from 'configs/app';
|
||||
import buildUrl from 'lib/api/buildUrl';
|
||||
import useFetch from 'lib/hooks/useFetch';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
|
||||
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
|
||||
|
||||
@@ -12,7 +12,6 @@ import AppErrorIcon from '../AppErrorIcon';
|
||||
import AppErrorTitle from '../AppErrorTitle';
|
||||
|
||||
const AppErrorTooManyRequests = () => {
|
||||
const toast = useToast();
|
||||
const fetch = useFetch();
|
||||
const recaptcha = useReCaptcha();
|
||||
|
||||
@@ -32,16 +31,14 @@ const AppErrorTooManyRequests = () => {
|
||||
window.location.reload();
|
||||
|
||||
} catch (error) {
|
||||
toast({
|
||||
position: 'top-right',
|
||||
toaster.create({
|
||||
placement: 'top-end',
|
||||
title: 'Error',
|
||||
description: 'Unable to get client key.',
|
||||
status: 'error',
|
||||
variant: 'subtle',
|
||||
isClosable: true,
|
||||
type: 'error',
|
||||
});
|
||||
}
|
||||
}, [ recaptcha, toast, fetch ]);
|
||||
}, [ recaptcha, fetch ]);
|
||||
|
||||
if (!config.services.reCaptchaV2.siteKey) {
|
||||
throw new Error('reCAPTCHA V2 site key is not set');
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { Box, Text, chakra } from '@chakra-ui/react';
|
||||
import { keyframes } from '@chakra-ui/system';
|
||||
import React from 'react';
|
||||
|
||||
const runnerAnimation = keyframes`
|
||||
0% { left: 0%; transform: translateX(-1%); }
|
||||
100% { left: '100%'; transform: translateX(-99%); }
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
text?: string;
|
||||
@@ -24,7 +18,8 @@ const ContentLoader = ({ className, text }: Props) => {
|
||||
position: 'absolute',
|
||||
width: '60px',
|
||||
height: '6px',
|
||||
animation: `${ runnerAnimation } 700ms ease-in-out infinite alternate`,
|
||||
// TODO @tom2drum check this animation
|
||||
animation: `slide-from-left-full 700ms ease-in-out infinite alternate`,
|
||||
left: '100%',
|
||||
top: 0,
|
||||
backgroundColor: 'blue.300',
|
||||
|
||||
@@ -15,7 +15,7 @@ import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import type { FontFace } from 'use-font-face-observer';
|
||||
import useFontFaceObserver from 'use-font-face-observer';
|
||||
|
||||
import { BODY_TYPEFACE, HEADING_TYPEFACE } from 'theme/foundations/typography';
|
||||
import { BODY_TYPEFACE, HEADING_TYPEFACE } from 'chakra/theme/foundations/typography';
|
||||
|
||||
const TAIL_LENGTH = 4;
|
||||
const HEAD_MIN_LENGTH = 4;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { toaster } from 'chakra/components/toaster';
|
||||
import config from 'configs/app';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import useAddOrSwitchChain from 'lib/web3/useAddOrSwitchChain';
|
||||
import useProvider from 'lib/web3/useProvider';
|
||||
@@ -12,7 +12,6 @@ import IconSvg from 'ui/shared/IconSvg';
|
||||
const feature = config.features.web3Wallet;
|
||||
|
||||
const NetworkAddToWallet = () => {
|
||||
const toast = useToast();
|
||||
const { provider, wallet } = useProvider();
|
||||
const addOrSwitchChain = useAddOrSwitchChain();
|
||||
|
||||
@@ -24,13 +23,9 @@ const NetworkAddToWallet = () => {
|
||||
try {
|
||||
await addOrSwitchChain();
|
||||
|
||||
toast({
|
||||
position: 'top-right',
|
||||
toaster.success({
|
||||
title: 'Success',
|
||||
description: 'Successfully added network to your wallet',
|
||||
status: 'success',
|
||||
variant: 'subtle',
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
mixpanel.logEvent(mixpanel.EventTypes.ADD_TO_WALLET, {
|
||||
@@ -39,16 +34,12 @@ const NetworkAddToWallet = () => {
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
toast({
|
||||
position: 'top-right',
|
||||
toaster.error({
|
||||
title: 'Error',
|
||||
description: (error as Error)?.message || 'Something went wrong',
|
||||
status: 'error',
|
||||
variant: 'subtle',
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
}, [ addOrSwitchChain, provider, toast, wallet ]);
|
||||
}, [ addOrSwitchChain, provider, wallet ]);
|
||||
|
||||
if (!provider || !wallet || !config.chain.rpcUrl || !feature.isEnabled) {
|
||||
return null;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Heading, Flex, Tooltip, Link, chakra, Skeleton, useDisclosure } from '@chakra-ui/react';
|
||||
import { Heading, Flex, Link, chakra, Skeleton, useDisclosure } from '@chakra-ui/react';
|
||||
import _debounce from 'lodash/debounce';
|
||||
import React from 'react';
|
||||
|
||||
import { Tooltip } from 'chakra/components/tooltip';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import TextAd from 'ui/shared/ad/TextAd';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
@@ -38,7 +39,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => {
|
||||
mr={ 3 }
|
||||
my={ 2 }
|
||||
verticalAlign="text-bottom"
|
||||
isLoaded={ !props.isLoading }
|
||||
loading={ props.isLoading }
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -47,7 +48,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => {
|
||||
|
||||
if ('url' in props) {
|
||||
return (
|
||||
<Tooltip label={ props.label }>
|
||||
<Tooltip content={ props.label }>
|
||||
<LinkInternal display="inline-flex" href={ props.url } h="40px" mr={ 3 }>
|
||||
{ icon }
|
||||
</LinkInternal>
|
||||
@@ -56,7 +57,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip label={ props.label }>
|
||||
<Tooltip content={ props.label }>
|
||||
<Link display="inline-flex" onClick={ props.onClick } h="40px" mr={ 3 }>
|
||||
{ icon }
|
||||
</Link>
|
||||
@@ -64,7 +65,7 @@ const BackLink = (props: BackLinkProp & { isLoading?: boolean }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoading, afterTitle, beforeTitle, secondRow }: Props) => {
|
||||
const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoading = false, afterTitle, beforeTitle, secondRow }: Props) => {
|
||||
const tooltip = useDisclosure();
|
||||
const isMobile = useIsMobile();
|
||||
const [ isTextTruncated, setIsTextTruncated ] = React.useState(false);
|
||||
@@ -101,6 +102,14 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa
|
||||
};
|
||||
}, [ updatedTruncateState ]);
|
||||
|
||||
const handleTooltipOpenChange = React.useCallback((details: { open: boolean }) => {
|
||||
if (details.open) {
|
||||
tooltip.onOpen();
|
||||
} else {
|
||||
tooltip.onClose();
|
||||
}
|
||||
}, [ tooltip ]);
|
||||
|
||||
return (
|
||||
<Flex className={ className } flexDir="column" rowGap={ 3 } mb={ 6 }>
|
||||
<Flex
|
||||
@@ -114,16 +123,16 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa
|
||||
{ backLink && <BackLink { ...backLink } isLoading={ isLoading }/> }
|
||||
{ beforeTitle }
|
||||
<Skeleton
|
||||
isLoaded={ !isLoading }
|
||||
loading={ isLoading }
|
||||
overflow="hidden"
|
||||
>
|
||||
<Tooltip
|
||||
label={ title }
|
||||
isOpen={ tooltip.isOpen }
|
||||
onClose={ tooltip.onClose }
|
||||
maxW={{ base: 'calc(100vw - 32px)', lg: '500px' }}
|
||||
content={ title }
|
||||
open={ tooltip.open }
|
||||
onOpenChange={ handleTooltipOpenChange }
|
||||
contentProps={{ maxW: { base: 'calc(100vw - 32px)', lg: '500px' } }}
|
||||
closeOnScroll={ isMobile ? true : false }
|
||||
isDisabled={ !isTextTruncated }
|
||||
disabled={ !isTextTruncated }
|
||||
>
|
||||
<Heading
|
||||
ref={ headingRef }
|
||||
@@ -154,7 +163,7 @@ const PageTitle = ({ title, contentAfter, withTextAd, backLink, className, isLoa
|
||||
{ withTextAd && <TextAd order={{ base: -1, lg: 100 }} mb={{ base: 6, lg: 0 }} ml="auto" w={{ base: '100%', lg: 'auto' }}/> }
|
||||
</Flex>
|
||||
{ secondRow && (
|
||||
<Skeleton isLoaded={ !isLoading } alignItems="center" minH={ 10 } overflow="hidden" display="flex" _empty={{ display: 'none' }}>
|
||||
<Skeleton loading={ isLoading } alignItems="center" minH={ 10 } overflow="hidden" display="flex" _empty={{ display: 'none' }}>
|
||||
{ secondRow }
|
||||
</Skeleton>
|
||||
) }
|
||||
|
||||
@@ -4,7 +4,7 @@ import debounce from 'lodash/debounce';
|
||||
import React from 'react';
|
||||
import useFontFaceObserver from 'use-font-face-observer';
|
||||
|
||||
import { BODY_TYPEFACE } from 'theme/foundations/typography';
|
||||
import { BODY_TYPEFACE } from 'chakra/theme/foundations/typography';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { useColorMode } from '@chakra-ui/react';
|
||||
import { createAppKit, useAppKitTheme } from '@reown/appkit/react';
|
||||
import React from 'react';
|
||||
import { WagmiProvider } from 'wagmi';
|
||||
|
||||
import { useColorMode } from 'chakra/components/color-mode';
|
||||
import colors from 'chakra/theme/foundations/colors';
|
||||
import { BODY_TYPEFACE } from 'chakra/theme/foundations/typography';
|
||||
import zIndex from 'chakra/theme/foundations/zIndex';
|
||||
import config from 'configs/app';
|
||||
import currentChain from 'lib/web3/currentChain';
|
||||
import wagmiConfig from 'lib/web3/wagmiConfig';
|
||||
import colors from 'theme/foundations/colors';
|
||||
import { BODY_TYPEFACE } from 'theme/foundations/typography';
|
||||
import zIndices from 'theme/foundations/zIndices';
|
||||
|
||||
const feature = config.features.blockchainInteraction;
|
||||
|
||||
@@ -37,9 +37,9 @@ const init = () => {
|
||||
},
|
||||
themeVariables: {
|
||||
'--w3m-font-family': `${ BODY_TYPEFACE }, sans-serif`,
|
||||
'--w3m-accent': colors.blue[600],
|
||||
'--w3m-accent': colors.blue[600].value,
|
||||
'--w3m-border-radius-master': '2px',
|
||||
'--w3m-z-index': zIndices.popover,
|
||||
'--w3m-z-index': zIndex.popover.value,
|
||||
},
|
||||
featuredWalletIds: [],
|
||||
allowUnsupportedChain: true,
|
||||
@@ -66,7 +66,7 @@ const Web3ModalProvider = ({ children }: Props) => {
|
||||
const { setThemeMode } = useAppKitTheme();
|
||||
|
||||
React.useEffect(() => {
|
||||
setThemeMode(colorMode);
|
||||
setThemeMode(colorMode ?? 'light');
|
||||
}, [ colorMode, setThemeMode ]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { FormControl, useToken, useColorMode } from '@chakra-ui/react';
|
||||
import type { CSSObjectWithLabel, GroupBase, SingleValue, MultiValue, AsyncProps, Props as SelectProps } from 'chakra-react-select';
|
||||
import { Select, AsyncSelect } from 'chakra-react-select';
|
||||
import React from 'react';
|
||||
import type { FieldError, FieldErrorsImpl, Merge } from 'react-hook-form';
|
||||
|
||||
import type { Option } from './types';
|
||||
|
||||
import { Select, AsyncSelect } from 'chakra-react-select';
|
||||
import type { CSSObjectWithLabel, GroupBase, SingleValue, MultiValue, AsyncProps, Props as SelectProps } from 'chakra-react-select';
|
||||
import FormInputPlaceholder from 'ui/shared/forms/inputs/FormInputPlaceholder';
|
||||
import { getChakraStyles } from 'ui/shared/forms/inputs/select/utils';
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { ColorMode } from '@chakra-ui/react';
|
||||
import type { Size, ChakraStylesConfig } from 'chakra-react-select';
|
||||
|
||||
import type { Option } from './types';
|
||||
|
||||
import type { Size, ChakraStylesConfig } from 'chakra-react-select';
|
||||
import theme from 'theme/theme';
|
||||
import getFormStyles from 'theme/utils/getFormStyles';
|
||||
|
||||
|
||||
@@ -2,14 +2,12 @@ import type {
|
||||
PlacementWithLogical } from '@chakra-ui/react';
|
||||
import {
|
||||
Box,
|
||||
DarkMode,
|
||||
Flex,
|
||||
Grid,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Portal,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
@@ -17,6 +15,7 @@ import type { HomeStats } from 'types/api/stats';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
|
||||
import { useColorModeValue } from 'chakra/components/color-mode';
|
||||
import config from 'configs/app';
|
||||
import dayjs from 'lib/date/dayjs';
|
||||
import Popover from 'ui/shared/chakra/Popover';
|
||||
@@ -56,28 +55,27 @@ const GasInfoTooltip = ({ children, data, dataUpdatedAt, isOpen, placement }: Pr
|
||||
<Portal>
|
||||
<PopoverContent bgColor={ tooltipBg } w="auto">
|
||||
<PopoverBody color="white">
|
||||
<DarkMode>
|
||||
<Flex flexDir="column" fontSize="xs" lineHeight={ 4 } rowGap={ 3 }>
|
||||
{ data.gas_price_updated_at && (
|
||||
<Flex justifyContent="space-between">
|
||||
<Box color="text_secondary">Last update</Box>
|
||||
<Flex color="text_secondary" justifyContent="flex-end" columnGap={ 2 } ml={ 3 }>
|
||||
{ dayjs(data.gas_price_updated_at).format('MMM DD, HH:mm:ss') }
|
||||
{ data.gas_prices_update_in !== 0 &&
|
||||
{ /* TODO @tom2drum add dark mode */ }
|
||||
<Flex flexDir="column" fontSize="xs" lineHeight={ 4 } rowGap={ 3 }>
|
||||
{ data.gas_price_updated_at && (
|
||||
<Flex justifyContent="space-between">
|
||||
<Box color="text_secondary">Last update</Box>
|
||||
<Flex color="text_secondary" justifyContent="flex-end" columnGap={ 2 } ml={ 3 }>
|
||||
{ dayjs(data.gas_price_updated_at).format('MMM DD, HH:mm:ss') }
|
||||
{ data.gas_prices_update_in !== 0 &&
|
||||
<GasInfoUpdateTimer key={ dataUpdatedAt } startTime={ dataUpdatedAt } duration={ data.gas_prices_update_in }/> }
|
||||
</Flex>
|
||||
</Flex>
|
||||
) }
|
||||
<Grid rowGap={ 2 } columnGap="10px" gridTemplateColumns={ `repeat(${ columnNum }, minmax(min-content, auto))` }>
|
||||
<GasInfoTooltipRow name="Fast" info={ data.gas_prices.fast }/>
|
||||
<GasInfoTooltipRow name="Normal" info={ data.gas_prices.average }/>
|
||||
<GasInfoTooltipRow name="Slow" info={ data.gas_prices.slow }/>
|
||||
</Grid>
|
||||
<LinkInternal href={ route({ pathname: '/gas-tracker' }) }>
|
||||
Gas tracker overview
|
||||
</LinkInternal>
|
||||
</Flex>
|
||||
</DarkMode>
|
||||
</Flex>
|
||||
) }
|
||||
<Grid rowGap={ 2 } columnGap="10px" gridTemplateColumns={ `repeat(${ columnNum }, minmax(min-content, auto))` }>
|
||||
<GasInfoTooltipRow name="Fast" info={ data.gas_prices.fast }/>
|
||||
<GasInfoTooltipRow name="Normal" info={ data.gas_prices.average }/>
|
||||
<GasInfoTooltipRow name="Slow" info={ data.gas_prices.slow }/>
|
||||
</Grid>
|
||||
<LinkInternal href={ route({ pathname: '/gas-tracker' }) }>
|
||||
Gas tracker overview
|
||||
</LinkInternal>
|
||||
</Flex>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
|
||||
@@ -4,22 +4,23 @@ import type { Props } from './types';
|
||||
|
||||
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
|
||||
import HeaderAlert from 'ui/snippets/header/HeaderAlert';
|
||||
import HeaderDesktop from 'ui/snippets/header/HeaderDesktop';
|
||||
import HeaderMobile from 'ui/snippets/header/HeaderMobile';
|
||||
// TODO @tom2drum fix main layout
|
||||
// import HeaderDesktop from 'ui/snippets/header/HeaderDesktop';
|
||||
// import HeaderMobile from 'ui/snippets/header/HeaderMobile';
|
||||
|
||||
import * as Layout from './components';
|
||||
|
||||
const LayoutDefault = ({ children }: Props) => {
|
||||
return (
|
||||
<Layout.Container>
|
||||
<Layout.TopRow/>
|
||||
<Layout.NavBar/>
|
||||
<HeaderMobile/>
|
||||
{ /* <Layout.TopRow/> */ }
|
||||
{ /* <Layout.NavBar/>
|
||||
<HeaderMobile/> */ }
|
||||
<Layout.MainArea>
|
||||
<Layout.SideBar/>
|
||||
{ /* <Layout.SideBar/> */ }
|
||||
<Layout.MainColumn>
|
||||
<HeaderAlert/>
|
||||
<HeaderDesktop/>
|
||||
{ /* <HeaderDesktop/> */ }
|
||||
<AppErrorBoundary>
|
||||
<Layout.Content>
|
||||
{ children }
|
||||
@@ -27,7 +28,7 @@ const LayoutDefault = ({ children }: Props) => {
|
||||
</AppErrorBoundary>
|
||||
</Layout.MainColumn>
|
||||
</Layout.MainArea>
|
||||
<Layout.Footer/>
|
||||
{ /* <Layout.Footer/> */ }
|
||||
</Layout.Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Box, chakra, useColorModeValue } from '@chakra-ui/react';
|
||||
import { Box, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { useColorModeValue } from 'chakra/components/color-mode';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
|
||||
@@ -5,10 +5,10 @@ import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import type { EmailFormFields, Screen } from '../types';
|
||||
|
||||
import { toaster } from 'chakra/components/toaster';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
import getErrorMessage from 'lib/errors/getErrorMessage';
|
||||
import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
import FormFieldEmail from 'ui/shared/forms/fields/FormFieldEmail';
|
||||
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
|
||||
@@ -27,7 +27,6 @@ interface Props {
|
||||
const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => {
|
||||
|
||||
const apiFetch = useApiFetch();
|
||||
const toast = useToast();
|
||||
const recaptcha = useReCaptcha();
|
||||
|
||||
const formApi = useForm<EmailFormFields>({
|
||||
@@ -64,13 +63,12 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => {
|
||||
}
|
||||
onSubmit({ type: 'otp_code', email: formData.email, isAuth });
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: 'error',
|
||||
toaster.error({
|
||||
title: 'Error',
|
||||
description: getErrorObjPayload<{ message: string }>(error)?.message || getErrorMessage(error) || 'Something went wrong',
|
||||
});
|
||||
}
|
||||
}, [ recaptcha, apiFetch, isAuth, onSubmit, mixpanelConfig?.account_link_info.source, toast ]);
|
||||
}, [ recaptcha, apiFetch, isAuth, onSubmit, mixpanelConfig?.account_link_info.source ]);
|
||||
|
||||
return (
|
||||
<FormProvider { ...formApi }>
|
||||
@@ -89,7 +87,7 @@ const AuthModalScreenEmail = ({ onSubmit, isAuth, mixpanelConfig }: Props) => {
|
||||
<Button
|
||||
mt={ 6 }
|
||||
type="submit"
|
||||
isDisabled={ formApi.formState.isSubmitting }
|
||||
disabled={ formApi.formState.isSubmitting }
|
||||
isLoading={ formApi.formState.isSubmitting }
|
||||
loadingText="Send a code"
|
||||
>
|
||||
|
||||
@@ -6,10 +6,10 @@ import { FormProvider, useForm } from 'react-hook-form';
|
||||
import type { OtpCodeFormFields, ScreenSuccess } from '../types';
|
||||
import type { UserInfo } from 'types/api/account';
|
||||
|
||||
import { toaster } from 'chakra/components/toaster';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
import getErrorMessage from 'lib/errors/getErrorMessage';
|
||||
import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import ReCaptcha from 'ui/shared/reCaptcha/ReCaptcha';
|
||||
import useReCaptcha from 'ui/shared/reCaptcha/useReCaptcha';
|
||||
@@ -25,7 +25,6 @@ interface Props {
|
||||
const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => {
|
||||
|
||||
const apiFetch = useApiFetch();
|
||||
const toast = useToast();
|
||||
const recaptcha = useReCaptcha();
|
||||
const [ isCodeSending, setIsCodeSending ] = React.useState(false);
|
||||
|
||||
@@ -61,13 +60,12 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
status: 'error',
|
||||
toaster.error({
|
||||
title: 'Error',
|
||||
description: getErrorMessage(error) || 'Something went wrong',
|
||||
});
|
||||
});
|
||||
}, [ apiFetch, email, onSuccess, isAuth, toast, formApi ]);
|
||||
}, [ apiFetch, email, onSuccess, isAuth, formApi ]);
|
||||
|
||||
const handleResendCodeClick = React.useCallback(async() => {
|
||||
try {
|
||||
@@ -81,23 +79,21 @@ const AuthModalScreenOtpCode = ({ email, onSuccess, isAuth }: Props) => {
|
||||
},
|
||||
});
|
||||
|
||||
toast({
|
||||
status: 'success',
|
||||
toaster.success({
|
||||
title: 'Success',
|
||||
description: 'Code has been sent to your email',
|
||||
});
|
||||
} catch (error) {
|
||||
const apiError = getErrorObjPayload<{ message: string }>(error);
|
||||
|
||||
toast({
|
||||
status: 'error',
|
||||
toaster.error({
|
||||
title: 'Error',
|
||||
description: apiError?.message || getErrorMessage(error) || 'Something went wrong',
|
||||
});
|
||||
} finally {
|
||||
setIsCodeSending(false);
|
||||
}
|
||||
}, [ apiFetch, email, formApi, toast, recaptcha ]);
|
||||
}, [ apiFetch, email, formApi, recaptcha ]);
|
||||
|
||||
return (
|
||||
<FormProvider { ...formApi }>
|
||||
|
||||
@@ -4,11 +4,11 @@ import React from 'react';
|
||||
|
||||
import type { Route } from 'nextjs-routes';
|
||||
|
||||
import { toaster } from 'chakra/components/toaster';
|
||||
import config from 'configs/app';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
import { getResourceKey } from 'lib/api/useApiQuery';
|
||||
import * as cookies from 'lib/cookies';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
|
||||
const PROTECTED_ROUTES: Array<Route['pathname']> = [
|
||||
@@ -24,7 +24,6 @@ const PROTECTED_ROUTES: Array<Route['pathname']> = [
|
||||
export default function useLogout() {
|
||||
const router = useRouter();
|
||||
const queryClient = useQueryClient();
|
||||
const toast = useToast();
|
||||
const apiFetch = useApiFetch();
|
||||
|
||||
return React.useCallback(async() => {
|
||||
@@ -62,11 +61,10 @@ export default function useLogout() {
|
||||
router.push({ pathname: '/' }, undefined, { shallow: true });
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
status: 'error',
|
||||
toaster.error({
|
||||
title: 'Logout failed',
|
||||
description: 'Please try again later',
|
||||
});
|
||||
}
|
||||
}, [ apiFetch, queryClient, router, toast ]);
|
||||
}, [ apiFetch, queryClient, router ]);
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useSignMessage } from 'wagmi';
|
||||
|
||||
import type { UserInfo } from 'types/api/account';
|
||||
|
||||
import { toaster } from 'chakra/components/toaster';
|
||||
import config from 'configs/app';
|
||||
import useApiFetch from 'lib/api/useApiFetch';
|
||||
import getErrorMessage from 'lib/errors/getErrorMessage';
|
||||
import getErrorObj from 'lib/errors/getErrorObj';
|
||||
import getErrorObjPayload from 'lib/errors/getErrorObjPayload';
|
||||
import useToast from 'lib/hooks/useToast';
|
||||
import type * as mixpanel from 'lib/mixpanel';
|
||||
import useWeb3Wallet from 'lib/web3/useWallet';
|
||||
|
||||
@@ -24,7 +24,6 @@ function useSignInWithWallet({ onSuccess, onError, source = 'Login', isAuth }: P
|
||||
const isConnectingWalletRef = React.useRef(false);
|
||||
|
||||
const apiFetch = useApiFetch();
|
||||
const toast = useToast();
|
||||
const web3Wallet = useWeb3Wallet({ source });
|
||||
const { signMessageAsync } = useSignMessage();
|
||||
|
||||
@@ -48,15 +47,14 @@ function useSignInWithWallet({ onSuccess, onError, source = 'Login', isAuth }: P
|
||||
const apiErrorMessage = getErrorObjPayload<{ message: string }>(error)?.message;
|
||||
const shortMessage = errorObj && 'shortMessage' in errorObj && typeof errorObj.shortMessage === 'string' ? errorObj.shortMessage : undefined;
|
||||
onError?.();
|
||||
toast({
|
||||
status: 'error',
|
||||
toaster.error({
|
||||
title: 'Error',
|
||||
description: apiErrorMessage || shortMessage || getErrorMessage(error) || 'Something went wrong',
|
||||
});
|
||||
} finally {
|
||||
setIsPending(false);
|
||||
}
|
||||
}, [ apiFetch, isAuth, onError, onSuccess, signMessageAsync, toast ]);
|
||||
}, [ apiFetch, isAuth, onError, onSuccess, signMessageAsync ]);
|
||||
|
||||
const start = React.useCallback(() => {
|
||||
setIsPending(true);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Box, Flex, Drawer, DrawerOverlay, DrawerContent, DrawerBody, useColorModeValue, useDisclosure } from '@chakra-ui/react';
|
||||
import { Box, Flex, Drawer, useDisclosure } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { useColorModeValue } from 'chakra/components/color-mode';
|
||||
import config from 'configs/app';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import NavigationMobile from 'ui/snippets/navigation/mobile/NavigationMobile';
|
||||
@@ -39,15 +40,15 @@ const Burger = ({ isMarketplaceAppPage }: Props) => {
|
||||
aria-label="Menu button"
|
||||
/>
|
||||
</Box>
|
||||
<Drawer
|
||||
<Drawer.Root
|
||||
isOpen={ isOpen }
|
||||
placement="left"
|
||||
onClose={ onClose }
|
||||
autoFocus={ false }
|
||||
>
|
||||
<DrawerOverlay/>
|
||||
<DrawerContent maxWidth="330px">
|
||||
<DrawerBody p={ 6 } display="flex" flexDirection="column">
|
||||
<Drawer.Backdrop/>
|
||||
<Drawer.Content maxWidth="330px">
|
||||
<Drawer.Body p={ 6 } display="flex" flexDirection="column">
|
||||
<TestnetBadge alignSelf="flex-start"/>
|
||||
<Flex alignItems="center" justifyContent="space-between">
|
||||
<NetworkLogo onClick={ handleNetworkLogoClick }/>
|
||||
@@ -63,9 +64,9 @@ const Burger = ({ isMarketplaceAppPage }: Props) => {
|
||||
<NetworkMenuContentMobile tabs={ networkMenu.availableTabs } items={ networkMenu.data }/> :
|
||||
<NavigationMobile onNavLinkClick={ onClose } isMarketplaceAppPage={ isMarketplaceAppPage }/>
|
||||
}
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</Drawer.Body>
|
||||
</Drawer.Content>
|
||||
</Drawer.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Box, Flex, Text, VStack, useColorModeValue } from '@chakra-ui/react';
|
||||
import { animate, motion, useMotionValue } from 'framer-motion';
|
||||
import { Box, Flex, Text, VStack } from '@chakra-ui/react';
|
||||
// import { animate, motion, useMotionValue } from 'framer-motion';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { useColorModeValue } from 'chakra/components/color-mode';
|
||||
import useNavItems, { isGroupItem } from 'lib/hooks/useNavItems';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import useIsAuth from 'ui/snippets/auth/useIsAuth';
|
||||
@@ -10,7 +11,7 @@ import NavLink from '../vertical/NavLink';
|
||||
import NavLinkRewards from '../vertical/NavLinkRewards';
|
||||
import NavLinkGroup from './NavLinkGroup';
|
||||
|
||||
const DRAWER_WIDTH = 330;
|
||||
// const DRAWER_WIDTH = 330;
|
||||
|
||||
interface Props {
|
||||
onNavLinkClick?: () => void;
|
||||
@@ -22,19 +23,20 @@ const NavigationMobile = ({ onNavLinkClick, isMarketplaceAppPage }: Props) => {
|
||||
|
||||
const [ openedGroupIndex, setOpenedGroupIndex ] = React.useState(-1);
|
||||
|
||||
const mainX = useMotionValue(0);
|
||||
const subX = useMotionValue(DRAWER_WIDTH);
|
||||
// const mainX = useMotionValue(0);
|
||||
// const subX = useMotionValue(DRAWER_WIDTH);
|
||||
|
||||
const onGroupItemOpen = (index: number) => () => {
|
||||
setOpenedGroupIndex(index);
|
||||
animate(mainX, -DRAWER_WIDTH, { ease: 'easeInOut' });
|
||||
animate(subX, 0, { ease: 'easeInOut' });
|
||||
// TODO @tom2drum animate navigation group menu
|
||||
// animate(mainX, -DRAWER_WIDTH, { ease: 'easeInOut' });
|
||||
// animate(subX, 0, { ease: 'easeInOut' });
|
||||
};
|
||||
|
||||
const onGroupItemClose = useCallback(() => {
|
||||
animate(mainX, 0, { ease: 'easeInOut' });
|
||||
animate(subX, DRAWER_WIDTH, { ease: 'easeInOut', onComplete: () => setOpenedGroupIndex(-1) });
|
||||
}, [ mainX, subX ]);
|
||||
// animate(mainX, 0, { ease: 'easeInOut' });
|
||||
// animate(subX, DRAWER_WIDTH, { ease: 'easeInOut', onComplete: () => setOpenedGroupIndex(-1) });
|
||||
}, []);
|
||||
|
||||
const isAuth = useIsAuth();
|
||||
|
||||
@@ -50,8 +52,8 @@ const NavigationMobile = ({ onNavLinkClick, isMarketplaceAppPage }: Props) => {
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
flexGrow={ 1 }
|
||||
as={ motion.div }
|
||||
style={{ x: mainX }}
|
||||
// as={ motion.div }
|
||||
// style={{ x: mainX }}
|
||||
maxHeight={ openedGroupIndex > -1 ? '100vh' : 'unset' }
|
||||
overflowY={ openedGroupIndex > -1 ? 'hidden' : 'unset' }
|
||||
>
|
||||
@@ -91,12 +93,12 @@ const NavigationMobile = ({ onNavLinkClick, isMarketplaceAppPage }: Props) => {
|
||||
</Box>
|
||||
{ openedGroupIndex >= 0 && (
|
||||
<Box
|
||||
as={ motion.nav }
|
||||
// as={ motion.nav }
|
||||
w="100%"
|
||||
mt={ 6 }
|
||||
position="absolute"
|
||||
top={ 0 }
|
||||
style={{ x: subX }}
|
||||
// style={{ x: subX }}
|
||||
key="sub"
|
||||
>
|
||||
<Flex alignItems="center" px={ 2 } py={ 2.5 } w="100%" h="50px" onClick={ onGroupItemClose } mb={ 1 }>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useColorModeValue, Button, forwardRef, chakra } from '@chakra-ui/react';
|
||||
import { Button, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { useColorModeValue } from 'chakra/components/color-mode';
|
||||
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
@@ -44,4 +45,4 @@ const NetworkMenuButton = ({ isMobile, isActive, onClick, className }: Props, re
|
||||
);
|
||||
};
|
||||
|
||||
export default chakra(forwardRef(NetworkMenuButton));
|
||||
export default chakra(React.forwardRef(NetworkMenuButton));
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { InputGroup, Input, InputLeftElement, chakra, useColorModeValue, forwardRef, InputRightElement, Center } from '@chakra-ui/react';
|
||||
import { InputGroup, Input, InputLeftElement, chakra, InputRightElement, Center } from '@chakra-ui/react';
|
||||
import throttle from 'lodash/throttle';
|
||||
import React from 'react';
|
||||
import type { ChangeEvent, FormEvent, FocusEvent } from 'react';
|
||||
|
||||
import { useColorModeValue } from 'chakra/components/color-mode';
|
||||
import { useScrollDirection } from 'lib/contexts/scrollDirection';
|
||||
import useIsMobile from 'lib/hooks/useIsMobile';
|
||||
import ClearButton from 'ui/shared/ClearButton';
|
||||
@@ -179,4 +180,4 @@ const SearchBarInput = (
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(forwardRef(SearchBarInput));
|
||||
export default React.memo(React.forwardRef(SearchBarInput));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Flex, Divider, useColorModeValue, Box } from '@chakra-ui/react';
|
||||
import { Flex, Divider, Box } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { useColorModeValue } from 'chakra/components/color-mode';
|
||||
import config from 'configs/app';
|
||||
import { CONTENT_MAX_WIDTH } from 'ui/shared/layout/utils';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user