Skip to content

Commit

Permalink
feat: profile modal
Browse files Browse the repository at this point in the history
  • Loading branch information
swkatmask committed May 17, 2024
1 parent db82ca5 commit 912b5b0
Show file tree
Hide file tree
Showing 17 changed files with 280 additions and 87 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ module.exports = {
rules: {
'prettier/prettier': 'off',
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'@typescript-eslint/no-unused-vars': 'warn',
},
}
26 changes: 13 additions & 13 deletions src/assets/social-media/twitter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/twitter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/Footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const Footer: FC = () => {
gap={3}
>
{SocialMedias.map((media) => (
<li>
<li key={media.href}>
<Center
as="a"
href={media.href}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ProgressiveText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const ProgressiveText = memo(
) {
if (loading) {
return (
<Text {...props}>
<Text as="div" {...props}>
<Skeleton height={skeletonHeight ?? '1.5em'} width={skeletonWidth ?? '100%'} />
</Text>
)
Expand Down
34 changes: 34 additions & 0 deletions src/components/TwitterAvatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Box, BoxProps, Center, Icon, Image } from '@chakra-ui/react'

import Twitter from '../assets/twitter.svg?react'

interface Props extends BoxProps {
omitBadge?: boolean
size?: number
src?: string
}

export function TwitterAvatar({ size, src, omitBadge, ...props }: Props) {
return (
<Box pos="relative" width={size} height={size} {...props}>
<Image
width={size}
height={size}
border="2px solid"
borderColor="gradient.purple"
borderRadius="50%"
objectFit="cover"
draggable={false}
userSelect="none"
src={src}
fallbackSrc={new URL('../assets/default-avatar.svg', import.meta.url).href}
alt="user name"
/>
{omitBadge ? null : (
<Center width="14px" height="14px" pos="absolute" bg="white" rounded={14} right={0} bottom="-4px" color="black">
<Icon as={Twitter} width="10px" height="10px" />
</Center>
)}
</Box>
)
}
7 changes: 3 additions & 4 deletions src/components/UserStatus/StakedMask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,9 @@ export function StakedMask(props: BoxProps) {
{chainData ? chainData[0].toLocaleString() : formatNumber(userInfo?.amount)}
</ProgressiveText>
<HStack alignItems="center" my="auto">
<ProgressiveText
loading={loadingUserInfo}
skeletonWidth="50px"
>{t`+${userInfo?.score_per_hour} Points/h`}</ProgressiveText>
<ProgressiveText as="div" loading={loadingUserInfo} skeletonWidth="50px">
{t`+${userInfo?.score_per_hour} Points/h`}
</ProgressiveText>
<Tooltip label={t`1 staked MASK will generate 1 point per hour.`} placement="top" hasArrow>
<Box as="span" w={6} h={6}>
<Icon as={Question} w="initial" h="initial" />
Expand Down
28 changes: 15 additions & 13 deletions src/components/UserStatus/UserTotalPoint.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Image, VStack, Text, Box, StackProps } from '@chakra-ui/react'
import { Box, StackProps, Text, VStack } from '@chakra-ui/react'
import { t } from '@lingui/macro'
import { UserInfo } from '../../types/api'
import { TextOverflowTooltip } from '../TextOverflowTooltip'
import { TwitterAvatar } from '../TwitterAvatar'
import { profileModal } from '../../modals'

interface Props extends StackProps {
user: UserInfo
Expand All @@ -11,20 +13,16 @@ export function UserTotalPoints({ user, ...props }: Props) {
return (
<VStack overflow="hidden" {...props}>
<Box flexDir="row" w="100%" pl="100px" pos="relative">
<Image
position="absolute"
<TwitterAvatar
size={100}
pos="absolute"
cursor="pointer"
left={0}
width={100}
height={100}
border="2px solid"
borderColor="gradient.purple"
borderRadius="50%"
objectFit="cover"
draggable={false}
userSelect="none"
src={user.twitter_image}
fallbackSrc={new URL('../../assets/default-avatar.svg', import.meta.url).href}
alt="user name"
omitBadge
onClick={() => {
profileModal.show()
}}
/>
<TextOverflowTooltip label={user.twitter_display_name} hasArrow placement="top">
<Text
Expand All @@ -34,6 +32,10 @@ export function UserTotalPoints({ user, ...props }: Props) {
textOverflow="ellipsis"
whiteSpace="nowrap"
overflow="hidden"
cursor="pointer"
onClick={() => {
profileModal.show()
}}
>
{user.twitter_display_name || 'N/A'}
</Text>
Expand Down
2 changes: 1 addition & 1 deletion src/constants/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ExternalEnvSchema = z.object({
REDDIT_URL: z.string().default('https://www.reddit.com/r/MaskNetwork'),
GITHUB_URL: z.string().default('https://github.com/DimensionDev'),
YOUTUBE_URL: z.string().default('https://www.youtube.com/c/MaskNetwork'),
MEDIUM_URL: z.string().default('https://www.youtube.com/c/MaskNetwork'),
MEDIUM_URL: z.string().default('https://masknetwork.medium.com/'),
})

export const env = {
Expand Down
35 changes: 35 additions & 0 deletions src/hooks/useUpdateUserInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { fetchJSON } from '../helpers/fetchJSON'
import { FIREFLY_API_ROOT } from '../constants/api'
import urlcat from 'urlcat'
import { UpdateUserInfoParams, UpdateUserInfoResponse } from '../types/api'
import { useAccount, useSignMessage } from 'wagmi'

// Any message is ok.
const message = 'Update Profile'
export function useUpdateUserInfo() {
const { signMessageAsync } = useSignMessage()
const account = useAccount()
const queryClient = useQueryClient()
return useMutation({
mutationKey: ['update-user-info', account.address],
mutationFn: async (params: Pick<UpdateUserInfoParams, 'show_avatar' | 'display_username'>) => {
if (!account.address) return
const signed = await signMessageAsync({ message })
const url = urlcat(FIREFLY_API_ROOT, '/v1/mask_stake/twitter/update')
const payload: UpdateUserInfoParams = {
...params,
original_message: message,
signature_message: signed.slice(2), // omit 0x
wallet_address: account.address,
}
return fetchJSON<UpdateUserInfoResponse>(url, {
method: 'POST',
body: JSON.stringify(payload),
})
},
onSuccess() {
queryClient.refetchQueries({ queryKey: ['user-info'] })
},
})
}
4 changes: 2 additions & 2 deletions src/modals/BaseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { ReactNode } from 'react'

interface Props extends ModalProps {
title: ReactNode
width: ModalContentProps['width']
height: ModalContentProps['height']
width?: ModalContentProps['width']
height?: ModalContentProps['height']
}
export function BaseModal({ title, width, ...rest }: Props) {
const isMobile = useBreakpointValue({ base: true, md: false })
Expand Down
80 changes: 80 additions & 0 deletions src/modals/ProfileModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Button, Flex, FormControl, FormLabel, Input, ModalProps, Switch, useToast } from '@chakra-ui/react'
import { t } from '@lingui/macro'
import { useId, useState } from 'react'
import { GradientButton } from '../components/GradientButton'
import { TwitterAvatar } from '../components/TwitterAvatar'
import { useUpdateUserInfo } from '../hooks/useUpdateUserInfo'
import { useUserInfo } from '../hooks/useUserInfo'
import { BaseModal } from './BaseModal'

export function ProfileModal(props: ModalProps) {
const { data: userInfo } = useUserInfo()
const switchId = useId()
const [username = userInfo?.twitter_display_name, setUsername] = useState<string>()
const [showAvatar = userInfo?.twitter_show_image, setShowAvatar] = useState<boolean>()
const updateUserInfo = useUpdateUserInfo()
const toast = useToast()
return (
<BaseModal title={t`Profile`} width={450} height={468} {...props}>
<FormControl>
<FormLabel mb={3}>{t`Profile Name`}</FormLabel>
<Input
size="lg"
color="neutrals.4"
rounded={12}
fontSize={14}
fontFamily="input"
fontWeight={700}
type="text"
value={username}
onChange={(e) => {
setUsername(e.currentTarget.value)
}}
/>
</FormControl>
<FormControl mt={6}>
<FormLabel mb={3}>{t`Avatar`}</FormLabel>
<TwitterAvatar size={90} src={userInfo?.twitter_image} omitBadge />
</FormControl>
<FormControl mt={6} display="flex" flexDir="row" alignItems="center">
<Switch
size="lg"
isChecked={showAvatar}
id={switchId}
onChange={(e) => {
setShowAvatar(e.currentTarget.checked)
}}
/>
<FormLabel htmlFor={switchId} mb={0} ml={3} fontSize={12} color="neutrals.1">{t`Show Avatar`}</FormLabel>
</FormControl>
<Flex justifyContent="space-between" gap={3} mt={6}>
<Button rounded={24} flexGrow={1}>{t`Cancel`}</Button>
<GradientButton
flexGrow={1}
disabled={!username}
onClick={async () => {
if (!username) {
toast({
status: 'error',
title: t`Profile Name is required`,
})
return
}
const res = await updateUserInfo.mutateAsync({
display_username: username,
show_avatar: !!showAvatar,
})
if (res?.code !== 0 && res?.reason) {
toast({
status: 'error',
title: res.reason,
})
return
}
props.onClose()
}}
>{t`Confirm`}</GradientButton>
</Flex>
</BaseModal>
)
}
Loading

0 comments on commit 912b5b0

Please sign in to comment.