finished app info

This commit is contained in:
PhilReact 2024-10-20 03:00:25 +03:00
parent 4a026bccc7
commit ba53e83b13
5 changed files with 349 additions and 240 deletions

View File

@ -1,13 +1,14 @@
import { Box } from "@mui/material"; import { Box } from "@mui/material";
export const Spacer = ({ height, width }: any) => { export const Spacer = ({ height, width, ...props }: any) => {
return ( return (
<Box <Box
sx={{ sx={{
height: height ? height : '0px', height: height ? height : '0px',
display: 'flex', display: 'flex',
flexShrink: 0, flexShrink: 0,
width: width ? width : '0px' width: width ? width : '0px',
...(props || {})
}} }}
/> />
); );

View File

@ -11,6 +11,11 @@ import {
AppInfoSnippetMiddle, AppInfoSnippetMiddle,
AppInfoSnippetRight, AppInfoSnippetRight,
AppInfoUserName, AppInfoUserName,
AppsCategoryInfo,
AppsCategoryInfoLabel,
AppsCategoryInfoSub,
AppsCategoryInfoValue,
AppsInfoDescription,
AppsLibraryContainer, AppsLibraryContainer,
AppsParent, AppsParent,
AppsWidthLimiter, AppsWidthLimiter,
@ -22,37 +27,39 @@ import LogoSelected from "../../assets/svgs/LogoSelected.svg";
import { Spacer } from "../../common/Spacer"; import { Spacer } from "../../common/Spacer";
import { executeEvent } from "../../utils/events"; import { executeEvent } from "../../utils/events";
import { AppRating } from "./AppRating";
export const AppInfo = ({ app }) => { export const AppInfo = ({ app, myName }) => {
const isInstalled = app?.status?.status === "READY";
const isInstalled = app?.status?.status === 'READY'
return ( return (
<AppsLibraryContainer> <AppsLibraryContainer>
<AppsWidthLimiter> <AppsWidthLimiter>
<AppInfoSnippetContainer> <AppInfoSnippetContainer>
<AppInfoSnippetLeft sx={{ <AppInfoSnippetLeft
sx={{
flexGrow: 1, flexGrow: 1,
gap: '18px' gap: "18px",
}}> }}
>
<AppCircleContainer sx={{ <AppCircleContainer
width: 'auto' sx={{
}}> width: "auto",
}}
>
<AppCircle <AppCircle
sx={{ sx={{
border: "none", border: "none",
height: '100px', height: "100px",
width: '100px' width: "100px",
}} }}
> >
<Avatar <Avatar
sx={{ sx={{
height: "43px", height: "43px",
width: "43px", width: "43px",
'& img': { "& img": {
objectFit: 'fill', objectFit: "fill",
} },
}} }}
alt={app?.name} alt={app?.name}
src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${ src={`${getBaseApiReact()}/arbitrary/THUMBNAIL/${
@ -71,39 +78,60 @@ export const AppInfo = ({ app }) => {
</AppCircle> </AppCircle>
</AppCircleContainer> </AppCircleContainer>
<AppInfoSnippetMiddle> <AppInfoSnippetMiddle>
<AppInfoAppName> <AppInfoAppName>
{app?.metadata?.title || app?.name} {app?.metadata?.title || app?.name}
</AppInfoAppName> </AppInfoAppName>
<Spacer height="6px" /> <Spacer height="6px" />
<AppInfoUserName> <AppInfoUserName>{app?.name}</AppInfoUserName>
{ app?.name}
</AppInfoUserName>
<Spacer height="3px" /> <Spacer height="3px" />
</AppInfoSnippetMiddle> </AppInfoSnippetMiddle>
</AppInfoSnippetLeft> </AppInfoSnippetLeft>
<AppInfoSnippetRight> <AppInfoSnippetRight></AppInfoSnippetRight>
</AppInfoSnippetRight>
</AppInfoSnippetContainer> </AppInfoSnippetContainer>
<Spacer height="11px" /> <Spacer height="11px" />
<AppDownloadButton onClick={()=> { <AppDownloadButton
onClick={() => {
executeEvent("addTab", { executeEvent("addTab", {
data: app data: app,
}) });
}} sx={{ }}
backgroundColor: isInstalled ? '#0091E1' : '#247C0E', sx={{
width: '100%', backgroundColor: isInstalled ? "#0091E1" : "#247C0E",
maxWidth: '320px', width: "100%",
height: '29px' maxWidth: "320px",
}}> height: "29px",
<AppDownloadButtonText>{isInstalled ? 'Open' : 'Download'}</AppDownloadButtonText> }}
>
<AppDownloadButtonText>
{isInstalled ? "Open" : "Download"}
</AppDownloadButtonText>
</AppDownloadButton> </AppDownloadButton>
</AppsWidthLimiter> </AppsWidthLimiter>
<Spacer height="20px" />
<AppsWidthLimiter>
<AppsCategoryInfo>
<AppRating ratingCountPosition="top" myName={myName} app={app} />
<Spacer width="16px" />
<Spacer height="40px" width="1px" backgroundColor="white" />
<Spacer width="16px" />
<AppsCategoryInfoSub>
<AppsCategoryInfoLabel>Category:</AppsCategoryInfoLabel>
<Spacer height="4px" />
<AppsCategoryInfoValue>
{app?.metadata?.categoryName || "none"}
</AppsCategoryInfoValue>
</AppsCategoryInfoSub>
</AppsCategoryInfo>
<Spacer height="30px" />
<AppInfoAppName>
About this Q-App
</AppInfoAppName>
</AppsWidthLimiter>
<Spacer height="20px" />
<AppsInfoDescription>
{app?.metadata?.description || "No description"}
</AppsInfoDescription>
</AppsLibraryContainer> </AppsLibraryContainer>
); );
}; };

View File

@ -1,29 +1,37 @@
import { Box, Rating, Typography } from "@mui/material"; import { Box, Rating, Typography } from "@mui/material";
import React, { useCallback, useContext, useEffect, useRef, useState } from "react"; import React, {
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { getFee } from "../../background"; import { getFee } from "../../background";
import { MyContext, getBaseApiReact } from "../../App"; import { MyContext, getBaseApiReact } from "../../App";
import { CustomizedSnackbars } from "../Snackbar/Snackbar"; import { CustomizedSnackbars } from "../Snackbar/Snackbar";
import { StarFilledIcon } from "../../assets/svgs/StarFilled"; import { StarFilledIcon } from "../../assets/svgs/StarFilled";
import { StarEmptyIcon } from "../../assets/svgs/StarEmpty"; import { StarEmptyIcon } from "../../assets/svgs/StarEmpty";
import { AppInfoUserName } from "./Apps-styles"; import { AppInfoUserName } from "./Apps-styles";
import { Spacer } from "../../common/Spacer";
export const AppRating = ({app, myName, ratingCountPosition = 'right'}) => { export const AppRating = ({ app, myName, ratingCountPosition = "right" }) => {
const [value, setValue] = useState(0); const [value, setValue] = useState(0);
const { show } = useContext(MyContext); const { show } = useContext(MyContext);
const [hasPublishedRating, setHasPublishedRating] = useState<null | boolean>(null) const [hasPublishedRating, setHasPublishedRating] = useState<null | boolean>(
const [pollInfo, setPollInfo] = useState(null) null
const [votesInfo, setVotesInfo] = useState(null) );
const [pollInfo, setPollInfo] = useState(null);
const [votesInfo, setVotesInfo] = useState(null);
const [openSnack, setOpenSnack] = useState(false); const [openSnack, setOpenSnack] = useState(false);
const [infoSnack, setInfoSnack] = useState(null); const [infoSnack, setInfoSnack] = useState(null);
const hasCalledRef = useRef(false) const hasCalledRef = useRef(false);
console.log(`pollinfo-${app?.service}-${app?.name}`, value) console.log(`pollinfo-${app?.service}-${app?.name}`, value);
console.log('hasPublishedRating', hasPublishedRating) console.log("hasPublishedRating", hasPublishedRating);
const getRating = useCallback(async (name, service) => { const getRating = useCallback(async (name, service) => {
try { try {
hasCalledRef.current = true;
hasCalledRef.current = true const pollName = `app-library-${service}-rating-${name}`;
const pollName = `app-library-${service}-rating-${name}`
const url = `${getBaseApiReact()}/polls/${pollName}`; const url = `${getBaseApiReact()}/polls/${pollName}`;
const response = await fetch(url, { const response = await fetch(url, {
@ -34,12 +42,12 @@ const getRating = useCallback(async (name, service)=> {
}); });
const responseData = await response.json(); const responseData = await response.json();
console.log('responseData', responseData) console.log("responseData", responseData);
if(responseData?.message?.includes('POLL_NO_EXISTS')){ if (responseData?.message?.includes("POLL_NO_EXISTS")) {
setHasPublishedRating(false) setHasPublishedRating(false);
} else if (responseData?.pollName) { } else if (responseData?.pollName) {
setPollInfo(responseData) setPollInfo(responseData);
setHasPublishedRating(true) setHasPublishedRating(true);
const urlVotes = `${getBaseApiReact()}/polls/votes/${pollName}`; const urlVotes = `${getBaseApiReact()}/polls/votes/${pollName}`;
const responseVotes = await fetch(urlVotes, { const responseVotes = await fetch(urlVotes, {
@ -50,16 +58,23 @@ const getRating = useCallback(async (name, service)=> {
}); });
const responseDataVotes = await responseVotes.json(); const responseDataVotes = await responseVotes.json();
setVotesInfo(responseDataVotes) setVotesInfo(responseDataVotes);
const voteCount = responseDataVotes.voteCounts const voteCount = responseDataVotes.voteCounts;
// Include initial value vote in the calculation // Include initial value vote in the calculation
const ratingVotes = voteCount.filter(vote => !vote.optionName.startsWith("initialValue-")); const ratingVotes = voteCount.filter(
const initialValueVote = voteCount.find(vote => vote.optionName.startsWith("initialValue-")); (vote) => !vote.optionName.startsWith("initialValue-")
console.log('initialValueVote', initialValueVote) );
const initialValueVote = voteCount.find((vote) =>
vote.optionName.startsWith("initialValue-")
);
console.log("initialValueVote", initialValueVote);
if (initialValueVote) { if (initialValueVote) {
// Convert "initialValue-X" to just "X" and add it to the ratingVotes array // Convert "initialValue-X" to just "X" and add it to the ratingVotes array
const initialRating = parseInt(initialValueVote.optionName.split("-")[1], 10); const initialRating = parseInt(
console.log('initialRating', initialRating) initialValueVote.optionName.split("-")[1],
10
);
console.log("initialRating", initialRating);
ratingVotes.push({ ratingVotes.push({
optionName: initialRating.toString(), optionName: initialRating.toString(),
voteCount: 1, voteCount: 1,
@ -70,38 +85,36 @@ const getRating = useCallback(async (name, service)=> {
let totalScore = 0; let totalScore = 0;
let totalVotes = 0; let totalVotes = 0;
ratingVotes.forEach(vote => { ratingVotes.forEach((vote) => {
const rating = parseInt(vote.optionName, 10); // Extract rating value (1-5) const rating = parseInt(vote.optionName, 10); // Extract rating value (1-5)
const count = vote.voteCount; const count = vote.voteCount;
totalScore += rating * count; // Weighted score totalScore += rating * count; // Weighted score
totalVotes += count; // Total number of votes totalVotes += count; // Total number of votes
}); });
console.log('ratingVotes', ratingVotes, totalScore, totalVotes) console.log("ratingVotes", ratingVotes, totalScore, totalVotes);
// Calculate average rating (ensure no division by zero) // Calculate average rating (ensure no division by zero)
const averageRating = totalVotes > 0 ? (totalScore / totalVotes) : 0; const averageRating = totalVotes > 0 ? totalScore / totalVotes : 0;
setValue(averageRating); setValue(averageRating);
} }
} catch (error) { } catch (error) {
console.log('error rating', error) console.log("error rating", error);
if(error?.message?.includes('POLL_NO_EXISTS')){ if (error?.message?.includes("POLL_NO_EXISTS")) {
setHasPublishedRating(false) setHasPublishedRating(false);
} }
} }
}, []);
}, [])
useEffect(() => { useEffect(() => {
if(hasCalledRef.current) return if (hasCalledRef.current) return;
if(!app) return if (!app) return;
getRating(app?.name, app?.service) getRating(app?.name, app?.service);
}, [getRating, app?.name]) }, [getRating, app?.name]);
const rateFunc = async (event, newValue) => { const rateFunc = async (event, newValue) => {
try { try {
if(!myName) throw new Error('You need a name to rate.') if (!myName) throw new Error("You need a name to rate.");
if(!app?.name) return if (!app?.name) return;
console.log('newValue', newValue) console.log("newValue", newValue);
const fee = await getFee("ARBITRARY"); const fee = await getFee("ARBITRARY");
await show({ await show({
@ -110,20 +123,27 @@ const getRating = useCallback(async (name, service)=> {
}); });
if (hasPublishedRating === false) { if (hasPublishedRating === false) {
const pollName = `app-library-${app.service}-rating-${app.name}` const pollName = `app-library-${app.service}-rating-${app.name}`;
const pollOptions = [`1, 2, 3, 4, 5, initialValue-${newValue}`] const pollOptions = [`1, 2, 3, 4, 5, initialValue-${newValue}`];
await new Promise((res, rej) => { await new Promise((res, rej) => {
chrome?.runtime?.sendMessage({ chrome?.runtime?.sendMessage(
action: 'CREATE_POLL', type: 'qortalRequest', payload: { {
pollName: pollName , pollDescription: `Rating for ${app.service} ${app.name}`, pollOptions: pollOptions , pollOwnerAddress : myName action: "CREATE_POLL",
} type: "qortalRequest",
}, (response) => { payload: {
console.log('response', response); pollName: pollName,
pollDescription: `Rating for ${app.service} ${app.name}`,
pollOptions: pollOptions,
pollOwnerAddress: myName,
},
},
(response) => {
console.log("response", response);
if (response.error) { if (response.error) {
rej(response?.message) rej(response?.message);
return return;
} else { } else {
res(response) res(response);
setInfoSnack({ setInfoSnack({
type: "success", type: "success",
message: message:
@ -131,24 +151,33 @@ const getRating = useCallback(async (name, service)=> {
}); });
setOpenSnack(true); 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 { } else {
res(response) 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({ setInfoSnack({
type: "success", type: "success",
message: message:
@ -156,10 +185,10 @@ const getRating = useCallback(async (name, service)=> {
}); });
setOpenSnack(true); setOpenSnack(true);
} }
});
})
} }
);
});
}
} catch (error) { } catch (error) {
setInfoSnack({ setInfoSnack({
type: "error", type: "error",
@ -167,15 +196,34 @@ const getRating = useCallback(async (name, service)=> {
}); });
setOpenSnack(true); setOpenSnack(true);
} }
} };
console.log('vvotes', (votesInfo?.totalVotes ?? 0 ) + votesInfo?.voteCounts?.length === 6 ? 1 : 0, votesInfo) console.log(
"vvotes",
(votesInfo?.totalVotes ?? 0) + votesInfo?.voteCounts?.length === 6 ? 1 : 0,
votesInfo
);
return ( return (
<div> <div>
<Box sx={{ <Box
display: 'flex', sx={{
gap: '5px', display: "flex",
alignItems: 'center' alignItems: "center",
}}> flexDirection: ratingCountPosition === "top" ? "column" : "row",
}}
>
{ratingCountPosition === "top" && (
<>
<AppInfoUserName>
{(votesInfo?.totalVotes ?? 0) +
(votesInfo?.voteCounts?.length === 6 ? 1 : 0)}{" "}
{" RATINGS"}
</AppInfoUserName>
<Spacer height="6px" />
<AppInfoUserName>{value?.toFixed(1)}</AppInfoUserName>
<Spacer height="6px" />
</>
)}
<Rating <Rating
value={value} value={value}
onChange={rateFunc} onChange={rateFunc}
@ -189,9 +237,10 @@ const getRating = useCallback(async (name, service)=> {
gap: "2px", gap: "2px",
}} }}
/> />
{ratingCountPosition && ( {ratingCountPosition === "right" && (
<AppInfoUserName> <AppInfoUserName>
{ (votesInfo?.totalVotes ?? 0) + (votesInfo?.voteCounts?.length === 6 ? 1 : 0)} {(votesInfo?.totalVotes ?? 0) +
(votesInfo?.voteCounts?.length === 6 ? 1 : 0)}
</AppInfoUserName> </AppInfoUserName>
)} )}
</Box> </Box>

View File

@ -278,3 +278,34 @@ import {
fontWeight: 600, fontWeight: 600,
fontSize: '10px' fontSize: '10px'
})); }));
export const AppsCategoryInfo = styled(Box)(({ theme }) => ({
display: "flex",
alignItems: 'center',
width: '100%',
}));
export const AppsCategoryInfoSub = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: 'column',
}));
export const AppsCategoryInfoLabel = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 700,
lineHeight: 1.2,
color: '#8D8F93',
}));
export const AppsCategoryInfoValue = styled(Typography)(({ theme }) => ({
fontSize: '12px',
fontWeight: 500,
lineHeight: 1.2,
color: '#8D8F93',
}));
export const AppsInfoDescription = styled(Typography)(({ theme }) => ({
fontSize: '13px',
fontWeight: 300,
lineHeight: 1.2,
width: '90%',
textAlign: 'start'
}));

View File

@ -267,7 +267,7 @@ export const Apps = ({ mode, setMode, show , myName}) => {
hasPublishApp={!!(myApp || myWebsite)} hasPublishApp={!!(myApp || myWebsite)}
/> />
)} )}
{mode === "appInfo" && <AppInfo app={selectedAppInfo} />} {mode === "appInfo" && <AppInfo app={selectedAppInfo} myName={myName} />}
{mode === "publish" && <AppPublish names={myName ? [myName] : []} categories={categories} />} {mode === "publish" && <AppPublish names={myName ? [myName] : []} categories={categories} />}
{tabs.map((tab) => { {tabs.map((tab) => {