Add ENS records profile edit modal - #412
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds an in-app ENS profile edit modal powered by EIK's
Confidence Score: 4/5Safe 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
Sequence DiagramsequenceDiagram
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()
Reviews (6): Last reviewed commit: "bump up EIK version" | Re-trigger Greptile |
| 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') => { |
There was a problem hiding this comment.
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!
| const { selectedList } = useEFPProfile() | ||
| const [ensRecordsOpen, setEnsRecordsOpen] = useState(false) | ||
| const { followState, profileName, isConnectedUserCard } = useProfileCard(profile) | ||
| const ensRecordsName = profile?.ens?.name ?? profileName |
There was a problem hiding this comment.
Silent no-op when ENS name is absent
ensRecordsName is profile?.ens?.name ?? profileName. profileName comes from useProfileCard → fetchedEnsProfile?.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 : undefined → undefined for hex-only profiles).
| className='bg-neutral' | ||
| extraOptions={{ | ||
| openListSettings: openListSettingsModal, | ||
| onEditProfileClick: () => setEnsRecordsOpen(true), |
There was a problem hiding this comment.
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.
| 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!
| showFollowerState={true} | ||
| extraOptions={{ | ||
| role: role, | ||
| onEditProfileClick: () => setEnsRecordsOpen(true), |
There was a problem hiding this comment.
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.
| 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!
| 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) | ||
| } |
There was a problem hiding this comment.
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.
Summary
ENSRecordscomponent and supports signed avatar/header uploads.ProfileCardandFullWidthProfileextraOptions.onEditProfileClickhandlers to open the modal from their Edit Profile buttons.Testing
bun typecheckcould not start becausetscis unavailable without installed dependencies in this sandbox.Notes