Skip to content

Commit

Permalink
Merge pull request #28 from bhavyagosai/page/account-details
Browse files Browse the repository at this point in the history
Account Details Page
  • Loading branch information
saimeunt authored Jun 26, 2024
2 parents 2c2db33 + 72cd850 commit 0eb5828
Show file tree
Hide file tree
Showing 18 changed files with 290 additions and 71 deletions.
16 changes: 10 additions & 6 deletions src/app/address/[address]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const AddressPage = ({ params: { address } }: { params: { address: string } }) => (
<h1>{address}</h1>
);

export default AddressPage;

import AddressComponent from "@/components/pages/address";
import { Address } from "viem";

const AddressPage = ({
params: { address },
}: {
params: { address: Address };
}) => <AddressComponent address={address} />;

export default AddressPage;
35 changes: 35 additions & 0 deletions src/components/common/copy-button/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import { CommonTooltip } from "@/components/common/tooltip";
import { Copy, Check } from "lucide-react";
import React, { useState } from "react";

export function CopyButton({ toCopy }: { toCopy: string }) {
const [copied, setCopied] = useState(false); // State to manage copy status

const copyAddress = () => {
navigator.clipboard
.writeText(toCopy)
.then(() => {
setCopied(true); // Set copied state to true
setTimeout(() => setCopied(false), 1000); // Revert back after 1 second
})
.catch((err) => console.error("Failed to copy address: ", err));
};

return (
<div>
{copied ? (
<Check size={16} className="ml-4 text-green-500" />
) : (
<CommonTooltip tooltipMessage="Copy Address" asChild>
<Copy
size={16}
className="ml-4 cursor-pointer"
onClick={copyAddress}
/>
</CommonTooltip>
)}
</div>
);
}
1 change: 1 addition & 0 deletions src/components/common/copy-button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./copy-button";
57 changes: 57 additions & 0 deletions src/components/common/tooltip/common-tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use client";

import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { ReactNode, useRef } from "react";

type Props = {
className?: string;
tooltipClassName?: string;
children: ReactNode;
tooltipMessage: ReactNode;
asChild?: boolean;
hideOnClick?: boolean;
delayDuration?: number;
};

