diff --git a/components/LastUpdatedTooltip.tsx b/components/LastUpdatedTooltip.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e13167ac33958bde32af849c92364812f11ee185 --- /dev/null +++ b/components/LastUpdatedTooltip.tsx @@ -0,0 +1,103 @@ +import React, { useEffect, useRef } from "react"; +import { + Tooltip, + TooltipProvider, + TooltipTrigger, + TooltipContent, +} from "@/components/ui/tooltip"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faClock } from "@fortawesome/free-solid-svg-icons"; +import moment from "moment"; + +type LastUpdatedProps = { + lastUpdatedAt: string | Date; +}; + +const calculateTimeDiff = (date: string | Date) => { + if (!date) return 0; + const timeDiff = moment.utc(date).diff(moment.utc(), "minutes"); // Calculate the difference in minutes + return Math.abs(timeDiff); +}; + +const LastUpdatedTooltip: React.FC<LastUpdatedProps> = ({ lastUpdatedAt }) => { + const hasTimeDiff = + typeof lastUpdatedAt === "string" && + lastUpdatedAt.split(" ").includes("ago"); + + const timeDiff = hasTimeDiff + ? Number(lastUpdatedAt.split(" ")[0]) + : calculateTimeDiff(lastUpdatedAt); + const iconRef = useRef<SVGSVGElement | null>(null); + + const getIconColor = (timeDiff: number) => { + let colorClass = ""; + let fillColor = ""; + + switch (true) { + case timeDiff <= 10: + colorClass = "text-green-500 "; + fillColor = "#48bb78"; // Green + break; + case timeDiff <= 60: + colorClass = "text-orange-500 bold"; + fillColor = "#ed8936"; // Orange + break; + default: + colorClass = "text-red-500 bold"; + fillColor = "#f56565"; // Red + break; + } + + return { colorClass, fillColor }; + }; + + useEffect(() => { + if (iconRef.current) { + const path = iconRef.current.querySelector("path"); + if (path) { + path.setAttribute("fill", getIconColor(timeDiff).fillColor); + } + } + }, [timeDiff]); + + const { colorClass } = getIconColor(timeDiff); + const tooltipMessage = `Last Updated - ${timeDiff} ${ + timeDiff === 1 ? "min" : "mins" + } ago`; + + return ( + <div className="flex items-center space-x-2"> + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> + <span> + <FontAwesomeIcon + icon={faClock} + size="lg" + data-testid="last-updated-icon" + ref={iconRef} + className={colorClass} + /> + <span className={`${colorClass} font-bold ml-2`}>{`${timeDiff} ${ + timeDiff === 1 ? "min" : "mins" + } ago`}</span> + </span> + </TooltipTrigger> + <TooltipContent + side="top" + align="start" + sideOffset={5} + alignOffset={10} + className="border-0" + > + <div className="bg-theme text-text p-2 ml-3"> + <p>{tooltipMessage}</p> + </div> + </TooltipContent> + </Tooltip> + </TooltipProvider> + </div> + ); +}; + +export default LastUpdatedTooltip; diff --git a/components/LastUpdatedWitnessIcon.tsx b/components/LastUpdatedWitnessIcon.tsx deleted file mode 100644 index d4a5f138f003b25bfcfdffe47f143656781f2fc1..0000000000000000000000000000000000000000 --- a/components/LastUpdatedWitnessIcon.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { - Tooltip, - TooltipProvider, - TooltipTrigger, - TooltipContent, -} from "@/components/ui/tooltip"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faClock } from '@fortawesome/free-solid-svg-icons'; -import moment from "moment"; - -type LastUpdatedProps = { - lastUpdatedAt: string | Date; // Allow both string and Date types -}; - -const calculateTimeDiff = (date: string | Date) => { - if (!date) return 0; - const timeDiff = moment.utc(date).diff(moment.utc(), 'minutes'); // Calculate the difference in minutes - return Math.abs(timeDiff); -}; - -const LastUpdatedWitnessIcon: React.FC<LastUpdatedProps> = ({ lastUpdatedAt }) => { - const timeDiff = calculateTimeDiff(lastUpdatedAt); - const iconRef = useRef<SVGSVGElement | null>(null); - - const getIconColor = (timeDiff: number) => { - let colorClass = ''; - let fillColor = ''; - - switch (true) { - case timeDiff <= 10: - colorClass = 'text-green-500 '; - fillColor = '#48bb78'; // Green - break; - case timeDiff <= 60: - colorClass = 'text-orange-500 bold'; - fillColor = '#ed8936'; // Orange - break; - default: - colorClass = 'text-red-500 bold'; - fillColor = '#f56565'; // Red - break; - } - - return { colorClass, fillColor }; - }; - - useEffect(() => { - if (iconRef.current) { - const path = iconRef.current.querySelector('path'); - if (path) { - path.setAttribute('fill', getIconColor(timeDiff).fillColor); - } - } - }, [timeDiff]); - - const { colorClass } = getIconColor(timeDiff); - const tooltipMessage = `Last Updated - ${timeDiff} ${timeDiff === 1 ? 'min' : 'mins'} ago`; - - return ( - <div className="flex items-center space-x-2"> - <TooltipProvider> - <Tooltip> - <TooltipTrigger asChild> - <span> - <FontAwesomeIcon - icon={faClock} - size="lg" - data-testid="last-updated-icon" - ref={iconRef} - className={colorClass} - /> - </span> - </TooltipTrigger> - <TooltipContent side="top" align="start" sideOffset={5} alignOffset={10}> - <div className="bg-theme text-text p-2 ml-3"> - <p>{tooltipMessage}</p> - </div> - </TooltipContent> - </Tooltip> - <span className={`${colorClass} font-bold`}>{`${timeDiff} ${timeDiff === 1 ? 'min' : 'mins'} ago`}</span> - </TooltipProvider> - </div> - ); -}; - -export default LastUpdatedWitnessIcon; diff --git a/components/ThemeToggle.tsx b/components/ThemeToggle.tsx index 69170ee880cbb990c81b3d342215ff8590fee022..20e892f46bf5a4a07a5f5a3debc49c7014e7f687 100644 --- a/components/ThemeToggle.tsx +++ b/components/ThemeToggle.tsx @@ -1,6 +1,5 @@ /* From Uiverse.io by jubayer-10 */ import { useTheme } from "@/contexts/ThemeContext"; -import { useEffect } from 'react'; const ThemeToggle = () => { const { theme, toggleTheme } = useTheme(); @@ -14,7 +13,7 @@ const ThemeToggle = () => { checked={theme === "dark"} onChange={toggleTheme} /> - <div className="relative w-[50px] h-[25px] bg-buttonBg peer-checked:bg-zinc-500 rounded-full after:absolute after:content-[''] after:w-[20px] after:h-[20px] after:bg-gradient-to-r from-orange-500 to-yellow-400 peer-checked:after:from-zinc-900 peer-checked:after:to-zinc-900 after:rounded-full after:top-[3px] after:left-[2px] peer-checked:after:left-[48px] peer-checked:after:translate-x-[-100%] shadow-sm duration-300 after:duration-300 after:shadow-md" /> + <div className="relative w-[50px] h-[25px] bg-buttonBg peer-checked:bg-zinc-500 rounded-full after:absolute after:content-[''] after:w-[20px] after:h-[20px] after:bg-gradient-to-r from-orange-500 to-yellow-400 peer-checked:after:from-zinc-900 peer-checked:after:to-zinc-900 after:rounded-full after:top-[3px] after:left-[2px] peer-checked:after:left-[48px] peer-checked:after:translate-x-[-100%] shadow-sm after:shadow-md" /> <svg height="50" width="50" diff --git a/components/Witnesses/VotersDialog.tsx b/components/Witnesses/VotersDialog.tsx index c5a0e2e06afebe3014044ba916828356894720a9..f93757c08a5c1f3982a707586623d97d11573bc6 100644 --- a/components/Witnesses/VotersDialog.tsx +++ b/components/Witnesses/VotersDialog.tsx @@ -1,7 +1,7 @@ -import { useState } from "react"; +import { useState ,useEffect } from "react"; import Link from "next/link"; import { MoveDown, MoveUp, Loader2 } from "lucide-react"; - +import { Switch } from "../ui/switch"; import { cn, formatNumber } from "@/lib/utils"; import useWitnessVoters from "@/hooks/api/common/useWitnessVoters"; import { Dialog, DialogContent } from "@/components/ui/dialog"; @@ -13,10 +13,13 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { Switch } from "../ui/switch"; import useWitnessDetails from "@/hooks/api/common/useWitnessDetails"; import CustomPagination from "../CustomPagination"; import { config } from "@/Config"; +import LastUpdatedTooltip from "../LastUpdatedTooltip"; +import { convertVestsToHP } from "@/utils/Calculations"; +import fetchingService from "@/services/FetchingService"; +import { useHiveChainContext } from "@/contexts/HiveChainContext"; type VotersDialogProps = { accountName: string; @@ -41,6 +44,7 @@ const VotersDialog: React.FC<VotersDialogProps> = ({ const [sortKey, setSortKey] = useState<string>("vests"); const [isAsc, setIsAsc] = useState<boolean>(false); const [pageNum, setPageNum] = useState<number>(1); + const [isHP, setIsHP] = useState<boolean>(true); // Toggle state const { witnessDetails } = useWitnessDetails(accountName, true); const { witnessVoters, isWitnessVotersLoading } = useWitnessVoters( @@ -69,6 +73,51 @@ const VotersDialog: React.FC<VotersDialogProps> = ({ } else return null; }; + interface Supply { + amount: string; + nai: string; + precision: number; + } + + const [totalVestingShares, setTotalVestingShares] = useState<Supply>({ + amount: "0", + nai: "", + precision: 0, + }); + + const [totalVestingFundHive, setTotalVestingFundHive] = useState<Supply>({ + amount: "0", + nai: "", + precision: 0, + }); + + const { hiveChain } = useHiveChainContext(); + + useEffect(() => { + const fetchDynamicGlobalProperties = async () => { + + const dynamicGlobalProperties = await fetchingService.getDynamicGlobalProperties(); + const _totalVestingfundHive = dynamicGlobalProperties.total_vesting_fund_hive; + const _totalVestingShares = dynamicGlobalProperties.total_vesting_shares; + + setTotalVestingFundHive(_totalVestingfundHive); + setTotalVestingShares(_totalVestingShares); + } + + fetchDynamicGlobalProperties(); + + }, []); + + const fetchHivePower = (value: string, isHP: boolean): string => { + + if (isHP) { + if (!hiveChain) return ""; + return convertVestsToHP(hiveChain,value,totalVestingFundHive,totalVestingShares); + + } + return formatNumber(parseInt(value),true,false)+ " Vests"; // Return raw vests if not toggled to HP + }; + return ( <Dialog open={isVotersOpen} @@ -91,13 +140,26 @@ const VotersDialog: React.FC<VotersDialogProps> = ({ <Loader2 className="animate-spin mt-1 h-4 w-4 ml-3 ..." /> )} </div> - <div className="flex justify-between"> - {witnessDetails && ( - <p>Last updated : {witnessDetails.votes_updated_at}</p> - )} - </div> + <div className="flex justify-between items-center w-full"> + <div className="flex items-center"> + {witnessDetails && ( + <LastUpdatedTooltip + lastUpdatedAt={witnessDetails.votes_updated_at} + /> + )} + </div> - <CustomPagination + <div className="flex items-center"> + <label className="mr-2">Vests</label> + <Switch + checked={isHP} + onCheckedChange={() => setIsHP((prev) => !prev)} + className="mx-1" + /> + <label>HP</label> + </div> + </div> + <CustomPagination currentPage={pageNum} onPageChange={(newPage: number) => { setPageNum(newPage); @@ -159,19 +221,19 @@ const VotersDialog: React.FC<VotersDialogProps> = ({ className="text-right" data-testid="vote-power" > - {formatNumber(voter.vests, true)} + {fetchHivePower(voter.vests.toString(), isHP)} </TableCell> <TableCell className="text-right" data-testid="account-power" > - {formatNumber(voter.account_vests, true)} + {fetchHivePower(voter.account_vests.toString(), isHP)} </TableCell> <TableCell className="text-right" data-testid="proxied-power" > - {formatNumber(voter.proxied_vests, true)} + {fetchHivePower(voter.proxied_vests.toString(), isHP)} </TableCell> </TableRow> ))} diff --git a/components/Witnesses/VotesHistoryDialog.tsx b/components/Witnesses/VotesHistoryDialog.tsx index 1225a1bf372e728c92ec32f5d4dc470ff45bf894..04a1e755ac22ee8497af8c9a83c65aa16ebb05bd 100644 --- a/components/Witnesses/VotesHistoryDialog.tsx +++ b/components/Witnesses/VotesHistoryDialog.tsx @@ -22,6 +22,10 @@ import JumpToPage from "../JumpToPage"; import CustomPagination from "../CustomPagination"; import DateTimePicker from "../DateTimePicker"; import useWitnessDetails from "@/hooks/api/common/useWitnessDetails"; +import LastUpdatedTooltip from "../LastUpdatedTooltip"; +import { useHiveChainContext } from "@/contexts/HiveChainContext"; +import { convertVestsToHP } from "@/utils/Calculations"; +import fetchingService from "@/services/FetchingService"; type VotersDialogProps = { accountName: string; @@ -52,6 +56,7 @@ const VotesHistoryDialog: React.FC<VotersDialogProps> = ({ moment().subtract(7, "days").toDate() ); const [toDate, setToDate] = useState<Date>(moment().toDate()); + const [isHP, setIsHP] = useState<boolean>(true); // Toggle state const { witnessDetails } = useWitnessDetails(accountName, true) as any; const { votesHistory, isVotesHistoryLoading } = useWitnessVotesHistory( @@ -77,6 +82,46 @@ const VotesHistoryDialog: React.FC<VotersDialogProps> = ({ votesHistory?.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE - 1) ); }; + interface Supply { + amount: string; + nai: string; + precision: number; + } + + const [totalVestingShares, setTotalVestingShares] = useState<Supply>({ + amount: "0", + nai: "", + precision: 0, + }); + const [totalVestingFundHive, setTotalVestingFundHive] = useState<Supply>({ + amount: "0", + nai: "", + precision: 0, + }); + const { hiveChain } = useHiveChainContext(); + + useEffect(() => { + const fetchDynamicGlobalProperties = async () => { + + const dynamicGlobalProperties = await fetchingService.getDynamicGlobalProperties(); + const _totalVestingfundHive = dynamicGlobalProperties.total_vesting_fund_hive; + const _totalVestingShares = dynamicGlobalProperties.total_vesting_shares; + + setTotalVestingFundHive(_totalVestingfundHive); + setTotalVestingShares(_totalVestingShares); + } + + fetchDynamicGlobalProperties(); + + }, []); + + const fetchHivePower = (value: string, isHP: boolean): string => { + if (isHP) { + if (!hiveChain) return ""; + return convertVestsToHP(hiveChain,value,totalVestingFundHive,totalVestingShares); + } + return formatNumber(parseInt(value),true,false)+ " Vests"; // Return raw vests if not toggled to HP + }; return ( <Dialog @@ -84,13 +129,9 @@ const VotesHistoryDialog: React.FC<VotersDialogProps> = ({ onOpenChange={changeVoteHistoryDialogue} > <DialogContent - className={cn( - "max-w-2xl max-h-[700px] bg-explorer-bg-start overflow-auto", - { - "flex column justify-center items-center": !votesHistory, - "h-3/4": votesHistory?.length >= 16, - } - )} + className={cn("h-3/4 max-w-4xl bg-explorer-bg-start", { + "flex justify-center items-center": !votesHistory, + })} data-testid="votes-history-dialog" > {votesHistory ? ( @@ -104,11 +145,25 @@ const VotesHistoryDialog: React.FC<VotersDialogProps> = ({ <Loader2 className="animate-spin mt-1 h-4 w-4 ml-3 ..." /> )} </div> - <div className="flex justify-between"> - {witnessDetails && ( - <p>Last updated : {witnessDetails.votes_updated_at}</p> - )} - </div> + <div className="flex justify-between items-center w-full"> + <div className="flex items-center"> + {witnessDetails && ( + <LastUpdatedTooltip + lastUpdatedAt={witnessDetails.votes_updated_at} + /> + )} + </div> + + <div className="flex items-center"> + <label className="mr-2">Vests</label> + <Switch + checked={isHP} + onCheckedChange={() => setIsHP((prev) => !prev)} + className="mx-1" + /> + <label>HP</label> + </div> + </div> <div className="flex justify-around items-center bg-explorer-bg-start rounded text-text p-2"> <div> <p>From: </p> @@ -203,8 +258,7 @@ const VotesHistoryDialog: React.FC<VotersDialogProps> = ({ <TableCell className="text-right" data-testid="current-voter-power" - > - {formatNumber(vote.vests, true)} + > {fetchHivePower(vote.vests.toString(), isHP)} </TableCell> </TableRow> ))} diff --git a/components/account/AccountBalanceCard.tsx b/components/account/AccountBalanceCard.tsx index 5b8b5baab8bb3e8e08ccb4e50eb94058e0a66fea..9fb8dc21f8068882ecc29c7d1b84422539f4ab78 100644 --- a/components/account/AccountBalanceCard.tsx +++ b/components/account/AccountBalanceCard.tsx @@ -88,7 +88,7 @@ const AccountBalanceCard: React.FC<AccountBalanceCardProps> = ({ }, 0); setTotalBalance(newBalance); - }, [keys, userDetails, cardNameMap, grabNumericValue]); + }, [keys, userDetails]); diff --git a/components/account/AccountWitnessVotesCard.tsx b/components/account/AccountWitnessVotesCard.tsx index 0ded391bafda9dbd00ad27cee3749b73bd308490..16e18c5f970613d560fa6781a800a0dc8bd2332b 100644 --- a/components/account/AccountWitnessVotesCard.tsx +++ b/components/account/AccountWitnessVotesCard.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, Fragment } from "react"; +import { useState, useEffect, useCallback, Fragment } from "react"; import { ArrowDown, ArrowUp } from "lucide-react"; import Link from "next/link"; import { Card, CardContent, CardHeader } from "../ui/card"; @@ -56,27 +56,26 @@ const AccountWitnessVotesCard: React.FC<AccountWitnessVotesCardProps> = ({ setIsPropertiesHidden(!isPropertiesHidden); }; - const fetchWitnessVotes = async ( - proxy: string, - currentDepth: number, - maxDepth: number, - proxiesList: string[], - ): Promise<any[]> => { - if (currentDepth > maxDepth || (!proxy)) return []; - - const result = await fetchingService.getAccount(proxy); - // Add the current proxy to the proxies list - proxiesList.push(proxy); - - // If there's another proxy, go deeper if within maxDepth - if (result.proxy && currentDepth < maxDepth) { - const nestedVotes = await fetchWitnessVotes(result.proxy, currentDepth + 1, maxDepth, proxiesList); - return [...result.witness_votes, ...nestedVotes]; - } - - return result.witness_votes?.slice().sort( - (a, b) => a.toLowerCase().localeCompare(b.toLowerCase())) || []; - }; + const fetchWitnessVotes = useCallback( + async (proxy: string, currentDepth: number, maxDepth: number, proxiesList: string[]): Promise<any[]> => { + if (currentDepth > maxDepth || !proxy) return []; + + const result = await fetchingService.getAccount(proxy); + + // Add the current proxy to the proxies list + proxiesList.push(proxy); + + // If there's another proxy, go deeper if within maxDepth + if (result.proxy && currentDepth < maxDepth) { + const nestedVotes = await fetchWitnessVotes(result.proxy, currentDepth + 1, maxDepth, proxiesList); + return [...result.witness_votes, ...nestedVotes]; + } + + return result.witness_votes?.slice().sort( + (a, b) => a.toLowerCase().localeCompare(b.toLowerCase())) || []; + }, + [] + ); useEffect(() => { @@ -88,7 +87,7 @@ const AccountWitnessVotesCard: React.FC<AccountWitnessVotesCardProps> = ({ setAllProxies(proxiesList); // Update state with the complete list of proxies }; getVotersForProxy(); - }, [proxy]); + }, [proxy,fetchWitnessVotes]); if (proxy != null && proxy.length > 0) { return ( diff --git a/components/home/SearchesSection.tsx b/components/home/SearchesSection.tsx index 2419dd675b9b0fa27c4f9e2203b56adef7ea532f..120cefc333577d40045605fd4b6717139c538c83 100644 --- a/components/home/SearchesSection.tsx +++ b/components/home/SearchesSection.tsx @@ -44,9 +44,21 @@ import { } from "../ui/accordion"; import usePermlinkSearch from "@/hooks/api/common/usePermlinkSearch"; import CommentPermlinkResultTable from "./searches/CommentPermlinkResultTable"; +import { + Select, + SelectTrigger, + SelectValue, + SelectGroup, + SelectContent, + SelectItem, +} from "../ui/select"; +import { capitalizeFirst } from "@/utils/StringUtils"; interface SearchesSectionProps {} +type CommentType = "all" | "post" | "comment"; +const COMMENT_TYPES = ["all", "post", "comment"]; + const SearchesSection: React.FC<SearchesSectionProps> = ({}) => { const [accordionValue, setAccordionValue] = useState<string>("block"); const [previousCommentSearchProps, setPreviousCommentSearchProps] = useState< @@ -75,6 +87,9 @@ const SearchesSection: React.FC<SearchesSectionProps> = ({}) => { const [accountOperationsSearchProps, setAccountOperationsSearchProps] = useState<Explorer.AccountSearchOperationsProps | undefined>(undefined); const [isAllSearchLoading, setIsAllSearchLoading] = useState<boolean>(false); + const [commentType, setCommentType] = useState<CommentType | undefined>( + undefined + ); const searchesRef = useRef<HTMLDivElement | null>(null); @@ -168,6 +183,7 @@ const SearchesSection: React.FC<SearchesSectionProps> = ({}) => { setIsAllSearchLoading(true); setPermlinkSearchProps(props); setCommentPaginationPage(1); + setCommentType(undefined); setLastSearchKey("comment-permlink"); }; @@ -347,6 +363,16 @@ const SearchesSection: React.FC<SearchesSectionProps> = ({}) => { return `/block/${blockNumber}${getPageUrlParams(urlParams)}`; }; + const handleChangeCommentType = (e: CommentType) => { + setCommentType(e); + setPermlinkSearchProps((prev: any) => { + return { + ...prev, + commentType: e, + }; + }); + }; + return ( <> <Card @@ -377,7 +403,7 @@ const SearchesSection: React.FC<SearchesSectionProps> = ({}) => { </AccordionItem> <AccordionItem value="account"> <AccordionTrigger className="p-3 mb-2"> - Account search + Account Search </AccordionTrigger> <AccordionContent className="px-2 flex flex-col gap-y-4"> <AccountSearch @@ -390,7 +416,7 @@ const SearchesSection: React.FC<SearchesSectionProps> = ({}) => { </AccordionItem> <AccordionItem value="comment-permlink"> <AccordionTrigger className="p-3 mb-2"> - Comment permlink search + Permalink Search </AccordionTrigger> <AccordionContent className="px-2 flex flex-col gap-y-4"> <CommentsPermlinkSearch @@ -404,7 +430,7 @@ const SearchesSection: React.FC<SearchesSectionProps> = ({}) => { </AccordionItem> <AccordionItem value="comment"> <AccordionTrigger className="p-3 mb-2"> - Comment search + Comment Search </AccordionTrigger> <AccordionContent className="px-2 flex flex-col gap-y-4"> <CommentsSearch @@ -424,7 +450,7 @@ const SearchesSection: React.FC<SearchesSectionProps> = ({}) => { > {blockSearchData && lastSearchKey === "block" && ( <div - className=" bg-theme dark:bg-theme p-2 md: h-fit rounded" + className="bg-theme dark:bg-theme p-2 md: h-fit rounded" data-testid="result-section" > <div @@ -456,16 +482,33 @@ const SearchesSection: React.FC<SearchesSectionProps> = ({}) => { )} {permlinkSearchData && lastSearchKey === "comment-permlink" && ( <div> - <div - className="text-center my-5" - data-testid="result-section-header" - > - Results: + <div className="flex justify-end my-4"> + <Select + onValueChange={handleChangeCommentType} + value={commentType} + > + <SelectTrigger className="w-[180px] border-0"> + <SelectValue placeholder="Comment Type" /> + </SelectTrigger> + <SelectContent className="border-0"> + <SelectGroup> + {COMMENT_TYPES.map((type, index) => ( + <SelectItem + key={index} + value={`${type}`} + > + {capitalizeFirst(type)} + </SelectItem> + ))} + </SelectGroup> + </SelectContent> + </Select> </div> <div className="flex flex-wrap"> {permlinkSearchData.total_permlinks > 0 ? ( <CommentPermlinkResultTable data={permlinkSearchData.permlinks_result} + accountName={permlinkSearchProps?.accountName} /> ) : ( <div className="flex justify-center w-full"> diff --git a/components/home/searches/BlockSearch.tsx b/components/home/searches/BlockSearch.tsx index f52b64c24a13fee4ef180bbd2dbd6effcb30c3db..29da9c81192cb3dc25d8508526ddb7fb19097b23 100644 --- a/components/home/searches/BlockSearch.tsx +++ b/components/home/searches/BlockSearch.tsx @@ -136,8 +136,9 @@ const BlockSearch: React.FC<BlockSearchProps> = ({ )} /> </div> - <div className="flex flex-col "> - <div className="flex mb-4 items-center"> + {/*TODO: Hide this for now, NOT REMOVE IT. It will be moved to search operation seaction when BE is done */} + {/* <div className="flex flex-col "> */} + {/* <div className="flex mb-4 items-center"> <label>Property</label> <TooltipProvider> <Tooltip> @@ -152,8 +153,8 @@ const BlockSearch: React.FC<BlockSearchProps> = ({ </TooltipContent> </Tooltip> </TooltipProvider> - </div> - <div className="flex"> + </div> */} + {/* <div className="flex"> <Select onValueChange={onSelect} value={selectedIndex} @@ -214,8 +215,8 @@ const BlockSearch: React.FC<BlockSearchProps> = ({ </Button> )} </div> - </div> - <div className="flex flex-col"> + </div> */} + {/* <div className="flex flex-col"> <Input className="w-1/2 border-0 border-b-2 bg-theme text-text" type="text" @@ -229,7 +230,7 @@ const BlockSearch: React.FC<BlockSearchProps> = ({ !selectedKeys.length } /> - </div> + </div> */} <div className="flex items-center "> <Button data-testid="block-search-btn" diff --git a/components/home/searches/CommentPermlinkResultTable.tsx b/components/home/searches/CommentPermlinkResultTable.tsx index 64128f0aeb23c0db3bd2ae45e6b2ee1bd7af3815..a2c8bb9c1cbdd4d29d818be3ec336070cc1d773f 100644 --- a/components/home/searches/CommentPermlinkResultTable.tsx +++ b/components/home/searches/CommentPermlinkResultTable.tsx @@ -8,9 +8,12 @@ import { TableBody, } from "@/components/ui/table"; import Hive from "@/types/Hive"; +import Link from "next/link"; +import { formatAndDelocalizeTime } from "@/utils/TimeUtils"; interface CommentPermlinkResultTableProps { data: Hive.Permlink[]; + accountName: string | undefined; } const TABLE_CELLS = [ @@ -34,21 +37,34 @@ const buildTableHeader = () => { }); }; -const buildTableBody = (data: Hive.Permlink[]) => { - if (!data || !data.length) return; +const buildTableBody = ( + data: Hive.Permlink[], + accountName: string | undefined +) => { + if (!data || !data.length || !accountName) return; return data.map( ({ block, operation_id, permlink, timestamp, trx_id }: any) => { return ( <React.Fragment key={trx_id}> <TableRow className="border-b border-gray-700 hover:bg-inherit p-[10px]"> - <TableCell className="text-left text-text">{block}</TableCell> + <TableCell className="text-left text-link"> + <Link href={`/block/${block}`}>{block}</Link> + </TableCell> <TableCell className="text-left text-text"> {operation_id} </TableCell> - <TableCell className="text-right">{permlink}</TableCell> - <TableCell className="text-left text-text">{timestamp}</TableCell> - <TableCell className="text-left text-text">{trx_id}</TableCell> + <TableCell className="text-center text-link"> + <Link href={`/comments/@${accountName}?&permlink=${permlink}`}> + {permlink} + </Link> + </TableCell> + <TableCell className="text-left text-text"> + {formatAndDelocalizeTime(timestamp)} + </TableCell> + <TableCell className="text-left text-link"> + <Link href={`/transaction/${trx_id}`}> {trx_id}</Link> + </TableCell> </TableRow> </React.Fragment> ); @@ -58,6 +74,7 @@ const buildTableBody = (data: Hive.Permlink[]) => { const CommentPermlinkResultTable = ({ data, + accountName, }: CommentPermlinkResultTableProps) => { return ( <> @@ -67,7 +84,7 @@ const CommentPermlinkResultTable = ({ <TableHeader> <TableRow>{buildTableHeader()}</TableRow> </TableHeader> - <TableBody>{buildTableBody(data)}</TableBody> + <TableBody>{buildTableBody(data, accountName)}</TableBody> </Table> </div> </div> diff --git a/lib/utils.ts b/lib/utils.ts index 2374210fc1f1162c44777c5642666ac392cb89bc..25dac000598a92628f25fb312bbcb4be87bfde5e 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -101,6 +101,7 @@ export const convertCommentsOperationResultToTableOperations = ( blockNumber: operation.block, operationId: Number(operation.operation_id), trxId: operation.trx_id, + timestamp: operation.timestamp, })); }; diff --git a/pages/witnesses.tsx b/pages/witnesses.tsx index c75258c6c34379a40c5a464d785e20d9b4c39138..42b3c716fd8f41a5c91405786a81c2bb02d82e2d 100644 --- a/pages/witnesses.tsx +++ b/pages/witnesses.tsx @@ -25,7 +25,7 @@ import { import VotersDialog from "@/components/Witnesses/VotersDialog"; import VotesHistoryDialog from "@/components/Witnesses/VotesHistoryDialog"; import WitnessScheduleIcon from "@/components/WitnessScheduleIcon"; -import LastUpdatedWitnessIcon from "@/components/LastUpdatedWitnessIcon"; +import LastUpdatedTooltip from "@/components/LastUpdatedTooltip"; const TABLE_CELLS = [ "Rank", @@ -170,8 +170,8 @@ export default function Witnesses() { </Head> <div className="md:m-8 max-w-[100vw] px-4"> <div className="flex justify-between mt-1 mx-1"> - <WitnessScheduleIcon/> - <LastUpdatedWitnessIcon lastUpdatedAt={witnessesData.votes_updated_at} /> + <WitnessScheduleIcon /> + <LastUpdatedTooltip lastUpdatedAt={witnessesData.votes_updated_at} /> </div> <VotersDialog diff --git a/services/FetchingService.ts b/services/FetchingService.ts index 7b62a1f220133917e9a188b3d0b393977806179e..e5bb48363e1f128ae8997b2f9a7c4d2e49137087 100644 --- a/services/FetchingService.ts +++ b/services/FetchingService.ts @@ -343,6 +343,7 @@ class FetchingService { ): Promise<Hive.CommentPermlinksResponse> { const requestParams: Hive.GetCommentPermlinksParams = { accountName: permlinkSearchProps.accountName, + "comment-type": permlinkSearchProps.commentType, page: 1, "page-size": 100, "from-block": diff --git a/utils/Calculations.ts b/utils/Calculations.ts index 08e5d9c0f5da8e66d795384affd4f584621b1461..71581eb2fcc677a74d1d8aca8f2954a8b71f1b17 100644 --- a/utils/Calculations.ts +++ b/utils/Calculations.ts @@ -14,7 +14,7 @@ import { formatNumber } from "@/lib/utils"; export const convertVestsToHP = ( hivechain: IHiveChainInterface, - vests: Hive.Supply, + vests: Hive.Supply|string, totalVestingFundHive: Hive.Supply, totalVestingShares: Hive.Supply ) => {