Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • hive/block_explorer_ui
1 result
Show changes
Commits on Source (10)
import React, { useState, useRef } from "react";
import React, { useState } from "react";
import { cn } from "@/lib/utils";
import { Button } from "./ui/button";
import AutocompleteInput from "./ui/AutoCompleteInput";
import { Search } from "lucide-react";
import { X } from "lucide-react";
import useMediaQuery from "@/hooks/common/useMediaQuery";
interface SearchBarProps {
open: boolean;
onChange?: (open: boolean) => void;
......@@ -13,25 +14,37 @@ interface SearchBarProps {
const SearchBar: React.FC<SearchBarProps> = ({ open, onChange, className }) => {
const [searchTerm, setSearchTerm] = useState("");
const [isAutocompleteVisible, setAutocompleteVisible] = useState(open);
const searchContainerRef = useRef(null);
const [isClicked, setIsClicked] = useState(false);
const isMobile = useMediaQuery("(max-width: 768px)");
const handleToggle = () => {
setAutocompleteVisible(!isAutocompleteVisible);
if (onChange) onChange(!isAutocompleteVisible);
};
const handleClick = (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
setIsClicked(true);
};
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
setIsClicked(false);
};
return (
<>
{isAutocompleteVisible && (
<AutocompleteInput
value={searchTerm}
onChange={setSearchTerm}
placeholder="Search user, block, transaction"
placeholder="User, Block, Trx #"
inputType={['block_num', 'transaction_hash', 'block_hash', 'account_name']}
className={cn(
"w-full md:w-1/4 bg-theme dark:bg-theme border-0 border-b-2 width rowHover",
"bg-theme dark:bg-theme border-b transition-width duration-300 ease-in-out",
(isClicked&& !isMobile) ? "w-3/5" : "w-[full]",
className
)}
onClick={handleClick}
onBlur={handleBlur}
linkResult={true}
addLabel={true}
/>
......@@ -39,7 +52,7 @@ const SearchBar: React.FC<SearchBarProps> = ({ open, onChange, className }) => {
{!isAutocompleteVisible && (
<Button
onClick={handleToggle}
className="md:hidden"
className="md:hidden px-2 py-1 h-[36px]"
>
<Search />
</Button>
......
/* From Uiverse.io by jubayer-10 */
import { useTheme } from "@/contexts/ThemeContext";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMoon } from "@fortawesome/free-solid-svg-icons";
import { Sun } from "lucide-react";
const ThemeToggle = () => {
const { theme, toggleTheme } = useTheme();
return (
<div className="flex items-center">
<label className="inline-flex items-center cursor-pointer relative">
<input
className="peer hidden"
id="toggle"
type="checkbox"
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 after:shadow-md" />
<svg
height="50"
width="50"
viewBox="0 0 24 24"
data-name="sun_svh"
id="sun_theme_switch_svg"
xmlns="http://www.w3.org/2000/svg"
className="fill-white peer-checked:opacity-60 absolute w-2 h-2 left-[8px]"
>
<path d="M12,17c-2.76,0-5-2.24-5-5s2.24-5,5-5,5,2.24,5,5-2.24,5-5,5ZM13,0h-2V5h2V0Zm0,19h-2v5h2v-5ZM5,11H0v2H5v-2Zm19,0h-5v2h5v-2Zm-2.81-6.78l-1.41-1.41-3.54,3.54,1.41,1.41,3.54-3.54ZM7.76,17.66l-1.41-1.41-3.54,3.54,1.41,1.41,3.54-3.54Zm0-11.31l-3.54-3.54-1.41,1.41,3.54,3.54,1.41-1.41Zm13.44,13.44l-3.54-3.54-1.41,1.41,3.54,3.54,1.41-1.41Z"></path>
</svg>
<svg
height="50"
width="50"
viewBox="0 0 24 24"
data-name="moon_svh"
id="moon_theme_switch_svg"
xmlns="http://www.w3.org/2000/svg"
className="fill-black opacity-60 peer-checked:opacity-70 peer-checked:fill-white absolute w-2 h-2 right-[8px]"
>
<path d="M12.009,24A12.067,12.067,0,0,1,.075,10.725,12.121,12.121,0,0,1,10.1.152a13,13,0,0,1,5.03.206,2.5,2.5,0,0,1,1.8,1.8,2.47,2.47,0,0,1-.7,2.425c-4.559,4.168-4.165,10.645.807,14.412h0a2.5,2.5,0,0,1-.7,4.319A13.875,13.875,0,0,1,12.009,24Zm.074-22a10.776,10.776,0,0,0-1.675.127,10.1,10.1,0,0,0-8.344,8.8A9.928,9.928,0,0,0,4.581,18.7a10.473,10.473,0,0,0,11.093,2.734.5.5,0,0,0,.138-.856h0C9.883,16.1,9.417,8.087,14.865,3.124a.459.459,0,0,0,.127-.465.491.491,0,0,0-.356-.362A10.68,10.68,0,0,0,12.083,2ZM20.5,12a1,1,0,0,1-.97-.757l-.358-1.43L17.74,9.428a1,1,0,0,1,.035-1.94l1.4-.325.351-1.406a1,1,0,0,1,1.94,0l.355,1.418,1.418.355a1,1,0,0,1,0,1.94l-1.418.355-.355,1.418A1,1,0,0,1,20.5,12ZM16,14a1,1,0,0,0,2,0A1,1,0,0,0,16,14Zm6,4a1,1,0,0,0,2,0A1,1,0,0,0,22,18Z"></path>
</svg>
</label>
<button
onClick={toggleTheme}
className="h-[34px] w-[34px] p-2 border-navbar-border border-[1px] bg-navbar rounded-full shadow-sm hover:bg-navbar-hover transition-colors duration-200 flex items-center justify-center"
aria-label="Toggle Theme"
>
{theme === "dark" ? (
<Sun
strokeWidth={3}/>
) : (
<FontAwesomeIcon
icon={faMoon}
className="text-lg" size="lg"
/> )}
</button>
</div>
);
};
......
......@@ -41,22 +41,26 @@ const ViewPopover: React.FC<ViewPopoverProps> = ({ isMobile }) => {
<PopoverTrigger asChild>
<div
className={cn(
"rounded-[6px] text-sm text-center cursor-pointer flex jusitfy-center items-center p-1 ml-3 py-0 border-2 border-explorer-blue dark:border-explorer-turquoise",
{ "p-0 m-0 border-none text-base justify-normal": isMobile }
" h-[35px] rounded-[6px] text-sm text-center cursor-pointer flex justify-center items-center p-2 bg-navbar hover:bg-navbar-hover border-navbar-border border-[1px] transition-colors duration-200",
{ "p-1 m-0 text-sm justify-normal": isMobile }
)}
data-testid="data-view-dropdown"
>
<span>Data View</span>
<ChevronDown className="w-4" />
<span className="font-semibold">Data View</span>
<ChevronDown className="w-4 ml-1" />
</div>
</PopoverTrigger>
<PopoverContent
className={`w-60 bg-theme dark:bg-theme text-white rounded-[8px] border ${
isMobile && "ml-[30px]"
}`}
className={`w-56
bg-theme dark:bg-theme text-white rounded-lg shadow-lg border border-gray-300 dark:border-gray-700 ${
isMobile ? "ml-[20px]" : ""
}`}
>
<RadioGroup defaultValue={popupDefaultValue} onValueChange={handleSelect}>
<div className="flex items-center space-x-2">
<RadioGroup
defaultValue={popupDefaultValue}
onValueChange={handleSelect}
>
<div className="flex items-center space-x-2 p-2 hover:bg-navbar-listHover rounded-md transition-colors duration-200">
<RadioGroupItem
onClick={() =>
setSettings({
......@@ -75,7 +79,7 @@ const ViewPopover: React.FC<ViewPopoverProps> = ({ isMobile }) => {
Visualised Data
</Label>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center space-x-2 p-2 hover:bg-navbar-listHover rounded-md transition-colors duration-200">
<RadioGroupItem
onClick={() =>
setSettings({
......@@ -94,7 +98,7 @@ const ViewPopover: React.FC<ViewPopoverProps> = ({ isMobile }) => {
Raw JSON
</Label>
</div>
<div className="flex items-center space-x-2">
<div className="flex items-center space-x-2 p-2 hover:bg-navbar-listHover rounded-md transition-colors duration-200">
<RadioGroupItem
onClick={() =>
setSettings({
......
......@@ -10,6 +10,10 @@ import {
DialogTitle,
DialogTrigger,
} from "../ui/dialog";
import { Tooltip , TooltipTrigger , TooltipProvider ,TooltipContent } from "../ui/tooltip";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCube, faCubes, faExclamationCircle, faClock } from '@fortawesome/free-solid-svg-icons';
import { RefreshCcw, RefreshCwOff } from "lucide-react";
interface SyncInfoProps {
className?: string;
......@@ -20,12 +24,7 @@ export const getBlockDifference = (
explorerBlockNumber: number | undefined
) => {
const difference = (hiveBlockNumber || 0) - (explorerBlockNumber || 0);
if (difference < 0) {
return 0;
} else {
return difference;
}
return difference < 0 ? 0 : difference;
};
const SyncInfo: React.FC<SyncInfoProps> = ({ className }) => {
......@@ -43,7 +42,7 @@ const SyncInfo: React.FC<SyncInfoProps> = ({ className }) => {
hiveBlockNumber,
explorerBlockNumber
);
const differenceColorText =
blockDifference > 20
? "text-explorer-red"
......@@ -51,15 +50,22 @@ const SyncInfo: React.FC<SyncInfoProps> = ({ className }) => {
? "text-explorer-orange"
: "text-explorer-light-green";
const iconColor =
blockDifference > 20
? "red"
: blockDifference > 3
? "orange"
: "green";
return !syncLoading ? (
<Dialog
open={dialogOpen}
onOpenChange={(open) => setDialogOpen(open)}
>
<DialogTrigger>
<DialogTrigger asChild={true} style={{ width: "32px"}}>
<div
className={cn(
"flex flex-row gap-x-1 border rounded-[6px] mt-px mx-6 px-1.5 py-px text-sm cursor-pointer",
"bg-navbar hover:bg-navbar-hover border rounded-[6px] py-px cursor-pointer",
{
"border-explorer-light-green": blockDifference <= 10,
"border-explorer-orange":
......@@ -71,54 +77,124 @@ const SyncInfo: React.FC<SyncInfoProps> = ({ className }) => {
)}
onClick={() => setDialogOpen(true)}
>
{blockDifference < 10 ? (
<p className="text-explorer-light-green">
Explorer synced with blockchain
</p>
) : (
<>
<p>Blocks out of sync:</p>
<p>{blockDifference.toLocaleString()}</p>
</>
)}
<div className=" h-[30px] w-[30px] relative p-1 flex items-center justify-center">
{blockDifference < 10 ? (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<RefreshCcw
color={iconColor}
size={18}
strokeWidth={2} />
</TooltipTrigger>
<TooltipContent className="bg-theme text-text">
Explorer synced with blockchain
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
<div className="relative">
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger>
<RefreshCwOff
color={iconColor}
size={18}
strokeWidth={2}
/>
</TooltipTrigger>
<TooltipContent className="bg-theme text-text">
<p>
{blockDifference.toLocaleString()} Blocks out of sync
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<span
className={cn(
"absolute top-[-17px] sm:top-[-17px] text-xs font-semibold text-white bg-red-600 rounded-full px-1 py-1 z-20",
{
"right-[-17px]": blockDifference >= 100,
"right-[-14px]": blockDifference < 100,
}
)}
>
{blockDifference > 999 ? "999+" : blockDifference.toLocaleString()}
</span>
</div>
)}
</div>
</div>
</DialogTrigger>
<DialogContent className="bg-theme dark:bg-theme text-white">
<DialogHeader>
<DialogTitle>Blockchain sync</DialogTitle>
<DialogContent className="dialog-content p-6 max-w-lg ">
<DialogHeader className="dialog-header">
<DialogTitle className="dialog-title">Blockchain Sync</DialogTitle>
</DialogHeader>
<section className="flex flex-col">
<div className="flex justify-between border-b py-1.5">
<div>Blockchain headblock: </div>
<div>{hiveBlockNumber?.toLocaleString()}</div>
<section className="dialog-section">
<div className="dialog-item">
<div className="dialog-item-text">
<FontAwesomeIcon icon={faCubes} className="text-sm" />
<div>Blockchain Headblock:</div>
</div>
<div className="dialog-item-value">
{hiveBlockNumber?.toLocaleString()}
</div>
</div>
<div className="flex justify-between border-b py-1.5">
<div>Hafbe last block: </div>
<div>{explorerBlockNumber?.toLocaleString()}</div>
<div className="dialog-item">
<div className="dialog-item-text">
<FontAwesomeIcon icon={faCube} className="text-sm" />
<div>Hafbe Last Block:</div>
</div>
<div className="dialog-item-value">
{explorerBlockNumber?.toLocaleString()}
</div>
</div>
<div
className={cn(
"flex justify-between border-b py-1.5",
differenceColorText
)}
>
<div>Block difference: </div>
<div>{blockDifference.toLocaleString()} blocks</div>
<div className={cn("dialog-item", differenceColorText)}>
<div className="dialog-item-text">
<FontAwesomeIcon icon={faExclamationCircle} className="text-sm" />
<div>Block Difference:</div>
</div>
<div className="dialog-item-value">
{blockDifference.toLocaleString()} blocks
</div>
</div>
<div
className={cn("flex justify-between py-1.5", differenceColorText)}
>
<div>Last synced block at: </div>
<div className={cn("dialog-item", differenceColorText)}>
<div className="dialog-item-text">
<FontAwesomeIcon icon={faClock} className="text-sm" />
<div>Last Synced Block At:</div>
</div>
{explorerTime && (
<div>{convertUTCDateToLocalDate(new Date(explorerTime))}</div>
<div className="dialog-item-value">
{convertUTCDateToLocalDate(new Date(explorerTime))}
</div>
)}
</div>
</section>
</DialogContent>
</Dialog>
) : (
<div className="border rounded-[6px] mt-px mx-6 px-1.5 py-px text-sm border-explorer-yellow text-explorer-yellow">
Connecting...
<div
className="h-[34px] w-[32px] bg-navbar hover:bg-navbar-hover items-center justify-center
flex flex-row border rounded-[6px] cursor-pointer border-explorer-orange text-explorer-orange"
>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<RefreshCcw
color="orange"
size={18}
strokeWidth={2}
style={{ animation: "spin 2s linear infinite" }}
/>
</TooltipTrigger>
<TooltipContent className="bg-theme text-text">
Connecting
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
);
};
......
import { useState, ChangeEvent } from "react";
import { useState, ChangeEvent, useEffect } from "react";
import { Loader2 } from "lucide-react";
import Explorer from "@/types/Explorer";
import { trimAccountName } from "@/utils/StringUtils";
......@@ -10,6 +10,7 @@ import { useSearchesContext } from "@/contexts/SearchesContext";
import usePermlinkSearch from "@/hooks/api/common/usePermlinkSearch";
import { startCommentPermlinkSearch } from "./utils/commentPermlinkSearchHelpers";
import PostTypeSelector from "./PostTypeSelector";
import useSearchRanges from "@/hooks/common/useSearchRanges";
const CommentsPermlinkSearch = () => {
const {
......@@ -18,16 +19,17 @@ const CommentsPermlinkSearch = () => {
setCommentPaginationPage,
setCommentType,
setLastSearchKey,
searchRanges,
} = useSearchesContext();
const searchRanges = useSearchRanges("lastTime");
const { permlinkSearchDataLoading } = usePermlinkSearch(permlinkSearchProps);
const [accountName, setAccountName] = useState<string>("");
const [localCommentType, setLocalCommentType] =
useState<Explorer.CommentType>("post");
const { getRangesValues } = searchRanges;
const { getRangesValues, setLastTimeUnitValue } = searchRanges;
const onButtonClick = async () => {
if (accountName !== "") {
......@@ -64,6 +66,14 @@ const CommentsPermlinkSearch = () => {
}
};
// Set inital permlink search range as last 30 days
useEffect(() => {
setLastTimeUnitValue(30);
return () => {
setLastTimeUnitValue(undefined);
};
}, []);
const handleChangeCommentType = (e: ChangeEvent<HTMLSelectElement>) => {
const {
target: { value },
......
......@@ -3,7 +3,11 @@ import { Menu, X } from "lucide-react";
import Link from "next/link";
import Image from "next/image";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faWrench, faCaretDown, faCircleNodes } from "@fortawesome/free-solid-svg-icons";
import {
faWrench,
faCaretDown,
faCircleNodes,
} from "@fortawesome/free-solid-svg-icons";
import { cn } from "@/lib/utils";
import useMediaQuery from "@/hooks/common/useMediaQuery";
......@@ -19,18 +23,11 @@ export default function Navbar() {
const [settingsOpen, setSettingsOpen] = useState(false);
return (
<div
className="fixed w-full top-0 left-0 z-50"
data-testid="navbar"
>
<div
className="flex p-2 justify-between bg-theme text-white items-center relative">
<div className="fixed w-full top-0 left-0 z-50" data-testid="navbar">
<div className="flex p-2 justify-between bg-theme text-white items-center relative">
{isMobile ? (
<div className="flex items-center justify-between w-full">
<Link
href={"/"}
className="relative pr-2"
>
<Link href={"/"} className="relative pr-2">
<Image
src="/hive-logo.png"
alt="Hive logo"
......@@ -38,12 +35,10 @@ export default function Navbar() {
height={40}
/>
</Link>
{!searchBarOpen && <SyncInfo />}
<div className="flex-grow flex items-center justify-end gap-x-3">
<SearchBar
open={searchBarOpen}
onChange={setSearchBarOpen}
/>
{!searchBarOpen}
<div className="flex-grow flex items-center justify-end gap-x-1 w-[90%]">
<SearchBar open={searchBarOpen} onChange={setSearchBarOpen} className="h-[36px]"/>
<SyncInfo />
<Menu
height={34}
width={34}
......@@ -58,7 +53,8 @@ export default function Navbar() {
)}
style={{
background: "var(--background-start-rgb)",
backgroundImage: "linear-gradient(var(--background-start-rgb), var(--background-end-rgb))",
backgroundImage:
"linear-gradient(var(--background-start-rgb), var(--background-end-rgb))",
}}
>
<div className="w-full flex items-center justify-end">
......@@ -69,7 +65,7 @@ export default function Navbar() {
className="cursor-pointer"
/>
</div>
<div className="text-left py-2 rounded-lg bg-white shadow-md mb-4 px-4 hover:bg-gray-100 transition dark:bg-gray-800 dark:hover:bg-gray-700">
<div className="text-left py-2 rounded-lg bg-white dark:bg-navbar shadow-md mb-4 px-4 hover:bg-navbar-listHover transition">
<div className="flex items-center">
<FontAwesomeIcon icon={faWrench} className="mr-2" />
<span
......@@ -82,17 +78,17 @@ export default function Navbar() {
</div>
{settingsOpen && (
<div className="mt-2 pl-8 space-y-2">
<div className="py-1 border-b-2 border-gray-300 dark:border-gray-600 flex items-center">
<div className="py-1 border-b-2 flex items-center">
<ThemeToggle />
<span className="text-base ml-2 text-gray-600 dark:text-gray-400">Dark/Light Mode</span>
<span className="text-base ml-2">Dark/Light Mode</span>
</div>
<div className="py-1 text-gray-600 dark:text-gray-400">
<ViewPopover isMobile={isMobile} />
<div className="py-1 max-w-fit">
<ViewPopover isMobile={isMobile} />
</div>
</div>
)}
</div>
<div className="text-left py-2 rounded-lg bg-white shadow-md px-4 hover:bg-gray-100 transition dark:bg-gray-800 dark:hover:bg-gray-700">
<div className="text-left py-2 rounded-lg bg-white dark:bg-navbar shadow-md px-4 hover:bg-navbar-listHover transition">
<Link
href={"/witnesses"}
className="flex items-center"
......@@ -106,30 +102,39 @@ export default function Navbar() {
</div>
) : (
<>
<div className="flex items-center pl-12 gap-x-4">
<Link href={"/"} className="pr-2 flex justify-normal items-center text-explorer-turquoise font-medium">
<Image
src="/hive-logo.png"
alt="Hive logo"
width={50}
height={50}
data-testid="hive-logo"
/>
<div
className="ml-4"
data-testid="hive-block-explorer"
>
Hive Block Explorer
<div
className="fixed w-full top-0 left-0 z-50"
data-testid="navbar"
>
<div className="flex flex-col bg-theme text-white relative p-2">
<div className="flex w-full justify-between">
<Link
href={"/"}
className="pr-2 flex items-center text-explorer-turquoise font-medium"
>
<Image
src="/hive-logo.png"
alt="Hive logo"
width={50}
height={50}
data-testid="hive-logo"
/>
<div
className="ml-4 whitespace-nowrap"
data-testid="hive-block-explorer"
>
Hive Block Explorer
</div>
</Link>
<div className="flex items-center gap-x-2 w-[60%] justify-end">
<SearchBar open={true} className=" justify-end" />
<ViewPopover />
<SyncInfo />
<ThemeToggle />
</div>
</div>
</Link>
<ViewPopover />
<SyncInfo />
<Link href={"/witnesses"} data-testid="navbar-witnesses-link">
Witnesses
</Link>
<ThemeToggle />
</div>
</div>
<SearchBar open={true} />
</>
)}
</div>
......
......@@ -10,9 +10,12 @@ import { trimAccountName } from "@/utils/StringUtils";
import Hive from "@/types/Hive";
import { capitalizeFirst } from "@/utils/StringUtils";
import Router, { useRouter } from "next/router";
interface AutocompleteInputProps {
value: string | null;
onChange: (value: string) => void;
onClick?: (event: React.MouseEvent<HTMLInputElement, MouseEvent>) => void;
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
placeholder: string;
inputType: string | string[]; // The input type (e.g., 'account_name', 'block', 'transaction')
className?: string; // Optional custom className for styling
......@@ -24,6 +27,8 @@ interface AutocompleteInputProps {
const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
value,
onChange,
onClick,
onBlur,
placeholder,
inputType,
className,
......@@ -42,9 +47,10 @@ const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
const router = useRouter();
const [isItemSelected, setIsItemSelected] = useState(false);
// Used to track if the user clicks inside the results container or input field
const resultsContainerRef = useRef<HTMLDivElement>(null);
const updateInput = async (value: string) => {
setSearchInputType(value);
};
......@@ -62,34 +68,35 @@ const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputFocus(true);
onChange(e.target.value);
setSearchTerm(e.target.value);
if(!isNumeric(e.target.value) && !isHash(e.target.value))
{
debouncedSearch(e.target.value + encodeURI("%"));
}else
{
debouncedSearch(e.target.value);
}
if (!isNumeric(e.target.value) && !isHash(e.target.value)) {
debouncedSearch(e.target.value + encodeURI("%"));
} else {
debouncedSearch(e.target.value);
}
};
// Close the search when clicking outside of the container
useOnClickOutside(searchContainerRef, () => closeSearchBar());
//Reset Search Bar
const resetSearchBar = () => {
setInputFocus(false);
onChange(""); // Clear the input field
setSearchInputType("");
setSelectedResult(0);
setIsItemSelected(false);
if (onBlur) {
onBlur({} as React.FocusEvent<HTMLInputElement>);
}
};
const closeSearchBar = () => {
setInputFocus(false);
};
//Ensure cleaning the searchbar when navigating away
// Ensure cleaning the searchbar when navigating away
useEffect(() => {
const handleRouteChange = () => {
resetSearchBar();
......@@ -101,7 +108,6 @@ const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
};
}, [router.events, resetSearchBar]);
//Handle keyboard events
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (inputFocus && inputTypeData?.input_value) {
......@@ -159,7 +165,18 @@ const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
}
}, [selectedResult]);
// Render search results based on input type
// Handle onBlur event
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
if (
resultsContainerRef.current &&
(resultsContainerRef.current.contains(event.relatedTarget) || inputRef.current?.contains(event.relatedTarget))
) {
event.stopPropagation(); // Prevent the blur event from triggering if focus is inside results container or input
} else {
if (onBlur) onBlur(event);
}
};
const getResultTypeHeader = (result: Hive.InputTypeResponse) => {
switch (result.input_type) {
case "block_num":
......@@ -173,7 +190,6 @@ const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
}
};
// Render search results based on input type
const renderSearchData = (
data: Hive.InputTypeResponse,
linkResult: boolean
......@@ -195,15 +211,18 @@ const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
Array.isArray(inputValue)
) {
return (
<div className="autocomplete-result-container scrollbar-autocomplete">
<div
ref={resultsContainerRef}
className="autocomplete-result-container scrollbar-autocomplete"
>
{inputValue.map((account, index) => (
<div
key={index}
ref={selectedResult === index ? selectedResultRef : null}
className={cn(
"autocomplete-result-item hover:bg-explorer-light-gray",
"autocomplete-result-item",
{
"bg-explorer-light-gray": selectedResult === index,
"bg-navbar-listHover": selectedResult === index,
"autocomplete-result-item": index > 0,
}
)}
......@@ -251,7 +270,10 @@ const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
? `/@${data.input_value}`
: `/${resultType}/${data.input_value}`;
return (
<div className="autocomplete-result-container scrollbar-autocomplete">
<div
ref={resultsContainerRef}
className="autocomplete-result-container scrollbar-autocomplete"
>
<div className="autocomplete-result-item">
{linkResult ? (
<>
......@@ -262,7 +284,7 @@ const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
)}
<Link href={href} data-testid="">
<span className="autocomplete-result-link">
{data.input_value}
{data.input_type === "transaction_hash" ? data.input_value.slice(0, 10) : data.input_value}
</span>
</Link>
</>
......@@ -292,11 +314,13 @@ const AutocompleteInput: React.FC<AutocompleteInputProps> = ({
<div className="flex items-center pr-2 z-50">
<Input
ref={inputRef}
className="border-0 w-full text-sm"
className={cn("autocomplete_input")}
type="text"
placeholder={required ? `${placeholder} *` : placeholder} // Add asterisk if required
value={value || ""}
onChange={handleInputChange}
onClick={onClick}
onBlur={handleBlur}
onFocus={() => setInputFocus(true)}
onKeyDown={handleKeyDown}
/>
......
......@@ -22,6 +22,8 @@ import {
} from "@/components/ui/card";
import { useHeadBlockNumber } from "@/contexts/HeadBlockContext";
import { useTheme } from "@/contexts/ThemeContext";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowRightLong } from '@fortawesome/free-solid-svg-icons'
export default function Home() {
const { settings } = useUserSettingsContext();
......@@ -60,64 +62,77 @@ export default function Home() {
/>
<SearchesSection />
</div>
<Card
className="col-span-4 md:col-span-4 lg:col-span-1"
data-testid="top-witnesses-sidebar"
>
<CardHeader>
<CardTitle>Top Witnesses</CardTitle>
</CardHeader>
<CardContent className="px-0">
<Table>
<TableBody>
{witnesses &&
witnesses.witnesses.map((witness, index) => (
<TableRow
className=" text-base"
key={index}
data-testid="witnesses-name"
>
<TableCell className="py-4">{index + 1}</TableCell>
<TableCell className="py-4">
<Link
href={`/@${witness.witness_name}`}
className="text-link"
>
{witness.witness_name}
</Link>
</TableCell>
<TableCell className="py-4">
<Link href={`/@${witness.witness_name}`}>
<div className="min-w-[30px]">
<Image
className="rounded-full border-2 border-link"
src={getHiveAvatarUrl(witness.witness_name)}
alt="avatar"
width={40}
height={40}
/>
</div>
</Link>{" "}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
<CardFooter>
<div className="w-full flex justify-center align-center text-lg hover:text-explorer-turquoise">
<Card
className="col-span-4 md:col-span-4 lg:col-span-1 overflow-hidden"
data-testid="top-witnesses-sidebar"
>
<CardHeader className="flex justify-between items-center border-b px-1 py-3">
<CardTitle>Top Witnesses</CardTitle>
<Link
data-testid="see-more-btn"
href={"/witnesses"}
className="hover:text-explorer-turquoise"
href="/witnesses"
className="text-sm flex items-center space-x-1"
data-testid="see-witnesses-link"
>
See More
<span>See All</span>
<FontAwesomeIcon
icon={faArrowRightLong}
size="lg"
width={16}
className=" opacity-70 hover:opacity-100 transition-opacity"
/>
</Link>
</div>
</CardFooter>
</Card>
</div>
</CardHeader>
<CardContent className="px-4 py-2">
<Table>
<TableBody>
{witnesses &&
witnesses.witnesses.map((witness, index) => (
<TableRow
className="text-base"
key={index}
data-testid="witnesses-name"
>
<TableCell className="py-2">{index + 1}</TableCell>
<TableCell className="py-2">
<Link
href={`/@${witness.witness_name}`}
className="text-link"
>
{witness.witness_name}
</Link>
</TableCell>
<TableCell className="py-2">
<Link href={`/@${witness.witness_name}`}>
<div className="min-w-[30px]">
<Image
className="rounded-full border-2 border-link transition-all transform hover:scale-110"
src={getHiveAvatarUrl(witness.witness_name)}
alt="avatar"
width={40}
height={40}
/>
</div>
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
<CardFooter className="py-4 px-4 bg-explorer-extra-light-gray">
<div className="w-full flex justify-center">
<Link
data-testid="see-more-btn"
href="/witnesses"
className="text-link"
>
See More
</Link>
</div>
</CardFooter>
</Card>
</div>
</>
);
}
......@@ -43,6 +43,17 @@
--color-light-green: #15a017;
--color-light-gray: #ADA9A9DC;
--color-dark-gray: #292F3D;
--color-extra-light-gray : #F9FAFB;
--error-bg-light: linear-gradient(135deg, rgba(247, 255, 255, 0.8), rgba(248, 247, 247, 0.8));
--error-bg: var(--error-bg-light);
--color-navbar-icon: #d9e0e8;
--color-navbar-icon-hover:#cbd5e1;
--color-navbar-icon-border: #6b7280;
--color-navbar-icon-list-hover : #eff2f4;
}
/* Dark theme */
......@@ -85,6 +96,16 @@
--color-light-green: #64FFAA;
--color-light-gray: #ADA9A9DC;
--color-dark-gray: #444c5e;
--color-extra-light-gray : #34373ed4;
--error-bg-dark: linear-gradient(135deg, rgba(159, 149, 149, 0.8), rgba(104, 100, 100, 0.8));
--error-bg: var(--error-bg-dark);
--color-navbar-icon:#292F3D;
--color-navbar-icon-hover: #34373ed4;
--color-navbar-icon-border: #6b7280;
--color-navbar-icon-list-hover : #374151;
}
* {
......@@ -143,6 +164,26 @@ pre {
overflow: hidden;
}
.autocomplete_input {
padding: 0.5rem 0.75rem;
font-size: 1rem;
background-color: transparent;
border: none;
transition: border-bottom-color 0.3s ease, background-color 0.3s ease;
outline: none;
}
.autocomplete_input:focus {
border-bottom-color: var(--color-navbar-icon-border);
background-color: var(--color-extra-light-gray);
}
.autocomplete_input::placeholder {
opacity: 1; /* Ensures placeholder is fully opaque */
transition: color 0.3s ease;
}
.autocomplete-result-container {
display: flex;
flex-direction: column;
......@@ -152,28 +193,28 @@ pre {
margin-top: 0.25rem;
max-height: 24rem;
overflow-y: auto;
}
.autocomplete-result-item {
padding: 0.5rem;
font-size: 0.875rem;
cursor: pointer;
padding: 6px 12px;
margin: 4px 8px;
border-radius: 4px;
color: var(--text-color);
display: flex;
align-items: left;
text-align: left;
border-radius: 0.5rem;
transition: background-color 0.3s ease;
align-items: center;
gap: 6px;
transition: background-color 0.3s ease, transform 0.2s ease;
font-size: 14px;
}
.autocomplete-result-item:hover {
background-color: var(--explorer-light-gray);
background-color: var(--color-navbar-icon-list-hover);
transform: translateY(-1px);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
}
.autocomplete-result-item {
border-top: 1px solid var(--gray-200);
text-align: left;
}
.autocomplete-result-link {
color: var(--color-link);
......@@ -189,7 +230,7 @@ pre {
}
/*End AutoCompleteInput Styles*/
/* Light and Dark Theme Error Component Styling */
/*Error Component Styling*/
.error-message {
background: var(--error-bg);
color: var(--error-text);
......@@ -225,22 +266,57 @@ pre {
opacity: 0;
transform: translateY(-20px);
}
/*Error Component Styling*/
/* Light Theme Error Background Gradient */
:root {
--error-bg-light: linear-gradient(135deg, rgba(247, 255, 255, 0.8), rgba(248, 247, 247, 0.8));
/* Light gray gradient */
--error-bg: var(--error-bg-light);
/* Sync Dialog Styles */
.dialog-content {
background: linear-gradient(to right, var(--background-start-rgb), var(--background-end-rgb));
color: var(--color-text);
backdrop-filter: blur(10px);
border-radius: 0.75rem;
box-shadow: 0 10px 15px var(--color-background);
transition: all 0.5s ease;
}
.dialog-header {
border-bottom: 1px solid var(--color-row-odd);
padding-bottom: 0.5rem;
margin-bottom: 1rem;
}
/* Dark Theme Error Background Gradient */
.dark {
--error-bg-dark: linear-gradient(135deg, rgba(159, 149, 149, 0.8), rgba(104, 100, 100, 0.8));
/* Dark gray gradient */
--error-bg: var(--error-bg-dark);
.dialog-title {
font-size: 1.25rem;
font-weight: 500;
}
/* Apply the color scheme dynamically */
body.dark {
background-color: var(--background-start-rgb);
.dialog-section {
display: flex;
flex-direction: column;
gap: 1rem;
}
.dialog-item {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--color-row-odd);
padding: 0.5rem 0;
transition: background-color 0.3s ease;
}
.dialog-item:hover {
background-color: var(--color-row-hover);
}
.dialog-item-text {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1rem;
}
.dialog-item-value {
font-weight: 400;
font-size: 1rem;
}
/*End Sync Dialog Styles*/
\ No newline at end of file
......@@ -60,6 +60,7 @@ module.exports = {
"explorer-light-green": "var(--color-light-green)",
"explorer-light-gray": "var(--color-light-gray)",
"explorer-dark-gray": "var(--color-dark-gray)",
"explorer-extra-light-gray": "var(--color-extra-light-gray)",
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
......@@ -93,6 +94,13 @@ module.exports = {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
navbar: {
DEFAULT: "var(--color-navbar-icon)",
hover: "var(--color-navbar-icon-hover)",
border: "var(--color-navbar-icon-border)",
listHover: "var(--color-navbar-icon-list-hover)",
},
},
borderRadius: {
lg: "var(--radius)",
......