Commit 8f5c41a4 authored by Max Alekseenko's avatar Max Alekseenko

add check if specific address can rate

parent a630b361
...@@ -53,6 +53,7 @@ NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6u ...@@ -53,6 +53,7 @@ NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6u
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json NEXT_PUBLIC_MARKETPLACE_SECURITY_REPORTS_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-security-reports/default.json
NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=gearbox-protocol NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=gearbox-protocol
NEXT_PUBLIC_MARKETPLACE_RATING_AIRTABLE_BASE_ID=app4iqrpmtJ5NrbjP
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}] NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'},{'text':'Get gas','icon':'gas','dappId':'smol-refuel'}]
NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'} NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'}
......
...@@ -24,6 +24,7 @@ interface Props extends MarketplaceAppWithSecurityReport { ...@@ -24,6 +24,7 @@ interface Props extends MarketplaceAppWithSecurityReport {
rateApp: (appId: string, recordId: string | undefined, rating: number) => void; rateApp: (appId: string, recordId: string | undefined, rating: number) => void;
isSendingRating: boolean; isSendingRating: boolean;
isRatingLoading: boolean; isRatingLoading: boolean;
canRate: boolean | undefined;
} }
const MarketplaceAppCard = ({ const MarketplaceAppCard = ({
...@@ -50,6 +51,7 @@ const MarketplaceAppCard = ({ ...@@ -50,6 +51,7 @@ const MarketplaceAppCard = ({
rateApp, rateApp,
isSendingRating, isSendingRating,
isRatingLoading, isRatingLoading,
canRate,
}: Props) => { }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const categoriesLabel = categories.join(', '); const categoriesLabel = categories.join(', ');
...@@ -177,7 +179,7 @@ const MarketplaceAppCard = ({ ...@@ -177,7 +179,7 @@ const MarketplaceAppCard = ({
rate={ rateApp } rate={ rateApp }
isSending={ isSendingRating } isSending={ isSendingRating }
isLoading={ isRatingLoading } isLoading={ isRatingLoading }
canRate={ Math.random() > 0.5 } canRate={ canRate }
/> />
<IconButton <IconButton
aria-label="Mark as favorite" aria-label="Mark as favorite"
......
...@@ -27,6 +27,7 @@ type Props = { ...@@ -27,6 +27,7 @@ type Props = {
rateApp: (appId: string, recordId: string | undefined, rating: number) => void; rateApp: (appId: string, recordId: string | undefined, rating: number) => void;
isSendingRating: boolean; isSendingRating: boolean;
isRatingLoading: boolean; isRatingLoading: boolean;
canRate: boolean | undefined;
} }
const MarketplaceAppModal = ({ const MarketplaceAppModal = ({
...@@ -39,6 +40,7 @@ const MarketplaceAppModal = ({ ...@@ -39,6 +40,7 @@ const MarketplaceAppModal = ({
rateApp, rateApp,
isSendingRating, isSendingRating,
isRatingLoading, isRatingLoading,
canRate,
}: Props) => { }: Props) => {
const { const {
id, id,
...@@ -172,7 +174,7 @@ const MarketplaceAppModal = ({ ...@@ -172,7 +174,7 @@ const MarketplaceAppModal = ({
isSending={ isSendingRating } isSending={ isSendingRating }
isLoading={ isRatingLoading } isLoading={ isRatingLoading }
fullView fullView
canRate={ Math.random() > 0.5 } canRate={ canRate }
/> />
</Box> </Box>
......
...@@ -22,11 +22,12 @@ type Props = { ...@@ -22,11 +22,12 @@ type Props = {
rateApp: (appId: string, recordId: string, rating: number) => void; rateApp: (appId: string, recordId: string, rating: number) => void;
isSendingRating: boolean; isSendingRating: boolean;
isRatingLoading: boolean; isRatingLoading: boolean;
canRate: boolean | undefined;
} }
const MarketplaceList = ({ const MarketplaceList = ({
apps, showAppInfo, favoriteApps, onFavoriteClick, isLoading, selectedCategoryId, apps, showAppInfo, favoriteApps, onFavoriteClick, isLoading, selectedCategoryId,
onAppClick, showContractList, userRatings, rateApp, isSendingRating, isRatingLoading, onAppClick, showContractList, userRatings, rateApp, isSendingRating, isRatingLoading, canRate,
}: Props) => { }: Props) => {
const handleInfoClick = useCallback((id: string) => { const handleInfoClick = useCallback((id: string) => {
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id, Source: 'Discovery view' }); mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, { Type: 'More button', Info: id, Source: 'Discovery view' });
...@@ -71,6 +72,7 @@ const MarketplaceList = ({ ...@@ -71,6 +72,7 @@ const MarketplaceList = ({
rateApp={ rateApp } rateApp={ rateApp }
isSendingRating={ isSendingRating } isSendingRating={ isSendingRating }
isRatingLoading={ isRatingLoading } isRatingLoading={ isRatingLoading }
canRate={ canRate }
/> />
)) } )) }
</Grid> </Grid>
......
...@@ -14,7 +14,7 @@ type Props = { ...@@ -14,7 +14,7 @@ type Props = {
isSending?: boolean; isSending?: boolean;
isLoading?: boolean; isLoading?: boolean;
fullView?: boolean; fullView?: boolean;
canRate: boolean; canRate: boolean | undefined;
}; };
const Rating = ({ const Rating = ({
......
import { Button, chakra, useColorModeValue, Tooltip } from '@chakra-ui/react'; import { Button, chakra, useColorModeValue, Tooltip } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { useAccount } from 'wagmi';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing'; import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -10,11 +9,11 @@ type Props = { ...@@ -10,11 +9,11 @@ type Props = {
fullView?: boolean; fullView?: boolean;
isActive: boolean; isActive: boolean;
onClick: () => void; onClick: () => void;
canRate: boolean; canRate: boolean | undefined;
}; };
const getTooltipText = (isWalletConnected: boolean, canRate: boolean) => { const getTooltipText = (canRate: boolean | undefined) => {
if (!isWalletConnected) { if (canRate === undefined) {
return <>You need a connected wallet to leave your rating.<br/>Link your wallet to Blockscout first</>; return <>You need a connected wallet to leave your rating.<br/>Link your wallet to Blockscout first</>;
} }
if (!canRate) { if (!canRate) {
...@@ -29,11 +28,10 @@ const TriggerButton = ( ...@@ -29,11 +28,10 @@ const TriggerButton = (
) => { ) => {
const textColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800'); const textColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800');
const onFocusCapture = usePreventFocusAfterModalClosing(); const onFocusCapture = usePreventFocusAfterModalClosing();
const { address } = useAccount();
return ( return (
<Tooltip <Tooltip
label={ getTooltipText(Boolean(address), canRate) } label={ getTooltipText(canRate) }
openDelay={ 100 } openDelay={ 100 }
textAlign="center" textAlign="center"
> >
...@@ -43,7 +41,7 @@ const TriggerButton = ( ...@@ -43,7 +41,7 @@ const TriggerButton = (
variant="outline" variant="outline"
border={ 0 } border={ 0 }
p={ 0 } p={ 0 }
onClick={ canRate && Boolean(address) ? onClick : undefined } onClick={ canRate ? onClick : undefined }
fontSize={ fullView ? 'md' : 'sm' } fontSize={ fullView ? 'md' : 'sm' }
fontWeight={ fullView ? '400' : '500' } fontWeight={ fullView ? '400' : '500' }
lineHeight="21px" lineHeight="21px"
......
...@@ -5,6 +5,8 @@ import { useAccount } from 'wagmi'; ...@@ -5,6 +5,8 @@ import { useAccount } from 'wagmi';
import type { UserRatings, AppRatings } from 'types/client/marketplace'; import type { UserRatings, AppRatings } from 'types/client/marketplace';
import config from 'configs/app'; import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery';
import { ADDRESS_COUNTERS } from 'stubs/address';
const feature = config.features.marketplace; const feature = config.features.marketplace;
const base = (feature.isEnabled && feature.rating) ? const base = (feature.isEnabled && feature.rating) ?
...@@ -14,10 +16,21 @@ const base = (feature.isEnabled && feature.rating) ? ...@@ -14,10 +16,21 @@ const base = (feature.isEnabled && feature.rating) ?
export default function useRatings() { export default function useRatings() {
const { address } = useAccount(); const { address } = useAccount();
const addressCountersQuery = useApiQuery<'address_counters', { status: number }>('address_counters', {
pathParams: { hash: address },
queryOptions: {
enabled: Boolean(address),
placeholderData: ADDRESS_COUNTERS,
refetchOnMount: false,
},
});
const [ ratings, setRatings ] = useState<AppRatings>({}); const [ ratings, setRatings ] = useState<AppRatings>({});
const [ userRatings, setUserRatings ] = useState<UserRatings>({}); const [ userRatings, setUserRatings ] = useState<UserRatings>({});
const [ isLoading, setIsLoading ] = useState<boolean>(false); const [ isRatingLoading, setIsRatingLoading ] = useState<boolean>(false);
const [ isUserRatingLoading, setIsUserRatingLoading ] = useState<boolean>(false);
const [ isSending, setIsSending ] = useState<boolean>(false); const [ isSending, setIsSending ] = useState<boolean>(false);
const [ canRate, setCanRate ] = useState<boolean | undefined>(undefined);
const fetchRatings = useCallback(async() => { const fetchRatings = useCallback(async() => {
if (!base) { if (!base) {
...@@ -37,15 +50,16 @@ export default function useRatings() { ...@@ -37,15 +50,16 @@ export default function useRatings() {
useEffect(() => { useEffect(() => {
async function fetch() { async function fetch() {
setIsLoading(true); setIsRatingLoading(true);
await fetchRatings(); await fetchRatings();
setIsLoading(false); setIsRatingLoading(false);
} }
fetch(); fetch();
}, [ fetchRatings ]); }, [ fetchRatings ]);
useEffect(() => { useEffect(() => {
async function fetchUserRatings() { async function fetchUserRatings() {
setIsUserRatingLoading(true);
let userRatings = {} as UserRatings; let userRatings = {} as UserRatings;
if (address && base) { if (address && base) {
const data = await base('users_ratings').select({ const data = await base('users_ratings').select({
...@@ -62,10 +76,17 @@ export default function useRatings() { ...@@ -62,10 +76,17 @@ export default function useRatings() {
}, {}); }, {});
} }
setUserRatings(userRatings); setUserRatings(userRatings);
setIsUserRatingLoading(false);
} }
fetchUserRatings(); fetchUserRatings();
}, [ address ]); }, [ address ]);
useEffect(() => {
const { isPlaceholderData, data } = addressCountersQuery;
const canRate = address && !isPlaceholderData && Number(data?.transactions_count) >= 10;
setCanRate(canRate);
}, [ address, addressCountersQuery ]);
const rateApp = useCallback(async(appId: string, recordId: string | undefined, rating: number) => { const rateApp = useCallback(async(appId: string, recordId: string | undefined, rating: number) => {
if (!address || !base || !recordId) { if (!address || !base || !recordId) {
return; return;
...@@ -92,6 +113,8 @@ export default function useRatings() { ...@@ -92,6 +113,8 @@ export default function useRatings() {
userRatings, userRatings,
rateApp, rateApp,
isSendingRating: isSending, isSendingRating: isSending,
isRatingLoading: isLoading, isRatingLoading,
isUserRatingLoading,
canRate,
}; };
} }
...@@ -87,7 +87,7 @@ export default function useMarketplace() { ...@@ -87,7 +87,7 @@ export default function useMarketplace() {
const { const {
isPlaceholderData, isError, error, data, displayedApps, setSorting, isPlaceholderData, isError, error, data, displayedApps, setSorting,
userRatings, rateApp, isSendingRating, isRatingLoading, userRatings, rateApp, isSendingRating, isRatingLoading, canRate,
} = useMarketplaceApps(debouncedFilterQuery, selectedCategoryId, favoriteApps, isFavoriteAppsLoaded); } = useMarketplaceApps(debouncedFilterQuery, selectedCategoryId, favoriteApps, isFavoriteAppsLoaded);
const { const {
isPlaceholderData: isCategoriesPlaceholderData, data: categories, isPlaceholderData: isCategoriesPlaceholderData, data: categories,
...@@ -156,6 +156,7 @@ export default function useMarketplace() { ...@@ -156,6 +156,7 @@ export default function useMarketplace() {
rateApp, rateApp,
isSendingRating, isSendingRating,
isRatingLoading, isRatingLoading,
canRate,
}), [ }), [
selectedCategoryId, selectedCategoryId,
categories, categories,
...@@ -183,5 +184,6 @@ export default function useMarketplace() { ...@@ -183,5 +184,6 @@ export default function useMarketplace() {
rateApp, rateApp,
isSendingRating, isSendingRating,
isRatingLoading, isRatingLoading,
canRate,
]); ]);
} }
...@@ -61,7 +61,7 @@ export default function useMarketplaceApps( ...@@ -61,7 +61,7 @@ export default function useMarketplaceApps(
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
const { data: securityReports, isPlaceholderData: isSecurityReportsPlaceholderData } = useSecurityReports(); const { data: securityReports, isPlaceholderData: isSecurityReportsPlaceholderData } = useSecurityReports();
const { ratings, userRatings, rateApp, isSendingRating, isRatingLoading } = useRatings(); const { ratings, userRatings, rateApp, isSendingRating, isRatingLoading, canRate } = useRatings();
// Set the value only 1 time to avoid unnecessary useQuery calls and re-rendering of all applications // Set the value only 1 time to avoid unnecessary useQuery calls and re-rendering of all applications
const [ snapshotFavoriteApps, setSnapshotFavoriteApps ] = React.useState<Array<string> | undefined>(); const [ snapshotFavoriteApps, setSnapshotFavoriteApps ] = React.useState<Array<string> | undefined>();
...@@ -124,6 +124,7 @@ export default function useMarketplaceApps( ...@@ -124,6 +124,7 @@ export default function useMarketplaceApps(
rateApp, rateApp,
isSendingRating, isSendingRating,
isRatingLoading, isRatingLoading,
canRate,
}), [ }), [
data, data,
displayedApps, displayedApps,
...@@ -136,5 +137,6 @@ export default function useMarketplaceApps( ...@@ -136,5 +137,6 @@ export default function useMarketplaceApps(
rateApp, rateApp,
isSendingRating, isSendingRating,
isRatingLoading, isRatingLoading,
canRate,
]); ]);
} }
...@@ -73,6 +73,7 @@ const Marketplace = () => { ...@@ -73,6 +73,7 @@ const Marketplace = () => {
rateApp, rateApp,
isSendingRating, isSendingRating,
isRatingLoading, isRatingLoading,
canRate,
} = useMarketplace(); } = useMarketplace();
const isMobile = useIsMobile(); const isMobile = useIsMobile();
...@@ -221,6 +222,7 @@ const Marketplace = () => { ...@@ -221,6 +222,7 @@ const Marketplace = () => {
rateApp={ rateApp } rateApp={ rateApp }
isSendingRating={ isSendingRating } isSendingRating={ isSendingRating }
isRatingLoading={ isRatingLoading } isRatingLoading={ isRatingLoading }
canRate={ canRate }
/> />
{ (selectedApp && isAppInfoModalOpen) && ( { (selectedApp && isAppInfoModalOpen) && (
...@@ -234,6 +236,7 @@ const Marketplace = () => { ...@@ -234,6 +236,7 @@ const Marketplace = () => {
rateApp={ rateApp } rateApp={ rateApp }
isSendingRating={ isSendingRating } isSendingRating={ isSendingRating }
isRatingLoading={ isRatingLoading } isRatingLoading={ isRatingLoading }
canRate={ canRate }
/> />
) } ) }
......
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