mirror of
https://github.com/luxfi/explore.git
synced 2026-09-24 00:28:18 +00:00
fix rewards modal
This commit is contained in:
@@ -43,7 +43,6 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
|
||||
{ endElement }
|
||||
{ closable && (
|
||||
<CloseButton
|
||||
size="sm"
|
||||
pos="relative"
|
||||
top="-2"
|
||||
insetEnd="-2"
|
||||
|
||||
+47
-15
@@ -3,14 +3,16 @@ import * as React from 'react';
|
||||
|
||||
import { space } from 'lib/html-entities';
|
||||
|
||||
import getComponentDisplayName from '../utils/getComponentDisplayName';
|
||||
import type { InputProps } from './input';
|
||||
import type { InputGroupProps } from './input-group';
|
||||
|
||||
export interface FieldProps extends Omit<ChakraField.RootProps, 'label' | 'children'> {
|
||||
label?: React.ReactNode;
|
||||
helperText?: React.ReactNode;
|
||||
errorText?: React.ReactNode;
|
||||
optionalText?: React.ReactNode;
|
||||
children: React.ReactElement<InputProps>;
|
||||
children: React.ReactElement<InputProps> | React.ReactElement<InputGroupProps>;
|
||||
}
|
||||
|
||||
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
||||
@@ -19,28 +21,58 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
||||
|
||||
// A floating field cannot be without a label.
|
||||
if (props.floating && label) {
|
||||
const child = React.Children.only<React.ReactElement<InputProps>>(children);
|
||||
const clonedChild = React.cloneElement(child, {
|
||||
const injectedProps = {
|
||||
className: 'peer',
|
||||
placeholder: ' ',
|
||||
size: props.size,
|
||||
floating: props.floating,
|
||||
bgColor: rest.bgColor,
|
||||
});
|
||||
};
|
||||
|
||||
const labelElement = (
|
||||
<ChakraField.Label bgColor={ rest.bgColor }>
|
||||
{ label }
|
||||
<ChakraField.RequiredIndicator fallback={ optionalText }/>
|
||||
{ errorText && (
|
||||
<ChakraField.ErrorText ml="2px">-{ space }{ errorText }</ChakraField.ErrorText>
|
||||
) }
|
||||
</ChakraField.Label>
|
||||
);
|
||||
|
||||
const helperTextElement = helperText && (
|
||||
<ChakraField.HelperText>{ helperText }</ChakraField.HelperText>
|
||||
);
|
||||
|
||||
const child = React.Children.only<React.ReactElement<InputProps | InputGroupProps>>(children);
|
||||
const isInputGroup = getComponentDisplayName(child.type) === 'InputGroup';
|
||||
|
||||
if (isInputGroup) {
|
||||
const inputElement = React.cloneElement(
|
||||
React.Children.only<React.ReactElement<InputProps>>(child.props.children as React.ReactElement<InputProps>),
|
||||
injectedProps,
|
||||
);
|
||||
|
||||
const groupInputElement = React.cloneElement(child,
|
||||
{},
|
||||
inputElement,
|
||||
labelElement,
|
||||
);
|
||||
|
||||
return (
|
||||
<ChakraField.Root pos="relative" w="full" ref={ ref } { ...rest }>
|
||||
{ groupInputElement }
|
||||
{ helperTextElement }
|
||||
</ChakraField.Root>
|
||||
);
|
||||
}
|
||||
|
||||
const inputElement = React.cloneElement(child, injectedProps);
|
||||
|
||||
return (
|
||||
<ChakraField.Root pos="relative" w="full" ref={ ref } { ...rest }>
|
||||
{ clonedChild }
|
||||
<ChakraField.Label bgColor={ rest.bgColor }>
|
||||
{ label }
|
||||
<ChakraField.RequiredIndicator fallback={ optionalText }/>
|
||||
{ errorText && (
|
||||
<ChakraField.ErrorText ml="2px">-{ space }{ errorText }</ChakraField.ErrorText>
|
||||
) }
|
||||
</ChakraField.Label>
|
||||
{ helperText && (
|
||||
<ChakraField.HelperText>{ helperText }</ChakraField.HelperText>
|
||||
) }
|
||||
{ inputElement }
|
||||
{ labelElement }
|
||||
{ helperTextElement }
|
||||
</ChakraField.Root>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { BoxProps, InputElementProps } from '@chakra-ui/react';
|
||||
import { Group, InputElement } from '@chakra-ui/react';
|
||||
import * as React from 'react';
|
||||
|
||||
import getComponentDisplayName from '../utils/getComponentDisplayName';
|
||||
import type { InputProps } from './input';
|
||||
|
||||
export interface InputGroupProps extends BoxProps {
|
||||
startElementProps?: InputElementProps;
|
||||
endElementProps?: InputElementProps;
|
||||
@@ -20,27 +23,25 @@ export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
|
||||
endElement,
|
||||
endElementProps,
|
||||
children,
|
||||
startOffset = '6px',
|
||||
endOffset = '6px',
|
||||
startOffset,
|
||||
endOffset,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const child =
|
||||
React.Children.only<React.ReactElement<InputElementProps>>(children);
|
||||
|
||||
return (
|
||||
<Group ref={ ref } { ...rest }>
|
||||
<Group ref={ ref } w="100%" { ...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,
|
||||
{ React.Children.map(children, (child: React.ReactElement<InputProps>) => {
|
||||
if (getComponentDisplayName(child.type) !== 'FieldInput') {
|
||||
return child;
|
||||
}
|
||||
return React.cloneElement(child, {
|
||||
...(startElement && { ps: startOffset ?? `calc(var(--input-height) - 6px)` }),
|
||||
...(endElement && { pe: endOffset ?? `calc(var(--input-height) - 6px)` }),
|
||||
});
|
||||
}) }
|
||||
{ endElement && (
|
||||
<InputElement placement="end" { ...endElementProps }>
|
||||
@@ -51,3 +52,5 @@ export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
InputGroup.displayName = 'InputGroup';
|
||||
|
||||
@@ -56,7 +56,7 @@ export const recipe = defineSlotRecipe({
|
||||
inline: {
|
||||
'true': {
|
||||
root: {
|
||||
alignItems: 'center',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
content: {
|
||||
display: 'inline-flex',
|
||||
@@ -82,6 +82,7 @@ export const recipe = defineSlotRecipe({
|
||||
},
|
||||
indicator: {
|
||||
boxSize: '5',
|
||||
mt: '2px',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ export const recipe = defineSlotRecipe({
|
||||
fontWeight: '500',
|
||||
gap: '0',
|
||||
userSelect: 'none',
|
||||
zIndex: '1',
|
||||
_disabled: {
|
||||
opacity: '0.5',
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ export const recipe = defineRecipe({
|
||||
variants: {
|
||||
loading: {
|
||||
'true': {
|
||||
borderRadius: 'sm',
|
||||
borderRadius: 'base',
|
||||
boxShadow: 'none',
|
||||
backgroundClip: 'padding-box',
|
||||
cursor: 'default',
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import type React from 'react';
|
||||
|
||||
export default function getComponentDisplayName(type: string | React.JSXElementConstructor<unknown>) {
|
||||
if (typeof type === 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('displayName' in type) {
|
||||
return type.displayName as string;
|
||||
}
|
||||
}
|
||||
+16
-1
@@ -8,6 +8,7 @@ import { Button } from 'toolkit/chakra/button';
|
||||
import { useColorMode } from 'toolkit/chakra/color-mode';
|
||||
import { Field } from 'toolkit/chakra/field';
|
||||
import { Input } from 'toolkit/chakra/input';
|
||||
import { InputGroup } from 'toolkit/chakra/input-group';
|
||||
import { PinInput } from 'toolkit/chakra/pin-input';
|
||||
import { ProgressCircleRing, ProgressCircleRoot } from 'toolkit/chakra/progress-circle';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
@@ -16,6 +17,7 @@ import { Textarea } from 'toolkit/chakra/textarea';
|
||||
import { toaster } from 'toolkit/chakra/toaster';
|
||||
import { Tooltip } from 'toolkit/chakra/tooltip';
|
||||
import ContentLoader from 'ui/shared/ContentLoader';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
import PageTitle from 'ui/shared/Page/PageTitle';
|
||||
|
||||
const TEXT = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
|
||||
@@ -44,6 +46,7 @@ const ChakraShowcases = () => {
|
||||
<Button visual="link">Link</Button>
|
||||
<Button loading loadingText="Solid">Solid</Button>
|
||||
<Button loading loadingText="Outline" visual="outline">Outline</Button>
|
||||
<Button loading>Loading</Button>
|
||||
</HStack>
|
||||
</section>
|
||||
|
||||
@@ -83,7 +86,7 @@ const ChakraShowcases = () => {
|
||||
</HStack>
|
||||
<Heading textStyle="heading.sm" mb={ 2 } mt={ 6 }>Floating (only XL size)</Heading>
|
||||
<HStack gap={ 4 } mt={ 4 } alignItems="flex-start">
|
||||
<Field label="Email" required floating size="xl" w="300px">
|
||||
<Field label="Email" required floating size="xl" helperText="Helper text" w="300px">
|
||||
<Input type="email"/>
|
||||
</Field>
|
||||
<Field label="Email" required floating invalid errorText="Something went wrong" size="xl" w="300px">
|
||||
@@ -101,6 +104,18 @@ const ChakraShowcases = () => {
|
||||
<Input type="email" value="[email protected]"/>
|
||||
</Field>
|
||||
</HStack>
|
||||
<Heading textStyle="heading.sm" mb={ 2 } mt={ 6 }>Input group</Heading>
|
||||
<HStack gap={ 4 } mt={ 4 } alignItems="flex-start" w="fit-content">
|
||||
<Field label="Referral code" required floating size="xl" w="300px" flexShrink={ 0 } helperText="Helper text">
|
||||
<InputGroup endElement={ <IconSvg name="copy" boxSize={ 5 }/> }>
|
||||
<Input/>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
<InputGroup startElement={ <IconSvg name="search" boxSize={ 5 }/> }>
|
||||
<Input placeholder="Search"/>
|
||||
</InputGroup>
|
||||
</HStack>
|
||||
<Heading textStyle="heading.sm" mb={ 2 } mt={ 6 }>Pin input</Heading>
|
||||
<HStack mt={ 4 }>
|
||||
<PinInput otp count={ 3 }/>
|
||||
<PinInput otp count={ 3 } value={ [ '1', '2', '3' ] } disabled bgColor="dialog.bg"/>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { Icon, useColorModeValue, chakra } from '@chakra-ui/react';
|
||||
import { Icon, chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
// This icon doesn't work properly when it is in the sprite
|
||||
// Probably because of the gradient
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import meritsIcon from 'icons/merits_colored.svg';
|
||||
import MeritsIconColored from 'icons/merits_colored.svg';
|
||||
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const MeritsIcon = ({ className }: Props) => {
|
||||
const shadow = useColorModeValue('drop-shadow(0px 4px 2px rgba(141, 179, 204, 0.25))', 'none');
|
||||
|
||||
return (
|
||||
<Icon as={ meritsIcon } className={ className } filter={ shadow }/>
|
||||
<Icon className={ className } filter={{ _light: 'drop-shadow(0px 4px 2px rgba(141, 179, 204, 0.25))', _dark: 'none' }}>
|
||||
<MeritsIconColored/>
|
||||
</Icon>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ type Props = {
|
||||
visual?: ButtonProps['visual'];
|
||||
};
|
||||
|
||||
// TODO @tom2drum check this component
|
||||
|
||||
const RewardsButton = ({ visual = 'header', size }: Props) => {
|
||||
const { isInitialized, apiToken, openLoginModal, dailyRewardQuery, balancesQuery } = useRewardsContext();
|
||||
const isMobile = useIsMobile();
|
||||
@@ -32,7 +30,6 @@ const RewardsButton = ({ visual = 'header', size }: Props) => {
|
||||
content="Earn Merits for using Blockscout"
|
||||
openDelay={ 500 }
|
||||
disabled={ isMobile || isLoading || Boolean(apiToken) }
|
||||
// width="150px"
|
||||
>
|
||||
<Button
|
||||
visual={ visual }
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { chakra } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { InputGroup } from 'toolkit/chakra/input-group';
|
||||
import Input from 'theme/components/Input';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
import FormInputPlaceholder from 'ui/shared/forms/inputs/FormInputPlaceholder';
|
||||
import { Field } from 'toolkit/chakra/field';
|
||||
import { Input } from 'toolkit/chakra/input';
|
||||
import { InputGroup } from 'toolkit/chakra/input-group';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import CopyToClipboard from 'ui/shared/CopyToClipboard';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
@@ -15,29 +14,16 @@ type Props = {
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
const RewardsReadOnlyInputWithCopy = ({ label, value, className, isLoading }: Props) => (
|
||||
<Field floating id={ label } className={ className }>
|
||||
<Skeleton loading={ isLoading }>
|
||||
<InputGroup>
|
||||
<Input
|
||||
readOnly
|
||||
fontWeight="500"
|
||||
value={ value }
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
sx={{
|
||||
'&:not(:placeholder-shown)': {
|
||||
pr: '40px',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<FormInputPlaceholder text={ label }/>
|
||||
<InputRightElement w="40px" display="flex" justifyContent="flex-end" pr={ 2 }>
|
||||
<CopyToClipboard text={ value }/>
|
||||
</InputRightElement>
|
||||
</InputGroup>
|
||||
const RewardsReadOnlyInputWithCopy = ({ label, value, className, isLoading }: Props) => {
|
||||
return (
|
||||
<Skeleton loading={ isLoading } className={ className }>
|
||||
<Field label={ label } floating size="xl" readOnly>
|
||||
<InputGroup endElement={ <CopyToClipboard text={ value }/> } endOffset="40px">
|
||||
<Input value={ value } fontWeight="500" overflow="hidden" textOverflow="ellipsis"/>
|
||||
</InputGroup>
|
||||
</Field>
|
||||
</Skeleton>
|
||||
</FormControl>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default chakra(RewardsReadOnlyInputWithCopy);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { DialogHeader } from '@chakra-ui/react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
|
||||
import { useRewardsContext } from 'lib/contexts/rewards';
|
||||
import useWallet from 'lib/web3/useWallet';
|
||||
import { DialogBody, DialogContent, DialogRoot } from 'toolkit/chakra/dialog';
|
||||
import { DialogBody, DialogContent, DialogRoot, DialogHeader } from 'toolkit/chakra/dialog';
|
||||
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
|
||||
import AuthModal from 'ui/snippets/auth/AuthModal';
|
||||
|
||||
@@ -65,7 +64,7 @@ const RewardsLoginModal = () => {
|
||||
<DialogRoot
|
||||
open={ isLoginModalOpen && !isWalletModalOpen && !authModal.open }
|
||||
onOpenChange={ handleOpenChange }
|
||||
size={{ base: 'full', md: isLoginStep ? 'sm' : 'md' }}
|
||||
size={{ base: 'full', lg: isLoginStep ? 'sm' : 'md' }}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Text, Box, Flex } from '@chakra-ui/react';
|
||||
import { Text, Box, Flex, Center } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
import { route } from 'nextjs-routes';
|
||||
@@ -6,7 +6,6 @@ import { route } from 'nextjs-routes';
|
||||
import { useRewardsContext } from 'lib/contexts/rewards';
|
||||
import { Button } from 'toolkit/chakra/button';
|
||||
import { Skeleton } from 'toolkit/chakra/skeleton';
|
||||
import { Tag } from 'toolkit/chakra/tag';
|
||||
import IconSvg from 'ui/shared/IconSvg';
|
||||
|
||||
import MeritsIcon from '../../MeritsIcon';
|
||||
@@ -16,7 +15,6 @@ type Props = {
|
||||
isReferral: boolean;
|
||||
};
|
||||
|
||||
// TODO @tom2drum fix this component
|
||||
const CongratsStepContent = ({ isReferral }: Props) => {
|
||||
const { referralsQuery, rewardsConfigQuery } = useRewardsContext();
|
||||
|
||||
@@ -78,9 +76,14 @@ const CongratsStepContent = ({ isReferral }: Props) => {
|
||||
</Flex>
|
||||
<Flex flexDirection="column" alignItems="flex-start" px={ 3 } mb={ 8 }>
|
||||
<Flex alignItems="center" gap={ 2 }>
|
||||
<Tag colorScheme="blue" w={ 8 } h={ 8 } display="flex" alignItems="center" justifyContent="center" borderRadius="8px">
|
||||
<Center
|
||||
boxSize={ 8 }
|
||||
borderRadius="8px"
|
||||
color={{ _light: 'blue.500', _dark: 'blue.100' }}
|
||||
bgColor={{ _light: 'blue.50', _dark: 'blue.800' }}
|
||||
>
|
||||
<IconSvg name="profile" boxSize={ 5 }/>
|
||||
</Tag>
|
||||
</Center>
|
||||
<Text fontSize="lg" fontWeight="500">
|
||||
Referral program
|
||||
</Text>
|
||||
@@ -95,27 +98,31 @@ const CongratsStepContent = ({ isReferral }: Props) => {
|
||||
</Skeleton>
|
||||
{ ' ' }bonus on all Merits earned by your referrals
|
||||
</Text>
|
||||
{ /* <RewardsReadOnlyInputWithCopy
|
||||
<RewardsReadOnlyInputWithCopy
|
||||
label="Referral link"
|
||||
value={ refLink }
|
||||
isLoading={ referralsQuery.isLoading }
|
||||
mt={ 3 }
|
||||
/> */ }
|
||||
<Button
|
||||
as="a"
|
||||
_target="_blank"
|
||||
mt={ 6 }
|
||||
loading={ referralsQuery.isLoading }
|
||||
href={ `https://x.com/intent/tweet?text=${ encodeURIComponent(shareText) }` }
|
||||
>
|
||||
Share on <IconSvg name="social/twitter" boxSize={ 6 } ml={ 1 }/>
|
||||
</Button>
|
||||
w="100%"
|
||||
/>
|
||||
<Skeleton loading={ referralsQuery.isLoading } mt={ 6 }>
|
||||
<Button asChild>
|
||||
<a href={ `https://x.com/intent/tweet?text=${ encodeURIComponent(shareText) }` } target="_blank">
|
||||
Share on <IconSvg name="social/twitter" boxSize={ 6 } ml={ 1 }/>
|
||||
</a>
|
||||
</Button>
|
||||
</Skeleton>
|
||||
</Flex>
|
||||
<Flex flexDirection="column" alignItems="flex-start" px={ 3 }>
|
||||
<Flex alignItems="center" gap={ 2 }>
|
||||
<Tag colorScheme="blue" w={ 8 } h={ 8 } display="flex" alignItems="center" justifyContent="center" borderRadius="8px">
|
||||
<Center
|
||||
boxSize={ 8 }
|
||||
borderRadius="8px"
|
||||
color={{ _light: 'blue.500', _dark: 'blue.100' }}
|
||||
bgColor={{ _light: 'blue.50', _dark: 'blue.800' }}
|
||||
>
|
||||
<IconSvg name="stats" boxSize={ 6 }/>
|
||||
</Tag>
|
||||
</Center>
|
||||
<Text fontSize="lg" fontWeight="500">
|
||||
Dashboard
|
||||
</Text>
|
||||
@@ -124,8 +131,8 @@ const CongratsStepContent = ({ isReferral }: Props) => {
|
||||
Explore your current Merits balance, find activities to boost your Merits,
|
||||
and view your capybara NFT badge collection on the dashboard
|
||||
</Text>
|
||||
<Button mt={ 3 } as="a" href={ route({ pathname: '/account/rewards' }) }>
|
||||
Open
|
||||
<Button asChild mt={ 3 }>
|
||||
<a href={ route({ pathname: '/account/rewards' }) }>Open</a>
|
||||
</Button>
|
||||
</Flex>
|
||||
</>
|
||||
|
||||
@@ -136,7 +136,7 @@ const LoginStepContent = ({ goNext, closeModal, openAuthModal }: Props) => {
|
||||
size="xl"
|
||||
mt={ 3 }
|
||||
invalid={ refCodeError }
|
||||
helperText={ !refCodeError ? 'The code should be in format XXXXXX' : undefined }
|
||||
helperText="The code should be in format XXXXXX"
|
||||
errorText={ refCodeError ? 'Incorrect code or format' : undefined }
|
||||
>
|
||||
<Input
|
||||
@@ -164,7 +164,7 @@ const LoginStepContent = ({ goNext, closeModal, openAuthModal }: Props) => {
|
||||
>
|
||||
{ buttonText }
|
||||
</Button>
|
||||
<Text fontSize="sm" color={{ _light: 'blackAlpha.500', _dark: 'whiteAlpha.500' }} textAlign="center">
|
||||
<Text textStyle="sm" color="text_secondary" textAlign="center">
|
||||
Already registered for Blockscout Merits on another network or chain? Connect the same wallet here.
|
||||
</Text>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user