-
Notifications
You must be signed in to change notification settings - Fork 2
feat(namekit-react): Identity #439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
db46f78
450998f
1d92fb1
105a487
ef9dbc5
3ddd190
ec2cb9c
752ac3a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| import React from "react"; | ||
| import type { Meta, StoryObj } from "@storybook/react"; | ||
| import { Identity } from "@namehash/namekit-react/client"; | ||
| import { Identity, NameKitProvider } from "@namehash/namekit-react/client"; | ||
|
|
||
| const meta: Meta<typeof Identity.Root> = { | ||
| title: "Namekit/Identity", | ||
|
|
@@ -21,7 +21,7 @@ export default meta; | |
|
|
||
| type Story = StoryObj<typeof Identity.Root>; | ||
|
|
||
| const IdentityCard: React.FC<{ | ||
| const DefaultIdentityCard: React.FC<{ | ||
| address: string; | ||
| network?: "mainnet" | "sepolia"; | ||
| returnNameGuardReport?: boolean; | ||
|
|
@@ -35,29 +35,147 @@ const IdentityCard: React.FC<{ | |
| <Identity.Name /> | ||
| <Identity.Address /> | ||
| <Identity.NameGuardShield /> | ||
| <Identity.ENSProfileLink /> | ||
| <Identity.ProfileLink> | ||
| <div> | ||
| <ENSLogo /> | ||
| <span>View on ENS App</span> | ||
| </div> | ||
| </Identity.ProfileLink> | ||
| <Identity.Followers /> | ||
| </Identity.Root> | ||
| ); | ||
|
|
||
| const customAppConfig = { | ||
| profileLinks: { | ||
| getProfileURL: (address: string) => `/profiles/${address}`, | ||
| getProfileLink: (address: string, children: React.ReactNode) => ( | ||
| <a href={`/profiles/${address}`}>{children}</a> | ||
|
||
| ), | ||
| }, | ||
| }; | ||
|
|
||
| const CustomAppIdentityCard: React.FC<{ address: string }> = ({ address }) => ( | ||
| <NameKitProvider config={customAppConfig}> | ||
| <Identity.Root address={address}> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest we make a few updates here to put our "Identity" components on a stronger foundation. Specifically: an "identity reference" is more than just an address. It is a combination of a chain and an address.
Here's an acceptance criteria for everything described above: If I use our Identity components to show the identity for a chain other than mainnet (such as sepolia) I shouldn't get a profile link to view the profile of that address on mainnet as that wouldn't actually be the correct profile / identity. Please ask me if any questions 👍 |
||
| <Identity.Avatar /> | ||
| <Identity.Name /> | ||
| <Identity.ProfileLink> | ||
| <button>View Profile</button> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any special reason not to use our
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I mentioned before, I'm avoiding the UI side of things until we nail the DX. Swapping out components is easy, but if we need to make changes to the UI components, it could cause conflicts so I've opted not to focus on that yet. |
||
| </Identity.ProfileLink> | ||
| </Identity.Root> | ||
| </NameKitProvider> | ||
| ); | ||
|
|
||
| const modalConfig = { | ||
| profileLinks: { | ||
| getProfileURL: (address: string) => `#${address}`, | ||
|
||
| getProfileLink: (address: string, children: React.ReactNode) => ( | ||
| <button | ||
|
||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| alert(`Would open modal for ${address}`); | ||
notrab marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }} | ||
| > | ||
| {children} | ||
| </button> | ||
| ), | ||
| }, | ||
| }; | ||
|
|
||
| const ModalIdentityCard: React.FC<{ address: string }> = ({ address }) => ( | ||
| <NameKitProvider config={modalConfig}> | ||
| <Identity.Root address={address}> | ||
| <Identity.Avatar /> | ||
| <Identity.Name /> | ||
| <Identity.ProfileLink> | ||
| <> | ||
| <span>Open Profile Modal</span> | ||
| <span>→</span> | ||
| </> | ||
| </Identity.ProfileLink> | ||
| </Identity.Root> | ||
| </NameKitProvider> | ||
| ); | ||
|
|
||
| export const Default: Story = { | ||
| args: { | ||
| address: "0x838aD0EAE54F99F1926dA7C3b6bFbF617389B4D9", | ||
| network: "mainnet", | ||
| className: "rounded-xl", | ||
| }, | ||
| render: (args) => <IdentityCard {...args} />, | ||
| render: (args) => <DefaultIdentityCard {...args} />, | ||
| }; | ||
|
|
||
| export const MultipleCards: Story = { | ||
| render: () => ( | ||
| <> | ||
| <IdentityCard address="0x838aD0EAE54F99F1926dA7C3b6bFbF617389B4D9" /> | ||
| <IdentityCard address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" /> | ||
| <IdentityCard | ||
| address="0xf81bc66316a3f2a60adc258f97f61dfcbdd23bb1" | ||
| returnNameGuardReport | ||
| /> | ||
| <DefaultIdentityCard address="0x838aD0EAE54F99F1926dA7C3b6bFbF617389B4D9" /> | ||
| <CustomAppIdentityCard address="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" /> | ||
| <ModalIdentityCard address="0xf81bc66316a3f2a60adc258f97f61dfcbdd23bb1" /> | ||
| </> | ||
| ), | ||
| }; | ||
|
|
||
| export const ProfileLinkVariants: Story = { | ||
| render: () => { | ||
| const address = "0x838aD0EAE54F99F1926dA7C3b6bFbF617389B4D9"; | ||
|
|
||
| return ( | ||
| <div> | ||
| <div> | ||
| <h3>Default ENS App Link</h3> | ||
| <Identity.Root address={address}> | ||
| <Identity.ProfileLink> | ||
| <div className="nk-flex nk-items-center nk-gap-2"> | ||
| <ENSLogo /> | ||
| <span>View on ENS App</span> | ||
| </div> | ||
| </Identity.ProfileLink> | ||
| </Identity.Root> | ||
| </div> | ||
|
|
||
| <div> | ||
| <h3>Custom App Link</h3> | ||
| <NameKitProvider config={customAppConfig}> | ||
| <Identity.Root address={address}> | ||
| <Identity.ProfileLink> | ||
| <button>View Profile</button> | ||
| </Identity.ProfileLink> | ||
| </Identity.Root> | ||
| </NameKitProvider> | ||
| </div> | ||
|
|
||
| <div> | ||
| <h3>Modal Trigger</h3> | ||
| <NameKitProvider config={modalConfig}> | ||
| <Identity.Root address={address}> | ||
| <Identity.ProfileLink> | ||
| <> | ||
| <span>Open Profile Modal</span> | ||
| <span>→</span> | ||
| </> | ||
| </Identity.ProfileLink> | ||
| </Identity.Root> | ||
| </NameKitProvider> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }, | ||
| }; | ||
|
|
||
| const ENSLogo = () => ( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest we move this into a dedicated UI component inside NameKit React. |
||
| <svg | ||
| fill="none" | ||
| height="16" | ||
| viewBox="0 0 202 231" | ||
| width="14" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <g fill="#0080bc"> | ||
| <path d="m98.3592 2.80337-63.5239 104.52363c-.4982.82-1.6556.911-2.2736.178-5.5924-6.641-26.42692-34.89-.6463-60.6377 23.5249-23.4947 53.4891-40.24601 64.5942-46.035595 1.2599-.656858 2.587.758365 1.8496 1.971665z" /> | ||
| <path d="m94.8459 230.385c1.2678.888 2.8299-.626 1.9802-1.918-14.1887-21.581-61.3548-93.386-67.8702-104.165-6.4264-10.632-19.06614-28.301-20.12056-43.4178-.10524-1.5091-2.19202-1.8155-2.71696-.3963-.8466 2.2888-1.74793 5.0206-2.58796 8.1413-10.60469 39.3938 4.79656 81.1968 38.24488 104.6088l53.0706 37.148z" /> | ||
| <path d="m103.571 228.526 63.524-104.523c.498-.82 1.656-.911 2.274-.178 5.592 6.64 26.427 34.89.646 60.638-23.525 23.494-53.489 40.246-64.594 46.035-1.26.657-2.587-.758-1.85-1.972z" /> | ||
| <path d="m107.154.930762c-1.268-.8873666-2.83.625938-1.98 1.918258 14.189 21.58108 61.355 93.38638 67.87 104.16498 6.427 10.632 19.066 28.301 20.121 43.418.105 1.509 2.192 1.815 2.717.396.846-2.289 1.748-5.02 2.588-8.141 10.604-39.394-4.797-81.1965-38.245-104.609z" /> | ||
| </g> | ||
| </svg> | ||
| ); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,9 +10,34 @@ import { | |
| Network, | ||
| type SecurePrimaryNameResult, | ||
| } from "@namehash/nameguard"; | ||
| import cc from "classcat"; | ||
|
|
||
| export interface ProfileLinkConfig { | ||
notrab marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| getProfileURL: (address: string) => string; | ||
|
||
| getProfileLink: (address: string, children: ReactNode) => JSX.Element; | ||
notrab marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I shared some other comments in this PR with the idea of having a default fallback implementation of the profile link anchor. Ex: An ENS Logo, etc.. It seems to me that one nice way to do this would be to define another function in this interface, for example: Appreciate your advice / suggestions 👍 |
||
|
|
||
| interface NameKitConfig { | ||
| profileLinks?: ProfileLinkConfig; | ||
|
||
| } | ||
|
|
||
| const NameKitConfigContext = createContext<NameKitConfig>({}); | ||
|
|
||
| export const NameKitProvider: React.FC<{ | ||
| children: React.ReactNode; | ||
| config: NameKitConfig; | ||
| }> = ({ children, config }) => { | ||
| return ( | ||
| <NameKitConfigContext.Provider value={config}> | ||
| {children} | ||
| </NameKitConfigContext.Provider> | ||
| ); | ||
| }; | ||
|
|
||
| export const useNameKitConfig = () => useContext(NameKitConfigContext); | ||
|
|
||
| interface IdentityContextType { | ||
| network: string; | ||
| network: Network; | ||
| address: string; | ||
| returnNameGuardReport: boolean; | ||
| loadingState: "loading" | "error" | "success"; | ||
|
|
@@ -243,42 +268,40 @@ const Followers = ({ className, ...props }: SubComponentProps) => { | |
| ); | ||
| }; | ||
|
|
||
| const ENSLogo = () => ( | ||
| <svg | ||
| fill="none" | ||
| height="16" | ||
| viewBox="0 0 202 231" | ||
| width="14" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <g fill="#0080bc"> | ||
| <path d="m98.3592 2.80337-63.5239 104.52363c-.4982.82-1.6556.911-2.2736.178-5.5924-6.641-26.42692-34.89-.6463-60.6377 23.5249-23.4947 53.4891-40.24601 64.5942-46.035595 1.2599-.656858 2.587.758365 1.8496 1.971665z" /> | ||
| <path d="m94.8459 230.385c1.2678.888 2.8299-.626 1.9802-1.918-14.1887-21.581-61.3548-93.386-67.8702-104.165-6.4264-10.632-19.06614-28.301-20.12056-43.4178-.10524-1.5091-2.19202-1.8155-2.71696-.3963-.8466 2.2888-1.74793 5.0206-2.58796 8.1413-10.60469 39.3938 4.79656 81.1968 38.24488 104.6088l53.0706 37.148z" /> | ||
| <path d="m103.571 228.526 63.524-104.523c.498-.82 1.656-.911 2.274-.178 5.592 6.64 26.427 34.89.646 60.638-23.525 23.494-53.489 40.246-64.594 46.035-1.26.657-2.587-.758-1.85-1.972z" /> | ||
| <path d="m107.154.930762c-1.268-.8873666-2.83.625938-1.98 1.918258 14.189 21.58108 61.355 93.38638 67.87 104.16498 6.427 10.632 19.066 28.301 20.121 43.418.105 1.509 2.192 1.815 2.717.396.846-2.289 1.748-5.02 2.588-8.141 10.604-39.394-4.797-81.1965-38.245-104.609z" /> | ||
| </g> | ||
| </svg> | ||
| ); | ||
|
|
||
| const ENSProfileLink = ({ className, ...props }: SubComponentProps) => { | ||
| const { identityData, loadingState } = useIdentity(); | ||
|
|
||
| if (loadingState !== "success" || !identityData?.display_name) { | ||
| interface ProfileLinkProps extends SubComponentProps { | ||
| config?: ProfileLinkConfig; | ||
| } | ||
|
|
||
| const ProfileLink = ({ | ||
| className, | ||
| children, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to make children an optional param, and if not provided, to have some default children that would for example just be the ENS Logo? |
||
| config: instanceConfig, | ||
| ...props | ||
| }: ProfileLinkProps) => { | ||
| const { loadingState, address } = useIdentity(); | ||
| const globalConfig = useNameKitConfig(); | ||
|
|
||
| if (loadingState !== "success") { | ||
notrab marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <a | ||
| href={`https://app.ens.domains/${identityData.display_name}`} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className={`namekit-ens-profile-link ${className}`} | ||
| {...props} | ||
| > | ||
| <ENSLogo /> | ||
| <span className="nk-ml-1">ENS Profile</span> | ||
| </a> | ||
| ); | ||
| const config = instanceConfig || | ||
| globalConfig.profileLinks || { | ||
|
||
| getProfileURL: (address) => `https://app.ens.domains/${address}`, | ||
| getProfileLink: (address, children) => ( | ||
| <a | ||
|
||
| href={`https://app.ens.domains/${address}`} | ||
|
||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className={cc(["namekit-profile-link", className])} | ||
| {...props} | ||
| > | ||
| {children} | ||
| </a> | ||
| ), | ||
| }; | ||
|
|
||
| return config.getProfileLink(address, children); | ||
| }; | ||
|
|
||
| export const Identity = { | ||
|
|
@@ -287,6 +310,6 @@ export const Identity = { | |
| Name, | ||
| Address, | ||
| NameGuardShield, | ||
| ENSProfileLink, | ||
| ProfileLink, | ||
| Followers, | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check all places in the code where you have an address param. These should all use the
Addresstype defined in viem.