Skip to content

Add ENS records profile edit modal - #412

Merged
encryptedDegen merged 7 commits into
mainfrom
ens-records-profile-modal
Jun 4, 2026
Merged

Add ENS records profile edit modal#412
encryptedDegen merged 7 commits into
mainfrom
ens-records-profile-modal

Conversation

@encryptedDegen

@encryptedDegen encryptedDegen commented Jun 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Add a shared ENS records modal wrapper that renders the EIK ENSRecords component and supports signed avatar/header uploads.
  • Wire ProfileCard and FullWidthProfile extraOptions.onEditProfileClick handlers to open the modal from their Edit Profile buttons.
  • Remove the local external ENS edit button override so EIK owns the connected-user edit action.

Testing

  • Not run: bun typecheck could not start because tsc is unavailable without installed dependencies in this sandbox.

Notes

  • Have to wait for new EIK version (0.2.75)

@vercel

vercel Bot commented Jun 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview Jun 3, 2026 10:51pm

Request Review

@greptile-apps

greptile-apps Bot commented Jun 1, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds an in-app ENS profile edit modal powered by EIK's ENSRecords component, replacing the previous external ENS app link. It wires onEditProfileClick into both the sidebar ProfileCard and the full-width FullWidthProfile, and implements signed avatar/header image uploads via eidk.me.

  • New ENSRecordsModal: wraps EIK's ENSRecords in a portal-based overlay; handles typed-data signing and PUT uploads to eidk.me, with cache-busting for updated images.
  • Profile card wiring: both UserProfileCard and UserProfile now open the modal via onEditProfileClick; the old ENS app link and EnsLogo import are removed.
  • Post-save refresh: setFetchFreshProfile is threaded down from useUserInfo through UserInfoUserProfileENSRecordsModal, triggering a fresh profile fetch after a successful record save.

Confidence Score: 4/5

Safe to merge with the refetch key bug acknowledged — it only affects users who access their own profile via ENS name and save records more than once in a session.

The onSuccess refetch logic in ens-records-modal.tsx uses connectedAddress as a React Query prefix, but the active profile query is keyed by the URL user parameter. When that parameter is an ENS name, the hex address prefix matches nothing, silently skipping the data refresh on every save after the first.

src/components/ens-records-modal.tsx — the onSuccess refetch logic; also worth re-checking the previous round's open items around the useCallback placement and the undefined-name silent no-op.

Important Files Changed

Filename Overview
src/components/ens-records-modal.tsx New modal component wrapping EIK's ENSRecords; handles signed avatar/header uploads. The onSuccess refetch logic uses connectedAddress as a query key prefix but the active profile queries are keyed by the URL user parameter (ENS name), causing a no-op refetch on second+ saves for ENS-named profiles.
src/components/user-profile-card/index.tsx Replaces the custom ENS edit button link with an in-app ENSRecordsModal; removes the EnsLogo import. onEditProfileClick is wired unconditionally but ENSRecordsModal silently no-ops when ensRecordsName is undefined (already flagged in previous review).
src/components/user-profile/index.tsx Adds ENSRecordsModal wired to the full-width profile; passes setFetchFreshProfile through. Gains 'use client' directive. ensRecordsName falls back to undefined for hex-only addresses (already flagged).
src/app/[user]/components/user-info.tsx Threads setFetchFreshProfile down from useUserInfo into UserProfile; minimal change, looks correct.
src/components/profile-tooltip-wrapper.tsx Removes the unnecessary as React.ReactElement cast from children — safe cleanup.
package.json Bumps ethereum-identity-kit from ^0.2.74 to ^0.2.77 to pick up ENSRecords and onEditProfileClick support.

Sequence Diagram

sequenceDiagram
    participant User
    participant ProfileCard/FullWidthProfile
    participant ENSRecordsModal
    participant EIK_ENSRecords
    participant eidk.me
    participant ReactQuery

    User->>ProfileCard/FullWidthProfile: clicks Edit Profile
    ProfileCard/FullWidthProfile->>ENSRecordsModal: setEnsRecordsOpen(true)
    Note over ENSRecordsModal: if (!name) return null (silent fail)
    ENSRecordsModal->>EIK_ENSRecords: render with onImageUpload, onSuccess

    User->>EIK_ENSRecords: selects image to upload
    EIK_ENSRecords->>ENSRecordsModal: uploadImage(dataURL, type)
    ENSRecordsModal->>ENSRecordsModal: sha256(dataURLToBytes(dataURL))
    ENSRecordsModal->>wagmi: signTypedDataAsync(typedData)
    wagmi-->>ENSRecordsModal: sig
    ENSRecordsModal->>eidk.me: PUT /name[/h] (JSON body w/ sig)
    eidk.me-->>ENSRecordsModal: "{ url }"
    ENSRecordsModal->>ENSRecordsModal: forceRefetchImage(finalUrl)

    User->>EIK_ENSRecords: saves records
    EIK_ENSRecords->>ENSRecordsModal: onSuccess()
    Note over ENSRecordsModal: setTimeout 1500ms
    ENSRecordsModal->>ReactQuery: "setFetchFreshProfile(state => true)"
    Note over ReactQuery: 1st save: key change triggers fresh fetch
    Note over ReactQuery: 2nd+ save: refetchQueries no-op for ENS names
    ENSRecordsModal->>ENSRecordsModal: onClose()
