[WIP] Add checkbox handler, chain image error handler, keplr init logic
This commit is contained in:
parent
8319467125
commit
921ec5d7e6
10
components/chain-list/chain-image.tsx
Normal file
10
components/chain-list/chain-image.tsx
Normal file
@ -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 <Image {...props} src={src} onError={() => setSrc(KeplrIcon)} />;
|
||||||
|
};
|
@ -1,39 +1,49 @@
|
|||||||
import { AccountInfo } from "../../types";
|
import { ChainItemType } from "../../types";
|
||||||
import { FunctionComponent } from "react";
|
import { ChangeEvent, FunctionComponent, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ChainImageContainer,
|
ChainImageContainer,
|
||||||
ChainInfoContainer,
|
ChainInfoContainer,
|
||||||
ChainItemContainer,
|
ChainItemContainer,
|
||||||
} from "./chain-list";
|
} from "./chain-list";
|
||||||
|
|
||||||
import Image from "next/image";
|
|
||||||
import color from "../../styles/color";
|
import color from "../../styles/color";
|
||||||
import { Flex1 } from "../../styles/flex-1";
|
import { Flex1 } from "../../styles/flex-1";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
import { ChainImage } from "./chain-image";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
chainInfo: AccountInfo;
|
chainItem: ChainItemType;
|
||||||
|
checkedItemHandler: (chainItem: ChainItemType, isChecked: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChainItem: FunctionComponent<Props> = (props) => {
|
export const ChainItem: FunctionComponent<Props> = (props) => {
|
||||||
const { chainInfo } = props;
|
const { chainItem, checkedItemHandler } = props;
|
||||||
|
const [checked, setChecked] = useState(false);
|
||||||
|
|
||||||
|
const checkHandler = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setChecked(!checked);
|
||||||
|
checkedItemHandler(chainItem, event.target.checked);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<ChainItemContainer key={chainInfo.prefix} isLoading={false}>
|
<ChainItemContainer key={chainItem.prefix} isLoading={false}>
|
||||||
<ChainImageContainer width="3rem" height="3rem">
|
<ChainImageContainer width="3rem" height="3rem">
|
||||||
<Image
|
<ChainImage
|
||||||
src={chainInfo.chainImageUrl}
|
src={chainItem.chainImageUrl}
|
||||||
fill={true}
|
fill={true}
|
||||||
alt={`${chainInfo.prefix} chain image`}
|
alt={`${chainItem.prefix} chain image`}
|
||||||
/>
|
/>
|
||||||
</ChainImageContainer>
|
</ChainImageContainer>
|
||||||
<ChainInfoContainer>
|
<ChainInfoContainer>
|
||||||
<ChainName>{`.${chainInfo.prefix}`}</ChainName>
|
<ChainName>{`.${chainItem.prefix}`}</ChainName>
|
||||||
<WalletAddress>{chainInfo.address}</WalletAddress>
|
<WalletAddress>{chainItem.address}</WalletAddress>
|
||||||
</ChainInfoContainer>
|
</ChainInfoContainer>
|
||||||
|
|
||||||
<Flex1 />
|
<Flex1 />
|
||||||
|
|
||||||
<ChainCheckBox />
|
<ChainCheckBox
|
||||||
|
checked={checked}
|
||||||
|
onChange={(event) => checkHandler(event)}
|
||||||
|
/>
|
||||||
</ChainItemContainer>
|
</ChainItemContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,38 @@
|
|||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent } from "react";
|
||||||
import { AccountInfo, WidthHeightProps } from "../../types";
|
import { ChainItemType, WidthHeightProps } from "../../types";
|
||||||
import color from "../../styles/color";
|
import color from "../../styles/color";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { ChainItem } from "./chain-item";
|
import { ChainItem } from "./chain-item";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
chainList: AccountInfo[];
|
chainList: ChainItemType[];
|
||||||
|
checkedItems: any;
|
||||||
|
setCheckedItems: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChainList: FunctionComponent<Props> = (props) => {
|
export const ChainList: FunctionComponent<Props> = (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 (
|
return (
|
||||||
<ChainContainer color={color.grey["800"]}>
|
<ChainContainer color={color.grey["800"]}>
|
||||||
{chainList.map((chainInfo) => (
|
{chainList.map((chainItem) => (
|
||||||
<ChainItem key={chainInfo.prefix} chainInfo={chainInfo} />
|
<ChainItem
|
||||||
|
key={chainItem.address}
|
||||||
|
chainItem={chainItem}
|
||||||
|
checkedItemHandler={checkedItemHandler}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</ChainContainer>
|
</ChainContainer>
|
||||||
);
|
);
|
||||||
|
@ -4,8 +4,9 @@ import color from "../../styles/color";
|
|||||||
import { Flex1 } from "../../styles/flex-1";
|
import { Flex1 } from "../../styles/flex-1";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { WalletType } from "../../constants/wallet";
|
import { SELECTED_WALLET_KEY, WalletType } from "../../constants/wallet";
|
||||||
import { TwitterAuthUrlResponse } from "../../types";
|
import { getKeplrFromWindow, KeplrWallet } from "../../wallets";
|
||||||
|
import { loginWithTwitter } from "../../constants/twitter";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
wallet: WalletType;
|
wallet: WalletType;
|
||||||
@ -15,11 +16,34 @@ export const WalletItem: FunctionComponent<Props> = (props: Props) => {
|
|||||||
const { wallet } = props;
|
const { wallet } = props;
|
||||||
|
|
||||||
const onClickWalletItem = async () => {
|
const onClickWalletItem = async () => {
|
||||||
const { authUrl }: TwitterAuthUrlResponse = await (
|
try {
|
||||||
await fetch("/api/twitter-auth-url")
|
if (wallet.name === "Keplr") {
|
||||||
).json();
|
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 (
|
return (
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { TwitterAuthUrlResponse } from "../types";
|
||||||
|
|
||||||
export const twitterOAuthBaseUrl = "https://twitter.com/i/oauth2/authorize";
|
export const twitterOAuthBaseUrl = "https://twitter.com/i/oauth2/authorize";
|
||||||
|
|
||||||
export const twitterOAuthScopes = [
|
export const twitterOAuthScopes = [
|
||||||
@ -7,3 +9,11 @@ export const twitterOAuthScopes = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const twitterApiBaseUrl = "https://api.twitter.com/2";
|
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;
|
||||||
|
};
|
||||||
|
@ -3,8 +3,12 @@ import { StaticImageData } from "next/image";
|
|||||||
import KeplrIcon from "../public/images/svg/keplr-icon.svg";
|
import KeplrIcon from "../public/images/svg/keplr-icon.svg";
|
||||||
import CosmostationIcon from "../public/images/svg/cosmostation-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 {
|
export interface WalletType {
|
||||||
name: string;
|
name: WalletName;
|
||||||
image: StaticImageData;
|
image: StaticImageData;
|
||||||
isReady: boolean;
|
isReady: boolean;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ import MainTitle from "../public/images/svg/main-title.svg";
|
|||||||
import MainLogo from "../public/images/svg/main-logo.svg";
|
import MainLogo from "../public/images/svg/main-logo.svg";
|
||||||
import CheckIcon from "../public/images/svg/check-icon.svg";
|
import CheckIcon from "../public/images/svg/check-icon.svg";
|
||||||
import { Logo } from "../components/logo";
|
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() {
|
export default function Home() {
|
||||||
const [isModalOpen, setModalOpen] = useState(false);
|
const [isModalOpen, setModalOpen] = useState(false);
|
||||||
@ -23,6 +24,10 @@ export default function Home() {
|
|||||||
setModalOpen(true);
|
setModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.removeItem(SELECTED_WALLET_KEY);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Logo />
|
<Logo />
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { IcnsVerificationResponse, TwitterAuthInfoResponse } from "../../types";
|
import {
|
||||||
|
ChainItemType,
|
||||||
|
IcnsVerificationResponse,
|
||||||
|
TwitterAuthInfoResponse,
|
||||||
|
} from "../../types";
|
||||||
import { request } from "../../utils/url";
|
import { request } from "../../utils/url";
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
@ -14,44 +18,33 @@ import { Logo } from "../../components/logo";
|
|||||||
import { SkeletonChainList } from "../../components/skeleton";
|
import { SkeletonChainList } from "../../components/skeleton";
|
||||||
|
|
||||||
import { PrimaryButton } from "../../components/primary-button";
|
import { PrimaryButton } from "../../components/primary-button";
|
||||||
import { AccountInfos } from "../../config";
|
|
||||||
import { TwitterProfile } from "../../components/twitter-profile";
|
import { TwitterProfile } from "../../components/twitter-profile";
|
||||||
import { ChainList } from "../../components/chain-list";
|
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() {
|
export default function VerificationPage() {
|
||||||
|
const router = useRouter();
|
||||||
const [twitterAuthInfo, setTwitterAuthInfo] =
|
const [twitterAuthInfo, setTwitterAuthInfo] =
|
||||||
useState<TwitterAuthInfoResponse | null>();
|
useState<TwitterAuthInfoResponse | null>();
|
||||||
|
|
||||||
|
const [chainList, setChainList] = useState<ChainItemType[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
const [checkedItems, setCheckedItems] = useState(new Set());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleVerification = async () => {
|
const handleVerification = async () => {
|
||||||
if (window.location.search) {
|
if (window.location.search) {
|
||||||
const [, state, code] =
|
if (window.location.search.match("error")) {
|
||||||
window.location.search.match(
|
await router.push("/");
|
||||||
/^(?=.*state=([^&]+)|)(?=.*code=([^&]+)|).+$/,
|
}
|
||||||
) || [];
|
|
||||||
|
|
||||||
const newTwitterAuthInfo = await request<TwitterAuthInfoResponse>(
|
await fetchTwitterInfo();
|
||||||
`/api/twitter-auth-info?state=${state}&code=${code}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
setTwitterAuthInfo(newTwitterAuthInfo);
|
await fetchChainList();
|
||||||
|
|
||||||
const icnsVerificationList = (
|
|
||||||
await request<IcnsVerificationResponse>("/api/icns-verification", {
|
|
||||||
method: "post",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
claimer: "osmo1y5mm5nj5m8ttddt5ccspek6xgyyavehrkak7gq",
|
|
||||||
authToken: newTwitterAuthInfo.accessToken,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
).verificationList;
|
|
||||||
|
|
||||||
console.log(icnsVerificationList);
|
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@ -60,6 +53,87 @@ export default function VerificationPage() {
|
|||||||
handleVerification();
|
handleVerification();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const fetchTwitterInfo = async () => {
|
||||||
|
const [, state, code] =
|
||||||
|
window.location.search.match(
|
||||||
|
/^(?=.*state=([^&]+)|)(?=.*code=([^&]+)|).+$/,
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
const newTwitterAuthInfo = await request<TwitterAuthInfoResponse>(
|
||||||
|
`/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<IcnsVerificationResponse>("/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 (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Logo />
|
<Logo />
|
||||||
@ -76,10 +150,19 @@ export default function VerificationPage() {
|
|||||||
<SearchContainer>Search</SearchContainer>
|
<SearchContainer>Search</SearchContainer>
|
||||||
</ChainListTitleContainer>
|
</ChainListTitleContainer>
|
||||||
|
|
||||||
<ChainList chainList={AccountInfos} />
|
<ChainList
|
||||||
|
chainList={chainList}
|
||||||
|
checkedItems={checkedItems}
|
||||||
|
setCheckedItems={setCheckedItems}
|
||||||
|
/>
|
||||||
|
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
<PrimaryButton>Register</PrimaryButton>
|
<PrimaryButton
|
||||||
|
disabled={checkedItems.size < 1}
|
||||||
|
onClick={onClickRegistration}
|
||||||
|
>
|
||||||
|
Register
|
||||||
|
</PrimaryButton>
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
)}
|
)}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export interface AccountInfo {
|
export interface ChainItemType {
|
||||||
prefix: string;
|
prefix: string;
|
||||||
chainImageUrl: string;
|
chainImageUrl: string;
|
||||||
address: string;
|
address: string;
|
||||||
|
4
window.d.ts
vendored
4
window.d.ts
vendored
@ -2,5 +2,7 @@ import { Window as KeplrWindow } from "@keplr-wallet/types";
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||||
interface Window extends KeplrWindow {}
|
interface Window extends KeplrWindow {
|
||||||
|
cosmostation: any;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user