Add disabled chain list

This commit is contained in:
HeesungB 2022-12-15 19:06:44 +09:00
parent 7de3767d03
commit bb58fb54ea
13 changed files with 221 additions and 200 deletions

View File

@ -1,20 +1,16 @@
import { ChainItemType } from "../../types";
import { Dispatch, FunctionComponent, SetStateAction } from "react";
import { ChainImage } from "./chain-image";
import { Flex1 } from "../../styles/flex-1";
import {
ChangeEvent,
Dispatch,
FunctionComponent,
SetStateAction,
useEffect,
useState,
} from "react";
import {
ChainCheckBox,
ChainImageContainer,
ChainInfoContainer,
ChainItemContainer,
} from "./chain-list";
import { ChainImage } from "./chain-image";
import { Flex1 } from "../../styles/flex-1";
import { ChainCheckBox, ChainName, WalletAddress } from "./chain-item";
ChainName,
WalletAddress,
} from "./chain-item";
import color from "../../styles/color";
import styled from "styled-components";

View File

@ -1,17 +1,5 @@
import { ChainItemType } from "../../types";
import {
ChangeEvent,
Dispatch,
FunctionComponent,
SetStateAction,
useEffect,
useState,
} from "react";
import {
ChainImageContainer,
ChainInfoContainer,
ChainItemContainer,
} from "./chain-list";
import { ChainItemType, WidthHeightProps } from "../../types";
import { FunctionComponent, useEffect, useState } from "react";
import color from "../../styles/color";
import { Flex1 } from "../../styles/flex-1";
@ -22,26 +10,31 @@ interface Props {
chainItem: ChainItemType;
checkedItemHandler: (chainItem: ChainItemType, isChecked: boolean) => void;
checkedItems: Set<unknown>;
disabled?: boolean;
}
export const ChainItem: FunctionComponent<Props> = (props) => {
const { chainItem, checkedItemHandler, checkedItems } = props;
const [checked, setChecked] = useState(false);
const { chainItem, checkedItemHandler, checkedItems, disabled } = props;
const [checked, setChecked] = useState(disabled);
const checkHandler = () => {
setChecked(!checked);
checkedItemHandler(chainItem, !checked);
if (!disabled) {
setChecked(!checked);
checkedItemHandler(chainItem, !checked);
}
};
useEffect(() => {
setChecked(checkedItems.has(chainItem));
if (!disabled) {
setChecked(checkedItems.has(chainItem));
}
}, [checkedItems]);
return (
<ChainItemContainer
key={chainItem.prefix}
isLoading={false}
checked={checked}
disabled={disabled}
onClick={checkHandler}
>
<ChainImageContainer width="3rem" height="3rem">
@ -63,6 +56,41 @@ export const ChainItem: FunctionComponent<Props> = (props) => {
);
};
export const ChainItemContainer = styled.div<{
isLoading: boolean;
checked?: boolean;
disabled?: boolean;
}>`
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
padding: 1.5rem;
cursor: pointer;
opacity: ${(props) => (props.disabled ? "0.3" : "1")};
&:hover {
background: ${(props) => (props.isLoading ? null : color.grey["600"])};
}
`;
export const ChainImageContainer = styled.div<WidthHeightProps>`
width: ${(props) => props.width};
height: ${(props) => props.height};
position: relative;
`;
export const ChainInfoContainer = styled.div`
display: flex;
flex-direction: column;
gap: 0.5rem;
`;
export const ChainName = styled.div`
font-weight: 600;
font-size: 0.8rem;

View File

@ -1,5 +1,5 @@
import { Dispatch, FunctionComponent, SetStateAction, useEffect } from "react";
import { ChainItemType, WidthHeightProps } from "../../types";
import { ChainItemType } from "../../types";
import color from "../../styles/color";
import styled from "styled-components";
import { ChainItem } from "./chain-item";
@ -8,6 +8,7 @@ interface Props {
allChecked: boolean;
setAllChecked: Dispatch<SetStateAction<boolean>>;
chainList: ChainItemType[];
disabledChainList: ChainItemType[];
checkedItems: Set<unknown>;
setCheckedItems: Dispatch<SetStateAction<Set<unknown>>>;
}
@ -17,6 +18,7 @@ export const ChainList: FunctionComponent<Props> = (props) => {
allChecked,
setAllChecked,
chainList,
disabledChainList,
checkedItems,
setCheckedItems,
} = props;
@ -59,6 +61,15 @@ export const ChainList: FunctionComponent<Props> = (props) => {
checkedItems={checkedItems}
/>
))}
{disabledChainList.map((chainItem) => (
<ChainItem
key={chainItem.address}
chainItem={chainItem}
checkedItemHandler={checkedItemHandler}
checkedItems={checkedItems}
disabled={true}
/>
))}
</ChainContainer>
);
};
@ -72,35 +83,3 @@ export const ChainContainer = styled.div`
background-color: ${(props) => props.color};
`;
export const ChainItemContainer = styled.div<{
isLoading: boolean;
checked?: boolean;
}>`
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
padding: 1.5rem;
cursor: pointer;
&:hover {
background: ${(props) => (props.isLoading ? null : color.grey["700"])};
}
`;
export const ChainImageContainer = styled.div<WidthHeightProps>`
width: ${(props) => props.width};
height: ${(props) => props.height};
position: relative;
`;
export const ChainInfoContainer = styled.div`
display: flex;
flex-direction: column;
gap: 0.5rem;
`;

View File

@ -10,12 +10,13 @@ import {
WalletType,
} from "../../constants/wallet";
import { getKeplrFromWindow, KeplrWallet } from "../../wallets";
import { loginWithTwitter } from "../../repository";
import { loginWithTwitter } from "../../queries";
interface Props {
wallet: WalletType;
}
// Todo: Wallet 관련된 부분을 Context로 빼는 부분
export const WalletItem: FunctionComponent<Props> = (props: Props) => {
const { wallet } = props;

View File

@ -3,6 +3,7 @@ export const MainChainId = "osmo-test-4";
export const RPC_URL = "https://rpc.testnet.osmosis.zone";
export const REST_URL = "https://lcd.testnet.osmosis.zone";
// TODO: .evn에 없으면 디폴트값 설정
export const REGISTRAR_ADDRESS =
"osmo1npn97g7hsgqlp70rw8nhd7c7vyvkukv9x0n25sn4fk5mgcjlz4gq9zlgf3";
export const RESOLVER_ADDRESS =

View File

@ -1,51 +1,8 @@
import { request } from "../utils/url";
import {
REGISTRAR_ADDRESS,
RESOLVER_ADDRESS,
REST_URL,
} from "../constants/icns";
import { Buffer } from "buffer/";
import {
AddressesQueryResponse,
CosmwasmExecuteMessageResult,
NameByTwitterIdQueryResponse,
QueryError,
} from "../types";
import { CosmwasmExecuteMessageResult } from "../types";
import { makeCosmwasmExecMsg } from "../wallets";
import { REGISTRAR_ADDRESS, RESOLVER_ADDRESS } from "../constants/icns";
import { ContractFee } from "../constants/wallet";
const getCosmwasmQueryUrl = (contractAddress: string, queryMsg: string) =>
`${REST_URL}/cosmwasm/wasm/v1/contract/${contractAddress}/smart/${queryMsg}`;
export const queryRegisteredTwitterId = async (
twitterId: string,
): Promise<NameByTwitterIdQueryResponse | QueryError> => {
const msg = {
name_by_twitter_id: { twitter_id: twitterId },
};
return request<NameByTwitterIdQueryResponse>(
getCosmwasmQueryUrl(
REGISTRAR_ADDRESS,
Buffer.from(JSON.stringify(msg)).toString("base64"),
),
);
};
export const queryAddressesFromTwitterName = async (
twitterUsername: string,
): Promise<AddressesQueryResponse> => {
const msg = {
addresses: { name: twitterUsername },
};
return request<AddressesQueryResponse>(
getCosmwasmQueryUrl(
RESOLVER_ADDRESS,
Buffer.from(JSON.stringify(msg)).toString("base64"),
),
);
};
export const makeClaimMessage = (
senderAddress: string,
twitterUserName: string,

1
messages/index.ts Normal file
View File

@ -0,0 +1 @@
export * from "./icns";

View File

@ -32,6 +32,8 @@ export default function CompletePage() {
const result = await txTracer.traceTx(Buffer.from(txHash, "hex"));
console.log(result);
// Todo rsult => 확인 후에 확인
};
return (

View File

@ -2,8 +2,12 @@
import { useEffect, useState } from "react";
// Types
import { ChainItemType, TwitterProfileType } from "../../types";
import { checkTwitterAuthQueryParameter, request } from "../../utils/url";
import {
ChainItemType,
RegisteredAddresses,
TwitterProfileType,
} from "../../types";
import { checkTwitterAuthQueryParameter } from "../../utils/url";
// Styles
import styled from "styled-components";
@ -20,7 +24,6 @@ import { useRouter } from "next/router";
import {
getKeplrFromWindow,
KeplrWallet,
makeCosmwasmExecMsg,
sendMsgs,
simulateMsgs,
} from "../../wallets";
@ -29,26 +32,20 @@ import { ChainIdHelper } from "@keplr-wallet/cosmos";
import AllChainsIcon from "../../public/images/svg/all-chains-icon.svg";
import { AllChainsItem } from "../../components/chain-list/all-chains-item";
import { SearchInput } from "../../components/search-input";
import {
MainChainId,
REGISTRAR_ADDRESS,
RESOLVER_ADDRESS,
REST_URL,
} from "../../constants/icns";
import { MainChainId, RESOLVER_ADDRESS, REST_URL } from "../../constants/icns";
import {
fetchTwitterInfo,
makeClaimMessage,
makeSetRecordMessage,
queryAddressesFromTwitterName,
queryRegisteredTwitterId,
verifyTwitterAccount,
} from "../../repository";
} from "../../queries";
import { ErrorHandler } from "../../utils/error";
import {
KEPLR_NOT_FOUND_ERROR,
TWITTER_LOGIN_ERROR,
} from "../../constants/error-message";
import { makeClaimMessage, makeSetRecordMessage } from "../../messages";
export default function VerificationPage() {
const router = useRouter();
@ -57,13 +54,19 @@ export default function VerificationPage() {
const [isLoading, setIsLoading] = useState(true);
const [wallet, setWallet] = useState<KeplrWallet>();
const [allChains, setAllChains] = useState<ChainItemType>();
const [chainList, setChainList] = useState<ChainItemType[]>([]);
const [registeredAddressList, setRegisteredAddressList] = useState<string[]>(
const [disabledChainList, setDisabledChainList] = useState<ChainItemType[]>(
[],
);
const [checkedItems, setCheckedItems] = useState(new Set());
const [registeredChainList, setRegisteredChainList] = useState<
RegisteredAddresses[]
>([]);
const [allChains, setAllChains] = useState<ChainItemType>();
const [allChecked, setAllChecked] = useState(false);
const [checkedItems, setCheckedItems] = useState(new Set());
const [searchValue, setSearchValue] = useState("");
useEffect(() => {
@ -80,7 +83,7 @@ export default function VerificationPage() {
// Fetch Twitter Profile
const twitterInfo = await fetchTwitterInfo(state, code);
// check registered
// contract check registered
const registeredQueryResponse = await queryRegisteredTwitterId(
twitterInfo.id,
);
@ -95,11 +98,7 @@ export default function VerificationPage() {
registeredQueryResponse.data.name,
);
setRegisteredAddressList(
addressesQueryResponse.data.addresses.map(
(address) => address.address,
),
);
setRegisteredChainList(addressesQueryResponse.data.addresses);
}
} catch (error) {
if (error instanceof Error && error.message === TWITTER_LOGIN_ERROR) {
@ -117,89 +116,96 @@ export default function VerificationPage() {
}, []);
useEffect(() => {
// After Wallet Initialize
if (wallet) {
fetchChainList();
}
}, [wallet]);
const disabledChainList = chainList.filter((chain) => {
for (const registeredChain of registeredChainList) {
if (
chain.prefix === registeredChain.bech32_prefix &&
chain.address === registeredChain.address
) {
return true;
}
}
useEffect(() => {
// To check registered chain
const filteredChainList = chainList.filter((chain) => {
return registeredAddressList.includes(chain.address);
return false;
});
setCheckedItems(new Set(filteredChainList));
}, [registeredAddressList]);
const filteredChainList = chainList.filter(
(chain) => !disabledChainList.includes(chain),
);
setAllChains({
chainId: "all chains",
chainName: "all chains",
prefix: `all chains(${filteredChainList.length})`,
address: filteredChainList.map((chain) => chain.chainName).join(", "),
chainImageUrl: AllChainsIcon,
});
setChainList(filteredChainList);
setDisabledChainList(disabledChainList);
}, [registeredChainList]);
const initWallet = async () => {
const keplr = await getKeplrFromWindow();
if (keplr) {
const keplrWallet = new KeplrWallet(keplr);
await fetchChainList(keplrWallet);
setWallet(keplrWallet);
} else {
ErrorHandler(KEPLR_NOT_FOUND_ERROR);
}
};
const fetchChainList = async () => {
if (wallet) {
const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map(
(c) => c.chainId,
);
const chainKeys = await Promise.all(
chainIds.map((chainId) => wallet.getKey(chainId)),
);
const fetchChainList = async (wallet: KeplrWallet) => {
const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map(
(c) => c.chainId,
);
const chainKeys = await Promise.all(
chainIds.map((chainId) => wallet.getKey(chainId)),
);
const chainInfos = (await wallet.getChainInfosWithoutEndpoints()).map(
(chainInfo) => {
return {
chainId: chainInfo.chainId,
chainName: chainInfo.chainName,
prefix: chainInfo.bech32Config.bech32PrefixAccAddr,
chainImageUrl: `https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/${
ChainIdHelper.parse(chainInfo.chainId).identifier
}/chain.png`,
};
},
);
const chainInfos = (await wallet.getChainInfosWithoutEndpoints()).map(
(chainInfo) => {
return {
chainId: chainInfo.chainId,
chainName: chainInfo.chainName,
prefix: chainInfo.bech32Config.bech32PrefixAccAddr,
chainImageUrl: `https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/${
ChainIdHelper.parse(chainInfo.chainId).identifier
}/chain.png`,
};
},
);
const chainArray = [];
for (let i = 0; i < chainKeys.length; i++) {
chainArray.push({
address: chainKeys[i].bech32Address,
...chainInfos[i],
});
}
// remove duplicated item
const filteredChainList = chainArray.filter((nextChain, index, self) => {
return (
index ===
self.findIndex((prevChain) => {
const isDuplicated = prevChain.prefix === nextChain.prefix;
if (isDuplicated && prevChain.chainName !== nextChain.chainName) {
console.log(
`${nextChain.chainName} has been deleted due to a duplicate name with ${prevChain.chainName}`,
);
}
return isDuplicated;
})
);
const chainArray = [];
for (let i = 0; i < chainKeys.length; i++) {
chainArray.push({
address: chainKeys[i].bech32Address,
...chainInfos[i],
});
setAllChains({
chainId: "all chains",
prefix: `all chains(${filteredChainList.length})`,
address: chainInfos.map((chainInfo) => chainInfo.chainName).join(", "),
chainImageUrl: AllChainsIcon,
});
setChainList(filteredChainList);
}
// remove duplicated item
const filteredChainList = chainArray.filter((nextChain, index, self) => {
return (
index ===
self.findIndex((prevChain) => {
const isDuplicated = prevChain.prefix === nextChain.prefix;
if (isDuplicated && prevChain.chainName !== nextChain.chainName) {
console.log(
`${nextChain.chainName} has been deleted due to a duplicate name with ${prevChain.chainName}`,
);
}
return isDuplicated;
})
);
});
setChainList(filteredChainList);
};
const checkAdr36 = async () => {
@ -224,7 +230,6 @@ export default function VerificationPage() {
const { state, code } = checkTwitterAuthQueryParameter(
window.location.search,
);
const twitterInfo = await fetchTwitterInfo(state, code);
const adr36Infos = await checkAdr36();
@ -336,6 +341,12 @@ export default function VerificationPage() {
chain.address.includes(searchValue) ||
chain.prefix.includes(searchValue),
)}
disabledChainList={disabledChainList.filter(
(chain) =>
chain.chainId.includes(searchValue) ||
chain.address.includes(searchValue) ||
chain.prefix.includes(searchValue),
)}
checkedItems={checkedItems}
setCheckedItems={setCheckedItems}
/>

44
queries/icns.ts Normal file
View File

@ -0,0 +1,44 @@
import { request } from "../utils/url";
import {
REGISTRAR_ADDRESS,
RESOLVER_ADDRESS,
REST_URL,
} from "../constants/icns";
import { Buffer } from "buffer/";
import {
AddressesQueryResponse,
NameByTwitterIdQueryResponse,
QueryError,
} from "../types";
const getCosmwasmQueryUrl = (contractAddress: string, queryMsg: string) =>
`${REST_URL}/cosmwasm/wasm/v1/contract/${contractAddress}/smart/${queryMsg}`;
export const queryRegisteredTwitterId = async (
twitterId: string,
): Promise<NameByTwitterIdQueryResponse | QueryError> => {
const msg = {
name_by_twitter_id: { twitter_id: twitterId },
};
return request<NameByTwitterIdQueryResponse>(
getCosmwasmQueryUrl(
REGISTRAR_ADDRESS,
Buffer.from(JSON.stringify(msg)).toString("base64"),
),
);
};
export const queryAddressesFromTwitterName = async (
twitterUsername: string,
): Promise<AddressesQueryResponse> => {
const msg = {
addresses: { name: twitterUsername },
};
return request<AddressesQueryResponse>(
getCosmwasmQueryUrl(
RESOLVER_ADDRESS,
Buffer.from(JSON.stringify(msg)).toString("base64"),
),
);
};

View File

@ -6,9 +6,9 @@ import {
import { request } from "../utils/url";
export const loginWithTwitter = async () => {
const { authUrl }: TwitterAuthUrlResponse = await (
await fetch("/api/twitter-auth-url")
).json();
const { authUrl } = await request<TwitterAuthUrlResponse>(
"/api/twitter-auth-url",
);
window.location.href = authUrl;
};

View File

@ -1,5 +1,6 @@
export interface ChainItemType {
chainId: string;
chainName: string;
prefix: string;
chainImageUrl: string;
address: string;