rate apps feature

This commit is contained in:
2024-10-20 01:46:22 +03:00
parent 9685a40e6a
commit 4a026bccc7
8 changed files with 289 additions and 40 deletions

View File

@@ -20,8 +20,9 @@ import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { Spacer } from "../../common/Spacer";
import { executeEvent } from "../../utils/events";
import { AppRating } from "./AppRating";
export const AppInfoSnippet = ({ app }) => {
export const AppInfoSnippet = ({ app, myName }) => {
const isInstalled = app?.status?.status === 'READY'
@@ -85,6 +86,7 @@ export const AppInfoSnippet = ({ app }) => {
{ app?.name}
</AppInfoUserName>
<Spacer height="3px" />
<AppRating app={app} myName={myName} />
</AppInfoSnippetMiddle>
</AppInfoSnippetLeft>
<AppInfoSnippetRight>

View File

@@ -1,14 +1,208 @@
import { Rating } from '@mui/material'
import React, { useState } from 'react'
import { Box, Rating, Typography } from "@mui/material";
import React, { useCallback, useContext, useEffect, useRef, useState } from "react";
import { getFee } from "../../background";
import { MyContext, getBaseApiReact } from "../../App";
import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { StarFilledIcon } from "../../assets/svgs/StarFilled";
import { StarEmptyIcon } from "../../assets/svgs/StarEmpty";
import { AppInfoUserName } from "./Apps-styles";
export const AppRating = () => {
const [value, setValue] = useState(0)
export const AppRating = ({app, myName, ratingCountPosition = 'right'}) => {
const [value, setValue] = useState(0);
const { show } = useContext(MyContext);
const [hasPublishedRating, setHasPublishedRating] = useState<null | boolean>(null)
const [pollInfo, setPollInfo] = useState(null)
const [votesInfo, setVotesInfo] = useState(null)
const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null);
const hasCalledRef = useRef(false)
console.log(`pollinfo-${app?.service}-${app?.name}`, value)
console.log('hasPublishedRating', hasPublishedRating)
const getRating = useCallback(async (name, service)=> {
try {
hasCalledRef.current = true
const pollName = `app-library-${service}-rating-${name}`
const url = `${getBaseApiReact()}/polls/${pollName}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseData = await response.json();
console.log('responseData', responseData)
if(responseData?.message?.includes('POLL_NO_EXISTS')){
setHasPublishedRating(false)
} else if(responseData?.pollName){
setPollInfo(responseData)
setHasPublishedRating(true)
const urlVotes = `${getBaseApiReact()}/polls/votes/${pollName}`;
const responseVotes = await fetch(urlVotes, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseDataVotes = await responseVotes.json();
setVotesInfo(responseDataVotes)
const voteCount = responseDataVotes.voteCounts
// Include initial value vote in the calculation
const ratingVotes = voteCount.filter(vote => !vote.optionName.startsWith("initialValue-"));
const initialValueVote = voteCount.find(vote => vote.optionName.startsWith("initialValue-"));
console.log('initialValueVote', initialValueVote)
if (initialValueVote) {
// Convert "initialValue-X" to just "X" and add it to the ratingVotes array
const initialRating = parseInt(initialValueVote.optionName.split("-")[1], 10);
console.log('initialRating', initialRating)
ratingVotes.push({
optionName: initialRating.toString(),
voteCount: 1,
});
}
// Calculate the weighted average
let totalScore = 0;
let totalVotes = 0;
ratingVotes.forEach(vote => {
const rating = parseInt(vote.optionName, 10); // Extract rating value (1-5)
const count = vote.voteCount;
totalScore += rating * count; // Weighted score
totalVotes += count; // Total number of votes
});
console.log('ratingVotes', ratingVotes, totalScore, totalVotes)
// Calculate average rating (ensure no division by zero)
const averageRating = totalVotes > 0 ? (totalScore / totalVotes) : 0;
setValue(averageRating);
}
} catch (error) {
console.log('error rating', error)
if(error?.message?.includes('POLL_NO_EXISTS')){
setHasPublishedRating(false)
}
}
}, [])
useEffect(()=> {
if(hasCalledRef.current) return
if(!app) return
getRating(app?.name, app?.service)
}, [getRating, app?.name])
const rateFunc = async (event, newValue)=> {
try {
if(!myName) throw new Error('You need a name to rate.')
if(!app?.name) return
console.log('newValue', newValue)
const fee = await getFee("ARBITRARY");
await show({
message: `Would you like to rate this app a rating of ${newValue}?`,
publishFee: fee.fee + " QORT",
});
if(hasPublishedRating === false){
const pollName = `app-library-${app.service}-rating-${app.name}`
const pollOptions = [`1, 2, 3, 4, 5, initialValue-${newValue}`]
await new Promise((res, rej)=> {
chrome?.runtime?.sendMessage({
action: 'CREATE_POLL', type: 'qortalRequest', payload: {
pollName: pollName , pollDescription: `Rating for ${app.service} ${app.name}`, pollOptions: pollOptions , pollOwnerAddress : myName
}
}, (response) => {
console.log('response', response);
if (response.error) {
rej(response?.message)
return
} else {
res(response)
setInfoSnack({
type: "success",
message:
"Successfully rated. Please wait a couple minutes for the network to propogate the changes.",
});
setOpenSnack(true);
}
});
})
} else {
const pollName = `app-library-${app.service}-rating-${app.name}`
const optionIndex = pollInfo?.pollOptions.findIndex((option)=> +option.optionName === +newValue)
if(isNaN(optionIndex) || optionIndex === -1) throw new Error('Cannot find rating option')
await new Promise((res, rej)=> {
chrome?.runtime?.sendMessage({
action: 'VOTE_ON_POLL', type: 'qortalRequest', payload: {
pollName: pollName , optionIndex
}
}, (response) => {
console.log('response', response);
if (response.error) {
rej(response?.message)
return
} else {
res(response)
setInfoSnack({
type: "success",
message:
"Successfully rated. Please wait a couple minutes for the network to propogate the changes.",
});
setOpenSnack(true);
}
});
})
}
} catch (error) {
setInfoSnack({
type: "error",
message: error.message || "An error occurred while trying to rate.",
});
setOpenSnack(true);
}
}
console.log('vvotes', (votesInfo?.totalVotes ?? 0 ) + votesInfo?.voteCounts?.length === 6 ? 1 : 0, votesInfo)
return (
<div>
<Rating value={value}
onChange={(event, newValue) => {
}} precision={0.1} />
<Box sx={{
display: 'flex',
gap: '5px',
alignItems: 'center'
}}>
<Rating
value={value}
onChange={rateFunc}
precision={1}
readOnly={hasPublishedRating === null}
size="small"
icon={<StarFilledIcon />}
emptyIcon={<StarEmptyIcon />}
sx={{
display: "flex",
gap: "2px",
}}
/>
{ratingCountPosition && (
<AppInfoUserName>
{ (votesInfo?.totalVotes ?? 0) + (votesInfo?.voteCounts?.length === 6 ? 1 : 0)}
</AppInfoUserName>
)}
</Box>
<CustomizedSnackbars
duration={2000}
open={openSnack}
setOpen={setOpenSnack}
info={infoSnack}
setInfo={setInfoSnack}
/>
</div>
)
}
);
};

View File

@@ -175,12 +175,14 @@ import {
fontSize: '16px',
fontWeight: 500,
lineHeight: 1.2,
textAlign: 'start'
}));
export const AppInfoUserName = styled(Typography)(({ theme }) => ({
fontSize: '13px',
fontWeight: 400,
lineHeight: 1.2,
color: '#8D8F93'
color: '#8D8F93',
textAlign: 'start'
}));

View File

@@ -263,6 +263,8 @@ export const Apps = ({ mode, setMode, show , myName}) => {
downloadedQapps={downloadedQapps}
availableQapps={availableQapps}
setMode={setMode}
myName={myName}
hasPublishApp={!!(myApp || myWebsite)}
/>
)}
{mode === "appInfo" && <AppInfo app={selectedAppInfo} />}

View File

@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import {
AppCircle,
AppCircleContainer,
@@ -55,12 +55,42 @@ const ScrollerStyled = styled('div')({
"-ms-overflow-style": "none",
});
const StyledVirtuosoContainer = styled('div')({
position: 'relative',
width: '100%',
display: 'flex',
flexDirection: 'column',
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
});
export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode }) => {
export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode, myName, hasPublishApp }) => {
const [searchValue, setSearchValue] = useState("");
const virtuosoRef = useRef();
const { rootHeight } = useContext(MyContext);
const [appStates, setAppStates] = useState({});
const handleStateChange = (appId, newState) => {
setAppStates((prevState) => ({
...prevState,
[appId]: {
...(prevState[appId] || {}), // Preserve existing state for the app
...newState, // Merge in the new state properties
},
}));
};
console.log('appStates', appStates)
const officialApps = useMemo(() => {
return availableQapps.filter(
(app) =>
@@ -75,7 +105,7 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode }) => {
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(searchValue);
}, 250);
}, 350);
// Cleanup timeout if searchValue changes before the timeout completes
return () => {
@@ -97,28 +127,10 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode }) => {
let app = searchedList[index];
console.log('appi', app)
return <AppInfoSnippet key={`${app?.service}-${app?.name}`} app={app} />;
return <AppInfoSnippet key={`${app?.service}-${app?.name}`} app={app} myName={myName} />;
};
const StyledVirtuosoContainer = styled('div')({
position: 'relative',
height: rootHeight,
width: '100%',
display: 'flex',
flexDirection: 'column',
// Hide scrollbar for WebKit browsers (Chrome, Safari)
"::-webkit-scrollbar": {
width: "0px",
height: "0px",
},
// Hide scrollbar for Firefox
scrollbarWidth: "none",
// Hide scrollbar for IE and older Edge
"-ms-overflow-style": "none",
});
return (
<AppsLibraryContainer>
@@ -162,7 +174,9 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode }) => {
<Spacer height="25px" />
{searchedList?.length > 0 ? (
<AppsWidthLimiter>
<StyledVirtuosoContainer>
<StyledVirtuosoContainer sx={{
height: rootHeight
}}>
<Virtuoso
ref={virtuosoRef}
data={searchedList}
@@ -229,7 +243,7 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode }) => {
})}
</AppsContainer>
<Spacer height="30px" />
<AppLibrarySubTitle>Create Apps!</AppLibrarySubTitle>
<AppLibrarySubTitle>{hasPublishApp ? 'Update Apps!' : 'Create Apps!'}</AppLibrarySubTitle>
<Spacer height="18px" />
</AppsWidthLimiter>
<PublishQAppCTAParent>
@@ -245,7 +259,7 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode }) => {
setMode('publish')
}}>
<PublishQAppCTAButton>
Publish
{hasPublishApp ? 'Update' : 'Publish'}
</PublishQAppCTAButton>
<Spacer width="20px" />
</PublishQAppCTARight>