feat: UI create functionality to resolve address (#227)
* feat: add ENS slice * feat: add resolved address hook * feat: add resolved address component
This commit is contained in:
parent
9934ad1cfd
commit
5ae09105c0
|
|
@ -7,3 +7,4 @@ export * from './toast';
|
||||||
export * from './step';
|
export * from './step';
|
||||||
export * from './nfa-card';
|
export * from './nfa-card';
|
||||||
export * from './nfa-preview';
|
export * from './nfa-preview';
|
||||||
|
export * from './resolved-address';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './resolved-address';
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { keyframes, styled } from '@/theme';
|
||||||
|
|
||||||
|
const Loading = keyframes({
|
||||||
|
'0%': {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
'50%': {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
'100%': {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ResolvedAddressStyles = {
|
||||||
|
Container: styled('span', {
|
||||||
|
'&[data-loading="true"]': {
|
||||||
|
animation: `${Loading} 1s ease-in-out infinite`,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { useResolvedAddress } from '@/store';
|
||||||
|
import { forwardStyledRef } from '@/theme';
|
||||||
|
|
||||||
|
import { ResolvedAddressStyles as RAS } from './resolved-address.styles';
|
||||||
|
|
||||||
|
export type ResolvedAddressProps = React.ComponentPropsWithRef<
|
||||||
|
typeof RAS.Container
|
||||||
|
> & {
|
||||||
|
children: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ResolvedAddress = forwardStyledRef<
|
||||||
|
HTMLSpanElement,
|
||||||
|
ResolvedAddressProps
|
||||||
|
>(({ children, ...props }, ref) => {
|
||||||
|
const [resolvedAddress, loading] = useResolvedAddress(children);
|
||||||
|
|
||||||
|
const text = useMemo(() => {
|
||||||
|
if (!resolvedAddress.endsWith('.eth'))
|
||||||
|
return `${resolvedAddress.slice(0, 6)}...${resolvedAddress.slice(-4)}`;
|
||||||
|
return resolvedAddress;
|
||||||
|
}, [resolvedAddress]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RAS.Container {...props} ref={ref} data-loading={loading}>
|
||||||
|
{text}
|
||||||
|
</RAS.Container>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './resolve-address';
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
|
||||||
|
import { env } from '@/constants';
|
||||||
|
import { ENSActions, RootState } from '@/store';
|
||||||
|
import { AppLog } from '@/utils';
|
||||||
|
|
||||||
|
export const resolveAddress = createAsyncThunk<void, string>(
|
||||||
|
'ENS/fetchAddress',
|
||||||
|
async (address, { dispatch, getState }) => {
|
||||||
|
const { addressMap } = (getState() as RootState).ENS;
|
||||||
|
const stored = addressMap[address] || {};
|
||||||
|
|
||||||
|
if (stored.state === 'loading') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
dispatch(
|
||||||
|
ENSActions.setAddress({ key: address, value: { state: 'loading' } })
|
||||||
|
);
|
||||||
|
|
||||||
|
const provider = new ethers.providers.JsonRpcProvider(env.goerli.rpc);
|
||||||
|
const value = (await provider.lookupAddress(address)) || undefined;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
ENSActions.setAddress({
|
||||||
|
key: address,
|
||||||
|
value: { state: 'success', value },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
AppLog.error('Failed to resolve ENS name by address', error);
|
||||||
|
dispatch(
|
||||||
|
ENSActions.setAddress({ key: address, value: { state: 'loading' } })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { RootState } from '@/store';
|
||||||
|
import { useAppSelector } from '@/store/hooks';
|
||||||
|
|
||||||
|
import * as asyncThunk from './async-thunk';
|
||||||
|
|
||||||
|
export namespace ENSState {
|
||||||
|
export type QueryState = undefined | 'loading' | 'failed' | 'success';
|
||||||
|
|
||||||
|
export type Address = {
|
||||||
|
state: QueryState;
|
||||||
|
value?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AddressMap = Record<string, Address>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ENSState {
|
||||||
|
addressMap: ENSState.AddressMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: ENSState = {
|
||||||
|
addressMap: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ENSSlice = createSlice({
|
||||||
|
name: 'ENSSLice',
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setAddress: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{
|
||||||
|
key: string;
|
||||||
|
value: ENSState.Address;
|
||||||
|
}>
|
||||||
|
) => {
|
||||||
|
state.addressMap[action.payload.key] = action.payload.value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ENSActions = {
|
||||||
|
...ENSSlice.actions,
|
||||||
|
...asyncThunk,
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectENSState = (state: RootState): ENSState => state.ENS;
|
||||||
|
|
||||||
|
export const useENSStore = (): ENSState => useAppSelector(selectENSState);
|
||||||
|
|
||||||
|
export default ENSSlice.reducer;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './use-resolved-address';
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { useAppDispatch } from '@/store/hooks';
|
||||||
|
|
||||||
|
import { ENSActions, useENSStore } from '../ens-slice';
|
||||||
|
|
||||||
|
export const useResolvedAddress = (address: string): [string, boolean] => {
|
||||||
|
const { addressMap } = useENSStore();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const stored = addressMap[address] || {};
|
||||||
|
if (typeof stored.state !== 'undefined') return;
|
||||||
|
dispatch(ENSActions.resolveAddress(address));
|
||||||
|
}, [address, dispatch, addressMap]);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const stored = addressMap[address] || {};
|
||||||
|
return [stored.value || address, addressMap[address]?.state === 'loading'];
|
||||||
|
}, [address, addressMap]);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './ens-slice';
|
||||||
|
export * from './hooks';
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
export * from './fleek-erc721';
|
export * from './fleek-erc721';
|
||||||
export * from './github';
|
export * from './github';
|
||||||
export * from './toasts';
|
export * from './toasts';
|
||||||
|
export * from './ens';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { configureStore } from '@reduxjs/toolkit';
|
import { configureStore } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import ENSReducer from './features/ens/ens-slice';
|
||||||
import fleekERC721Reducer from './features/fleek-erc721/fleek-erc721-slice';
|
import fleekERC721Reducer from './features/fleek-erc721/fleek-erc721-slice';
|
||||||
import githubReducer from './features/github/github-slice';
|
import githubReducer from './features/github/github-slice';
|
||||||
import toastsReducer from './features/toasts/toasts-slice';
|
import toastsReducer from './features/toasts/toasts-slice';
|
||||||
|
|
@ -9,6 +10,7 @@ export const store = configureStore({
|
||||||
fleekERC721: fleekERC721Reducer,
|
fleekERC721: fleekERC721Reducer,
|
||||||
github: githubReducer,
|
github: githubReducer,
|
||||||
toasts: toastsReducer,
|
toasts: toastsReducer,
|
||||||
|
ENS: ENSReducer,
|
||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware({
|
getDefaultMiddleware({
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Flex } from '@/components';
|
import { Flex, ResolvedAddress } from '@/components';
|
||||||
|
|
||||||
import { ColorPickerTest } from './color-picker';
|
import { ColorPickerTest } from './color-picker';
|
||||||
import { ComboboxTest } from './combobox-test';
|
import { ComboboxTest } from './combobox-test';
|
||||||
|
|
@ -7,6 +7,9 @@ import { ToastTest } from './toast-test';
|
||||||
export const ComponentsTest: React.FC = () => {
|
export const ComponentsTest: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<Flex css={{ flexDirection: 'column' }}>
|
<Flex css={{ flexDirection: 'column' }}>
|
||||||
|
<ResolvedAddress css={{ alignSelf: 'center' }}>
|
||||||
|
{'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049'}
|
||||||
|
</ResolvedAddress>
|
||||||
<ComboboxTest />
|
<ComboboxTest />
|
||||||
<ColorPickerTest />
|
<ColorPickerTest />
|
||||||
<ToastTest />
|
<ToastTest />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue