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 './nfa-card';
|
||||
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 './github';
|
||||
export * from './toasts';
|
||||
export * from './ens';
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
import ENSReducer from './features/ens/ens-slice';
|
||||
import fleekERC721Reducer from './features/fleek-erc721/fleek-erc721-slice';
|
||||
import githubReducer from './features/github/github-slice';
|
||||
import toastsReducer from './features/toasts/toasts-slice';
|
||||
|
|
@ -9,6 +10,7 @@ export const store = configureStore({
|
|||
fleekERC721: fleekERC721Reducer,
|
||||
github: githubReducer,
|
||||
toasts: toastsReducer,
|
||||
ENS: ENSReducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Flex } from '@/components';
|
||||
import { Flex, ResolvedAddress } from '@/components';
|
||||
|
||||
import { ColorPickerTest } from './color-picker';
|
||||
import { ComboboxTest } from './combobox-test';
|
||||
|
|
@ -7,6 +7,9 @@ import { ToastTest } from './toast-test';
|
|||
export const ComponentsTest: React.FC = () => {
|
||||
return (
|
||||
<Flex css={{ flexDirection: 'column' }}>
|
||||
<ResolvedAddress css={{ alignSelf: 'center' }}>
|
||||
{'0x7ed735b7095c05d78df169f991f2b7f1a1f1a049'}
|
||||
</ResolvedAddress>
|
||||
<ComboboxTest />
|
||||
<ColorPickerTest />
|
||||
<ToastTest />
|
||||
|
|
|
|||
Loading…
Reference in New Issue