diff --git a/components/chain-list/chain-image.tsx b/components/chain-list/chain-image.tsx new file mode 100644 index 0000000..167a5af --- /dev/null +++ b/components/chain-list/chain-image.tsx @@ -0,0 +1,10 @@ +import { useState } from "react"; +import Image, { ImageProps } from "next/image"; + +import KeplrIcon from "../../public/images/svg/keplr-icon.svg"; + +export const ChainImage = (props: ImageProps) => { + const [src, setSrc] = useState(props.src); + + return setSrc(KeplrIcon)} />; +}; diff --git a/components/chain-list/chain-item.tsx b/components/chain-list/chain-item.tsx index 695201b..4492420 100644 --- a/components/chain-list/chain-item.tsx +++ b/components/chain-list/chain-item.tsx @@ -1,39 +1,49 @@ -import { AccountInfo } from "../../types"; -import { FunctionComponent } from "react"; +import { ChainItemType } from "../../types"; +import { ChangeEvent, FunctionComponent, useState } from "react"; import { ChainImageContainer, ChainInfoContainer, ChainItemContainer, } from "./chain-list"; -import Image from "next/image"; import color from "../../styles/color"; import { Flex1 } from "../../styles/flex-1"; import styled from "styled-components"; +import { ChainImage } from "./chain-image"; interface Props { - chainInfo: AccountInfo; + chainItem: ChainItemType; + checkedItemHandler: (chainItem: ChainItemType, isChecked: boolean) => void; } export const ChainItem: FunctionComponent = (props) => { - const { chainInfo } = props; + const { chainItem, checkedItemHandler } = props; + const [checked, setChecked] = useState(false); + + const checkHandler = (event: ChangeEvent) => { + setChecked(!checked); + checkedItemHandler(chainItem, event.target.checked); + }; return ( - + - {`${chainInfo.prefix} - {`.${chainInfo.prefix}`} - {chainInfo.address} + {`.${chainItem.prefix}`} + {chainItem.address} - + checkHandler(event)} + /> ); }; diff --git a/components/chain-list/chain-list.tsx b/components/chain-list/chain-list.tsx index a3530ef..fbdb442 100644 --- a/components/chain-list/chain-list.tsx +++ b/components/chain-list/chain-list.tsx @@ -1,19 +1,38 @@ import { FunctionComponent } from "react"; -import { AccountInfo, WidthHeightProps } from "../../types"; +import { ChainItemType, WidthHeightProps } from "../../types"; import color from "../../styles/color"; import styled from "styled-components"; import { ChainItem } from "./chain-item"; interface Props { - chainList: AccountInfo[]; + chainList: ChainItemType[]; + checkedItems: any; + setCheckedItems: any; } export const ChainList: FunctionComponent = (props) => { - const { chainList } = props; + const { chainList, checkedItems, setCheckedItems } = props; + + const checkedItemHandler = (chainItem: ChainItemType, isChecked: boolean) => { + const tempSet = new Set(checkedItems); + + if (isChecked) { + tempSet.add(chainItem); + } else if (!isChecked && checkedItems.has(chainItem)) { + tempSet.delete(chainItem); + } + + setCheckedItems(tempSet); + }; + return ( - {chainList.map((chainInfo) => ( - + {chainList.map((chainItem) => ( + ))} ); diff --git a/components/connect-wallet-modal/wallet-item.tsx b/components/connect-wallet-modal/wallet-item.tsx index 6bfbe8a..13885b1 100644 --- a/components/connect-wallet-modal/wallet-item.tsx +++ b/components/connect-wallet-modal/wallet-item.tsx @@ -4,8 +4,9 @@ import color from "../../styles/color"; import { Flex1 } from "../../styles/flex-1"; import styled from "styled-components"; import Image from "next/image"; -import { WalletType } from "../../constants/wallet"; -import { TwitterAuthUrlResponse } from "../../types"; +import { SELECTED_WALLET_KEY, WalletType } from "../../constants/wallet"; +import { getKeplrFromWindow, KeplrWallet } from "../../wallets"; +import { loginWithTwitter } from "../../constants/twitter"; interface Props { wallet: WalletType; @@ -15,11 +16,34 @@ export const WalletItem: FunctionComponent = (props: Props) => { const { wallet } = props; const onClickWalletItem = async () => { - const { authUrl }: TwitterAuthUrlResponse = await ( - await fetch("/api/twitter-auth-url") - ).json(); + try { + if (wallet.name === "Keplr") { + await connectKeplr(); + localStorage.setItem(SELECTED_WALLET_KEY, wallet.name); + } - window.location.href = authUrl; + await loginWithTwitter(); + } catch (e) { + console.log(e); + } + }; + + const connectKeplr = async () => { + const keplr = await getKeplrFromWindow(); + + if (keplr === undefined) { + window.location.href = + "https://chrome.google.com/webstore/detail/keplr/dmkamcknogkgcdfhhbddcghachkejeap"; + } + + if (keplr) { + const wallet = new KeplrWallet(keplr); + const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map( + (c) => c.chainId, + ); + + await wallet.init(chainIds); + } }; return ( diff --git a/constants/twitter.ts b/constants/twitter.ts index 9f91183..0b4d16f 100644 --- a/constants/twitter.ts +++ b/constants/twitter.ts @@ -1,3 +1,5 @@ +import { TwitterAuthUrlResponse } from "../types"; + export const twitterOAuthBaseUrl = "https://twitter.com/i/oauth2/authorize"; export const twitterOAuthScopes = [ @@ -7,3 +9,11 @@ export const twitterOAuthScopes = [ ]; export const twitterApiBaseUrl = "https://api.twitter.com/2"; + +export const loginWithTwitter = async () => { + const { authUrl }: TwitterAuthUrlResponse = await ( + await fetch("/api/twitter-auth-url") + ).json(); + + window.location.href = authUrl; +}; diff --git a/constants/wallet.ts b/constants/wallet.ts index 62deef8..ed04ff2 100644 --- a/constants/wallet.ts +++ b/constants/wallet.ts @@ -3,8 +3,12 @@ import { StaticImageData } from "next/image"; import KeplrIcon from "../public/images/svg/keplr-icon.svg"; import CosmostationIcon from "../public/images/svg/cosmostation-icon.svg"; +export const SELECTED_WALLET_KEY = "SELECTED_WALLET_KEY"; +export const MainChainId = "osmo-test-4"; + +export type WalletName = "Keplr" | "Cosmostation"; export interface WalletType { - name: string; + name: WalletName; image: StaticImageData; isReady: boolean; } diff --git a/pages/index.tsx b/pages/index.tsx index 7f4e190..394c039 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -14,7 +14,8 @@ import MainTitle from "../public/images/svg/main-title.svg"; import MainLogo from "../public/images/svg/main-logo.svg"; import CheckIcon from "../public/images/svg/check-icon.svg"; import { Logo } from "../components/logo"; -import { useState } from "react"; +import { useEffect, useState } from "react"; +import { SELECTED_WALLET_KEY } from "../constants/wallet"; export default function Home() { const [isModalOpen, setModalOpen] = useState(false); @@ -23,6 +24,10 @@ export default function Home() { setModalOpen(true); }; + useEffect(() => { + localStorage.removeItem(SELECTED_WALLET_KEY); + }, []); + return ( diff --git a/pages/verification/index.tsx b/pages/verification/index.tsx index cf0cf1f..431af3e 100644 --- a/pages/verification/index.tsx +++ b/pages/verification/index.tsx @@ -2,7 +2,11 @@ import { useEffect, useState } from "react"; // Types -import { IcnsVerificationResponse, TwitterAuthInfoResponse } from "../../types"; +import { + ChainItemType, + IcnsVerificationResponse, + TwitterAuthInfoResponse, +} from "../../types"; import { request } from "../../utils/url"; // Styles @@ -14,44 +18,33 @@ import { Logo } from "../../components/logo"; import { SkeletonChainList } from "../../components/skeleton"; import { PrimaryButton } from "../../components/primary-button"; -import { AccountInfos } from "../../config"; import { TwitterProfile } from "../../components/twitter-profile"; import { ChainList } from "../../components/chain-list"; +import { useRouter } from "next/router"; +import { MainChainId } from "../../constants/wallet"; +import { getKeplrFromWindow, KeplrWallet } from "../../wallets"; +import { ChainIdHelper } from "@keplr-wallet/cosmos"; export default function VerificationPage() { + const router = useRouter(); const [twitterAuthInfo, setTwitterAuthInfo] = useState(); + const [chainList, setChainList] = useState([]); const [isLoading, setIsLoading] = useState(true); + const [checkedItems, setCheckedItems] = useState(new Set()); + useEffect(() => { const handleVerification = async () => { if (window.location.search) { - const [, state, code] = - window.location.search.match( - /^(?=.*state=([^&]+)|)(?=.*code=([^&]+)|).+$/, - ) || []; + if (window.location.search.match("error")) { + await router.push("/"); + } - const newTwitterAuthInfo = await request( - `/api/twitter-auth-info?state=${state}&code=${code}`, - ); + await fetchTwitterInfo(); - setTwitterAuthInfo(newTwitterAuthInfo); - - const icnsVerificationList = ( - await request("/api/icns-verification", { - method: "post", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - claimer: "osmo1y5mm5nj5m8ttddt5ccspek6xgyyavehrkak7gq", - authToken: newTwitterAuthInfo.accessToken, - }), - }) - ).verificationList; - - console.log(icnsVerificationList); + await fetchChainList(); setIsLoading(false); } @@ -60,6 +53,87 @@ export default function VerificationPage() { handleVerification(); }, []); + const fetchTwitterInfo = async () => { + const [, state, code] = + window.location.search.match( + /^(?=.*state=([^&]+)|)(?=.*code=([^&]+)|).+$/, + ) || []; + + const newTwitterAuthInfo = await request( + `/api/twitter-auth-info?state=${state}&code=${code}`, + ); + + setTwitterAuthInfo(newTwitterAuthInfo); + }; + + const fetchChainList = async () => { + const keplr = await getKeplrFromWindow(); + + if (keplr) { + const wallet = new KeplrWallet(keplr); + 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 { + prefix: chainInfo.bech32Config.bech32PrefixAccAddr, + chainImageUrl: `https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/${ + ChainIdHelper.parse(chainInfo.chainId).identifier + }/chain.png`, + }; + }, + ); + + const chainArray: ChainItemType[] = []; + for (let i = 0; i < chainKeys.length; i++) { + chainArray.push({ + address: chainKeys[i].bech32Address, + ...chainInfos[i], + }); + } + + // remove duplicated item + // const filteredChainList = chainArray.filter((chain, index, self) => { + // return index === self.findIndex((t) => chain.prefix === t.prefix); + // }); + + setChainList(chainArray); + } + }; + + const verifyTwitterAccount = async () => { + const keplr = await getKeplrFromWindow(); + + if (twitterAuthInfo && keplr) { + const wallet = new KeplrWallet(keplr); + const key = await wallet.getKey(MainChainId); + + const icnsVerificationList = ( + await request("/api/icns-verification", { + method: "post", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + claimer: key.bech32Address, + authToken: twitterAuthInfo.accessToken, + }), + }) + ).verificationList; + + console.log(icnsVerificationList); + } + }; + + const onClickRegistration = async () => { + await verifyTwitterAccount(); + }; + return ( @@ -76,10 +150,19 @@ export default function VerificationPage() { Search - + - Register + + Register + )} diff --git a/types/account-info.ts b/types/account-info.ts index 3471b49..8c73a23 100644 --- a/types/account-info.ts +++ b/types/account-info.ts @@ -1,4 +1,4 @@ -export interface AccountInfo { +export interface ChainItemType { prefix: string; chainImageUrl: string; address: string; diff --git a/window.d.ts b/window.d.ts index f875c10..28c92b5 100644 --- a/window.d.ts +++ b/window.d.ts @@ -2,5 +2,7 @@ import { Window as KeplrWindow } from "@keplr-wallet/types"; declare global { // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface Window extends KeplrWindow {} + interface Window extends KeplrWindow { + cosmostation: any; + } }