diff --git a/src/assets/svgs/StarEmpty.tsx b/src/assets/svgs/StarEmpty.tsx
new file mode 100644
index 0000000..8375111
--- /dev/null
+++ b/src/assets/svgs/StarEmpty.tsx
@@ -0,0 +1,16 @@
+
+
+
+import React from 'react';
+
+
+export const StarEmptyIcon = () => {
+ return (
+
+
+
+
+ );
+};
diff --git a/src/assets/svgs/StarFilled.tsx b/src/assets/svgs/StarFilled.tsx
new file mode 100644
index 0000000..fb82e49
--- /dev/null
+++ b/src/assets/svgs/StarFilled.tsx
@@ -0,0 +1,18 @@
+import React from "react";
+
+export const StarFilledIcon = () => {
+ return (
+
+ );
+};
diff --git a/src/components/Apps/AppInfoSnippet.tsx b/src/components/Apps/AppInfoSnippet.tsx
index 9153945..e63b4d0 100644
--- a/src/components/Apps/AppInfoSnippet.tsx
+++ b/src/components/Apps/AppInfoSnippet.tsx
@@ -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}
+
diff --git a/src/components/Apps/AppRating.tsx b/src/components/Apps/AppRating.tsx
index b063563..5e58271 100644
--- a/src/components/Apps/AppRating.tsx
+++ b/src/components/Apps/AppRating.tsx
@@ -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)
+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 (
-
{
-
- }} precision={0.1} />
+
+ }
+ emptyIcon={}
+ sx={{
+ display: "flex",
+ gap: "2px",
+ }}
+ />
+ {ratingCountPosition && (
+
+ { (votesInfo?.totalVotes ?? 0) + (votesInfo?.voteCounts?.length === 6 ? 1 : 0)}
+
+ )}
+
+
+
- )
-}
+ );
+};
diff --git a/src/components/Apps/Apps-styles.tsx b/src/components/Apps/Apps-styles.tsx
index 74b0526..ca0f660 100644
--- a/src/components/Apps/Apps-styles.tsx
+++ b/src/components/Apps/Apps-styles.tsx
@@ -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'
}));
diff --git a/src/components/Apps/Apps.tsx b/src/components/Apps/Apps.tsx
index 2ea6037..0c2d1f3 100644
--- a/src/components/Apps/Apps.tsx
+++ b/src/components/Apps/Apps.tsx
@@ -263,6 +263,8 @@ export const Apps = ({ mode, setMode, show , myName}) => {
downloadedQapps={downloadedQapps}
availableQapps={availableQapps}
setMode={setMode}
+ myName={myName}
+ hasPublishApp={!!(myApp || myWebsite)}
/>
)}
{mode === "appInfo" && }
diff --git a/src/components/Apps/AppsLibrary.tsx b/src/components/Apps/AppsLibrary.tsx
index 8f89078..d220a4e 100644
--- a/src/components/Apps/AppsLibrary.tsx
+++ b/src/components/Apps/AppsLibrary.tsx
@@ -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 ;
+ return ;
};
- 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 (
@@ -162,7 +174,9 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode }) => {
{searchedList?.length > 0 ? (
-
+
{
})}
- Create Apps!
+ {hasPublishApp ? 'Update Apps!' : 'Create Apps!'}
@@ -245,7 +259,7 @@ export const AppsLibrary = ({ downloadedQapps, availableQapps, setMode }) => {
setMode('publish')
}}>
- Publish
+ {hasPublishApp ? 'Update' : 'Publish'}
diff --git a/src/qortalRequests/get.ts b/src/qortalRequests/get.ts
index 1f460ec..104c4e5 100644
--- a/src/qortalRequests/get.ts
+++ b/src/qortalRequests/get.ts
@@ -74,7 +74,7 @@ const _createPoll = async (pollName, pollDescription, options) => {
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
if (!res?.signature)
- throw new Error("Transaction was not able to be processed");
+ throw new Error(res?.message || "Transaction was not able to be processed");
return res;
} else {
throw new Error("User declined request");
@@ -172,8 +172,9 @@ const _voteOnPoll = async (pollName, optionIndex, optionName) => {
});
const signedBytes = Base58.encode(tx.signedBytes);
const res = await processTransactionVersion2(signedBytes);
+ console.log('res', res)
if (!res?.signature)
- throw new Error("Transaction was not able to be processed");
+ throw new Error(res?.message || "Transaction was not able to be processed");
return res;
} else {
throw new Error("User declined request");