Loading

Fix All in Conductor Fix All in Cursor Fix All in Codex Fix All in Claude Code

Reviews (6): Last reviewed commit: "bump up EIK version" | Re-trigger Greptile

Comment thread src/components/ens-records-modal.tsx Outdated
Comment on lines +26 to +33
const ENSRecordsModal: React.FC<ENSRecordsModalProps> = ({ name, onClose }) => {
const { resolvedTheme } = useTheme()
const { address: connectedAddress } = useAccount()
const { signTypedDataAsync } = useSignTypedData()

if (!name) return null

const uploadImage = async (dataURL: string, type: 'avatar' | 'header') => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 uploadImage cannot be stabilised with useCallback due to early-return placement

uploadImage is a new function reference on every render because it is declared after the if (!name) return null guard, which prevents it from being wrapped in a hook. If ENSRecords internally compares onImageUpload by reference in a useEffect or memo dependency, every re-render of the parent (e.g. a resolvedTheme change) will be seen as a new callback and could reset internal upload state mid-operation. The fix is to move the early-return to the bottom of the component body so useCallback can be used at the top level.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Conductor Fix in Cursor Fix in Codex Fix in Claude Code

Comment thread src/components/ens-records-modal.tsx Outdated
const { selectedList } = useEFPProfile()
const [ensRecordsOpen, setEnsRecordsOpen] = useState(false)
const { followState, profileName, isConnectedUserCard } = useProfileCard(profile)
const ensRecordsName = profile?.ens?.name ?? profileName

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Silent no-op when ENS name is absent

ensRecordsName is profile?.ens?.name ?? profileName. profileName comes from useProfileCardfetchedEnsProfile?.name, which is undefined during the initial query load and permanently undefined for any address that has no primary ENS name. When ensRecordsOpen becomes true but ensRecordsName is undefined, ENSRecordsModal hits its if (!name) return null guard and renders nothing—the user clicks "Edit Profile" and the UI silently does nothing. A guard before calling setEnsRecordsOpen(true) (or surfacing a message) would prevent the invisible dead-end.

The same issue exists in src/components/user-profile/index.tsx line 57 (addressOrName.endsWith('.eth') ? addressOrName : undefinedundefined for hex-only profiles).

Fix in Conductor Fix in Cursor Fix in Codex Fix in Claude Code

className='bg-neutral'
extraOptions={{
openListSettings: openListSettingsModal,
onEditProfileClick: () => setEnsRecordsOpen(true),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 onEditProfileClick is registered unconditionally, but the "Edit Profile" button should only be reachable for the connected user's own card. Binding it only when isConnectedUserCard is true makes the intent explicit and eliminates any accidental invocation if EIK's internal guard ever differs from the app's logic.

Suggested change
onEditProfileClick: () => setEnsRecordsOpen(true),
onEditProfileClick: isConnectedUserCard ? () => setEnsRecordsOpen(true) : undefined,

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Conductor Fix in Cursor Fix in Codex Fix in Claude Code

showFollowerState={true}
extraOptions={{
role: role,
onEditProfileClick: () => setEnsRecordsOpen(true),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Same defensive guard as in UserProfileCard: binding onEditProfileClick only when isMyProfile is true ensures the handler cannot fire from another user's profile view, regardless of how EIK resolves connectedAddress internally.

Suggested change
onEditProfileClick: () => setEnsRecordsOpen(true),
onEditProfileClick: isMyProfile ? () => setEnsRecordsOpen(true) : undefined,

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Conductor Fix in Cursor Fix in Codex Fix in Claude Code

Comment thread src/components/ens-records-modal.tsx
Comment on lines +124 to +133
const onSuccess = () => {
setTimeout(() => {
// @ts-expect-error - both types support function calls with a state argument
setFetchFreshProfile?.((state) => {
if (state) queryClient.refetchQueries({ queryKey: ['profile', connectedAddress] })
return true
})
onClose()
}, 1500)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Second save silently skips refetch when profile is open via ENS name

queryClient.refetchQueries({ queryKey: ['profile', connectedAddress] }) is only reached when setFetchFreshProfile's previous state is already true (second and subsequent saves). At that point, the active query in use-user-profile.ts is keyed as ['profile', user, true] where user is the URL path segment — which is the ENS name (e.g., 'vitalik.eth') when a user navigates to /vitalik.eth. Since connectedAddress is always a hex address, the prefix ['profile', connectedAddress] does not match any active query key, making the refetchQueries call a no-op. The profile shows stale data after every save past the first one for any user whose profile URL is their ENS name.

Fix in Conductor Fix in Cursor Fix in Codex Fix in Claude Code

@encryptedDegen
encryptedDegen merged commit 8894b6a into main Jun 4, 2026
7 checks passed
@encryptedDegen
encryptedDegen deleted the ens-records-profile-modal branch June 4, 2026 00:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant