Commit 490d61b0 authored by Max Alekseenko's avatar Max Alekseenko

implement rating change

parent dc28dff6
...@@ -26,10 +26,14 @@ export type MarketplaceAppOverview = MarketplaceAppPreview & MarketplaceAppSocia ...@@ -26,10 +26,14 @@ export type MarketplaceAppOverview = MarketplaceAppPreview & MarketplaceAppSocia
site?: string; site?: string;
} }
export type AppRating = {
recordId: string;
value: number;
}
export type MarketplaceAppWithSecurityReport = MarketplaceAppOverview & { export type MarketplaceAppWithSecurityReport = MarketplaceAppOverview & {
securityReport?: MarketplaceAppSecurityReport; securityReport?: MarketplaceAppSecurityReport;
rating?: number; rating?: AppRating;
ratingRecordId?: string;
} }
export enum MarketplaceCategory { export enum MarketplaceCategory {
...@@ -65,14 +69,3 @@ export type MarketplaceAppSecurityReportRaw = { ...@@ -65,14 +69,3 @@ export type MarketplaceAppSecurityReportRaw = {
[chainId: string]: MarketplaceAppSecurityReport; [chainId: string]: MarketplaceAppSecurityReport;
}; };
} }
export type UserRatings = {
[appId: string]: number;
};
export type AppRatings = {
[appId: string]: {
recordId: string;
rating: number;
};
};
...@@ -2,16 +2,16 @@ import { IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue, chakra, ...@@ -2,16 +2,16 @@ import { IconButton, Image, Link, LinkBox, Skeleton, useColorModeValue, chakra,
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { MarketplaceAppWithSecurityReport, ContractListTypes } from 'types/client/marketplace'; import type { MarketplaceAppWithSecurityReport, ContractListTypes, AppRating } from 'types/client/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import type { EventTypes, EventPayload } from 'lib/mixpanel/index';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import AppSecurityReport from './AppSecurityReport'; import AppSecurityReport from './AppSecurityReport';
import MarketplaceAppCardLink from './MarketplaceAppCardLink'; import MarketplaceAppCardLink from './MarketplaceAppCardLink';
import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon'; import MarketplaceAppIntegrationIcon from './MarketplaceAppIntegrationIcon';
import Rating from './Rating/Rating'; import Rating from './Rating/Rating';
import type { RateFunction } from './Rating/useRatings';
interface Props extends MarketplaceAppWithSecurityReport { interface Props extends MarketplaceAppWithSecurityReport {
onInfoClick: (id: string) => void; onInfoClick: (id: string) => void;
...@@ -21,8 +21,8 @@ interface Props extends MarketplaceAppWithSecurityReport { ...@@ -21,8 +21,8 @@ interface Props extends MarketplaceAppWithSecurityReport {
onAppClick: (event: MouseEvent, id: string) => void; onAppClick: (event: MouseEvent, id: string) => void;
className?: string; className?: string;
showContractList: (id: string, type: ContractListTypes) => void; showContractList: (id: string, type: ContractListTypes) => void;
userRating: number | undefined; userRating?: AppRating;
rateApp: (appId: string, recordId: string | undefined, rating: number, source: EventPayload<EventTypes.APP_FEEDBACK>['Source']) => void; rateApp: RateFunction;
isSendingRating: boolean; isSendingRating: boolean;
isRatingLoading: boolean; isRatingLoading: boolean;
canRate: boolean | undefined; canRate: boolean | undefined;
...@@ -47,7 +47,6 @@ const MarketplaceAppCard = ({ ...@@ -47,7 +47,6 @@ const MarketplaceAppCard = ({
className, className,
showContractList, showContractList,
rating, rating,
ratingRecordId,
userRating, userRating,
rateApp, rateApp,
isSendingRating, isSendingRating,
...@@ -175,7 +174,6 @@ const MarketplaceAppCard = ({ ...@@ -175,7 +174,6 @@ const MarketplaceAppCard = ({
<Rating <Rating
appId={ id } appId={ id }
rating={ rating } rating={ rating }
recordId={ ratingRecordId }
userRating={ userRating } userRating={ userRating }
rate={ rateApp } rate={ rateApp }
isSending={ isSendingRating } isSending={ isSendingRating }
......
...@@ -15,7 +15,10 @@ const props = { ...@@ -15,7 +15,10 @@ const props = {
data: { data: {
...appsMock[0], ...appsMock[0],
securityReport: securityReportsMock[0].chainsData['1'], securityReport: securityReportsMock[0].chainsData['1'],
rating: 4.3, rating: {
recordId: 'test',
value: 4.3,
},
} as MarketplaceAppWithSecurityReport, } as MarketplaceAppWithSecurityReport,
isFavorite: false, isFavorite: false,
userRating: undefined, userRating: undefined,
......
...@@ -4,13 +4,12 @@ import { ...@@ -4,13 +4,12 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { MarketplaceAppWithSecurityReport } from 'types/client/marketplace'; import type { MarketplaceAppWithSecurityReport, AppRating } from 'types/client/marketplace';
import { ContractListTypes } from 'types/client/marketplace'; import { ContractListTypes } from 'types/client/marketplace';
import config from 'configs/app'; import config from 'configs/app';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import type { EventTypes, EventPayload } from 'lib/mixpanel/index';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import type { IconName } from 'ui/shared/IconSvg'; import type { IconName } from 'ui/shared/IconSvg';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -18,6 +17,7 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -18,6 +17,7 @@ import IconSvg from 'ui/shared/IconSvg';
import AppSecurityReport from './AppSecurityReport'; import AppSecurityReport from './AppSecurityReport';
import MarketplaceAppModalLink from './MarketplaceAppModalLink'; import MarketplaceAppModalLink from './MarketplaceAppModalLink';
import Rating from './Rating/Rating'; import Rating from './Rating/Rating';
import type { RateFunction } from './Rating/useRatings';
const feature = config.features.marketplace; const feature = config.features.marketplace;
const isRatingEnabled = feature.isEnabled && feature.rating; const isRatingEnabled = feature.isEnabled && feature.rating;
...@@ -28,8 +28,8 @@ type Props = { ...@@ -28,8 +28,8 @@ type Props = {
onFavoriteClick: (id: string, isFavorite: boolean, source: 'App modal') => void; onFavoriteClick: (id: string, isFavorite: boolean, source: 'App modal') => void;
data: MarketplaceAppWithSecurityReport; data: MarketplaceAppWithSecurityReport;
showContractList: (id: string, type: ContractListTypes, hasPreviousStep: boolean) => void; showContractList: (id: string, type: ContractListTypes, hasPreviousStep: boolean) => void;
userRating: number | undefined; userRating?: AppRating;
rateApp: (appId: string, recordId: string | undefined, rating: number, source: EventPayload<EventTypes.APP_FEEDBACK>['Source']) => void; rateApp: RateFunction;
isSendingRating: boolean; isSendingRating: boolean;
isRatingLoading: boolean; isRatingLoading: boolean;
canRate: boolean | undefined; canRate: boolean | undefined;
...@@ -64,7 +64,6 @@ const MarketplaceAppModal = ({ ...@@ -64,7 +64,6 @@ const MarketplaceAppModal = ({
categories, categories,
securityReport, securityReport,
rating, rating,
ratingRecordId,
} = data; } = data;
const socialLinks = [ const socialLinks = [
...@@ -175,7 +174,6 @@ const MarketplaceAppModal = ({ ...@@ -175,7 +174,6 @@ const MarketplaceAppModal = ({
<Rating <Rating
appId={ id } appId={ id }
rating={ rating } rating={ rating }
recordId={ ratingRecordId }
userRating={ userRating } userRating={ userRating }
rate={ rateApp } rate={ rateApp }
isSending={ isSendingRating } isSending={ isSendingRating }
......
...@@ -89,8 +89,7 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props) ...@@ -89,8 +89,7 @@ const MarketplaceAppTopBar = ({ appId, data, isLoading, securityReport }: Props)
) } ) }
<Rating <Rating
appId={ appId } appId={ appId }
rating={ ratings?.[appId]?.rating } rating={ ratings[appId] }
recordId={ ratings?.[appId]?.recordId }
userRating={ userRatings[appId] } userRating={ userRatings[appId] }
rate={ rateApp } rate={ rateApp }
isSending={ isSendingRating } isSending={ isSendingRating }
......
...@@ -2,13 +2,13 @@ import { Grid } from '@chakra-ui/react'; ...@@ -2,13 +2,13 @@ import { Grid } from '@chakra-ui/react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import type { MarketplaceAppWithSecurityReport, ContractListTypes, UserRatings } from 'types/client/marketplace'; import type { MarketplaceAppWithSecurityReport, ContractListTypes, AppRating } from 'types/client/marketplace';
import type { EventTypes, EventPayload } from 'lib/mixpanel/index';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import EmptySearchResult from './EmptySearchResult'; import EmptySearchResult from './EmptySearchResult';
import MarketplaceAppCard from './MarketplaceAppCard'; import MarketplaceAppCard from './MarketplaceAppCard';
import type { RateFunction } from './Rating/useRatings';
type Props = { type Props = {
apps: Array<MarketplaceAppWithSecurityReport>; apps: Array<MarketplaceAppWithSecurityReport>;
...@@ -19,8 +19,8 @@ type Props = { ...@@ -19,8 +19,8 @@ type Props = {
selectedCategoryId?: string; selectedCategoryId?: string;
onAppClick: (event: MouseEvent, id: string) => void; onAppClick: (event: MouseEvent, id: string) => void;
showContractList: (id: string, type: ContractListTypes) => void; showContractList: (id: string, type: ContractListTypes) => void;
userRatings: UserRatings; userRatings: Record<string, AppRating>;
rateApp: (appId: string, recordId: string, rating: number, source: EventPayload<EventTypes.APP_FEEDBACK>['Source']) => void; rateApp: RateFunction;
isSendingRating: boolean; isSendingRating: boolean;
isRatingLoading: boolean; isRatingLoading: boolean;
canRate: boolean | undefined; canRate: boolean | undefined;
...@@ -68,7 +68,6 @@ const MarketplaceList = ({ ...@@ -68,7 +68,6 @@ const MarketplaceList = ({
securityReport={ app.securityReport } securityReport={ app.securityReport }
showContractList={ showContractList } showContractList={ showContractList }
rating={ app.rating } rating={ app.rating }
ratingRecordId={ app.ratingRecordId }
userRating={ userRatings[app.id] } userRating={ userRatings[app.id] }
rateApp={ rateApp } rateApp={ rateApp }
isSendingRating={ isSendingRating } isSendingRating={ isSendingRating }
......
import { Text, Flex, Spinner, Button } from '@chakra-ui/react'; import { Text, Flex, Spinner } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { mdash } from 'lib/html-entities'; import type { AppRating } from 'types/client/marketplace';
import type { EventTypes, EventPayload } from 'lib/mixpanel/index'; import type { EventTypes, EventPayload } from 'lib/mixpanel/index';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import Stars from './Stars'; import Stars from './Stars';
import type { RateFunction } from './useRatings';
const ratingDescriptions = [ 'Terrible', 'Poor', 'Average', 'Very good', 'Outstanding' ]; const ratingDescriptions = [ 'Terrible', 'Poor', 'Average', 'Very good', 'Outstanding' ];
type Props = { type Props = {
appId: string; appId: string;
recordId?: string; rating?: AppRating;
userRating: number | undefined; userRating?: AppRating;
rate: (appId: string, recordId: string | undefined, rating: number, source: EventPayload<EventTypes.APP_FEEDBACK>['Source']) => void; rate: RateFunction;
isSending?: boolean; isSending?: boolean;
source: EventPayload<EventTypes.APP_FEEDBACK>['Source']; source: EventPayload<EventTypes.APP_FEEDBACK>['Source'];
}; };
const PopoverContent = ({ appId, recordId, userRating, rate, isSending, source }: Props) => { const PopoverContent = ({ appId, rating, userRating, rate, isSending, source }: Props) => {
const [ hovered, setHovered ] = React.useState(-1); const [ hovered, setHovered ] = React.useState(-1);
const [ selected, setSelected ] = React.useState(-1);
const filledIndex = React.useMemo(() => {
if (hovered >= 0) {
return hovered;
}
return userRating?.value ? userRating?.value - 1 : -1;
}, [ userRating, hovered ]);
const handleMouseOverFactory = React.useCallback((index: number) => () => { const handleMouseOverFactory = React.useCallback((index: number) => () => {
setHovered(index); setHovered(index);
}, []); }, []);
const handleSelectFactory = React.useCallback((index: number) => () => {
setSelected(index);
}, []);
const handleMouseOut = React.useCallback(() => { const handleMouseOut = React.useCallback(() => {
setHovered(-1); setHovered(-1);
}, []); }, []);
const handleRate = React.useCallback(() => { const handleRateFactory = React.useCallback((index: number) => () => {
if (selected < 0) { rate(appId, rating?.recordId, userRating?.recordId, index + 1, source);
return; }, [ appId, rating, rate, userRating, source ]);
}
rate(appId, recordId, selected + 1, source);
}, [ appId, recordId, selected, rate, source ]);
if (userRating) {
return (
<>
<Flex alignItems="center">
<IconSvg
name="verified"
color="green.400"
boxSize="30px"
mr={ 1 }
ml="-5px"
/>
<Text fontWeight="500" fontSize="xs" lineHeight="30px" variant="secondary">
App is already rated by you
</Text>
</Flex>
<Flex alignItems="center" h="32px">
<IconSvg
name="star_filled"
color="yellow.400"
boxSize={ 5 }
mr={ 1 }
/>
<Text fontSize="md" fontWeight="500" mr={ 3 }>
{ userRating.toFixed(1) }
</Text>
<Text fontSize="md">
{ mdash } { ratingDescriptions[ userRating - 1 ] }
</Text>
</Flex>
</>
);
}
if (isSending) { if (isSending) {
return ( return (
...@@ -85,25 +53,27 @@ const PopoverContent = ({ appId, recordId, userRating, rate, isSending, source } ...@@ -85,25 +53,27 @@ const PopoverContent = ({ appId, recordId, userRating, rate, isSending, source }
return ( return (
<> <>
<Text fontWeight="500" fontSize="xs" lineHeight="30px" variant="secondary"> <Flex alignItems="center">
How was your experience? { userRating && (
</Text> <IconSvg name="verified" color="green.400" boxSize="30px" mr={ 1 } ml="-5px"/>
) }
<Text fontWeight="500" fontSize="xs" lineHeight="30px" variant="secondary">
{ userRating ? 'App is already rated by you' : 'How was your experience?' }
</Text>
</Flex>
<Flex alignItems="center" h="32px"> <Flex alignItems="center" h="32px">
<Stars <Stars
filledIndex={ hovered >= 0 ? hovered : selected } filledIndex={ filledIndex }
onMouseOverFactory={ handleMouseOverFactory } onMouseOverFactory={ handleMouseOverFactory }
onMouseOut={ handleMouseOut } onMouseOut={ handleMouseOut }
onClickFactory={ handleSelectFactory } onClickFactory={ handleRateFactory }
/> />
{ (hovered >= 0 || selected >= 0) && ( { (filledIndex >= 0) && (
<Text fontSize="md" ml={ 2 }> <Text fontSize="md" ml={ 2 }>
{ ratingDescriptions[ hovered >= 0 ? hovered : selected ] } { ratingDescriptions[filledIndex] }
</Text> </Text>
) } ) }
</Flex> </Flex>
<Button size="sm" px={ 4 } mt={ 3 } onClick={ handleRate } isDisabled={ selected < 0 }>
Rate it
</Button>
</> </>
); );
}; };
......
import { Text, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Skeleton, useOutsideClick, Box } from '@chakra-ui/react'; import { Text, PopoverTrigger, PopoverBody, PopoverContent, useDisclosure, Skeleton, useOutsideClick, Box } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AppRating } from 'types/client/marketplace';
import config from 'configs/app'; import config from 'configs/app';
import type { EventTypes, EventPayload } from 'lib/mixpanel/index'; import type { EventTypes, EventPayload } from 'lib/mixpanel/index';
import Popover from 'ui/shared/chakra/Popover'; import Popover from 'ui/shared/chakra/Popover';
...@@ -8,16 +10,16 @@ import Popover from 'ui/shared/chakra/Popover'; ...@@ -8,16 +10,16 @@ import Popover from 'ui/shared/chakra/Popover';
import Content from './PopoverContent'; import Content from './PopoverContent';
import Stars from './Stars'; import Stars from './Stars';
import TriggerButton from './TriggerButton'; import TriggerButton from './TriggerButton';
import type { RateFunction } from './useRatings';
const feature = config.features.marketplace; const feature = config.features.marketplace;
const isEnabled = feature.isEnabled && feature.rating; const isEnabled = feature.isEnabled && feature.rating;
type Props = { type Props = {
appId: string; appId: string;
rating?: number; rating?: AppRating;
recordId?: string; userRating?: AppRating;
userRating: number | undefined; rate: RateFunction;
rate: (appId: string, recordId: string | undefined, rating: number, source: EventPayload<EventTypes.APP_FEEDBACK>['Source']) => void;
isSending?: boolean; isSending?: boolean;
isLoading?: boolean; isLoading?: boolean;
fullView?: boolean; fullView?: boolean;
...@@ -26,7 +28,7 @@ type Props = { ...@@ -26,7 +28,7 @@ type Props = {
}; };
const Rating = ({ const Rating = ({
appId, rating, recordId, userRating, rate, appId, rating, userRating, rate,
isSending, isLoading, fullView, canRate, source, isSending, isLoading, fullView, canRate, source,
}: Props) => { }: Props) => {
const { isOpen, onToggle, onClose } = useDisclosure(); const { isOpen, onToggle, onClose } = useDisclosure();
...@@ -47,8 +49,8 @@ const Rating = ({ ...@@ -47,8 +49,8 @@ const Rating = ({
> >
{ fullView && ( { fullView && (
<> <>
<Stars filledIndex={ (rating || 0) - 1 }/> <Stars filledIndex={ (rating?.value || 0) - 1 }/>
<Text fontSize="md" ml={ 1 }>{ rating }</Text> <Text fontSize="md" ml={ 1 }>{ rating?.value }</Text>
</> </>
) } ) }
<Box ref={ popoverRef }> <Box ref={ popoverRef }>
...@@ -66,7 +68,7 @@ const Rating = ({ ...@@ -66,7 +68,7 @@ const Rating = ({
<PopoverBody p={ 4 }> <PopoverBody p={ 4 }>
<Content <Content
appId={ appId } appId={ appId }
recordId={ recordId } rating={ rating }
userRating={ userRating } userRating={ userRating }
rate={ rate } rate={ rate }
isSending={ isSending } isSending={ isSending }
......
import { Button, chakra, useColorModeValue, Tooltip, useDisclosure } from '@chakra-ui/react'; import { Button, chakra, useColorModeValue, Tooltip, useDisclosure } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { AppRating } from 'types/client/marketplace';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing'; import usePreventFocusAfterModalClosing from 'lib/hooks/usePreventFocusAfterModalClosing';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
type Props = { type Props = {
rating?: number; rating?: AppRating;
fullView?: boolean; fullView?: boolean;
isActive: boolean; isActive: boolean;
onClick: () => void; onClick: () => void;
...@@ -76,7 +78,7 @@ const TriggerButton = ( ...@@ -76,7 +78,7 @@ const TriggerButton = (
) } ) }
{ (rating && !fullView) ? ( { (rating && !fullView) ? (
<chakra.span color={ textColor } transition="inherit"> <chakra.span color={ textColor } transition="inherit">
{ rating } { rating.value }
</chakra.span> </chakra.span>
) : ( ) : (
'Rate it!' 'Rate it!'
......
...@@ -2,7 +2,7 @@ import Airtable from 'airtable'; ...@@ -2,7 +2,7 @@ import Airtable from 'airtable';
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { useAccount } from 'wagmi'; import { useAccount } from 'wagmi';
import type { UserRatings, AppRatings } from 'types/client/marketplace'; import type { AppRating } from 'types/client/marketplace';
import config from 'configs/app'; import config from 'configs/app';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
...@@ -16,6 +16,28 @@ const base = (feature.isEnabled && feature.rating) ? ...@@ -16,6 +16,28 @@ const base = (feature.isEnabled && feature.rating) ?
new Airtable({ apiKey: feature.rating.airtableApiKey }).base(feature.rating.airtableBaseId) : new Airtable({ apiKey: feature.rating.airtableApiKey }).base(feature.rating.airtableBaseId) :
undefined; undefined;
export type RateFunction = (
appId: string,
appRecordId: string | undefined,
userRecordId: string | undefined,
rating: number,
source: EventPayload<EventTypes.APP_FEEDBACK>['Source'],
) => void;
function formatRatings(data: Airtable.Records<Airtable.FieldSet>) {
return data.reduce((acc: Record<string, AppRating>, record) => {
const fields = record.fields as { appId: string; rating: number };
if (!fields.appId || typeof fields.rating !== 'number') {
return acc;
}
acc[fields.appId] = {
recordId: record.id,
value: fields.rating,
};
return acc;
}, {});
}
export default function useRatings() { export default function useRatings() {
const { address } = useAccount(); const { address } = useAccount();
const toast = useToast(); const toast = useToast();
...@@ -29,8 +51,8 @@ export default function useRatings() { ...@@ -29,8 +51,8 @@ export default function useRatings() {
}, },
}); });
const [ ratings, setRatings ] = useState<AppRatings>({}); const [ ratings, setRatings ] = useState<Record<string, AppRating>>({});
const [ userRatings, setUserRatings ] = useState<UserRatings>({}); const [ userRatings, setUserRatings ] = useState<Record<string, AppRating>>({});
const [ isRatingLoading, setIsRatingLoading ] = useState<boolean>(false); const [ isRatingLoading, setIsRatingLoading ] = useState<boolean>(false);
const [ isUserRatingLoading, setIsUserRatingLoading ] = useState<boolean>(false); const [ isUserRatingLoading, setIsUserRatingLoading ] = useState<boolean>(false);
const [ isSending, setIsSending ] = useState<boolean>(false); const [ isSending, setIsSending ] = useState<boolean>(false);
...@@ -41,14 +63,7 @@ export default function useRatings() { ...@@ -41,14 +63,7 @@ export default function useRatings() {
return; return;
} }
const data = await base('apps_ratings').select({ fields: [ 'appId', 'rating' ] }).all(); const data = await base('apps_ratings').select({ fields: [ 'appId', 'rating' ] }).all();
const ratings = data.reduce((acc: AppRatings, record) => { const ratings = formatRatings(data);
const fields = record.fields as { appId: string; rating: number };
acc[fields.appId] = {
recordId: record.id,
rating: fields.rating,
};
return acc;
}, {});
setRatings(ratings); setRatings(ratings);
}, []); }, []);
...@@ -64,20 +79,13 @@ export default function useRatings() { ...@@ -64,20 +79,13 @@ export default function useRatings() {
useEffect(() => { useEffect(() => {
async function fetchUserRatings() { async function fetchUserRatings() {
setIsUserRatingLoading(true); setIsUserRatingLoading(true);
let userRatings = {} as UserRatings; let userRatings = {} as Record<string, AppRating>;
if (address && base) { if (address && base) {
const data = await base('users_ratings').select({ const data = await base('users_ratings').select({
filterByFormula: `address = "${ address }"`, filterByFormula: `address = "${ address }"`,
fields: [ 'appId', 'rating' ], fields: [ 'appId', 'rating' ],
}).all(); }).all();
userRatings = data.reduce((acc: UserRatings, record) => { userRatings = formatRatings(data);
const fields = record.fields as { appId: string; rating: number };
if (!fields.appId || typeof fields.rating !== 'number') {
return acc;
}
acc[fields.appId] = fields.rating;
return acc;
}, {});
} }
setUserRatings(userRatings); setUserRatings(userRatings);
setIsUserRatingLoading(false); setIsUserRatingLoading(false);
...@@ -93,16 +101,18 @@ export default function useRatings() { ...@@ -93,16 +101,18 @@ export default function useRatings() {
const rateApp = useCallback(async( const rateApp = useCallback(async(
appId: string, appId: string,
recordId: string | undefined, appRecordId: string | undefined,
userRecordId: string | undefined,
rating: number, rating: number,
source: EventPayload<EventTypes.APP_FEEDBACK>['Source'], source: EventPayload<EventTypes.APP_FEEDBACK>['Source'],
) => { ) => {
setIsSending(true); setIsSending(true);
try { try {
if (!address || !base) { if (!address || !base) {
throw new Error('Address is missing'); throw new Error('Address is missing');
} }
let appRecordId = recordId;
if (!appRecordId) { if (!appRecordId) {
const records = await base('apps_ratings').create([ { fields: { appId } } ]); const records = await base('apps_ratings').create([ { fields: { appId } } ]);
appRecordId = records[0].id; appRecordId = records[0].id;
...@@ -110,22 +120,36 @@ export default function useRatings() { ...@@ -110,22 +120,36 @@ export default function useRatings() {
throw new Error('Record ID is missing'); throw new Error('Record ID is missing');
} }
} }
await base('users_ratings').create([
{ if (!userRecordId) {
fields: { const userRecords = await base('users_ratings').create([
address, {
appRecordId: [ appRecordId ], fields: {
rating, address,
appRecordId: [ appRecordId ],
rating,
},
}, },
]);
userRecordId = userRecords[0].id;
} else {
await base('users_ratings').update(userRecordId, { rating });
}
setUserRatings({
...userRatings,
[appId]: {
recordId: userRecordId,
value: rating,
}, },
]); });
setUserRatings({ ...userRatings, [appId]: rating }); fetchRatings();
toast({ toast({
status: 'success', status: 'success',
title: 'Awesome! Thank you 💜', title: 'Awesome! Thank you 💜',
description: 'Your rating improves the service', description: 'Your rating improves the service',
}); });
fetchRatings();
mixpanel.logEvent( mixpanel.logEvent(
mixpanel.EventTypes.APP_FEEDBACK, mixpanel.EventTypes.APP_FEEDBACK,
{ Action: 'Rating', Source: source, AppId: appId, Score: rating }, { Action: 'Rating', Source: source, AppId: appId, Score: rating },
...@@ -137,6 +161,7 @@ export default function useRatings() { ...@@ -137,6 +161,7 @@ export default function useRatings() {
description: 'Please try again later', description: 'Please try again later',
}); });
} }
setIsSending(false); setIsSending(false);
}, [ address, userRatings, fetchRatings, toast ]); }, [ address, userRatings, fetchRatings, toast ]);
......
...@@ -97,8 +97,7 @@ export default function useMarketplaceApps( ...@@ -97,8 +97,7 @@ export default function useMarketplaceApps(
data?.map((app) => ({ data?.map((app) => ({
...app, ...app,
securityReport: securityReports?.[app.id], securityReport: securityReports?.[app.id],
rating: ratings?.[app.id]?.rating, rating: ratings?.[app.id],
ratingRecordId: ratings?.[app.id]?.recordId,
})), })),
[ data, securityReports, ratings ]); [ data, securityReports, ratings ]);
...@@ -110,7 +109,7 @@ export default function useMarketplaceApps( ...@@ -110,7 +109,7 @@ export default function useMarketplaceApps(
return (b.securityReport?.overallInfo.securityScore || 0) - (a.securityReport?.overallInfo.securityScore || 0); return (b.securityReport?.overallInfo.securityScore || 0) - (a.securityReport?.overallInfo.securityScore || 0);
} }
if (sorting === 'rating') { if (sorting === 'rating') {
return (b.rating || 0) - (a.rating || 0); return (b.rating?.value || 0) - (a.rating?.value || 0);
} }
return 0; return 0;
}) || []; }) || [];
......
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