Commit 079b6d5c authored by Max Alekseenko's avatar Max Alekseenko

implement referral logic

parent 7c58695e
......@@ -92,6 +92,7 @@ import type {
import type { RawTracesResponse } from 'types/api/rawTrace';
import type {
RewardsConfigResponse,
RewardsCheckRefCodeResponse,
RewardsNonceResponse,
RewardsCheckUserResponse,
RewardsLoginResponse,
......@@ -335,6 +336,12 @@ export const RESOURCES = {
endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint,
basePath: getFeaturePayload(config.features.rewards)?.api.basePath,
},
rewards_check_ref_code: {
path: '/api/v1/auth/code/:code',
pathParams: [ 'code' as const ],
endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint,
basePath: getFeaturePayload(config.features.rewards)?.api.basePath,
},
rewards_nonce: {
path: '/api/v1/auth/nonce',
endpoint: getFeaturePayload(config.features.rewards)?.api.endpoint,
......@@ -1228,6 +1235,7 @@ Q extends 'address_mud_record' ? AddressMudRecord :
Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'withdrawals_counters' ? WithdrawalsCounters :
Q extends 'rewards_config' ? RewardsConfigResponse :
Q extends 'rewards_check_ref_code' ? RewardsCheckRefCodeResponse :
Q extends 'rewards_nonce' ? RewardsNonceResponse :
Q extends 'rewards_check_user' ? RewardsCheckUserResponse :
Q extends 'rewards_login' ? RewardsLoginResponse :
......
......@@ -7,6 +7,10 @@ export type RewardsConfigResponse = {
};
};
export type RewardsCheckRefCodeResponse = {
valid: boolean;
};
export type RewardsNonceResponse = {
nonce: string;
};
......
import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalCloseButton, ModalBody, useBoolean } from '@chakra-ui/react';
import React, { useEffect } from 'react';
import React, { useCallback, useEffect } from 'react';
import { useRewardsContext } from 'lib/contexts/rewards';
import useIsMobile from 'lib/hooks/useIsMobile';
......@@ -14,12 +14,21 @@ const RewardsLoginModal = () => {
const { isLoginModalOpen, closeLoginModal } = useRewardsContext();
const [ isLoginStep, setIsLoginStep ] = useBoolean(true);
const [ isReferral, setIsReferral ] = useBoolean(false);
useEffect(() => {
if (!isLoginModalOpen) {
setIsLoginStep.on();
setIsReferral.off();
}
}, [ isLoginModalOpen, setIsLoginStep ]);
}, [ isLoginModalOpen, setIsLoginStep, setIsReferral ]);
const goNext = useCallback((isReferral: boolean) => {
if (isReferral) {
setIsReferral.on();
}
setIsLoginStep.off();
}, [ setIsLoginStep, setIsReferral ]);
return (
<Modal
......@@ -36,8 +45,8 @@ const RewardsLoginModal = () => {
<ModalCloseButton top={ 6 } right={ 6 }/>
<ModalBody mb={ 0 }>
{ isLoginStep ?
<LoginStepContent goNext={ setIsLoginStep.off } closeModal={ closeLoginModal }/> :
<CongratsStepContent/>
<LoginStepContent goNext={ goNext } closeModal={ closeLoginModal }/> :
<CongratsStepContent isReferral={ isReferral }/>
}
</ModalBody>
</ModalContent>
......
......@@ -10,7 +10,11 @@ import CopyField from '../CopyField';
import useReferrals from '../useReferrals';
import useRewardsConfig from '../useRewardsConfig';
const CongratsStepContent = () => {
type Props = {
isReferral: boolean;
}
const CongratsStepContent = ({ isReferral }: Props) => {
const referralsQuery = useReferrals();
const rewardsConfigQuery = useRewardsConfig();
......@@ -25,12 +29,41 @@ const CongratsStepContent = () => {
mb={ 8 }
>
<Flex alignItems="center" pl={ 2 } mb={ 4 }>
<IconSvg name="merits_colored" boxSize={ 16 }/>
<IconSvg name="merits_colored" boxSize="72px" m={ -2 }/>
<Skeleton isLoaded={ !rewardsConfigQuery.isLoading }>
<Text fontSize="30px" fontWeight="700" color="blue.700" ml={ 1 }>
+{ rewardsConfigQuery.data?.rewards[ isReferral ? 'registration_with_referral' : 'registration' ] }
</Text>
</Skeleton>
{ isReferral && (
<Flex alignItems="center" h="56px">
<Box w="1px" h="full" bgColor="whiteAlpha.800" mx={ 8 }/>
<Flex flexDirection="column" justifyContent="space-between">
{ [
{
title: 'Registration',
value: rewardsConfigQuery.data?.rewards.registration,
},
{
title: 'Referral program',
value: Number(rewardsConfigQuery.data?.rewards.registration_with_referral) - Number(rewardsConfigQuery.data?.rewards.registration),
},
].map(({ title, value }) => (
<Flex key={ title } alignItems="center">
<IconSvg name="merits_colored" boxSize={ 8 }/>
<Skeleton isLoaded={ !rewardsConfigQuery.isLoading }>
<Text fontSize="30px" fontWeight="700" color="blue.700">
+{ rewardsConfigQuery.data?.rewards.registration }
<Text fontSize="sm" fontWeight="700" color="blue.700">
+{ value }
</Text>
</Skeleton>
<Text fontSize="sm" color="blue.700" ml={ 2 }>
{ title }
</Text>
</Flex>
)) }
</Flex>
</Flex>
) }
</Flex>
<Flex
flexDirection="column"
......
import { Text, Button, useColorModeValue, Image, Box, Flex, Switch, useBoolean, Input, FormControl } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import type { ChangeEvent } from 'react';
import React, { useCallback, useState, useEffect } from 'react';
import InputPlaceholder from 'ui/shared/InputPlaceholder';
import LinkExternal from 'ui/shared/links/LinkExternal';
......@@ -9,7 +10,7 @@ import useWallet from 'ui/snippets/walletMenu/useWallet';
import useLogin from '../useLogin';
type Props = {
goNext: () => void;
goNext: (isReferral: boolean) => void;
closeModal: () => void;
};
......@@ -18,22 +19,37 @@ const LoginStepContent = ({ goNext, closeModal }: Props) => {
const { connect, isWalletConnected } = useWallet({ source: 'Merits' });
const [ isSwitchChecked, setIsSwitchChecked ] = useBoolean(false);
const [ isLoading, setIsLoading ] = useBoolean(false);
const [ refCode, setRefCode ] = useState('');
const [ refCodeError, setRefCodeError ] = useBoolean(false);
const dividerColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const login = useLogin();
const handleRefCodeChange = React.useCallback((event: ChangeEvent<HTMLInputElement>) => {
setRefCode(event.target.value);
}, []);
const handleLogin = useCallback(async() => {
try {
setRefCodeError.off();
setIsLoading.on();
const { isNewUser } = await login();
const { isNewUser, invalidRefCodeError } = await login(refCode);
if (invalidRefCodeError) {
setRefCodeError.on();
} else {
if (isNewUser) {
goNext();
goNext(Boolean(refCode));
} else {
closeModal();
router.push({ pathname: '/account/rewards' }, undefined, { shallow: true });
}
}
} catch (error) {}
setIsLoading.off();
}, [ login, goNext, setIsLoading, router, closeModal ]);
}, [ login, goNext, setIsLoading, router, closeModal, refCode, setRefCodeError ]);
useEffect(() => {
setRefCodeError.off();
}, [ refCode ]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<>
......@@ -59,7 +75,13 @@ const LoginStepContent = ({ goNext, closeModal }: Props) => {
</Flex>
{ isSwitchChecked && (
<FormControl variant="floating" id="referral-code" mt={ 3 }>
<Input fontWeight="500" borderRadius="12px !important"/>
<Input
fontWeight="500"
borderRadius="12px !important"
value={ refCode }
onChange={ handleRefCodeChange }
isInvalid={ refCodeError }
/>
<InputPlaceholder text="Code"/>
</FormControl>
) }
......
import { useCallback } from 'react';
import { useAccount, useSignMessage } from 'wagmi';
import type { RewardsNonceResponse, RewardsCheckUserResponse, RewardsLoginResponse } from 'types/api/rewards';
import type {
RewardsNonceResponse, RewardsCheckUserResponse,
RewardsLoginResponse, RewardsCheckRefCodeResponse,
} from 'types/api/rewards';
import config from 'configs/app';
import type { ResourceError } from 'lib/api/resources';
......@@ -34,16 +37,22 @@ export default function useLogin() {
const { address } = useAccount();
const { signMessageAsync } = useSignMessage();
return useCallback(async() => {
return useCallback(async(refCode: string) => {
try {
const [ nonceResponse, userResponse ] = await Promise.all([
const [ nonceResponse, userResponse, checkCodeResponse ] = await Promise.all([
apiFetch<'rewards_nonce', RewardsNonceResponse>('rewards_nonce'),
apiFetch<'rewards_check_user', RewardsCheckUserResponse>('rewards_check_user', { pathParams: { address } }),
refCode ?
apiFetch<'rewards_check_ref_code', RewardsCheckRefCodeResponse>('rewards_check_ref_code', { pathParams: { code: refCode } }) :
Promise.resolve({ valid: true }),
]);
if (!address || !('nonce' in nonceResponse) || !('exists' in userResponse)) {
if (!address || !('nonce' in nonceResponse) || !('exists' in userResponse) || !('valid' in checkCodeResponse)) {
throw new Error();
}
const message = getMessageToSign(address, nonceResponse.nonce, userResponse.exists);
if (!checkCodeResponse.valid) {
return { invalidRefCodeError: true };
}
const message = getMessageToSign(address, nonceResponse.nonce, userResponse.exists, refCode);
const signature = await signMessageAsync({ message });
const loginResponse = await apiFetch<'rewards_login', RewardsLoginResponse>('rewards_login', {
fetchParams: {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment