Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c69788989d | ||
|
|
ef68b49027 | ||
|
|
c900e86e3a | ||
|
|
de2a90457e | ||
|
|
7ae09a0c11 | ||
|
|
1ea85324aa |
@@ -38,7 +38,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["@types/react", "npm:18.0.25"],\
|
||||
["@types/react-dom", "npm:18.0.9"],\
|
||||
["@types/react-modal", "npm:3.13.1"],\
|
||||
["@types/semver", "npm:7.3.13"],\
|
||||
["@types/styled-components", "npm:5.1.26"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:5.45.0"],\
|
||||
["@typescript-eslint/parser", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:5.45.0"],\
|
||||
@@ -62,8 +61,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["react-dom", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:18.2.0"],\
|
||||
["react-is", "npm:18.2.0"],\
|
||||
["react-modal", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:3.16.1"],\
|
||||
["react-typed", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:1.2.0"],\
|
||||
["semver", "npm:7.3.8"],\
|
||||
["styled-components", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:5.3.6"],\
|
||||
["typescript", "patch:typescript@npm%3A4.9.3#~builtin<compat/typescript>::version=4.9.3&hash=d73830"]\
|
||||
],\
|
||||
@@ -3987,7 +3984,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["@types/react", "npm:18.0.25"],\
|
||||
["@types/react-dom", "npm:18.0.9"],\
|
||||
["@types/react-modal", "npm:3.13.1"],\
|
||||
["@types/semver", "npm:7.3.13"],\
|
||||
["@types/styled-components", "npm:5.1.26"],\
|
||||
["@typescript-eslint/eslint-plugin", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:5.45.0"],\
|
||||
["@typescript-eslint/parser", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:5.45.0"],\
|
||||
@@ -4011,8 +4007,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["react-dom", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:18.2.0"],\
|
||||
["react-is", "npm:18.2.0"],\
|
||||
["react-modal", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:3.16.1"],\
|
||||
["react-typed", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:1.2.0"],\
|
||||
["semver", "npm:7.3.8"],\
|
||||
["styled-components", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:5.3.6"],\
|
||||
["typescript", "patch:typescript@npm%3A4.9.3#~builtin<compat/typescript>::version=4.9.3&hash=d73830"]\
|
||||
],\
|
||||
@@ -5735,34 +5729,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["react-typed", [\
|
||||
["npm:1.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/react-typed-npm-1.2.0-8feca2dd12-c750d68710.zip/node_modules/react-typed/",\
|
||||
"packageDependencies": [\
|
||||
["react-typed", "npm:1.2.0"]\
|
||||
],\
|
||||
"linkType": "SOFT"\
|
||||
}],\
|
||||
["virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:1.2.0", {\
|
||||
"packageLocation": "./.yarn/__virtual__/react-typed-virtual-c12f16610e/0/cache/react-typed-npm-1.2.0-8feca2dd12-c750d68710.zip/node_modules/react-typed/",\
|
||||
"packageDependencies": [\
|
||||
["react-typed", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:1.2.0"],\
|
||||
["@types/react", "npm:18.0.25"],\
|
||||
["@types/react-dom", "npm:18.0.9"],\
|
||||
["prop-types", "npm:15.8.1"],\
|
||||
["react", "npm:18.2.0"],\
|
||||
["react-dom", "virtual:4b77e00d446246df1ed27001550885fbf1b51be18c660c1b5c357d3d763078ecef2a676194291a120f149b87573081e5af0621dc83cf1f83383639f39ac133c7#npm:18.2.0"],\
|
||||
["typed.js", "npm:2.0.12"]\
|
||||
],\
|
||||
"packagePeers": [\
|
||||
"@types/react-dom",\
|
||||
"@types/react",\
|
||||
"react-dom",\
|
||||
"react"\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["read-pkg", [\
|
||||
["npm:5.2.0", {\
|
||||
"packageLocation": "./.yarn/cache/read-pkg-npm-5.2.0-50426bd8dc-eb696e6052.zip/node_modules/read-pkg/",\
|
||||
@@ -6647,15 +6613,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["typed.js", [\
|
||||
["npm:2.0.12", {\
|
||||
"packageLocation": "./.yarn/cache/typed.js-npm-2.0.12-7618b42dfc-ea753bf8e0.zip/node_modules/typed.js/",\
|
||||
"packageDependencies": [\
|
||||
["typed.js", "npm:2.0.12"]\
|
||||
],\
|
||||
"linkType": "HARD"\
|
||||
}]\
|
||||
]],\
|
||||
["typeforce", [\
|
||||
["npm:1.18.0", {\
|
||||
"packageLocation": "./.yarn/cache/typeforce-npm-1.18.0-372e34e2a2-e3b21e27e7.zip/node_modules/typeforce/",\
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import { FunctionComponent } from "react";
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
|
||||
import ArrowLeftIcon from "../../public/images/svg/arrow-left.svg";
|
||||
import Image from "next/image";
|
||||
|
||||
export const BackButton: FunctionComponent = () => {
|
||||
return (
|
||||
<Container
|
||||
onClick={() => {
|
||||
location.href = "/";
|
||||
}}
|
||||
>
|
||||
<ContentContainer>
|
||||
<IconContainer>
|
||||
<Image
|
||||
src={ArrowLeftIcon}
|
||||
fill={true}
|
||||
sizes="1rem"
|
||||
alt="arrow left icon"
|
||||
/>
|
||||
</IconContainer>
|
||||
<div>BACK</div>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
width: 100%;
|
||||
padding: 0.7rem 0.5rem;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
line-height: 0.8rem;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
gap: 0.25rem;
|
||||
`;
|
||||
|
||||
const IconContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
`;
|
||||
@@ -1,78 +0,0 @@
|
||||
import { ChainItemType } from "../../types";
|
||||
import {
|
||||
Dispatch,
|
||||
FunctionComponent,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import { ChainImage } from "./chain-image";
|
||||
import { Flex1 } from "../../styles/flex-1";
|
||||
import {
|
||||
ChainInfoContainer,
|
||||
ChainItemContainer,
|
||||
ChainName,
|
||||
WalletAddress,
|
||||
} from "./chain-item";
|
||||
import color from "../../styles/color";
|
||||
import styled from "styled-components";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import AllChainsIcon from "../../public/images/svg/all-chains-icon.svg";
|
||||
|
||||
interface Props {
|
||||
chainList: ChainItemType[];
|
||||
checkedItems: Set<unknown>;
|
||||
setCheckedItems: Dispatch<SetStateAction<Set<unknown>>>;
|
||||
}
|
||||
|
||||
export const AllChainsItem: FunctionComponent<Props> = (props) => {
|
||||
const { chainList, checkedItems, setCheckedItems } = props;
|
||||
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
const checkHandler = () => {
|
||||
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 (
|
||||
<AllChainsContainer>
|
||||
<ChainItemContainer
|
||||
key="all chains"
|
||||
isLoading={false}
|
||||
checked={checked}
|
||||
onClick={checkHandler}
|
||||
>
|
||||
<ChainImage src={AllChainsIcon} fill={true} alt={`all chain images`} />
|
||||
<ChainInfoContainer>
|
||||
<ChainName>{`.all chains(${chainList.length})`}</ChainName>
|
||||
<WalletAddress>
|
||||
{chainList.map((chain) => chain.chainName).join(", ")}
|
||||
</WalletAddress>
|
||||
</ChainInfoContainer>
|
||||
|
||||
<Flex1 />
|
||||
|
||||
<Checkbox checked={checked} />
|
||||
</ChainItemContainer>
|
||||
</AllChainsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const AllChainsContainer = styled.div`
|
||||
width: 100%;
|
||||
|
||||
background-color: ${color.grey["900"]};
|
||||
`;
|
||||
@@ -1,31 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import Image, { ImageProps } from "next/image";
|
||||
|
||||
import KeplrIcon from "../../public/images/svg/keplr-icon.svg";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const ChainImage = ({ src, ...props }: ImageProps) => {
|
||||
const [srcState, setSrcState] = useState(src);
|
||||
|
||||
return (
|
||||
<ImageWrapper>
|
||||
<Image
|
||||
{...props}
|
||||
src={srcState}
|
||||
alt="chain image"
|
||||
sizes="3rem"
|
||||
onError={() => setSrcState(KeplrIcon)}
|
||||
/>
|
||||
</ImageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
`;
|
||||
@@ -1,119 +1,60 @@
|
||||
import { ChainItemType } from "../../types";
|
||||
import { FunctionComponent, useEffect, useState } from "react";
|
||||
import { AccountInfo } from "../../types";
|
||||
import { FunctionComponent } 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";
|
||||
import { Checkbox } from "../checkbox";
|
||||
|
||||
interface Props {
|
||||
chainItem: ChainItemType;
|
||||
checkedItemHandler: (chainItem: ChainItemType, isChecked: boolean) => void;
|
||||
checkedItems: Set<unknown>;
|
||||
disabled?: boolean;
|
||||
chainInfo: AccountInfo;
|
||||
}
|
||||
|
||||
export const ChainItem: FunctionComponent<Props> = (props) => {
|
||||
const { chainItem, checkedItemHandler, checkedItems, disabled } = props;
|
||||
const [checked, setChecked] = useState(!!disabled);
|
||||
|
||||
const checkHandler = () => {
|
||||
if (!disabled) {
|
||||
setChecked(!checked);
|
||||
checkedItemHandler(chainItem, !checked);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!disabled) {
|
||||
setChecked(checkedItems.has(chainItem));
|
||||
}
|
||||
}, [checkedItems]);
|
||||
|
||||
const { chainInfo } = props;
|
||||
return (
|
||||
<ChainItemContainer
|
||||
key={chainItem.prefix}
|
||||
isLoading={false}
|
||||
disabled={disabled}
|
||||
checked={checked}
|
||||
onClick={checkHandler}
|
||||
>
|
||||
<ChainImage
|
||||
src={chainItem.chainImageUrl}
|
||||
fill={true}
|
||||
alt={`${chainItem.prefix} chain image`}
|
||||
/>
|
||||
<ChainItemContainer key={chainInfo.prefix} isLoading={false}>
|
||||
<ChainImageContainer width="3rem" height="3rem">
|
||||
<Image
|
||||
src={chainInfo.chainImageUrl}
|
||||
fill={true}
|
||||
alt={`${chainInfo.prefix} chain image`}
|
||||
/>
|
||||
</ChainImageContainer>
|
||||
<ChainInfoContainer>
|
||||
<ChainName>{`.${chainItem.prefix}`}</ChainName>
|
||||
<WalletAddress>{chainItem.address}</WalletAddress>
|
||||
<ChainName>{`.${chainInfo.prefix}`}</ChainName>
|
||||
<WalletAddress>{chainInfo.address}</WalletAddress>
|
||||
</ChainInfoContainer>
|
||||
|
||||
<Flex1 />
|
||||
|
||||
<Checkbox checked={checked} />
|
||||
<ChainCheckBox />
|
||||
</ChainItemContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChainItemContainer = styled.div<{
|
||||
isLoading: boolean;
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
isSkeleton?: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
gap: 1rem;
|
||||
|
||||
padding: 1.5rem;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
opacity: ${(props) => (props.disabled ? "0.5" : "1")};
|
||||
|
||||
background-color: ${(props) =>
|
||||
props.disabled
|
||||
? color.black
|
||||
: props.checked
|
||||
? color.grey["800"]
|
||||
: props.isSkeleton
|
||||
? color.grey["800"]
|
||||
: color.grey["900"]};
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => (props.isLoading ? null : color.grey["700"])};
|
||||
}
|
||||
`;
|
||||
|
||||
export const ChainInfoContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
export const ChainName = styled.div`
|
||||
const ChainName = styled.div`
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
|
||||
color: ${color.white};
|
||||
color: ${color.grey["100"]};
|
||||
`;
|
||||
|
||||
export const WalletAddress = styled.div`
|
||||
const WalletAddress = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
|
||||
max-height: 2rem;
|
||||
max-width: 27rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
|
||||
const ChainCheckBox = styled.input.attrs({ type: "checkbox" })`
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
`;
|
||||
|
||||
@@ -1,49 +1,19 @@
|
||||
import { Dispatch, FunctionComponent, SetStateAction, useEffect } from "react";
|
||||
import { ChainItemType } from "../../types";
|
||||
import { FunctionComponent } from "react";
|
||||
import { AccountInfo, WidthHeightProps } from "../../types";
|
||||
import color from "../../styles/color";
|
||||
import styled from "styled-components";
|
||||
import { ChainItem } from "./chain-item";
|
||||
|
||||
interface Props {
|
||||
chainList: ChainItemType[];
|
||||
disabledChainList: ChainItemType[];
|
||||
checkedItems: Set<unknown>;
|
||||
setCheckedItems: Dispatch<SetStateAction<Set<unknown>>>;
|
||||
chainList: AccountInfo[];
|
||||
}
|
||||
|
||||
export const ChainList: FunctionComponent<Props> = (props) => {
|
||||
const { chainList, disabledChainList, 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);
|
||||
};
|
||||
|
||||
const { chainList } = props;
|
||||
return (
|
||||
<ChainContainer color={color.grey["900"]}>
|
||||
{chainList.map((chainItem) => (
|
||||
<ChainItem
|
||||
key={chainItem.address}
|
||||
chainItem={chainItem}
|
||||
checkedItemHandler={checkedItemHandler}
|
||||
checkedItems={checkedItems}
|
||||
/>
|
||||
))}
|
||||
{disabledChainList.map((chainItem) => (
|
||||
<ChainItem
|
||||
key={chainItem.address}
|
||||
chainItem={chainItem}
|
||||
checkedItemHandler={checkedItemHandler}
|
||||
checkedItems={checkedItems}
|
||||
disabled={true}
|
||||
/>
|
||||
<ChainContainer color={color.grey["800"]}>
|
||||
{chainList.map((chainInfo) => (
|
||||
<ChainItem key={chainInfo.prefix} chainInfo={chainInfo} />
|
||||
))}
|
||||
</ChainContainer>
|
||||
);
|
||||
@@ -53,10 +23,37 @@ export const ChainContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
//max-height: 33rem;
|
||||
max-height: 33rem;
|
||||
overflow: scroll;
|
||||
|
||||
flex: 1;
|
||||
|
||||
background-color: ${(props) => props.color};
|
||||
`;
|
||||
|
||||
export const ChainItemContainer = styled.div<{ isLoading: 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;
|
||||
`;
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import { FunctionComponent } from "react";
|
||||
import color from "../../styles/color";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const Checkbox: FunctionComponent<{ checked: boolean }> = ({
|
||||
checked,
|
||||
}) => {
|
||||
return (
|
||||
<ChainCheckBoxContainer>
|
||||
<ChainCheckBoxHidden checked={checked} readOnly={true} />
|
||||
<ChainCheckBoxStyled checked={checked}>
|
||||
<Center>
|
||||
<Icon width="18" height="14" viewBox="0 0 15 12">
|
||||
<path
|
||||
strokeLinecap="square"
|
||||
strokeWidth="2"
|
||||
d="M2.25 4.426l4.083 5.011 6.417-7.874"
|
||||
/>
|
||||
</Icon>
|
||||
</Center>
|
||||
</ChainCheckBoxStyled>
|
||||
</ChainCheckBoxContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const Center = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const Icon = styled.svg`
|
||||
fill: none;
|
||||
stroke: ${color.orange["50"]};
|
||||
`;
|
||||
|
||||
const ChainCheckBoxContainer = styled.div`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
`;
|
||||
|
||||
const ChainCheckBoxStyled = styled.div<{ checked: boolean }>`
|
||||
display: inline-block;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border: ${(props) => (props.checked ? "none" : "2px solid #868686")};
|
||||
background: ${(props) =>
|
||||
props.checked ? color.orange["200"] : color.grey["400"]};
|
||||
|
||||
${Icon} {
|
||||
visibility: ${(props) => (props.checked ? "visible" : "hidden")};
|
||||
}
|
||||
`;
|
||||
|
||||
const ChainCheckBoxHidden = styled.input.attrs({ type: "checkbox" })`
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
`;
|
||||
@@ -1,2 +1,74 @@
|
||||
export * from "./modal";
|
||||
export * from "./wallet-item";
|
||||
import { FunctionComponent } from "react";
|
||||
import ReactModal from "react-modal";
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
import { WalletList } from "../../constants/wallet";
|
||||
import { WalletItem } from "./wallet-item";
|
||||
|
||||
interface Props {
|
||||
isModalOpen: boolean;
|
||||
onCloseModal: () => void;
|
||||
}
|
||||
|
||||
export const ConnectWalletModal: FunctionComponent<Props> = (props) => {
|
||||
const { isModalOpen, onCloseModal } = props;
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={onCloseModal}
|
||||
style={{
|
||||
overlay: { background: "#181818b3" },
|
||||
content: {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
right: "auto",
|
||||
bottom: "auto",
|
||||
padding: 0,
|
||||
marginRight: "-50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
background: color.grey["800"],
|
||||
border: 0,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ModalContainer>
|
||||
<ModalTitle>Connect Wallet</ModalTitle>
|
||||
<ModalDescription>
|
||||
Plz check which account is selected after you connect it
|
||||
</ModalDescription>
|
||||
|
||||
{WalletList.map((walletItem) => {
|
||||
return <WalletItem wallet={walletItem} key={walletItem.name} />;
|
||||
})}
|
||||
</ModalContainer>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
const ModalContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.9rem;
|
||||
padding: 2.2rem;
|
||||
`;
|
||||
|
||||
const ModalTitle = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.8rem;
|
||||
|
||||
color: ${color.white};
|
||||
`;
|
||||
|
||||
const ModalDescription = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.1rem;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { FunctionComponent } from "react";
|
||||
import ReactModal from "react-modal";
|
||||
import { FunctionComponent } from "react";
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
import { WalletList } from "../../constants/wallet";
|
||||
import { WalletItem } from "./wallet-item";
|
||||
|
||||
interface Props {
|
||||
isModalOpen: boolean;
|
||||
@@ -17,9 +15,8 @@ export const ConnectWalletModal: FunctionComponent<Props> = (props) => {
|
||||
<ReactModal
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={onCloseModal}
|
||||
ariaHideApp={false}
|
||||
style={{
|
||||
overlay: { background: "#121212cc" },
|
||||
overlay: { background: "#181818b3" },
|
||||
content: {
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
@@ -39,9 +36,35 @@ export const ConnectWalletModal: FunctionComponent<Props> = (props) => {
|
||||
Plz check which account is selected after you connect it
|
||||
</ModalDescription>
|
||||
|
||||
{WalletList.map((walletItem) => {
|
||||
return <WalletItem wallet={walletItem} key={walletItem.name} />;
|
||||
})}
|
||||
{/*<ChainConnectItem>*/}
|
||||
{/* <ChainConnectIcon>*/}
|
||||
{/* <Image src={KeplrIcon} fill={true} alt="Keplr Icon" />*/}
|
||||
{/* </ChainConnectIcon>*/}
|
||||
{/* <ChainConnectContentContainer>*/}
|
||||
{/* <ChainConnectContentTitle>Keplr</ChainConnectContentTitle>*/}
|
||||
{/* </ChainConnectContentContainer>*/}
|
||||
|
||||
{/* <Flex1 />*/}
|
||||
|
||||
{/* <Image src={ArrowRightIcon} alt="arrow right icon" />*/}
|
||||
{/*</ChainConnectItem>*/}
|
||||
|
||||
{/*<ChainConnectItem>*/}
|
||||
{/* <ChainConnectIcon>*/}
|
||||
{/* <Image src={CosmostationIcon} fill={true} alt="Cosmostation Icon" />*/}
|
||||
{/* </ChainConnectIcon>*/}
|
||||
|
||||
{/* <ChainConnectContentContainer>*/}
|
||||
{/* <ChainConnectContentTitle>Cosmostation</ChainConnectContentTitle>*/}
|
||||
|
||||
{/* <ChainConnectContentDescription>*/}
|
||||
{/* Coming soon*/}
|
||||
{/* </ChainConnectContentDescription>*/}
|
||||
{/* </ChainConnectContentContainer>*/}
|
||||
{/* <Flex1 />*/}
|
||||
|
||||
{/* <Image src={ArrowRightIcon} alt="arrow right icon" />*/}
|
||||
{/*</ChainConnectItem>*/}
|
||||
</ModalContainer>
|
||||
</ReactModal>
|
||||
);
|
||||
@@ -52,8 +75,6 @@ const ModalContainer = styled.div`
|
||||
flex-direction: column;
|
||||
gap: 0.9rem;
|
||||
padding: 2.2rem;
|
||||
|
||||
min-width: 28rem;
|
||||
`;
|
||||
|
||||
const ModalTitle = styled.div`
|
||||
|
||||
@@ -1,22 +1,11 @@
|
||||
import { FunctionComponent, useEffect, useState } from "react";
|
||||
import { FunctionComponent } from "react";
|
||||
import ArrowRightIcon from "../../public/images/svg/arrow-right.svg";
|
||||
import color from "../../styles/color";
|
||||
import { Flex1 } from "../../styles/flex-1";
|
||||
import styled from "styled-components";
|
||||
import Image from "next/image";
|
||||
import {
|
||||
MINIMUM_VERSION,
|
||||
SELECTED_WALLET_KEY,
|
||||
WALLET_INSTALL_URL,
|
||||
WalletType,
|
||||
} from "../../constants/wallet";
|
||||
import { getKeplrFromWindow, KeplrWallet } from "../../wallets";
|
||||
import { loginWithTwitter } from "../../queries";
|
||||
import {
|
||||
KEPLR_NOT_FOUND_ERROR,
|
||||
KEPLR_VERSION_ERROR,
|
||||
} from "../../constants/error-message";
|
||||
import semver from "semver/preload";
|
||||
import { WalletType } from "../../constants/wallet";
|
||||
import { TwitterAuthUrlResponse } from "../../types";
|
||||
|
||||
interface Props {
|
||||
wallet: WalletType;
|
||||
@@ -24,64 +13,23 @@ interface Props {
|
||||
|
||||
export const WalletItem: FunctionComponent<Props> = (props: Props) => {
|
||||
const { wallet } = props;
|
||||
const [isInstalled, setIsInstalled] = useState<boolean>();
|
||||
|
||||
useEffect(() => {
|
||||
setIsInstalled(!!window.keplr);
|
||||
}, []);
|
||||
|
||||
const onClickWalletItem = async () => {
|
||||
try {
|
||||
if (wallet.name === "Keplr") {
|
||||
await connectKeplr();
|
||||
localStorage.setItem(SELECTED_WALLET_KEY, wallet.name);
|
||||
}
|
||||
const { authUrl }: TwitterAuthUrlResponse = await (
|
||||
await fetch("/api/twitter-auth-url")
|
||||
).json();
|
||||
|
||||
await loginWithTwitter();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const connectKeplr = async () => {
|
||||
const keplr = await getKeplrFromWindow();
|
||||
|
||||
if (keplr === undefined) {
|
||||
window.location.href = WALLET_INSTALL_URL;
|
||||
throw new Error(KEPLR_NOT_FOUND_ERROR);
|
||||
}
|
||||
|
||||
if (semver.lt(keplr.version, MINIMUM_VERSION)) {
|
||||
throw new Error(KEPLR_VERSION_ERROR);
|
||||
}
|
||||
|
||||
if (keplr) {
|
||||
const wallet = new KeplrWallet(keplr);
|
||||
const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map(
|
||||
(c) => c.chainId,
|
||||
);
|
||||
|
||||
await wallet.init(chainIds);
|
||||
}
|
||||
window.location.href = authUrl;
|
||||
};
|
||||
|
||||
return (
|
||||
<WalletContainer isReady={wallet.isReady} onClick={onClickWalletItem}>
|
||||
<WalletIcon>
|
||||
<Image
|
||||
src={wallet.image}
|
||||
fill={true}
|
||||
sizes="3.75rem"
|
||||
alt="wallet Icon"
|
||||
/>
|
||||
<Image src={wallet.image} fill={true} alt="wallet Icon" />
|
||||
</WalletIcon>
|
||||
<WalletContentContainer>
|
||||
<WalletName>{wallet.name}</WalletName>
|
||||
{wallet.isReady ? (
|
||||
isInstalled ? null : (
|
||||
<WalletDescription>Go to install Keplr Extension</WalletDescription>
|
||||
)
|
||||
) : (
|
||||
{wallet.isReady ? null : (
|
||||
<WalletDescription>Comming soon</WalletDescription>
|
||||
)}
|
||||
</WalletContentContainer>
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import React, { Component, ErrorInfo, ReactNode } from "react";
|
||||
|
||||
interface Props {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends Component<Props, State> {
|
||||
public state: State = {
|
||||
hasError: false,
|
||||
};
|
||||
|
||||
public static getDerivedStateFromError(): State {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error("Uncaught error:", error, errorInfo);
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<h1>
|
||||
Oops.. there is something wrong.
|
||||
<br /> Please try again. <Link href="/">Go to home</Link>
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
@@ -6,34 +6,15 @@ import Link from "next/link";
|
||||
import LogoIcon from "../../public/images/svg/logo.svg";
|
||||
|
||||
// Styles
|
||||
import styled from "styled-components";
|
||||
import { LogoContainer } from "./styled";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
export const Logo: FunctionComponent = () => {
|
||||
return (
|
||||
<Link href="/">
|
||||
<LogoContainer>
|
||||
<Image
|
||||
src={LogoIcon}
|
||||
fill={true}
|
||||
sizes="10rem"
|
||||
alt="Home Logo"
|
||||
priority
|
||||
/>
|
||||
<Image src={LogoIcon} fill={true} alt="Home Logo" />
|
||||
</LogoContainer>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: absolute;
|
||||
width: 10rem;
|
||||
height: 5rem;
|
||||
|
||||
margin-top: 5rem;
|
||||
margin-left: 5rem;
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Styles
|
||||
import styled from "styled-components";
|
||||
|
||||
export const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: 160px;
|
||||
height: 80px;
|
||||
|
||||
margin-top: 80px;
|
||||
margin-left: 80px;
|
||||
`;
|
||||
@@ -7,18 +7,16 @@ export const PrimaryButton = styled.button`
|
||||
|
||||
border: none;
|
||||
|
||||
background-color: ${color.orange["100"]};
|
||||
padding: 11px 30px;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.25rem;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
font-size: 1.5rem;
|
||||
line-height: 20px;
|
||||
|
||||
color: ${color.orange["50"]};
|
||||
background-color: ${color.orange["100"]};
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
@@ -28,8 +26,6 @@ export const PrimaryButton = styled.button`
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
|
||||
background-color: ${color.orange["300"]};
|
||||
background-color: ${color.orange["200"]};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { Dispatch, FunctionComponent, SetStateAction } from "react";
|
||||
|
||||
import Image from "next/image";
|
||||
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
|
||||
import SearchIcon from "../../public/images/svg/search-icon.svg";
|
||||
|
||||
interface Props {
|
||||
searchValue: string;
|
||||
setSearchValue: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const SearchInput: FunctionComponent<Props> = (props) => {
|
||||
const { searchValue, setSearchValue } = props;
|
||||
|
||||
return (
|
||||
<SearchContainer>
|
||||
<SearchIconContainer>
|
||||
<Image src={SearchIcon} fill={true} sizes="1.3rem" alt="search icon" />
|
||||
</SearchIconContainer>
|
||||
|
||||
<SearchText
|
||||
type="text"
|
||||
placeholder="search"
|
||||
value={searchValue}
|
||||
onChange={(event) => {
|
||||
setSearchValue(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</SearchContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
|
||||
height: 2.3rem;
|
||||
|
||||
padding: 0.6rem 1.4rem;
|
||||
|
||||
border-radius: 3rem;
|
||||
background-color: ${color.grey["700"]};
|
||||
`;
|
||||
|
||||
const SearchIconContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
width: 1.3rem;
|
||||
height: 1.3rem;
|
||||
`;
|
||||
|
||||
const SearchText = styled.input`
|
||||
border: none;
|
||||
|
||||
color: ${color.white};
|
||||
background-color: ${color.grey["700"]};
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2rem;
|
||||
|
||||
width: 5.5rem;
|
||||
transition: width 0.2s ease-out;
|
||||
|
||||
::placeholder,
|
||||
::-webkit-input-placeholder {
|
||||
color: ${color.grey["400"]};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
width: 10rem;
|
||||
}
|
||||
`;
|
||||
@@ -9,13 +9,13 @@ export const SkeletonAnimation = styled.div`
|
||||
${color.grey["400"]},
|
||||
transparent
|
||||
),
|
||||
linear-gradient(${color.grey["600"]}, ${color.grey["600"]}),
|
||||
linear-gradient(${color.grey["500"]}, ${color.grey["500"]}),
|
||||
radial-gradient(
|
||||
38px circle at 19px 19px,
|
||||
${color.grey["600"]} 50%,
|
||||
${color.grey["500"]} 50%,
|
||||
transparent 51%
|
||||
),
|
||||
linear-gradient(${color.grey["600"]}, ${color.grey["600"]});
|
||||
linear-gradient(${color.grey["500"]}, ${color.grey["500"]});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 315px 250px, 315px 180px, 100px 100px, 225px 30px;
|
||||
background-position: -315px 0, 0 0, 0px 190px, 50px 195px;
|
||||
|
||||
@@ -21,99 +21,120 @@ import {
|
||||
} from "../twitter-profile";
|
||||
import {
|
||||
ChainContainer,
|
||||
ChainImageContainer,
|
||||
ChainInfoContainer,
|
||||
ChainItemContainer,
|
||||
} from "../chain-list";
|
||||
|
||||
export const SkeletonChainList: FunctionComponent = () => (
|
||||
<ContentContainer>
|
||||
<SkeletonContainer>
|
||||
<ProfileContainer color={color.grey["800"]}>
|
||||
<SkeletonCircle width="5.5rem" height="5.5rem" />
|
||||
<ProfileContainer color={color.grey["700"]}>
|
||||
<SkeletonCircle width="5.5rem" height="5.5rem" />
|
||||
|
||||
<ProfileContentContainer>
|
||||
<ProfileNameContainer>
|
||||
<SkeletonText width="5rem" height="1.5rem" />
|
||||
</ProfileNameContainer>
|
||||
<ProfileUserNameContainer>
|
||||
<SkeletonText width="5rem" height="1rem" />
|
||||
</ProfileUserNameContainer>
|
||||
<ProfileContentContainer>
|
||||
<ProfileNameContainer>
|
||||
<SkeletonText width="5rem" height="1.5rem" />
|
||||
</ProfileNameContainer>
|
||||
<ProfileUserNameContainer>
|
||||
<SkeletonText width="5rem" height="1rem" />
|
||||
</ProfileUserNameContainer>
|
||||
|
||||
<ProfileFollowContainer>
|
||||
<SkeletonText width="8rem" height="1rem" />
|
||||
<SkeletonText width="8rem" height="1rem" />
|
||||
</ProfileFollowContainer>
|
||||
<ProfileFollowContainer>
|
||||
<SkeletonText width="8rem" height="1rem" />
|
||||
<SkeletonText width="8rem" height="1rem" />
|
||||
</ProfileFollowContainer>
|
||||
|
||||
<SkeletonText width="20rem" height="1rem" />
|
||||
</ProfileContentContainer>
|
||||
</ProfileContainer>
|
||||
</SkeletonContainer>
|
||||
<SkeletonText width="20rem" height="1rem" />
|
||||
</ProfileContentContainer>
|
||||
</ProfileContainer>
|
||||
|
||||
<ChainListTitleContainer>
|
||||
<SkeletonTitle />
|
||||
</ChainListTitleContainer>
|
||||
|
||||
<ChainContainer color={color.grey["800"]}>
|
||||
{Array(10)
|
||||
.fill("")
|
||||
.map((_, index) => (
|
||||
<div key={index}>
|
||||
<ChainItemContainer isLoading={true} isSkeleton={true}>
|
||||
<SkeletonImageContainer>
|
||||
<SkeletonCircle width="3rem" height="3rem" />
|
||||
</SkeletonImageContainer>
|
||||
<ChainInfoContainer>
|
||||
<SkeletonText width="4rem" height="1rem" />
|
||||
<SkeletonText width="12rem" height="1rem" />
|
||||
</ChainInfoContainer>
|
||||
</ChainItemContainer>
|
||||
<ChainContainer color={color.grey["700"]}>
|
||||
<ChainItemContainer isLoading={true}>
|
||||
<ChainImageContainer width="3rem" height="3rem">
|
||||
<SkeletonCircle width="3rem" height="3rem" />
|
||||
</ChainImageContainer>
|
||||
<ChainInfoContainer>
|
||||
<SkeletonText width="4rem" height="1rem" />
|
||||
<SkeletonText width="12rem" height="1rem" />
|
||||
</ChainInfoContainer>
|
||||
</ChainItemContainer>
|
||||
|
||||
<SkeletonDivider />
|
||||
</div>
|
||||
))}
|
||||
<SkeletonDivider />
|
||||
|
||||
<ChainItemContainer isLoading={true}>
|
||||
<ChainImageContainer width="3rem" height="3rem">
|
||||
<SkeletonCircle width="3rem" height="3rem" />
|
||||
</ChainImageContainer>
|
||||
<ChainInfoContainer>
|
||||
<SkeletonText width="4rem" height="1rem" />
|
||||
<SkeletonText width="12rem" height="1rem" />
|
||||
</ChainInfoContainer>
|
||||
</ChainItemContainer>
|
||||
|
||||
<SkeletonDivider />
|
||||
|
||||
<ChainItemContainer isLoading={true}>
|
||||
<ChainImageContainer width="3rem" height="3rem">
|
||||
<SkeletonCircle width="3rem" height="3rem" />
|
||||
</ChainImageContainer>
|
||||
<ChainInfoContainer>
|
||||
<SkeletonText width="4rem" height="1rem" />
|
||||
<SkeletonText width="12rem" height="1rem" />
|
||||
</ChainInfoContainer>
|
||||
</ChainItemContainer>
|
||||
|
||||
<SkeletonDivider />
|
||||
|
||||
<ChainItemContainer isLoading={true}>
|
||||
<ChainImageContainer width="3rem" height="3rem">
|
||||
<SkeletonCircle width="3rem" height="3rem" />
|
||||
</ChainImageContainer>
|
||||
<ChainInfoContainer>
|
||||
<SkeletonText width="4rem" height="1rem" />
|
||||
<SkeletonText width="12rem" height="1rem" />
|
||||
</ChainInfoContainer>
|
||||
</ChainItemContainer>
|
||||
|
||||
<SkeletonDivider />
|
||||
|
||||
<ChainItemContainer isLoading={true}>
|
||||
<ChainImageContainer width="3rem" height="3rem">
|
||||
<SkeletonCircle width="3rem" height="3rem" />
|
||||
</ChainImageContainer>
|
||||
<ChainInfoContainer>
|
||||
<SkeletonText width="4rem" height="1rem" />
|
||||
<SkeletonText width="12rem" height="1rem" />
|
||||
</ChainInfoContainer>
|
||||
</ChainItemContainer>
|
||||
|
||||
<SkeletonDivider />
|
||||
</ChainContainer>
|
||||
|
||||
<SkeletonButtonContainer>
|
||||
<ButtonContainer>
|
||||
<SkeletonButton />
|
||||
</ButtonContainer>
|
||||
</SkeletonButtonContainer>
|
||||
<ButtonContainer>
|
||||
<SkeletonButton />
|
||||
</ButtonContainer>
|
||||
</ContentContainer>
|
||||
);
|
||||
|
||||
const SkeletonContainer = styled.div`
|
||||
width: 100%;
|
||||
padding-top: 2.4rem;
|
||||
`;
|
||||
|
||||
const SkeletonTitle = styled.div`
|
||||
width: 8rem;
|
||||
height: 1.5rem;
|
||||
background-color: ${color.grey["800"]};
|
||||
`;
|
||||
|
||||
const SkeletonImageContainer = styled.div`
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const SkeletonButtonContainer = styled.div`
|
||||
margin-top: 1.5rem;
|
||||
background-color: ${color.grey["700"]};
|
||||
`;
|
||||
|
||||
const SkeletonButton = styled.div`
|
||||
width: 12rem;
|
||||
height: 4rem;
|
||||
|
||||
padding-top: 1.5rem;
|
||||
|
||||
background-color: ${color.grey["800"]};
|
||||
background-color: ${color.grey["700"]};
|
||||
`;
|
||||
|
||||
const SkeletonDivider = styled(SkeletonAnimation)`
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: ${color.grey["600"]};
|
||||
background-color: ${color.grey["500"]};
|
||||
`;
|
||||
|
||||
@@ -12,6 +12,6 @@ export const SkeletonCircle = styled(SkeletonAnimation)<WidthHeightProps>`
|
||||
width: ${(props) => props.width};
|
||||
height: ${(props) => props.height};
|
||||
|
||||
background-color: ${color.grey["600"]};
|
||||
background-color: ${color.grey["500"]};
|
||||
border-radius: 50%;
|
||||
`;
|
||||
|
||||
@@ -12,12 +12,11 @@ export const TwitterProfile: FunctionComponent<Props> = (props) => {
|
||||
const { twitterProfileInformation } = props;
|
||||
|
||||
return (
|
||||
<ProfileContainer color={color.grey["900"]}>
|
||||
<ProfileContainer color={color.grey["800"]}>
|
||||
<ProfileImageContainer>
|
||||
<Image
|
||||
src={twitterProfileInformation?.profile_image_url ?? ""}
|
||||
fill={true}
|
||||
sizes="5rem"
|
||||
alt="twitter profile image"
|
||||
/>
|
||||
</ProfileImageContainer>
|
||||
@@ -130,10 +129,5 @@ export const ProfileDescriptionContainer = styled.div`
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
|
||||
max-width: 27.5rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
color: ${color.grey["100"]};
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import { AccountInfo } from "./types";
|
||||
|
||||
export const AccountInfos: AccountInfo[] = [
|
||||
{
|
||||
prefix: "cosmos",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/cosmoshub/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "osmo",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/osmosis/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "agoric",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/agoric/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "akash",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/akashnet/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "axelar",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/axelar-dojo/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "bostrom",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/bostrom/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "persistence",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/core/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "cro",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/crypto-org-chain-mainnet/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "emoney",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/emoney/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "evmos",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/evmos_9001/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "gravity",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/gravity-bridge/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "ixo",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/impacthub/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "star",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/iov-mainnet-ibc/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "iaa",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/irishub/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "juno",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/juno/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "kava",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/kava_2222/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "regen",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/regen/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "secret",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/secret/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "sent",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/sentinelhub/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "certik",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/shentu-2.2/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "sif",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/sifchain/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "somm",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/sommelier/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "stars",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/stargaze/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "stride",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/stride/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "tgrade",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/tgrade-mainnet/chain.png",
|
||||
},
|
||||
{
|
||||
prefix: "umee",
|
||||
address: "cosmos14ky6udatsvdx859050mrnr7rvml0huue2wszvs",
|
||||
chainImageUrl:
|
||||
"https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/umee/chain.png",
|
||||
},
|
||||
];
|
||||
@@ -1,5 +0,0 @@
|
||||
export const TWITTER_LOGIN_ERROR = "Twitter login access denied";
|
||||
export const TWITTER_PROFILE_ERROR = "Twitter auth code is not valid";
|
||||
|
||||
export const KEPLR_NOT_FOUND_ERROR = "Can't fount window.keplr";
|
||||
export const KEPLR_VERSION_ERROR = "You should update keplr";
|
||||
@@ -1,18 +0,0 @@
|
||||
export const MainChainId = "osmo-test-4";
|
||||
|
||||
export const REFERRAL_KEY = "icns-referral";
|
||||
|
||||
export const RPC_URL =
|
||||
process.env.OSMOSIS_RPC_URL ?? "https://rpc.testnet.osmosis.zone";
|
||||
export const REST_URL =
|
||||
process.env.OSMOSIS_REST_URL ?? "https://lcd.testnet.osmosis.zone";
|
||||
|
||||
export const NAME_NFT_ADDRESS =
|
||||
process.env.ICNS_NAME_NFT_CONTRACT_ADDRESS ??
|
||||
"osmo1xahnjn872smah6xle8n3z5a5teqq390qr959l805mkuw0kcy8g5smtdagg";
|
||||
export const REGISTRAR_ADDRESS =
|
||||
process.env.ICNS_REGISTRAR_CONTRACT_ADDRESS ??
|
||||
"osmo1npn97g7hsgqlp70rw8nhd7c7vyvkukv9x0n25sn4fk5mgcjlz4gq9zlgf3";
|
||||
export const RESOLVER_ADDRESS =
|
||||
process.env.ICNS_RESOLVER_CONTRACT_ADDRESS ??
|
||||
"osmo1002awr7frr9wk44lc3vfzt0d2w6vz5z03ql6fszjsjy8vdcvk0sskruz3c";
|
||||
@@ -7,5 +7,3 @@ export const twitterOAuthScopes = [
|
||||
];
|
||||
|
||||
export const twitterApiBaseUrl = "https://api.twitter.com/2";
|
||||
|
||||
export const SHARE_URL = "https://twitter.com/share";
|
||||
|
||||
@@ -3,16 +3,8 @@ 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 WALLET_INSTALL_URL =
|
||||
"https://chrome.google.com/webstore/detail/keplr/dmkamcknogkgcdfhhbddcghachkejeap";
|
||||
export const SELECTED_WALLET_KEY = "SELECTED_WALLET_KEY";
|
||||
|
||||
export const MINIMUM_VERSION = "0.11.22";
|
||||
export const MINIMUM_OSMO_FEE = process.env.MINIMUM_OSMO_FEE ?? "0.5 OSMO";
|
||||
|
||||
export type WalletName = "Keplr" | "Cosmostation";
|
||||
export interface WalletType {
|
||||
name: WalletName;
|
||||
name: string;
|
||||
image: StaticImageData;
|
||||
isReady: boolean;
|
||||
}
|
||||
@@ -29,8 +21,3 @@ export const WalletList: WalletType[] = [
|
||||
isReady: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const ContractFee = {
|
||||
denom: "uosmo",
|
||||
amount: "500000",
|
||||
};
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
import { CosmwasmExecuteMessageResult } from "../types";
|
||||
import { makeCosmwasmExecMsg } from "../wallets";
|
||||
import { REGISTRAR_ADDRESS, RESOLVER_ADDRESS } from "../constants/icns";
|
||||
import { ContractFee } from "../constants/wallet";
|
||||
|
||||
export const makeClaimMessage = (
|
||||
senderAddress: string,
|
||||
twitterUserName: string,
|
||||
verificationList: any[],
|
||||
referral?: string,
|
||||
): CosmwasmExecuteMessageResult => {
|
||||
verificationList = verificationList.filter((verification) => {
|
||||
if (verification.status !== "fulfilled") {
|
||||
console.log("verification", verification);
|
||||
}
|
||||
|
||||
return verification.status === "fulfilled";
|
||||
});
|
||||
|
||||
if (verificationList.length === 0) {
|
||||
throw new Error("Verifications rejected all");
|
||||
}
|
||||
|
||||
return makeCosmwasmExecMsg(
|
||||
senderAddress,
|
||||
REGISTRAR_ADDRESS,
|
||||
{
|
||||
claim: {
|
||||
name: twitterUserName,
|
||||
verifying_msg:
|
||||
verificationList[0].status === "fulfilled"
|
||||
? verificationList[0].value.data.verifying_msg
|
||||
: "",
|
||||
verifications: verificationList.map((verification) => {
|
||||
if (verification.status === "fulfilled") {
|
||||
return {
|
||||
public_key: verification.value.data.public_key,
|
||||
signature: verification.value.data.signature,
|
||||
};
|
||||
}
|
||||
}),
|
||||
referral,
|
||||
},
|
||||
},
|
||||
[ContractFee],
|
||||
);
|
||||
};
|
||||
|
||||
export const makeSetRecordMessage = (
|
||||
senderAddress: string,
|
||||
twitterUserName: string,
|
||||
adr36Info: any,
|
||||
): CosmwasmExecuteMessageResult => {
|
||||
return makeCosmwasmExecMsg(
|
||||
senderAddress,
|
||||
RESOLVER_ADDRESS,
|
||||
{
|
||||
set_record: {
|
||||
name: twitterUserName,
|
||||
bech32_prefix: adr36Info.bech32Prefix,
|
||||
adr36_info: {
|
||||
signer_bech32_address: adr36Info.bech32Address,
|
||||
address_hash: adr36Info.addressHash,
|
||||
pub_key: Buffer.from(adr36Info.pubKey).toString("base64"),
|
||||
signature: Buffer.from(adr36Info.signature).toString("base64"),
|
||||
signature_salt: adr36Info.signatureSalt.toString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export const makeRemoveRecordMessage = (
|
||||
twitterUserName: string,
|
||||
senderAddress: string,
|
||||
removeAddress: string,
|
||||
): CosmwasmExecuteMessageResult => {
|
||||
return makeCosmwasmExecMsg(
|
||||
senderAddress,
|
||||
RESOLVER_ADDRESS,
|
||||
{
|
||||
remove_record: {
|
||||
name: twitterUserName,
|
||||
bech32_address: removeAddress,
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./icns";
|
||||
@@ -15,10 +15,6 @@ const nextConfig = {
|
||||
protocol: "https",
|
||||
hostname: "raw.githubusercontent.com",
|
||||
},
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "abs.twimg.com",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -23,8 +23,6 @@
|
||||
"react-dom": "18.2.0",
|
||||
"react-is": "^18.2.0",
|
||||
"react-modal": "^3.16.1",
|
||||
"react-typed": "^1.2.0",
|
||||
"semver": "^7.3.8",
|
||||
"styled-components": "^5.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -34,7 +32,6 @@
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "18.0.9",
|
||||
"@types/react-modal": "^3",
|
||||
"@types/semver": "^7",
|
||||
"@types/styled-components": "^5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
||||
"@typescript-eslint/parser": "^5.45.0",
|
||||
|
||||
@@ -1,138 +1,13 @@
|
||||
import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useMemo } from "react";
|
||||
import { DefaultTheme, ThemeProvider } from "styled-components";
|
||||
import ErrorBoundary from "../components/error-boundary";
|
||||
import React from "react";
|
||||
|
||||
import { GlobalStyle } from "../styles/global";
|
||||
|
||||
const homePageTheme: DefaultTheme = {
|
||||
bgColor: "rgba(18, 18, 18, 1)",
|
||||
bgGridColor: "rgba(51, 51, 51, 1)",
|
||||
};
|
||||
|
||||
const defaultPageTheme: DefaultTheme = {
|
||||
bgColor: "rgba(18, 18, 18, 0.8)",
|
||||
bgGridColor: "rgba(51, 51, 51, 0.3)",
|
||||
};
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter();
|
||||
|
||||
const pageTheme = useMemo(() => {
|
||||
return router.pathname === "/" ? homePageTheme : defaultPageTheme;
|
||||
}, [router.pathname]);
|
||||
|
||||
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={pageTheme}>
|
||||
<Head>
|
||||
<title>Interchain Name Service</title>
|
||||
<meta content="Interchain Name Service" property="og:title" />
|
||||
<meta content="Interchain Name Service" property="twitter:title" />
|
||||
<meta
|
||||
content="Your identity for the Interchain. Claim yours today."
|
||||
property="og:description"
|
||||
/>
|
||||
<meta
|
||||
content="Your identity for the Interchain. Claim yours today."
|
||||
property="twitter:description"
|
||||
/>
|
||||
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
|
||||
<meta
|
||||
content={`${origin}/images/og-image.webp`}
|
||||
property="twitter:image"
|
||||
/>
|
||||
<meta content={`${origin}/images/og-image.webp`} property="og:image" />
|
||||
<meta content="summary_large_image" name="twitter:card" />
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
{/* generated favicons */}
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="57x57"
|
||||
href="/images/favicon/apple-icon-57x57.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="60x60"
|
||||
href="/images/favicon/apple-icon-60x60.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="72x72"
|
||||
href="/images/favicon/apple-icon-72x72.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="76x76"
|
||||
href="/images/favicon/apple-icon-76x76.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="114x114"
|
||||
href="/images/favicon/apple-icon-114x114.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="120x120"
|
||||
href="/images/favicon/apple-icon-120x120.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="144x144"
|
||||
href="/images/favicon/apple-icon-144x144.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="152x152"
|
||||
href="/images/favicon/apple-icon-152x152.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/images/favicon/apple-icon-180x180.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="192x192"
|
||||
href="/images/favicon/android-icon-192x192.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/images/favicon/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="96x96"
|
||||
href="/images/favicon/favicon-96x96.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/images/favicon/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/images/favicon/manifest.json" />
|
||||
<meta name="msapplication-TileColor" content="#ffffff" />
|
||||
<meta
|
||||
name="msapplication-TileImage"
|
||||
content="/images/favicon/ms-icon-144x144.png"
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
</Head>
|
||||
<React.Fragment>
|
||||
<GlobalStyle />
|
||||
<ErrorBoundary>
|
||||
<Component {...pageProps} />
|
||||
</ErrorBoundary>
|
||||
</React.Fragment>
|
||||
</ThemeProvider>
|
||||
<React.Fragment>
|
||||
<GlobalStyle />
|
||||
<Component {...pageProps} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,39 +5,24 @@ import Document, {
|
||||
Main,
|
||||
NextScript,
|
||||
} from "next/document";
|
||||
import { ServerStyleSheet } from "styled-components";
|
||||
|
||||
class MyDocument extends Document {
|
||||
static async getInitialProps(ctx: DocumentContext) {
|
||||
const sheet = new ServerStyleSheet();
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
|
||||
try {
|
||||
// Run the React rendering logic synchronously
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
// Useful for wrapping the whole react tree
|
||||
enhanceApp: (App) => (props) =>
|
||||
sheet.collectStyles(<App {...props} />),
|
||||
// Useful for wrapping in a per-page basis
|
||||
enhanceComponent: (Component) => Component,
|
||||
});
|
||||
// Run the React rendering logic synchronously
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
// Useful for wrapping the whole react tree
|
||||
enhanceApp: (App) => App,
|
||||
// Useful for wrapping in a per-page basis
|
||||
enhanceComponent: (Component) => Component,
|
||||
});
|
||||
|
||||
// Run the parent `getInitialProps`, it now includes the custom `renderPage`
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
// Run the parent `getInitialProps`, it now includes the custom `renderPage`
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
styles: (
|
||||
<div>
|
||||
{initialProps.styles}
|
||||
{sheet.getStyleElement()}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
} finally {
|
||||
sheet.seal();
|
||||
}
|
||||
return initialProps;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { withIronSessionApiRoute } from "iron-session/next";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { ironOptions } from "../../iron.config";
|
||||
import { IcnsVerificationResponse } from "../../types";
|
||||
import { IcnsVerificationResponse } from "../../types/api-response";
|
||||
import { request } from "../../utils/url";
|
||||
|
||||
export default withIronSessionApiRoute(async function handler(
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
import Image from "next/image";
|
||||
import styled from "styled-components";
|
||||
|
||||
import Typed from "react-typed";
|
||||
|
||||
import { Logo } from "../../components/logo";
|
||||
import color from "../../styles/color";
|
||||
|
||||
import AlertCircleOutlineIcon from "../../public/images/svg/alert-circle-outline.svg";
|
||||
import TwitterIcon from "../../public/images/svg/twitter-icon.svg";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { TendermintTxTracer } from "@keplr-wallet/cosmos";
|
||||
import { queryAddressesFromTwitterName } from "../../queries";
|
||||
import { RegisteredAddresses } from "../../types";
|
||||
import { SHARE_URL } from "../../constants/twitter";
|
||||
|
||||
export default function CompletePage() {
|
||||
const router = useRouter();
|
||||
|
||||
const [registeredAddressed, setRegisteredAddressed] =
|
||||
useState<RegisteredAddresses[]>();
|
||||
|
||||
const [availableAddress, setAvailableAddress] = useState("");
|
||||
|
||||
const [isSuccess, setIsSuccess] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const { txHash, twitterUsername } = router.query;
|
||||
if (txHash && twitterUsername) {
|
||||
initialize(txHash as string, twitterUsername as string);
|
||||
}
|
||||
}, [router.query]);
|
||||
|
||||
const initialize = async (txHash: string, twitterUserName: string) => {
|
||||
const txTracer = new TendermintTxTracer(
|
||||
"https://rpc.testnet.osmosis.zone",
|
||||
"/websocket",
|
||||
);
|
||||
|
||||
try {
|
||||
const result: { code?: number } = await txTracer.traceTx(
|
||||
Buffer.from(txHash, "hex"),
|
||||
);
|
||||
|
||||
if (!result.code || result.code === 0) {
|
||||
const addresses = await queryAddressesFromTwitterName(twitterUserName);
|
||||
setRegisteredAddressed(addresses.data.addresses);
|
||||
setIsSuccess(true);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error", e);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickShareButton = () => {
|
||||
const { twitterUsername } = router.query;
|
||||
|
||||
const shareMessage = `👨🚀 To Interchain... And Beyond!%0a%0aHey frens, I just minted my name for the interchain on @icns_xyz: ${twitterUsername}%0a%0aClaim yours now ▶`;
|
||||
|
||||
const width = 500;
|
||||
const height = 700;
|
||||
window.open(
|
||||
`${SHARE_URL}?url=https://app.icns.xyz?referral=${twitterUsername}/&text=${shareMessage}`,
|
||||
"Share Twitter",
|
||||
`top=${(window.screen.height - height) / 2}, left=${
|
||||
(window.screen.width - width) / 2
|
||||
}, width=${width}, height=${height}, status=no, menubar=no, toolbar=no, resizable=no`,
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Logo />
|
||||
|
||||
<MainContainer>
|
||||
<MainTitle>
|
||||
{isSuccess && (
|
||||
<Typed
|
||||
strings={["Your Name is Active Now!"]}
|
||||
stopped={!isSuccess}
|
||||
typeSpeed={100}
|
||||
/>
|
||||
)}
|
||||
</MainTitle>
|
||||
<ContentContainer>
|
||||
<RecipentContainer>
|
||||
<RecipentTitle>Recipent</RecipentTitle>
|
||||
<AddressContainer>
|
||||
{`${router.query.twitterUsername}.`}
|
||||
{registeredAddressed && (
|
||||
<Typed
|
||||
strings={registeredAddressed.map(
|
||||
(address) => address.bech32_prefix,
|
||||
)}
|
||||
typeSpeed={150}
|
||||
backSpeed={150}
|
||||
backDelay={1000}
|
||||
loop
|
||||
smartBackspace
|
||||
onStringTyped={(arrayPos: number) => {
|
||||
setAvailableAddress(registeredAddressed[arrayPos].address);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AddressContainer>
|
||||
<AvailableAddressText>{availableAddress}</AvailableAddressText>
|
||||
</RecipentContainer>
|
||||
</ContentContainer>
|
||||
|
||||
<DescriptionContainer>
|
||||
<AlertIcon>
|
||||
<Image src={AlertCircleOutlineIcon} fill={true} alt="alert icon" />
|
||||
</AlertIcon>
|
||||
<DescriptionText>
|
||||
If you want to make that name address with same twitter account,
|
||||
just go to home and start again.
|
||||
</DescriptionText>
|
||||
</DescriptionContainer>
|
||||
|
||||
{/*<ShareButtonContainer onClick={onClickShareButton}>*/}
|
||||
{/* <ShareButtonText>SHARE MY NAME</ShareButtonText>*/}
|
||||
{/* <Image src={TwitterIcon} alt="twitter icon" />*/}
|
||||
{/*</ShareButtonContainer>*/}
|
||||
</MainContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
`;
|
||||
|
||||
const MainContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
padding-top: 10.1rem;
|
||||
|
||||
color: white;
|
||||
`;
|
||||
|
||||
const MainTitle = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-size: 2rem;
|
||||
line-height: 2rem;
|
||||
|
||||
height: 5rem;
|
||||
`;
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
width: 30rem;
|
||||
|
||||
padding: 2rem 2rem;
|
||||
|
||||
background-color: ${color.grey["900"]};
|
||||
`;
|
||||
|
||||
const RecipentContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
padding: 2rem;
|
||||
`;
|
||||
|
||||
const RecipentTitle = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 1rem;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
|
||||
const AddressContainer = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
line-height: 0.9rem;
|
||||
|
||||
color: ${color.white};
|
||||
|
||||
padding: 1rem;
|
||||
|
||||
background-color: ${color.grey["600"]};
|
||||
`;
|
||||
|
||||
const AvailableAddressText = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 0.75rem;
|
||||
line-height: 0.75rem;
|
||||
|
||||
min-height: 0.75rem;
|
||||
|
||||
color: ${color.blue};
|
||||
`;
|
||||
|
||||
const DescriptionContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
gap: 1rem;
|
||||
|
||||
width: 30rem;
|
||||
|
||||
margin-top: 1.5rem;
|
||||
padding: 1.25rem 2rem;
|
||||
|
||||
background-color: ${color.grey["900"]};
|
||||
`;
|
||||
|
||||
const AlertIcon = styled.div`
|
||||
position: relative;
|
||||
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
`;
|
||||
|
||||
const DescriptionText = styled.div`
|
||||
width: 100%;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 0.8rem;
|
||||
line-height: 140%;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
|
||||
const ShareButtonContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
|
||||
width: 20rem;
|
||||
height: 5rem;
|
||||
|
||||
margin-top: 2.5rem;
|
||||
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
background-color: ${color.grey["700"]};
|
||||
`;
|
||||
|
||||
const ShareButtonText = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.25rem;
|
||||
|
||||
color: ${color.grey["100"]};
|
||||
`;
|
||||
@@ -7,18 +7,14 @@ import color from "../styles/color";
|
||||
|
||||
// Components
|
||||
import { PrimaryButton } from "../components/primary-button";
|
||||
import { ConnectWalletModal } from "../components/connect-wallet-modal";
|
||||
|
||||
// Image Assets
|
||||
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 StarIcon from "../public/images/svg/bg-asset-3.svg";
|
||||
import { Logo } from "../components/logo";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MINIMUM_OSMO_FEE, SELECTED_WALLET_KEY } from "../constants/wallet";
|
||||
import { replaceToInstallPage } from "../utils/url";
|
||||
import { REFERRAL_KEY } from "../constants/icns";
|
||||
import { useState } from "react";
|
||||
import { ConnectWalletModal } from "../components/connect-wallet-modal";
|
||||
|
||||
export default function Home() {
|
||||
const [isModalOpen, setModalOpen] = useState(false);
|
||||
@@ -27,21 +23,6 @@ export default function Home() {
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.removeItem(REFERRAL_KEY);
|
||||
|
||||
if (window.location.search) {
|
||||
const [, referral] =
|
||||
window.location.search.match(/^(?=.*referral=([^&]+)|).+$/) || [];
|
||||
|
||||
if (referral) {
|
||||
localStorage.setItem(REFERRAL_KEY, referral);
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.removeItem(SELECTED_WALLET_KEY);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Logo />
|
||||
@@ -49,28 +30,8 @@ export default function Home() {
|
||||
<MainContainer>
|
||||
<MainTitleContainer>
|
||||
<MainTitleImageBackground>
|
||||
<Image
|
||||
src={StarIcon}
|
||||
width={50}
|
||||
height={50}
|
||||
alt="Star Icon"
|
||||
className="starIcon first"
|
||||
/>
|
||||
<Image
|
||||
src={StarIcon}
|
||||
width={50}
|
||||
height={50}
|
||||
alt="Star Icon"
|
||||
className="starIcon last"
|
||||
/>
|
||||
<MainTitleImageContainer>
|
||||
<Image
|
||||
src={MainTitle}
|
||||
fill={true}
|
||||
sizes="60rem"
|
||||
alt="Main Title"
|
||||
priority
|
||||
/>
|
||||
<Image src={MainTitle} fill={true} alt="Main Title" />
|
||||
</MainTitleImageContainer>
|
||||
</MainTitleImageBackground>
|
||||
|
||||
@@ -82,34 +43,23 @@ export default function Home() {
|
||||
<SubContainer>
|
||||
<CheckContainer>
|
||||
<CheckIconContainer>
|
||||
<Image
|
||||
src={CheckIcon}
|
||||
fill={true}
|
||||
sizes="1.6rem"
|
||||
alt="Check Icon"
|
||||
/>
|
||||
<Image src={CheckIcon} fill={true} alt="Check Icon" />
|
||||
</CheckIconContainer>
|
||||
You are a <CheckBoldText> keplr </CheckBoldText> user.
|
||||
if not, you can install
|
||||
<InstallLInk onClick={replaceToInstallPage}>HERE</InstallLInk>
|
||||
if not, you can install here
|
||||
</CheckContainer>
|
||||
<CheckContainer>
|
||||
<CheckIconContainer>
|
||||
<Image
|
||||
src={CheckIcon}
|
||||
fill={true}
|
||||
sizes="1.6rem"
|
||||
alt="Check Icon"
|
||||
/>
|
||||
<Image src={CheckIcon} fill={true} alt="Check Icon" />
|
||||
</CheckIconContainer>
|
||||
<CheckBoldText>{MINIMUM_OSMO_FEE} </CheckBoldText> is
|
||||
required for this transaction
|
||||
<CheckBoldText>Osmo </CheckBoldText> is required for this
|
||||
transaction
|
||||
</CheckContainer>
|
||||
</SubContainer>
|
||||
</MainTitleContainer>
|
||||
|
||||
<MainLogoContainer>
|
||||
<Image src={MainLogo} fill={true} sizes="25rem" alt="Main Logo" />
|
||||
<Image src={MainLogo} layout="fixed" fill={true} alt="Main Logo" />
|
||||
</MainLogoContainer>
|
||||
</MainContainer>
|
||||
|
||||
@@ -127,15 +77,6 @@ const Container = styled.div`
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
background-image: url("/images/svg/bg-asset-1.svg"),
|
||||
url("/images/svg/bg-asset-2.svg"), url("/images/svg/bg-asset-2.svg"),
|
||||
url("/images/svg/bg-asset-3.svg"), url("/images/svg/bg-asset-3.svg");
|
||||
background-size: 5rem 5rem, 5rem 5rem, 5rem 5rem, 3.125rem 3.125rem,
|
||||
3.125rem 3.125rem;
|
||||
background-position: 80px 640px, 960px 80px, 1200px 720px, 136px 776px,
|
||||
1016px 696px;
|
||||
background-repeat: no-repeat;
|
||||
`;
|
||||
|
||||
const MainContainer = styled.div`
|
||||
@@ -144,20 +85,6 @@ const MainContainer = styled.div`
|
||||
|
||||
margin-top: 15rem;
|
||||
margin-left: 10rem;
|
||||
|
||||
img.starIcon {
|
||||
position: absolute;
|
||||
|
||||
&.first {
|
||||
left: -24px;
|
||||
top: -24px;
|
||||
}
|
||||
|
||||
&.last {
|
||||
right: -23px;
|
||||
top: -24px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const MainTitleContainer = styled.div`
|
||||
@@ -234,16 +161,7 @@ const CheckContainer = styled.div`
|
||||
|
||||
text-transform: uppercase;
|
||||
|
||||
padding-left: 0.75rem;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
|
||||
const InstallLInk = styled.a`
|
||||
color: ${color.grey["400"]};
|
||||
text-decoration: underline;
|
||||
|
||||
cursor: pointer;
|
||||
color: ${color.grey["300"]};
|
||||
`;
|
||||
|
||||
const CheckBoldText = styled.span`
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// React
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// NextJs
|
||||
import Image from "next/image";
|
||||
|
||||
// Types
|
||||
import {
|
||||
ChainItemType,
|
||||
QueryError,
|
||||
RegisteredAddresses,
|
||||
TwitterProfileType,
|
||||
} from "../../types";
|
||||
import { checkTwitterAuthQueryParameter } from "../../utils/url";
|
||||
import { IcnsVerificationResponse, TwitterAuthInfoResponse } from "../../types";
|
||||
import { request } from "../../utils/url";
|
||||
|
||||
// Styles
|
||||
import styled from "styled-components";
|
||||
@@ -19,340 +17,186 @@ 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 {
|
||||
getKeplrFromWindow,
|
||||
KeplrWallet,
|
||||
makeCosmwasmExecMsg,
|
||||
sendMsgs,
|
||||
simulateMsgs,
|
||||
} from "../../wallets";
|
||||
import { ChainIdHelper } from "@keplr-wallet/cosmos";
|
||||
|
||||
import { AllChainsItem } from "../../components/chain-list/all-chains-item";
|
||||
import { SearchInput } from "../../components/search-input";
|
||||
import {
|
||||
MainChainId,
|
||||
REFERRAL_KEY,
|
||||
RESOLVER_ADDRESS,
|
||||
REST_URL,
|
||||
} from "../../constants/icns";
|
||||
|
||||
import {
|
||||
fetchTwitterInfo,
|
||||
queryAddressesFromTwitterName,
|
||||
queryOwnerOfTwitterName,
|
||||
queryRegisteredTwitterId,
|
||||
verifyTwitterAccount,
|
||||
} from "../../queries";
|
||||
import { ErrorHandler } from "../../utils/error";
|
||||
import {
|
||||
KEPLR_NOT_FOUND_ERROR,
|
||||
TWITTER_LOGIN_ERROR,
|
||||
} from "../../constants/error-message";
|
||||
import {
|
||||
makeClaimMessage,
|
||||
makeRemoveRecordMessage,
|
||||
makeSetRecordMessage,
|
||||
} from "../../messages";
|
||||
import Axios from "axios";
|
||||
import { BackButton } from "../../components/back-button";
|
||||
import { TendermintTxTracer } from "@keplr-wallet/cosmos";
|
||||
import { Buffer } from "buffer/";
|
||||
|
||||
export default function VerificationPage() {
|
||||
const router = useRouter();
|
||||
const [twitterAuthInfo, setTwitterAuthInfo] = useState<TwitterProfileType>();
|
||||
const [twitterAuthInfo, setTwitterAuthInfo] =
|
||||
useState<TwitterAuthInfoResponse | null>();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const [wallet, setWallet] = useState<KeplrWallet>();
|
||||
|
||||
const [chainList, setChainList] = useState<ChainItemType[]>([]);
|
||||
const [disabledChainList, setDisabledChainList] = useState<ChainItemType[]>(
|
||||
[],
|
||||
);
|
||||
const [registeredChainList, setRegisteredChainList] = useState<
|
||||
RegisteredAddresses[]
|
||||
>([]);
|
||||
const [checkedItems, setCheckedItems] = useState(new Set());
|
||||
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const [isOwner, setIsOwner] = useState(false);
|
||||
const [isAgree, setIsAgree] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
}, []);
|
||||
const handleVerification = async () => {
|
||||
if (window.location.search) {
|
||||
const [, state, code] =
|
||||
window.location.search.match(
|
||||
/^(?=.*state=([^&]+)|)(?=.*code=([^&]+)|).+$/,
|
||||
) || [];
|
||||
|
||||
useEffect(() => {
|
||||
if (wallet) {
|
||||
window.addEventListener("keplr_keystorechange", async () => {
|
||||
await init();
|
||||
});
|
||||
}
|
||||
}, [wallet]);
|
||||
|
||||
useEffect(() => {
|
||||
const disabledChainList = chainList.filter((chain) => {
|
||||
for (const registeredChain of registeredChainList) {
|
||||
if (
|
||||
chain.prefix === registeredChain.bech32_prefix &&
|
||||
chain.address === registeredChain.address
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
const filteredChainList = chainList.filter(
|
||||
(chain) => !disabledChainList.includes(chain),
|
||||
);
|
||||
|
||||
setChainList(filteredChainList);
|
||||
setDisabledChainList(disabledChainList);
|
||||
|
||||
setCheckedItems(new Set(filteredChainList));
|
||||
}, [registeredChainList]);
|
||||
|
||||
useEffect(() => {
|
||||
setCheckedItems(new Set(chainList));
|
||||
}, [chainList]);
|
||||
|
||||
const init = async () => {
|
||||
if (window.location.search) {
|
||||
try {
|
||||
const { state, code } = checkTwitterAuthQueryParameter(
|
||||
window.location.search,
|
||||
const newTwitterAuthInfo = await request<TwitterAuthInfoResponse>(
|
||||
`/api/twitter-auth-info?state=${state}&code=${code}`,
|
||||
);
|
||||
|
||||
// Initialize Wallet
|
||||
const keplrWallet = await initWallet();
|
||||
setTwitterAuthInfo(newTwitterAuthInfo);
|
||||
|
||||
// Fetch Twitter Profile
|
||||
const twitterInfo = await fetchTwitterInfo(state, code);
|
||||
await (async () => {
|
||||
const keplr = await getKeplrFromWindow();
|
||||
if (keplr) {
|
||||
const wallet = new KeplrWallet(keplr);
|
||||
|
||||
// contract check registered
|
||||
const registeredQueryResponse = await queryRegisteredTwitterId(
|
||||
twitterInfo.id,
|
||||
);
|
||||
const mainChainId = "osmo-test-4";
|
||||
const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map(
|
||||
(c) => c.chainId,
|
||||
);
|
||||
|
||||
setTwitterAuthInfo({
|
||||
...twitterInfo,
|
||||
isRegistered: "data" in registeredQueryResponse,
|
||||
});
|
||||
await wallet.init(chainIds);
|
||||
|
||||
if ("data" in registeredQueryResponse) {
|
||||
const ownerOfQueryResponse = await queryOwnerOfTwitterName(
|
||||
registeredQueryResponse.data.name,
|
||||
);
|
||||
const key = await wallet.getKey(mainChainId);
|
||||
|
||||
const addressesQueryResponse = await queryAddressesFromTwitterName(
|
||||
registeredQueryResponse.data.name,
|
||||
);
|
||||
const icnsVerificationList = (
|
||||
await request<IcnsVerificationResponse>(
|
||||
"/api/icns-verification",
|
||||
{
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
claimer: key.bech32Address,
|
||||
authToken: newTwitterAuthInfo.accessToken,
|
||||
}),
|
||||
},
|
||||
)
|
||||
).verificationList;
|
||||
|
||||
if (keplrWallet) {
|
||||
const key = await keplrWallet.getKey(MainChainId);
|
||||
setIsOwner(ownerOfQueryResponse.data.owner === key.bech32Address);
|
||||
console.log(icnsVerificationList);
|
||||
|
||||
const registerMsg = makeCosmwasmExecMsg(
|
||||
key.bech32Address,
|
||||
"osmo1u3sm9029430ca7xqz5afx4n0d42mgs2w97syex23g3vz2m673hxsv905sn",
|
||||
{
|
||||
claim: {
|
||||
name: newTwitterAuthInfo.username,
|
||||
verifying_msg:
|
||||
icnsVerificationList[0].status === "fulfilled"
|
||||
? icnsVerificationList[0].value.data.verifying_msg
|
||||
: "",
|
||||
verifications: icnsVerificationList.map((verification) => {
|
||||
if (verification.status === "fulfilled") {
|
||||
return {
|
||||
public_key: verification.value.data.public_key,
|
||||
signature: verification.value.data.signature,
|
||||
};
|
||||
}
|
||||
}),
|
||||
},
|
||||
},
|
||||
[
|
||||
{
|
||||
denom: "uosmo",
|
||||
amount: "500000",
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const test = await wallet.signICNSAdr36(
|
||||
mainChainId,
|
||||
"osmo1hnjg39mu9r9tygy6zpypd25rhmh9726jm369uh7z0r6gjkmnk5zs3tdtug",
|
||||
key.bech32Address,
|
||||
newTwitterAuthInfo.username,
|
||||
chainIds,
|
||||
);
|
||||
|
||||
const addressMsgs = test.map((adr36Info) => {
|
||||
console.log(adr36Info);
|
||||
|
||||
return makeCosmwasmExecMsg(
|
||||
key.bech32Address,
|
||||
"osmo1hnjg39mu9r9tygy6zpypd25rhmh9726jm369uh7z0r6gjkmnk5zs3tdtug",
|
||||
{
|
||||
set_record: {
|
||||
name: newTwitterAuthInfo.username,
|
||||
bech32_prefix: adr36Info.bech32Prefix,
|
||||
adr36_info: {
|
||||
signer_bech32_address: adr36Info.bech32Address,
|
||||
address_hash: adr36Info.addressHash,
|
||||
pub_key: Buffer.from(adr36Info.pubKey).toString("base64"),
|
||||
signature: Buffer.from(adr36Info.signature).toString(
|
||||
"base64",
|
||||
),
|
||||
signature_salt: adr36Info.signatureSalt.toString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
});
|
||||
|
||||
const aminoMsgs = [registerMsg.amino];
|
||||
const protoMsgs = [registerMsg.proto];
|
||||
for (const addressMsg of addressMsgs) {
|
||||
aminoMsgs.push(addressMsg.amino);
|
||||
protoMsgs.push(addressMsg.proto);
|
||||
}
|
||||
|
||||
const chainInfo = {
|
||||
chainId: mainChainId,
|
||||
rest: "https://lcd.testnet.osmosis.zone",
|
||||
};
|
||||
|
||||
const simulated = await simulateMsgs(
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
{
|
||||
proto: protoMsgs,
|
||||
},
|
||||
{
|
||||
amount: [],
|
||||
},
|
||||
);
|
||||
|
||||
const txHash = await sendMsgs(
|
||||
wallet,
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
{
|
||||
amino: aminoMsgs,
|
||||
proto: protoMsgs,
|
||||
},
|
||||
{
|
||||
amount: [],
|
||||
gas: Math.floor(simulated.gasUsed * 1.5).toString(),
|
||||
},
|
||||
);
|
||||
|
||||
const txTracer = new TendermintTxTracer(
|
||||
"https://rpc.testnet.osmosis.zone",
|
||||
"/websocket",
|
||||
);
|
||||
|
||||
const result = await txTracer.traceTx(txHash);
|
||||
|
||||
console.log(result);
|
||||
}
|
||||
})();
|
||||
|
||||
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 keplr = await getKeplrFromWindow();
|
||||
|
||||
if (keplr) {
|
||||
const keplrWallet = new KeplrWallet(keplr);
|
||||
|
||||
await fetchChainList(keplrWallet);
|
||||
setWallet(keplrWallet);
|
||||
|
||||
return keplrWallet;
|
||||
} else {
|
||||
ErrorHandler(KEPLR_NOT_FOUND_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
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 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;
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
setChainList(filteredChainList);
|
||||
};
|
||||
|
||||
const checkAdr36 = async () => {
|
||||
if (twitterAuthInfo && wallet) {
|
||||
const key = await wallet.getKey(MainChainId);
|
||||
|
||||
const chainIds = Array.from(checkedItems).map((chain) => {
|
||||
return (chain as ChainItemType).chainId;
|
||||
});
|
||||
|
||||
return wallet.signICNSAdr36(
|
||||
MainChainId,
|
||||
RESOLVER_ADDRESS,
|
||||
key.bech32Address,
|
||||
twitterAuthInfo.username,
|
||||
chainIds,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickRegistration = async () => {
|
||||
try {
|
||||
const { state, code } = checkTwitterAuthQueryParameter(
|
||||
window.location.search,
|
||||
);
|
||||
const twitterInfo = await fetchTwitterInfo(state, code);
|
||||
|
||||
const adr36Infos = await checkAdr36();
|
||||
|
||||
if (wallet && adr36Infos) {
|
||||
const key = await wallet.getKey(MainChainId);
|
||||
|
||||
const icnsVerificationList = await verifyTwitterAccount(
|
||||
key.bech32Address,
|
||||
twitterInfo.accessToken,
|
||||
);
|
||||
|
||||
const registerMsg = makeClaimMessage(
|
||||
key.bech32Address,
|
||||
twitterInfo.username,
|
||||
icnsVerificationList,
|
||||
localStorage.getItem(REFERRAL_KEY) ?? undefined,
|
||||
);
|
||||
|
||||
const addressMsgs = adr36Infos.map((adr36Info) => {
|
||||
return makeSetRecordMessage(
|
||||
key.bech32Address,
|
||||
twitterInfo.username,
|
||||
adr36Info,
|
||||
);
|
||||
});
|
||||
|
||||
const aminoMsgs = twitterAuthInfo?.isRegistered
|
||||
? []
|
||||
: [registerMsg.amino];
|
||||
const protoMsgs = twitterAuthInfo?.isRegistered
|
||||
? []
|
||||
: [registerMsg.proto];
|
||||
|
||||
for (const addressMsg of addressMsgs) {
|
||||
aminoMsgs.push(addressMsg.amino);
|
||||
protoMsgs.push(addressMsg.proto);
|
||||
}
|
||||
|
||||
const chainInfo = {
|
||||
chainId: MainChainId,
|
||||
rest: REST_URL,
|
||||
};
|
||||
|
||||
const simulated = await simulateMsgs(
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
{
|
||||
proto: protoMsgs,
|
||||
},
|
||||
{
|
||||
amount: [],
|
||||
},
|
||||
);
|
||||
|
||||
const txHash = await sendMsgs(
|
||||
wallet,
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
{
|
||||
amino: aminoMsgs,
|
||||
proto: protoMsgs,
|
||||
},
|
||||
{
|
||||
amount: [],
|
||||
gas: Math.floor(simulated.gasUsed * 1.5).toString(),
|
||||
},
|
||||
);
|
||||
|
||||
await router.push({
|
||||
pathname: "complete",
|
||||
query: {
|
||||
txHash: Buffer.from(txHash).toString("hex"),
|
||||
twitterUsername: twitterInfo.username,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
if (Axios.isAxiosError(error)) {
|
||||
console.error((error?.response?.data as QueryError).message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const isRegisterButtonDisable = (() => {
|
||||
const hasCheckedItem = checkedItems.size > 0;
|
||||
|
||||
if (isOwner) {
|
||||
return !hasCheckedItem;
|
||||
} else {
|
||||
return !(isAgree && hasCheckedItem);
|
||||
}
|
||||
})();
|
||||
handleVerification();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@@ -363,124 +207,18 @@ export default function VerificationPage() {
|
||||
<SkeletonChainList />
|
||||
) : (
|
||||
<ContentContainer>
|
||||
<BackButton />
|
||||
<div>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (twitterAuthInfo && wallet) {
|
||||
const key = await wallet.getKey(MainChainId);
|
||||
|
||||
const removeMessages = registeredChainList.map((chain) => {
|
||||
return makeRemoveRecordMessage(
|
||||
twitterAuthInfo.username,
|
||||
key.bech32Address,
|
||||
chain.address,
|
||||
);
|
||||
});
|
||||
|
||||
const aminoMsgs = [];
|
||||
const protoMsgs = [];
|
||||
|
||||
for (const msg of removeMessages) {
|
||||
aminoMsgs.push(msg.amino);
|
||||
protoMsgs.push(msg.proto);
|
||||
}
|
||||
|
||||
console.log(aminoMsgs);
|
||||
|
||||
const chainInfo = {
|
||||
chainId: MainChainId,
|
||||
rest: REST_URL,
|
||||
};
|
||||
|
||||
const simulated = await simulateMsgs(
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
{
|
||||
proto: protoMsgs,
|
||||
},
|
||||
{
|
||||
amount: [],
|
||||
},
|
||||
);
|
||||
|
||||
const txHash = await sendMsgs(
|
||||
wallet,
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
{
|
||||
amino: aminoMsgs,
|
||||
proto: protoMsgs,
|
||||
},
|
||||
{
|
||||
amount: [],
|
||||
gas: Math.floor(simulated.gasUsed * 1.5).toString(),
|
||||
},
|
||||
);
|
||||
|
||||
console.log(txHash);
|
||||
}
|
||||
}}
|
||||
>
|
||||
All Remove
|
||||
</button>
|
||||
</div>
|
||||
<TwitterProfile twitterProfileInformation={twitterAuthInfo} />
|
||||
|
||||
<ChainListTitleContainer>
|
||||
<ChainListTitle>Chain List</ChainListTitle>
|
||||
<SearchInput
|
||||
searchValue={searchValue}
|
||||
setSearchValue={setSearchValue}
|
||||
/>
|
||||
<SearchContainer>Search</SearchContainer>
|
||||
</ChainListTitleContainer>
|
||||
|
||||
{!searchValue ? (
|
||||
<AllChainsItem
|
||||
chainList={chainList}
|
||||
checkedItems={checkedItems}
|
||||
setCheckedItems={setCheckedItems}
|
||||
/>
|
||||
) : null}
|
||||
<ChainList chainList={AccountInfos} />
|
||||
|
||||
<ChainList
|
||||
chainList={chainList.filter(
|
||||
(chain) =>
|
||||
chain.chainId.includes(searchValue) ||
|
||||
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}
|
||||
/>
|
||||
|
||||
{!isOwner && (
|
||||
<AgreeContainer
|
||||
onClick={() => {
|
||||
setIsAgree(!isAgree);
|
||||
}}
|
||||
>
|
||||
<AgreeCheckBox type="checkbox" checked={isAgree} readOnly />I
|
||||
check that Osmo is required for this transaction
|
||||
</AgreeContainer>
|
||||
)}
|
||||
|
||||
{chainList.length > 0 && (
|
||||
<ButtonContainer disabled={isRegisterButtonDisable}>
|
||||
<PrimaryButton
|
||||
disabled={isRegisterButtonDisable}
|
||||
onClick={onClickRegistration}
|
||||
>
|
||||
Register
|
||||
</PrimaryButton>
|
||||
</ButtonContainer>
|
||||
)}
|
||||
<ButtonContainer>
|
||||
<PrimaryButton>Register</PrimaryButton>
|
||||
</ButtonContainer>
|
||||
</ContentContainer>
|
||||
)}
|
||||
</MainContainer>
|
||||
@@ -497,18 +235,7 @@ const MainContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
height: 100vh;
|
||||
|
||||
padding: 2.7rem 0;
|
||||
|
||||
color: white;
|
||||
|
||||
background-image: url("/images/svg/bg-asset-3.svg"),
|
||||
url("/images/svg/bg-asset-3.svg"), url("/images/svg/bg-asset-3.svg"),
|
||||
url("/images/svg/bg-asset-3.svg");
|
||||
background-size: 3.125rem 3.125rem;
|
||||
background-position: 296px 536px, 1256px 216px, 376px 776px, 1176px 856px;
|
||||
background-repeat: no-repeat;
|
||||
`;
|
||||
|
||||
export const ContentContainer = styled.div`
|
||||
@@ -517,14 +244,15 @@ export const ContentContainer = styled.div`
|
||||
align-items: center;
|
||||
|
||||
width: 40rem;
|
||||
|
||||
margin-top: 5rem;
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div<{ disabled?: boolean }>`
|
||||
width: 11rem;
|
||||
height: 3.5rem;
|
||||
export const ButtonContainer = styled.div`
|
||||
width: 12rem;
|
||||
height: 4rem;
|
||||
|
||||
background-color: ${(props) =>
|
||||
props.disabled ? color.orange["300"] : color.orange["100"]};
|
||||
margin-top: 2rem;
|
||||
`;
|
||||
|
||||
export const ChainListTitleContainer = styled.div`
|
||||
@@ -547,30 +275,14 @@ const ChainListTitle = styled.div`
|
||||
color: ${color.white};
|
||||
`;
|
||||
|
||||
const AgreeContainer = styled.div`
|
||||
const SearchContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
line-height: 0.8rem;
|
||||
border-radius: 3rem;
|
||||
|
||||
text-transform: uppercase;
|
||||
user-select: none;
|
||||
min-width: 10rem;
|
||||
height: 2rem;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
|
||||
padding: 2rem 0;
|
||||
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const AgreeCheckBox = styled.input.attrs({ type: "checkbox" })`
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
|
||||
accent-color: ${color.orange["200"]};
|
||||
background-color: ${color.grey["700"]};
|
||||
`;
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "\/android-icon-36x36.png",
|
||||
"sizes": "36x36",
|
||||
"type": "image\/png",
|
||||
"density": "0.75"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image\/png",
|
||||
"density": "1.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image\/png",
|
||||
"density": "1.5"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image\/png",
|
||||
"density": "2.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image\/png",
|
||||
"density": "3.0"
|
||||
},
|
||||
{
|
||||
"src": "\/android-icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image\/png",
|
||||
"density": "4.0"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21 12C21 7.03125 16.9688 3 12 3C7.03125 3 3 7.03125 3 12C3 16.9688 7.03125 21 12 21C16.9688 21 21 16.9688 21 12Z" stroke="#64646D" stroke-width="1.5" stroke-miterlimit="10"/>
|
||||
<path d="M11.0007 8.03664L11.803 13.2223C11.838 13.4482 12.1634 13.4482 12.1983 13.2223L12.999 8.04535C13.0051 7.90953 12.9835 7.77388 12.9355 7.64669C12.8875 7.5195 12.814 7.40344 12.7196 7.3056C12.6252 7.20777 12.5118 7.13021 12.3864 7.07767C12.261 7.02513 12.1262 6.99872 11.9902 7.00005C11.8567 7.00135 11.7247 7.02941 11.6021 7.08256C11.4796 7.13571 11.3689 7.21287 11.2767 7.3095C11.1844 7.40612 11.1125 7.52025 11.0651 7.64514C11.0177 7.77003 10.9958 7.90315 11.0007 8.03664Z" fill="#64646D" stroke="#64646D" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 17C11.8022 17 11.6089 16.9414 11.4444 16.8315C11.28 16.7216 11.1518 16.5654 11.0761 16.3827C11.0004 16.2 10.9806 15.9989 11.0192 15.8049C11.0578 15.6109 11.153 15.4327 11.2929 15.2929C11.4327 15.153 11.6109 15.0578 11.8049 15.0192C11.9989 14.9806 12.2 15.0004 12.3827 15.0761C12.5654 15.1518 12.7216 15.28 12.8315 15.4444C12.9414 15.6089 13 15.8022 13 16C13 16.2652 12.8947 16.5196 12.7071 16.7071C12.5196 16.8946 12.2652 17 12 17Z" fill="#64646D"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 646 KiB |
@@ -1,3 +0,0 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.57812 14.0625L3.51562 9L8.57812 3.9375M4.21875 9H14.4844" stroke="#5B5B5B" stroke-width="1.5" stroke-linecap="square"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 235 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0C-9.27438e-07 10.6087 4.21427 20.7828 11.7157 28.2843C19.2172 35.7857 29.3913 40 40 40C50.6087 40 60.7828 35.7857 68.2843 28.2843C75.7857 20.7828 80 10.6087 80 1.30337e-05L0 0Z" fill="#242424"/>
|
||||
<path d="M0 40C-9.27438e-07 50.6087 4.21427 60.7828 11.7157 68.2843C19.2172 75.7857 29.3913 80 40 80C50.6087 80 60.7828 75.7857 68.2843 68.2843C75.7857 60.7828 80 50.6087 80 40L0 40Z" fill="#242424"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 512 B |
@@ -1,4 +0,0 @@
|
||||
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 80C10.6087 80 20.7828 75.7857 28.2843 68.2843C35.7857 60.7828 40 50.6087 40 40C40 29.3913 35.7857 19.2172 28.2843 11.7157C20.7828 4.21428 10.6087 -4.6372e-07 9.53674e-06 0L0 80Z" fill="#242424"/>
|
||||
<path d="M40 80C50.6087 80 60.7828 75.7857 68.2843 68.2843C75.7857 60.7828 80 50.6087 80 40C80 29.3913 75.7857 19.2172 68.2843 11.7157C60.7828 4.21428 50.6087 -4.6372e-07 40 0L40 80Z" fill="#242424"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 512 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M25 0L25.3504 2.74704C26.8096 14.1848 35.8152 23.1904 47.253 24.6496L50 25L47.253 25.3504C35.8152 26.8096 26.8096 35.8152 25.3504 47.253L25 50L24.6496 47.253C23.1904 35.8152 14.1848 26.8096 2.74704 25.3504L0 25L2.74704 24.6496C14.1848 23.1904 23.1904 14.1848 24.6496 2.74704L25 0Z" fill="#2B2B2B"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 411 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.08358 3.5C5.9967 3.5 3.5 5.93947 3.5 8.95559C3.5 11.9717 5.9967 14.4112 9.08358 14.4112C10.1857 14.4112 11.2056 14.0953 12.0704 13.5587L16.1041 17.5L17.5 16.1361L13.5176 12.255C14.2335 11.3374 14.6672 10.2004 14.6672 8.95559C14.6672 5.93947 12.1705 3.5 9.08358 3.5ZM9.08358 4.78367C11.4481 4.78367 13.3534 6.64524 13.3534 8.95559C13.3534 11.2659 11.4481 13.1275 9.08358 13.1275C6.71902 13.1275 4.81378 11.2659 4.81378 8.95559C4.81378 6.64524 6.71902 4.78367 9.08358 4.78367Z" fill="#5B5B5B"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 608 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="28" height="29" viewBox="0 0 28 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M24.3502 7.84668C23.5894 8.18363 22.7712 8.40845 21.9081 8.51483C22.7856 7.99388 23.4606 7.163 23.7775 6.18148C22.9564 6.66275 22.0467 7.01638 21.079 7.20268C20.3062 6.3833 19.2039 5.875 17.9826 5.875C15.6366 5.875 13.7351 7.75985 13.7351 10.084C13.7351 10.4129 13.7736 10.7332 13.8461 11.0443C10.3167 10.8672 7.18641 9.1893 5.09226 6.6432C4.72426 7.26535 4.51726 7.99388 4.51726 8.76265C4.51726 10.2237 5.26534 11.51 6.40614 12.2679C5.70981 12.2466 5.05431 12.0528 4.48046 11.7423C4.48046 11.755 4.48046 11.7739 4.48046 11.7918C4.48046 13.8336 5.94499 15.5339 7.88619 15.9208C7.53141 16.018 7.15651 16.0721 6.77011 16.0721C6.49584 16.0721 6.22846 16.041 5.96971 15.9944C6.51021 17.6631 8.07824 18.8827 9.93606 18.9189C8.48246 20.0459 6.65166 20.7209 4.66101 20.7209C4.31716 20.7209 3.98079 20.7014 3.64844 20.6611C5.52926 21.852 7.76256 22.55 10.1597 22.55C17.9711 22.55 22.2445 16.1353 22.2445 10.5705C22.2445 10.3882 22.2388 10.2065 22.2301 10.0271C23.0639 9.43828 23.7826 8.69538 24.3502 7.84668Z" fill="#5081FF"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,66 +0,0 @@
|
||||
import { request } from "../utils/url";
|
||||
import {
|
||||
NAME_NFT_ADDRESS,
|
||||
REGISTRAR_ADDRESS,
|
||||
RESOLVER_ADDRESS,
|
||||
REST_URL,
|
||||
} from "../constants/icns";
|
||||
import { Buffer } from "buffer/";
|
||||
import {
|
||||
AddressesQueryResponse,
|
||||
NameByTwitterIdQueryResponse,
|
||||
OwnerOfQueryResponse,
|
||||
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"),
|
||||
),
|
||||
{},
|
||||
true,
|
||||
);
|
||||
};
|
||||
|
||||
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 queryOwnerOfTwitterName = async (
|
||||
twitterUsername: string,
|
||||
): Promise<OwnerOfQueryResponse> => {
|
||||
const msg = {
|
||||
owner_of: { token_id: twitterUsername },
|
||||
};
|
||||
|
||||
return request<OwnerOfQueryResponse>(
|
||||
getCosmwasmQueryUrl(
|
||||
NAME_NFT_ADDRESS,
|
||||
Buffer.from(JSON.stringify(msg)).toString("base64"),
|
||||
),
|
||||
{},
|
||||
true,
|
||||
);
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from "./twitter";
|
||||
export * from "./icns";
|
||||
@@ -1,41 +0,0 @@
|
||||
import {
|
||||
IcnsVerificationResponse,
|
||||
TwitterAuthInfoResponse,
|
||||
TwitterAuthUrlResponse,
|
||||
} from "../types";
|
||||
import { request } from "../utils/url";
|
||||
|
||||
export const loginWithTwitter = async () => {
|
||||
const { authUrl } = await request<TwitterAuthUrlResponse>(
|
||||
"/api/twitter-auth-url",
|
||||
);
|
||||
|
||||
window.location.href = authUrl;
|
||||
};
|
||||
|
||||
export const fetchTwitterInfo = async (
|
||||
state: string,
|
||||
code: string,
|
||||
): Promise<TwitterAuthInfoResponse> => {
|
||||
return await request<TwitterAuthInfoResponse>(
|
||||
`/api/twitter-auth-info?state=${state}&code=${code}`,
|
||||
);
|
||||
};
|
||||
|
||||
export const verifyTwitterAccount = async (
|
||||
claimer: string,
|
||||
accessToken: string,
|
||||
) => {
|
||||
return (
|
||||
await request<IcnsVerificationResponse>("/api/icns-verification", {
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
claimer: claimer,
|
||||
authToken: accessToken,
|
||||
}),
|
||||
})
|
||||
).verificationList;
|
||||
};
|
||||
@@ -6,8 +6,6 @@ const orange = {
|
||||
400: "#442924",
|
||||
};
|
||||
|
||||
const blue = "#536EF8";
|
||||
|
||||
const white = "#FCFCFC";
|
||||
const grey = {
|
||||
100: "#A3A3A3",
|
||||
@@ -16,8 +14,8 @@ const grey = {
|
||||
400: "#5B5B5B",
|
||||
500: "#424242",
|
||||
600: "#333333",
|
||||
700: "#222222",
|
||||
800: "#1D1D1D",
|
||||
700: "#2B2B2B",
|
||||
800: "#242424",
|
||||
900: "#181818",
|
||||
};
|
||||
const black = "#121212";
|
||||
@@ -27,7 +25,6 @@ const color = {
|
||||
white,
|
||||
black,
|
||||
orange,
|
||||
blue,
|
||||
};
|
||||
|
||||
export default color;
|
||||
|
||||
@@ -8,19 +8,20 @@ export const GlobalStyle = createGlobalStyle`
|
||||
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-size: 16px;
|
||||
|
||||
background-color: ${({ theme }) => theme.bgColor ?? color.black};
|
||||
|
||||
background-color: ${color.black};
|
||||
|
||||
background-size: 5rem 5rem;
|
||||
background-position: top left;
|
||||
background-repeat: repeat;
|
||||
background-image: linear-gradient(${({ theme }) =>
|
||||
theme.bgGridColor ?? color.grey[600]} 0.1rem,
|
||||
transparent 0.1rem),
|
||||
linear-gradient(90deg, ${({ theme }) =>
|
||||
theme.bgGridColor ?? color.grey[600]} 0.1rem, transparent 0.1rem);
|
||||
background-image: linear-gradient(
|
||||
rgba(51, 51, 51, 1) 0.1rem,
|
||||
transparent 0.1rem
|
||||
),
|
||||
linear-gradient(90deg, rgba(51, 51, 51, 1) 0.1rem, transparent 0.1rem);
|
||||
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import "styled-components";
|
||||
|
||||
declare module "styled-components" {
|
||||
export interface DefaultTheme {
|
||||
bgColor: string;
|
||||
bgGridColor: string;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface AccountInfo {
|
||||
prefix: string;
|
||||
chainImageUrl: string;
|
||||
address: string;
|
||||
}
|
||||
@@ -37,31 +37,3 @@ export interface IcnsVerificationResponse {
|
||||
}
|
||||
)[];
|
||||
}
|
||||
|
||||
export interface NameByTwitterIdQueryResponse {
|
||||
data: {
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AddressesQueryResponse {
|
||||
data: {
|
||||
addresses: RegisteredAddresses[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface RegisteredAddresses {
|
||||
address: string;
|
||||
bech32_prefix: string;
|
||||
}
|
||||
|
||||
export interface QueryError {
|
||||
code: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface OwnerOfQueryResponse {
|
||||
data: {
|
||||
owner: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export interface ChainItemType {
|
||||
chainId: string;
|
||||
chainName: string;
|
||||
prefix: string;
|
||||
chainImageUrl: string;
|
||||
address: string;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { Any } from "@keplr-wallet/proto-types/google/protobuf/any";
|
||||
|
||||
export interface CosmwasmExecuteMessageResult {
|
||||
amino: {
|
||||
readonly type: string;
|
||||
readonly value: any;
|
||||
};
|
||||
proto: Any;
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
export * from "./width-height-props";
|
||||
export * from "./api-response";
|
||||
export * from "./chain-item-type";
|
||||
export * from "./account-info";
|
||||
export * from "./msg";
|
||||
export * from "./twitter";
|
||||
export * from "./icns";
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { TwitterAuthInfoResponse } from "./api-response";
|
||||
|
||||
export interface TwitterProfileType extends TwitterAuthInfoResponse {
|
||||
isRegistered: boolean;
|
||||
}
|
||||
|
||||
export interface TwitterLoginSuccess {
|
||||
code: string;
|
||||
state: string;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export const ErrorHandler = (message: string) => {
|
||||
console.error(message);
|
||||
};
|
||||
@@ -1,21 +1,9 @@
|
||||
import { TwitterLoginSuccess } from "../types";
|
||||
import { TWITTER_LOGIN_ERROR } from "../constants/error-message";
|
||||
import { WALLET_INSTALL_URL } from "../constants/wallet";
|
||||
|
||||
export function request<TResponse>(
|
||||
url: string,
|
||||
config: RequestInit = {},
|
||||
isIgnore?: boolean,
|
||||
): Promise<TResponse> {
|
||||
return fetch(url, config)
|
||||
.then((response) => {
|
||||
if (!response.ok && !isIgnore) {
|
||||
throw new Error(
|
||||
`This is an HTTP error: The status is ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => data as TResponse);
|
||||
}
|
||||
|
||||
@@ -28,25 +16,3 @@ export function buildQueryString(query: Record<string, any>): string {
|
||||
)
|
||||
.join("&");
|
||||
}
|
||||
|
||||
export const checkTwitterAuthQueryParameter = (
|
||||
query: string,
|
||||
): TwitterLoginSuccess => {
|
||||
// Twitter Login Error Check
|
||||
if (query.match("error")) {
|
||||
throw new Error(TWITTER_LOGIN_ERROR);
|
||||
}
|
||||
|
||||
// Twitter state, auth code check
|
||||
const [, state, code] =
|
||||
query.match(/^(?=.*state=([^&]+)|)(?=.*code=([^&]+)|).+$/) || [];
|
||||
|
||||
return {
|
||||
state,
|
||||
code,
|
||||
};
|
||||
};
|
||||
|
||||
export const replaceToInstallPage = () => {
|
||||
window.location.href = WALLET_INSTALL_URL;
|
||||
};
|
||||
|
||||
@@ -15,7 +15,6 @@ import { Any } from "@keplr-wallet/proto-types/google/protobuf/any";
|
||||
import { PubKey } from "@keplr-wallet/proto-types/cosmos/crypto/secp256k1/keys";
|
||||
import { SignMode } from "@keplr-wallet/proto-types/cosmos/tx/signing/v1beta1/signing";
|
||||
import { MsgExecuteContract } from "@keplr-wallet/proto-types/cosmwasm/wasm/v1/tx";
|
||||
import { CosmwasmExecuteMessageResult } from "../types";
|
||||
|
||||
export async function sendMsgs(
|
||||
wallet: Wallet,
|
||||
@@ -190,7 +189,13 @@ export function makeCosmwasmExecMsg(
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
obj: object,
|
||||
funds: { readonly amount: string; readonly denom: string }[],
|
||||
): CosmwasmExecuteMessageResult {
|
||||
): {
|
||||
amino: {
|
||||
readonly type: string;
|
||||
readonly value: any;
|
||||
};
|
||||
proto: Any;
|
||||
} {
|
||||
const amino = {
|
||||
type: "wasm/MsgExecuteContract",
|
||||
value: {
|
||||
|
||||
@@ -2,7 +2,5 @@ import { Window as KeplrWindow } from "@keplr-wallet/types";
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface Window extends KeplrWindow {
|
||||
cosmostation: any;
|
||||
}
|
||||
interface Window extends KeplrWindow {}
|
||||
}
|
||||
|
||||
@@ -1109,7 +1109,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/semver@npm:^7, @types/semver@npm:^7.3.12":
|
||||
"@types/semver@npm:^7.3.12":
|
||||
version: 7.3.13
|
||||
resolution: "@types/semver@npm:7.3.13"
|
||||
checksum: 00c0724d54757c2f4bc60b5032fe91cda6410e48689633d5f35ece8a0a66445e3e57fa1d6e07eb780f792e82ac542948ec4d0b76eb3484297b79bd18b8cf1cb0
|
||||
@@ -3175,7 +3175,6 @@ __metadata:
|
||||
"@types/react": 18.0.25
|
||||
"@types/react-dom": 18.0.9
|
||||
"@types/react-modal": ^3
|
||||
"@types/semver": ^7
|
||||
"@types/styled-components": ^5
|
||||
"@typescript-eslint/eslint-plugin": ^5.45.0
|
||||
"@typescript-eslint/parser": ^5.45.0
|
||||
@@ -3199,8 +3198,6 @@ __metadata:
|
||||
react-dom: 18.2.0
|
||||
react-is: ^18.2.0
|
||||
react-modal: ^3.16.1
|
||||
react-typed: ^1.2.0
|
||||
semver: ^7.3.8
|
||||
styled-components: ^5.3.6
|
||||
typescript: 4.9.3
|
||||
languageName: unknown
|
||||
@@ -4577,7 +4574,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prop-types@npm:^15.6.0, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
|
||||
"prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
|
||||
version: 15.8.1
|
||||
resolution: "prop-types@npm:15.8.1"
|
||||
dependencies:
|
||||
@@ -4699,19 +4696,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-typed@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "react-typed@npm:1.2.0"
|
||||
dependencies:
|
||||
prop-types: ^15.6.0
|
||||
typed.js: ^2.0.6
|
||||
peerDependencies:
|
||||
react: ^16.3.0
|
||||
react-dom: ^16.3.0
|
||||
checksum: c750d68710f918904b9bf07ea253e51d3447f9533c8277bd0afcdd20725b9e42e52ddcccd63ce93b4d0eb40db14573ccc82fce27b568b72a250d2167fc375b6f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react@npm:18.2.0":
|
||||
version: 18.2.0
|
||||
resolution: "react@npm:18.2.0"
|
||||
@@ -5497,13 +5481,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typed.js@npm:^2.0.6":
|
||||
version: 2.0.12
|
||||
resolution: "typed.js@npm:2.0.12"
|
||||
checksum: ea753bf8e01fd6e37390f8d02de58cc084e7b11620e937e75a6cad58829dd55a9273954191529cddad58ab102479f097db4587c6790ba4ff2dabc5d4fb6192d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typeforce@npm:^1.11.5":
|
||||
version: 1.18.0
|
||||
resolution: "typeforce@npm:1.18.0"
|
||||
|
||||