This commit is contained in:
delivan 2022-12-17 23:46:02 +09:00
commit e0151666b2
6 changed files with 152 additions and 161 deletions

View File

@ -1,10 +1,15 @@
import { ChainItemType } from "../../types"; import { ChainItemType } from "../../types";
import { Dispatch, FunctionComponent, SetStateAction } from "react"; import {
Dispatch,
FunctionComponent,
SetStateAction,
useEffect,
useState,
} from "react";
import { ChainImage } from "./chain-image"; import { ChainImage } from "./chain-image";
import { Flex1 } from "../../styles/flex-1"; import { Flex1 } from "../../styles/flex-1";
import { import {
ChainImageContainer,
ChainInfoContainer, ChainInfoContainer,
ChainItemContainer, ChainItemContainer,
ChainName, ChainName,
@ -13,43 +18,54 @@ import {
import color from "../../styles/color"; import color from "../../styles/color";
import styled from "styled-components"; import styled from "styled-components";
import { Checkbox } from "../checkbox"; import { Checkbox } from "../checkbox";
import AllChainsIcon from "../../public/images/svg/all-chains-icon.svg";
interface Props { interface Props {
allChecked: boolean; chainList: ChainItemType[];
setAllChecked: Dispatch<SetStateAction<boolean>>; checkedItems: Set<unknown>;
chainItem: ChainItemType; setCheckedItems: Dispatch<SetStateAction<Set<unknown>>>;
} }
export const AllChainsItem: FunctionComponent<Props> = (props) => { export const AllChainsItem: FunctionComponent<Props> = (props) => {
const { allChecked, setAllChecked, chainItem } = props; const { chainList, checkedItems, setCheckedItems } = props;
const [checked, setChecked] = useState(false);
const checkHandler = () => { const checkHandler = () => {
setAllChecked(!allChecked); if (checked) {
setCheckedItems(new Set());
} else if (chainList.length !== checkedItems.size) {
setCheckedItems(new Set(chainList));
}
}; };
useEffect(() => {
if (chainList.length === checkedItems.size && checkedItems.size !== 0) {
setChecked(true);
} else {
setChecked(false);
}
}, [checkedItems]);
return ( return (
<AllChainsContainer> <AllChainsContainer>
<ChainItemContainer <ChainItemContainer
key={chainItem.prefix} key="all chains"
isLoading={false} isLoading={false}
checked={allChecked} checked={checked}
onClick={checkHandler} onClick={checkHandler}
> >
<ChainImageContainer width="3rem" height="3rem"> <ChainImage src={AllChainsIcon} fill={true} alt={`all chain images`} />
<ChainImage
src={chainItem.chainImageUrl}
fill={true}
alt={`${chainItem.prefix} chain image`}
/>
</ChainImageContainer>
<ChainInfoContainer> <ChainInfoContainer>
<ChainName>{`.${chainItem.prefix}`}</ChainName> <ChainName>{`.all chains(${chainList.length})`}</ChainName>
<WalletAddress>{chainItem.address}</WalletAddress> <WalletAddress>
{chainList.map((chain) => chain.chainName).join(", ")}
</WalletAddress>
</ChainInfoContainer> </ChainInfoContainer>
<Flex1 /> <Flex1 />
<Checkbox checked={allChecked} /> <Checkbox checked={checked} />
</ChainItemContainer> </ChainItemContainer>
</AllChainsContainer> </AllChainsContainer>
); );

View File

@ -21,6 +21,10 @@ export const ChainImage = ({ src, ...props }: ImageProps) => {
}; };
const ImageWrapper = styled.div` const ImageWrapper = styled.div`
position: relative;
width: 3rem;
height: 3rem;
img { img {
border-radius: 50%; border-radius: 50%;
} }

View File

@ -1,4 +1,4 @@
import { ChainItemType, WidthHeightProps } from "../../types"; import { ChainItemType } from "../../types";
import { FunctionComponent, useEffect, useState } from "react"; import { FunctionComponent, useEffect, useState } from "react";
import color from "../../styles/color"; import color from "../../styles/color";
@ -39,13 +39,11 @@ export const ChainItem: FunctionComponent<Props> = (props) => {
checked={checked} checked={checked}
onClick={checkHandler} onClick={checkHandler}
> >
<ChainImageContainer width="3rem" height="3rem"> <ChainImage
<ChainImage src={chainItem.chainImageUrl}
src={chainItem.chainImageUrl} fill={true}
fill={true} alt={`${chainItem.prefix} chain image`}
alt={`${chainItem.prefix} chain image`} />
/>
</ChainImageContainer>
<ChainInfoContainer> <ChainInfoContainer>
<ChainName>{`.${chainItem.prefix}`}</ChainName> <ChainName>{`.${chainItem.prefix}`}</ChainName>
<WalletAddress>{chainItem.address}</WalletAddress> <WalletAddress>{chainItem.address}</WalletAddress>
@ -90,13 +88,6 @@ export const ChainItemContainer = styled.div<{
} }
`; `;
export const ChainImageContainer = styled.div<WidthHeightProps>`
width: ${(props) => props.width};
height: ${(props) => props.height};
position: relative;
`;
export const ChainInfoContainer = styled.div` export const ChainInfoContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -5,8 +5,6 @@ import styled from "styled-components";
import { ChainItem } from "./chain-item"; import { ChainItem } from "./chain-item";
interface Props { interface Props {
allChecked: boolean;
setAllChecked: Dispatch<SetStateAction<boolean>>;
chainList: ChainItemType[]; chainList: ChainItemType[];
disabledChainList: ChainItemType[]; disabledChainList: ChainItemType[];
checkedItems: Set<unknown>; checkedItems: Set<unknown>;
@ -14,14 +12,7 @@ interface Props {
} }
export const ChainList: FunctionComponent<Props> = (props) => { export const ChainList: FunctionComponent<Props> = (props) => {
const { const { chainList, disabledChainList, checkedItems, setCheckedItems } = props;
allChecked,
setAllChecked,
chainList,
disabledChainList,
checkedItems,
setCheckedItems,
} = props;
const checkedItemHandler = (chainItem: ChainItemType, isChecked: boolean) => { const checkedItemHandler = (chainItem: ChainItemType, isChecked: boolean) => {
const tempSet = new Set(checkedItems); const tempSet = new Set(checkedItems);
@ -35,22 +26,6 @@ export const ChainList: FunctionComponent<Props> = (props) => {
setCheckedItems(tempSet); setCheckedItems(tempSet);
}; };
useEffect(() => {
if (allChecked) {
setCheckedItems(new Set(chainList));
} else if (chainList.length === checkedItems.size) {
setCheckedItems(new Set());
}
}, [allChecked]);
useEffect(() => {
if (chainList.length === checkedItems.size && checkedItems.size !== 0) {
setAllChecked(true);
} else {
setAllChecked(false);
}
}, [checkedItems]);
return ( return (
<ChainContainer color={color.grey["900"]}> <ChainContainer color={color.grey["900"]}>
{chainList.map((chainItem) => ( {chainList.map((chainItem) => (

View File

@ -21,7 +21,6 @@ import {
} from "../twitter-profile"; } from "../twitter-profile";
import { import {
ChainContainer, ChainContainer,
ChainImageContainer,
ChainInfoContainer, ChainInfoContainer,
ChainItemContainer, ChainItemContainer,
} from "../chain-list"; } from "../chain-list";
@ -60,9 +59,9 @@ export const SkeletonChainList: FunctionComponent = () => (
.map((_, index) => ( .map((_, index) => (
<div key={index}> <div key={index}>
<ChainItemContainer isLoading={true} isSkeleton={true}> <ChainItemContainer isLoading={true} isSkeleton={true}>
<ChainImageContainer width="3rem" height="3rem"> <SkeletonImageContainer>
<SkeletonCircle width="3rem" height="3rem" /> <SkeletonCircle width="3rem" height="3rem" />
</ChainImageContainer> </SkeletonImageContainer>
<ChainInfoContainer> <ChainInfoContainer>
<SkeletonText width="4rem" height="1rem" /> <SkeletonText width="4rem" height="1rem" />
<SkeletonText width="12rem" height="1rem" /> <SkeletonText width="12rem" height="1rem" />
@ -93,6 +92,13 @@ const SkeletonTitle = styled.div`
background-color: ${color.grey["800"]}; background-color: ${color.grey["800"]};
`; `;
const SkeletonImageContainer = styled.div`
width: 3rem;
height: 3rem;
position: relative;
`;
const SkeletonButtonContainer = styled.div` const SkeletonButtonContainer = styled.div`
margin-top: 1.5rem; margin-top: 1.5rem;
`; `;

View File

@ -32,7 +32,6 @@ import {
} from "../../wallets"; } from "../../wallets";
import { ChainIdHelper } from "@keplr-wallet/cosmos"; 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 { AllChainsItem } from "../../components/chain-list/all-chains-item";
import { SearchInput } from "../../components/search-input"; import { SearchInput } from "../../components/search-input";
import { import {
@ -55,7 +54,7 @@ import {
TWITTER_LOGIN_ERROR, TWITTER_LOGIN_ERROR,
} from "../../constants/error-message"; } from "../../constants/error-message";
import { makeClaimMessage, makeSetRecordMessage } from "../../messages"; import { makeClaimMessage, makeSetRecordMessage } from "../../messages";
import Axios, { AxiosError } from "axios"; import Axios from "axios";
import { BackButton } from "../../components/back-button"; import { BackButton } from "../../components/back-button";
export default function VerificationPage() { export default function VerificationPage() {
@ -73,9 +72,6 @@ export default function VerificationPage() {
const [registeredChainList, setRegisteredChainList] = useState< const [registeredChainList, setRegisteredChainList] = useState<
RegisteredAddresses[] RegisteredAddresses[]
>([]); >([]);
const [allChains, setAllChains] = useState<ChainItemType>();
const [allChecked, setAllChecked] = useState(false);
const [checkedItems, setCheckedItems] = useState(new Set()); const [checkedItems, setCheckedItems] = useState(new Set());
const [searchValue, setSearchValue] = useState(""); const [searchValue, setSearchValue] = useState("");
@ -84,69 +80,16 @@ export default function VerificationPage() {
const [isAgree, setIsAgree] = useState(false); const [isAgree, setIsAgree] = useState(false);
useEffect(() => { useEffect(() => {
const init = async () => {
if (window.location.search) {
try {
const { state, code } = checkTwitterAuthQueryParameter(
window.location.search,
);
// Initialize Wallet
const keplrWallet = await initWallet();
// Fetch Twitter Profile
const twitterInfo = await fetchTwitterInfo(state, code);
// contract check registered
const registeredQueryResponse = await queryRegisteredTwitterId(
twitterInfo.id,
);
setTwitterAuthInfo({
...twitterInfo,
isRegistered: "data" in registeredQueryResponse,
});
if ("data" in registeredQueryResponse) {
const ownerOfQueryResponse = await queryOwnerOfTwitterName(
registeredQueryResponse.data.name,
);
const addressesQueryResponse = await queryAddressesFromTwitterName(
registeredQueryResponse.data.name,
);
if (keplrWallet) {
const key = await keplrWallet.getKey(MainChainId);
setIsOwner(ownerOfQueryResponse.data.owner === key.bech32Address);
}
setRegisteredChainList(addressesQueryResponse.data.addresses);
}
} catch (error) {
if (error instanceof Error && error.message === TWITTER_LOGIN_ERROR) {
await router.push("/");
}
console.error(error);
} finally {
setIsLoading(false);
}
}
};
init(); init();
}, []); }, []);
useEffect(() => { useEffect(() => {
setAllChains({ if (wallet) {
chainId: "all chains", window.addEventListener("keplr_keystorechange", async () => {
chainName: "all chains", await init();
prefix: `all chains(${chainList.length})`, });
address: chainList.map((chain) => chain.chainName).join(", "), }
chainImageUrl: AllChainsIcon, }, [wallet]);
});
}, [chainList]);
useEffect(() => { useEffect(() => {
const disabledChainList = chainList.filter((chain) => { const disabledChainList = chainList.filter((chain) => {
@ -166,18 +109,67 @@ export default function VerificationPage() {
(chain) => !disabledChainList.includes(chain), (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); setChainList(filteredChainList);
setDisabledChainList(disabledChainList); setDisabledChainList(disabledChainList);
setCheckedItems(new Set(filteredChainList));
}, [registeredChainList]); }, [registeredChainList]);
useEffect(() => {
setCheckedItems(new Set(chainList));
}, [chainList]);
const init = async () => {
if (window.location.search) {
try {
const { state, code } = checkTwitterAuthQueryParameter(
window.location.search,
);
// Initialize Wallet
const keplrWallet = await initWallet();
// Fetch Twitter Profile
const twitterInfo = await fetchTwitterInfo(state, code);
// contract check registered
const registeredQueryResponse = await queryRegisteredTwitterId(
twitterInfo.id,
);
setTwitterAuthInfo({
...twitterInfo,
isRegistered: "data" in registeredQueryResponse,
});
if ("data" in registeredQueryResponse) {
const ownerOfQueryResponse = await queryOwnerOfTwitterName(
registeredQueryResponse.data.name,
);
const addressesQueryResponse = await queryAddressesFromTwitterName(
registeredQueryResponse.data.name,
);
if (keplrWallet) {
const key = await keplrWallet.getKey(MainChainId);
setIsOwner(ownerOfQueryResponse.data.owner === key.bech32Address);
}
setRegisteredChainList(addressesQueryResponse.data.addresses);
}
} catch (error) {
if (error instanceof Error && error.message === TWITTER_LOGIN_ERROR) {
await router.push("/");
}
console.error(error);
} finally {
setIsLoading(false);
}
}
};
const initWallet = async () => { const initWallet = async () => {
const keplr = await getKeplrFromWindow(); const keplr = await getKeplrFromWindow();
@ -351,10 +343,15 @@ export default function VerificationPage() {
} }
}; };
const isRegisterButtonDisable = const isRegisterButtonDisable = (() => {
checkedItems.size < 1 || const hasCheckedItem = checkedItems.size > 0;
(!isOwner && registeredChainList.length > 0) ||
!isAgree; if (isOwner) {
return !hasCheckedItem;
} else {
return !(isAgree && hasCheckedItem);
}
})();
return ( return (
<Container> <Container>
@ -376,17 +373,15 @@ export default function VerificationPage() {
/> />
</ChainListTitleContainer> </ChainListTitleContainer>
{allChains && !searchValue ? ( {!searchValue ? (
<AllChainsItem <AllChainsItem
allChecked={allChecked} chainList={chainList}
setAllChecked={setAllChecked} checkedItems={checkedItems}
chainItem={allChains} setCheckedItems={setCheckedItems}
/> />
) : null} ) : null}
<ChainList <ChainList
allChecked={allChecked}
setAllChecked={setAllChecked}
chainList={chainList.filter( chainList={chainList.filter(
(chain) => (chain) =>
chain.chainId.includes(searchValue) || chain.chainId.includes(searchValue) ||
@ -403,23 +398,27 @@ export default function VerificationPage() {
setCheckedItems={setCheckedItems} setCheckedItems={setCheckedItems}
/> />
<AgreeContainer {!isOwner && (
onClick={() => { <AgreeContainer
setIsAgree(!isAgree); onClick={() => {
}} setIsAgree(!isAgree);
> }}
<AgreeCheckBox type="checkbox" checked={isAgree} readOnly />I
check that Osmo is required for this transaction
</AgreeContainer>
<ButtonContainer disabled={isRegisterButtonDisable}>
<PrimaryButton
disabled={isRegisterButtonDisable}
onClick={onClickRegistration}
> >
Register <AgreeCheckBox type="checkbox" checked={isAgree} readOnly />I
</PrimaryButton> check that Osmo is required for this transaction
</ButtonContainer> </AgreeContainer>
)}
{chainList.length > 0 && (
<ButtonContainer disabled={isRegisterButtonDisable}>
<PrimaryButton
disabled={isRegisterButtonDisable}
onClick={onClickRegistration}
>
Register
</PrimaryButton>
</ButtonContainer>
)}
</ContentContainer> </ContentContainer>
)} )}
</MainContainer> </MainContainer>