export function CommonTooltip(props: Props) {
const {
className,
children,
tooltipMessage,
asChild,
hideOnClick = true,
tooltipClassName,
delayDuration = 0,
} = props;
const triggerRef = useRef(null);
return (
<TooltipProvider>
<Tooltip delayDuration={delayDuration}>
<TooltipTrigger
className={className}
asChild={asChild}
onClick={(event) => {
if (!hideOnClick) event.preventDefault();
}}
ref={triggerRef}
>
{children}
</TooltipTrigger>
<TooltipContent
className={tooltipClassName}
onPointerDownOutside={(event) => {
if (event.target === triggerRef.current && !hideOnClick)
event.preventDefault();
}}
>
{tooltipMessage}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
1 change: 1 addition & 0 deletions src/components/common/tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./common-tooltip";
14 changes: 12 additions & 2 deletions src/components/lib/description-list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,28 @@ const DescriptionListItem = ({
title,
border = false,
children,
secondary = false,
}: {
title: string;
border?: boolean;
secondary?: boolean;
children: ReactNode;
}) => (
<div
className={cn(
{ "border-t border-gray-100 dark:border-white/10": border },
"px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0",
"px-4 py-3 sm:px-0",
secondary ? "flex flex-col sm:gap-3" : "sm:grid sm:grid-cols-3 sm:gap-4",
)}
>
<dt className="text-sm font-medium leading-6">{title}:</dt>
<dt
className={cn("text-sm font-medium leading-6", {
"text-muted-foreground": secondary,
})}
>
{title}
{secondary ? "" : ":"}
</dt>
<dd className="mt-1 flex items-center text-sm font-medium leading-6 sm:col-span-2 sm:mt-0">
{children}
</dd>
Expand Down
12 changes: 6 additions & 6 deletions src/components/lib/navbar/category-values-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ interface Props {
}

export function CategoryValuesList({ selectedCategory, searchResult }: Props) {
const categoryToRedirect: string =
selectedCategory === "Transactions"
? "tx"
: selectedCategory === "Blocks"
? "block"
: "address";
const categoryToRedirect: string =
selectedCategory === "Transactions"
? "tx"
: selectedCategory === "Blocks"
? "block"
: "address";

return (
<div className="custom-scroll flex max-h-64 flex-col items-center justify-start overflow-y-auto px-4">
Expand Down
24 changes: 14 additions & 10 deletions src/components/lib/navbar/search-result-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { CategoryListDropdown } from "./category-list-dropdown";
import { CategoryValuesList } from "./category-values-list";

interface Props {
showResult : boolean;
selectedCategory : string;
searchResult : SearchInputResult[];
showResult: boolean;
selectedCategory: string;
searchResult: SearchInputResult[];
handleCategorySelect: (category: string) => void;
handleShowResult : (value: boolean) => void
handleShowResult: (value: boolean) => void;
}

export function SearchResultDropDown({
Expand All @@ -20,9 +20,13 @@ export function SearchResultDropDown({
}: Props) {
const dropdownRef = useRef<HTMLDivElement>(null);

useEffect(() => { // Ensures that the menu is hidden if the user clicks on any part of the screen
useEffect(() => {
// Ensures that the menu is hidden if the user clicks on any part of the screen
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
if (
dropdownRef.current &&
!dropdownRef.current.contains(event.target as Node)
) {
handleShowResult(false);
}
};
Expand All @@ -47,15 +51,15 @@ export function SearchResultDropDown({
>
{searchResult.length !== 0 ? (
<div>
<CategoryListDropdown
<CategoryListDropdown
searchResult={searchResult}
selectedCategory={selectedCategory}
handleCategorySelect={handleCategorySelect}
/>
<CategoryValuesList
/>
<CategoryValuesList
searchResult={searchResult}
selectedCategory={selectedCategory}
/>
/>
</div>
) : (
<div className="w-full items-center justify-start p-4 text-base opacity-[0.6]">
Expand Down
18 changes: 12 additions & 6 deletions src/components/lib/navbar/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,34 @@ import { useSearch } from "@/hooks";
import { SearchResultDropDown } from "./search-result-dropdown";

const Search = () => {
const {
searchResult, showResult, selectedCategory, handleCategorySelect, onQueryChanged, handleShowResult } = useSearch()
const {
searchResult,
showResult,
selectedCategory,
handleCategorySelect,
onQueryChanged,
handleShowResult,
} = useSearch();

return (
<form className="ml-auto flex-1 relative sm:flex-initial">
<form className="relative ml-auto flex-1 sm:flex-initial">
<div className="relative">
<div className="relative flex items-center">
<SearchIcon className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
<Input
type="search"
onChange={onQueryChanged}
placeholder="Search by Address / Txn Hash / Block / Token"
className="pl-8 w-full sm:w-[400px] md:w-[360px] lg:w-[480px]"
className="w-full pl-8 sm:w-[400px] md:w-[360px] lg:w-[480px]"
/>
</div>
<SearchResultDropDown
showResult={showResult}
showResult={showResult}
searchResult={searchResult}
selectedCategory={selectedCategory}
handleCategorySelect={handleCategorySelect}
handleShowResult={handleShowResult}
/>
/>
</div>
</form>
);
Expand Down
44 changes: 44 additions & 0 deletions src/components/pages/address/address-details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import DescriptionListItem from "@/components/lib/description-list-item";
import EthereumIcon from "@/components/lib/ethereum-icon";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { AddressDetails } from "@/lib/types";
import { formatEther } from "viem";

const AddressDetailsSection = ({
address,
ethPriceToday,
}: {
address: AddressDetails;
ethPriceToday: number;
}) => (
<Card>
<CardHeader className="p-4">
<CardTitle>Overview</CardTitle>
</CardHeader>

<CardContent>
<dl>
<DescriptionListItem secondary title="ETH BALANCE">
<EthereumIcon className="mr-1 size-4" />
{formatEther(address.balance)} ETH
</DescriptionListItem>
<DescriptionListItem secondary title="ETH VALUE">
{(
Number(formatEther(address.balance)) * ethPriceToday
).toLocaleString("en-US", {
style: "currency",
currency: "USD",
})}{" "}
(@{" "}
{ethPriceToday.toLocaleString("en-US", {
style: "currency",
currency: "USD",
})}
/ETH)
</DescriptionListItem>
</dl>
</CardContent>
</Card>
);

export default AddressDetailsSection;
31 changes: 31 additions & 0 deletions src/components/pages/address/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Address } from "viem";
import { l2PublicClient } from "@/lib/chains";
import AddressDetails from "./address-details";
import { fetchTokensPrices } from "@/lib/utils";
import { CopyButton } from "@/components/common/copy-button";

const AddressComponent = async ({ address }: { address: Address }) => {
const ethPrice = await fetchTokensPrices();
const byteCode = await l2PublicClient.getCode({ address });
const balance = await l2PublicClient.getBalance({ address });

const addressType = byteCode ? "Contract" : "Address";

return (
<main className="flex flex-1 flex-col gap-4 p-4 md:gap-4 md:p-4">
<h2 className="mt-10 flex scroll-m-20 items-end border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
{addressType}
<span className="ml-2 flex items-center text-base">
{address}
<CopyButton toCopy={address} />
</span>
</h2>
<AddressDetails
address={{ addressType, balance }}
ethPriceToday={ethPrice.eth.today}
/>
</main>
);
};

export default AddressComponent;
2 changes: 1 addition & 1 deletion src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './useSearch';
export * from "./useSearch";
Loading

0 comments on commit 0eb5828

Please sign in to comment.