-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(explorer): As a user, I want to see a card view of my Attestatio…
…ns (#778) Co-authored-by: Alain Nicolas <[email protected]>
- Loading branch information
1 parent
fc3418b
commit 5863e88
Showing
17 changed files
with
513 additions
and
162 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { | ||
autoUpdate, | ||
flip, | ||
offset, | ||
shift, | ||
useDismiss, | ||
useFloating, | ||
useFocus, | ||
useHover, | ||
useInteractions, | ||
useRole, | ||
} from "@floating-ui/react"; | ||
import { useState } from "react"; | ||
|
||
interface TooltipProps { | ||
content: React.ReactNode; | ||
children: React.ReactNode; | ||
placement?: "top" | "bottom" | "left" | "right"; | ||
isDarkMode?: boolean; | ||
} | ||
|
||
export const Tooltip: React.FC<TooltipProps> = ({ content, children, placement = "bottom", isDarkMode = false }) => { | ||
const [isVisible, setIsVisible] = useState(false); | ||
const { refs, floatingStyles, context } = useFloating({ | ||
open: isVisible, | ||
onOpenChange: setIsVisible, | ||
placement: placement, | ||
whileElementsMounted: autoUpdate, | ||
middleware: [ | ||
offset(5), | ||
flip({ | ||
fallbackAxisSideDirection: "start", | ||
}), | ||
shift(), | ||
], | ||
}); | ||
|
||
const hover = useHover(context, { move: false }); | ||
const focus = useFocus(context); | ||
const dismiss = useDismiss(context); | ||
const role = useRole(context, { role: "tooltip" }); | ||
|
||
const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus, dismiss, role]); | ||
|
||
return ( | ||
<div | ||
className="relative inline-block" | ||
onMouseEnter={() => setIsVisible(true)} | ||
onMouseLeave={() => setIsVisible(false)} | ||
ref={refs.setReference} | ||
{...getReferenceProps()} | ||
> | ||
{children} | ||
{isVisible && ( | ||
<div | ||
ref={refs.setFloating} | ||
style={{ ...floatingStyles, zIndex: 1000 }} | ||
{...getFloatingProps()} | ||
className={`p-2 rounded-md ${ | ||
isDarkMode ? "bg-whiteDefault text-blackDefault" : "bg-blackDefault text-whiteDefault" | ||
}`} | ||
> | ||
{content} | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
125 changes: 125 additions & 0 deletions
125
explorer/src/pages/Attestation/components/AttestationCard/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { ChevronRight } from "lucide-react"; | ||
import moment from "moment"; | ||
import { generatePath, useLocation, useNavigate } from "react-router-dom"; | ||
import { useTernaryDarkMode } from "usehooks-ts"; | ||
|
||
import circleInfo from "@/assets/icons/circle-info.svg"; | ||
import { Button } from "@/components/Buttons"; | ||
import { EButtonType } from "@/components/Buttons/enum"; | ||
import { Tooltip } from "@/components/Tooltip"; | ||
import { issuersData } from "@/pages/Home/data"; | ||
import { IIssuer } from "@/pages/Home/interface"; | ||
import { useNetworkContext } from "@/providers/network-provider/context"; | ||
import { APP_ROUTES } from "@/routes/constants"; | ||
|
||
import { IAttestationCardProps } from "./interface"; | ||
|
||
export const AttestationCard: React.FC<IAttestationCardProps> = ({ | ||
id, | ||
schemaId, | ||
portalId, | ||
issuanceDate, | ||
expiryDate, | ||
}) => { | ||
const { | ||
network: { network }, | ||
} = useNetworkContext(); | ||
const navigate = useNavigate(); | ||
const location = useLocation(); | ||
const { isDarkMode } = useTernaryDarkMode(); | ||
const isExpired = expiryDate ? new Date(expiryDate * 1000) < new Date() : false; | ||
|
||
const issuerData = issuersData.find((issuer) => | ||
issuer.attestationDefinitions.some( | ||
(definition) => | ||
definition.schema.toLowerCase() === schemaId.toLowerCase() && | ||
definition.portal.toLowerCase() === portalId.toLowerCase(), | ||
), | ||
) as IIssuer; | ||
const attestationDefinitions = issuerData?.attestationDefinitions.find( | ||
(definition) => definition.schema.toLowerCase() === schemaId.toLowerCase(), | ||
); | ||
|
||
if (!issuerData) { | ||
return null; | ||
} | ||
|
||
const logo = attestationDefinitions?.logo ?? issuerData?.logo; | ||
const logoDark = attestationDefinitions?.logoDark ?? issuerData?.logoDark; | ||
const name = attestationDefinitions?.name ?? issuerData?.name; | ||
const description = attestationDefinitions?.description ?? ""; | ||
const issuerName = issuerData.name; | ||
|
||
const maxDescriptionLength = 140; | ||
const isDescriptionLong = description.length > maxDescriptionLength; | ||
const truncatedDescription = isDescriptionLong ? `${description.slice(0, maxDescriptionLength)}...` : description; | ||
|
||
const handleViewDetailsClick = (id: string) => { | ||
navigate(generatePath(APP_ROUTES.ATTESTATION_BY_ID, { chainId: network, id }), { | ||
state: { from: location.pathname }, | ||
}); | ||
}; | ||
|
||
const displayLogo = () => { | ||
const Logo: React.FC<React.SVGProps<SVGSVGElement>> = isDarkMode && logoDark ? logoDark : logo; | ||
return <Logo className="w-full h-auto max-w-[2.5rem] md:max-w-[3rem] max-h-[2.5rem] md:max-h-[3rem]" />; | ||
}; | ||
|
||
return ( | ||
<div | ||
key={`${id}`} | ||
className="group flex flex-col justify-between gap-4 border border-border-card dark:border-border-cardDark rounded-xl p-4 md:p-6 hover:bg-surface-secondary dark:hover:bg-surface-secondaryDark transition md:min-h-[20rem]" | ||
> | ||
<div> | ||
<div className="flex items-start gap-3 text-xl md:text-md font-semibold text-blackDefault dark:text-whiteDefault"> | ||
<div className="w-[2.5rem] h-[2.5rem] md:w-[3rem] md:h-[3rem] flex items-center mr-2 justify-center"> | ||
{displayLogo()} | ||
</div> | ||
<div className="flex flex-col"> | ||
<div>{name}</div> | ||
<div className="text-sm font-normal text-blackDefault dark:text-whiteDefault">{issuerName}</div> | ||
</div> | ||
</div> | ||
{description && description.trim() ? ( | ||
<div className="text-sm font-normal text-text-darkGrey dark:text-tertiary mt-4"> | ||
<span>{truncatedDescription}</span> | ||
</div> | ||
) : null} | ||
</div> | ||
<div className="flex flex-col gap-2 mt-auto"> | ||
<div className="flex justify-between text-sm font-normal text-text-darkGrey dark:text-tertiary"> | ||
<span>Issued</span> <span>{moment.unix(issuanceDate).fromNow()}</span> | ||
</div> | ||
{!!expiryDate && isExpired && ( | ||
<div className="flex justify-between text-sm font-semibold text-text-darkGrey dark:text-tertiary"> | ||
<div className="flex items-center"> | ||
<span>Expired</span> | ||
<Tooltip | ||
content={ | ||
<div style={{ width: "350px", fontWeight: "normal" }}> | ||
The validity of this Attestation is determined by the Issuer, and consumers may choose to adhere to | ||
or ignore this expiration date. | ||
</div> | ||
} | ||
placement="right" | ||
isDarkMode={isDarkMode} | ||
> | ||
<img src={circleInfo} className="!h-[16px] !w-[16px] ml-1" /> | ||
</Tooltip> | ||
</div> | ||
<span>{moment.unix(expiryDate).fromNow()}</span> | ||
</div> | ||
)} | ||
<div className="flex mt-4 lg:flex-row lg:items-end justify-end lg:justify-start"> | ||
<Button | ||
isSmall | ||
name="View details" | ||
handler={() => handleViewDetailsClick(id)} | ||
buttonType={EButtonType.OUTLINED} | ||
iconRight={<ChevronRight />} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; |
7 changes: 7 additions & 0 deletions
7
explorer/src/pages/Attestation/components/AttestationCard/interface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export interface IAttestationCardProps { | ||
id: string; | ||
schemaId: string; | ||
portalId: string; | ||
issuanceDate: number; | ||
expiryDate?: number; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
explorer/src/pages/Attestations/components/CardView/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { AttestationCard } from "@/pages/Attestation/components/AttestationCard"; | ||
|
||
import { ICardViewProps } from "./interface"; | ||
|
||
export const CardView: React.FC<ICardViewProps> = ({ attestationsList }) => { | ||
return ( | ||
<div className="flex flex-col gap-14 md:gap-[4.5rem] container mt-14 md:mt-12"> | ||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6"> | ||
{attestationsList?.map((attestation) => { | ||
return ( | ||
<AttestationCard | ||
key={attestation.id} | ||
id={attestation.id} | ||
schemaId={attestation.schema.id} | ||
portalId={attestation.portal.id} | ||
issuanceDate={attestation.attestedDate} | ||
expiryDate={attestation.expirationDate} | ||
/> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
); | ||
}; |
13 changes: 13 additions & 0 deletions
13
explorer/src/pages/Attestations/components/CardView/interface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
export interface ICardViewProps { | ||
attestationsList: Array<{ | ||
id: string; | ||
schema: { | ||
id: string; | ||
}; | ||
portal: { | ||
id: string; | ||
}; | ||
attestedDate: number; | ||
expirationDate?: number; | ||
}>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.