fix rewards modal

This commit is contained in:
tom
2025-01-16 13:46:15 +01:00
parent 211bfeda3b
commit 276f53cae9
14 changed files with 144 additions and 93 deletions
-1
View File
@@ -43,7 +43,6 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>(
{ endElement }
{ closable && (
<CloseButton
size="sm"
pos="relative"
top="-2"
insetEnd="-2"
+47 -15
View File
@@ -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>
);
}
+16 -13
View File
@@ -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';
+2 -1
View File
@@ -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',
},
},
},
+1
View File
@@ -22,6 +22,7 @@ export const recipe = defineSlotRecipe({
fontWeight: '500',
gap: '0',
userSelect: 'none',
zIndex: '1',
_disabled: {
opacity: '0.5',
},
+1 -1
View File
@@ -6,7 +6,7 @@ export const recipe = defineRecipe({
variants: {
loading: {
'true': {
borderRadius: 'sm',
borderRadius: 'base',
boxShadow: 'none',
backgroundClip: 'padding-box',
cursor: 'default',
+11
View File
@@ -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
View File
@@ -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"/>
+5 -5
View File
@@ -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>
);
};
-3
View File
@@ -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 }
+14 -28
View File
@@ -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);
+2 -3
View File
@@ -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>
+27 -20
View File
@@ -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>
</>
+2 -2
View File
@@ -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>
</>