forked from LaconicNetwork/icns-frontend
Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28a44a9195 | ||
|
|
6a49b69a02 | ||
|
|
53ecf825a0 | ||
|
|
5d48012133 | ||
|
|
5fac2f9156 | ||
|
|
3de363af0f | ||
|
|
eead90d4b9 | ||
|
|
a61856557c | ||
|
|
7e72ab82b6 | ||
|
|
68d8ba31aa | ||
|
|
e0151666b2 | ||
|
|
b41ef751fb | ||
|
|
e884fc1d86 | ||
|
|
1abc475354 | ||
|
|
d488d31345 | ||
|
|
80907781ad |
+3
-1
@@ -49,4 +49,6 @@ build
|
||||
.sentryclirc
|
||||
|
||||
# Intelij files
|
||||
.idea
|
||||
.idea
|
||||
# Sentry
|
||||
.sentryclirc
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import Image, { ImageProps } from "next/image";
|
||||
|
||||
import KeplrIcon from "../../public/images/svg/keplr-icon.svg";
|
||||
import ICNSLogo from "../../public/images/icns-logo-120x120.png";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const ChainImage = ({ src, ...props }: ImageProps) => {
|
||||
@@ -14,7 +14,7 @@ export const ChainImage = ({ src, ...props }: ImageProps) => {
|
||||
src={srcState}
|
||||
alt="chain image"
|
||||
sizes="3rem"
|
||||
onError={() => setSrcState(KeplrIcon)}
|
||||
onError={() => setSrcState(ICNSLogo)}
|
||||
/>
|
||||
</ImageWrapper>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ChainItemType } from "../../types";
|
||||
import { ChainItemType, DisabledChainItemType } from "../../types";
|
||||
import { FunctionComponent, useEffect, useState } from "react";
|
||||
|
||||
import color from "../../styles/color";
|
||||
@@ -8,15 +8,31 @@ import { ChainImage } from "./chain-image";
|
||||
import { Checkbox } from "../checkbox";
|
||||
|
||||
interface Props {
|
||||
chainItem: ChainItemType;
|
||||
chainItem: ChainItemType | DisabledChainItemType;
|
||||
checkedItemHandler: (chainItem: ChainItemType, isChecked: boolean) => void;
|
||||
checkedItems: Set<unknown>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const ChainItem: FunctionComponent<Props> = (props) => {
|
||||
const { chainItem, checkedItemHandler, checkedItems, disabled } = props;
|
||||
const [checked, setChecked] = useState(!!disabled);
|
||||
const { chainItem, checkedItemHandler, checkedItems } = props;
|
||||
const disabled = "disabled" in chainItem && chainItem.disabled;
|
||||
// XXX: Currently, this component can't handle `checked` state well,
|
||||
// If chain is disabled, it should be disabled in general.
|
||||
// However, if it is disabled due to the limitation of ethermint and ledger,
|
||||
// it should be not checked.
|
||||
// To solve this problem, for now, just use dumb way.
|
||||
// If chain is disabled with explicit reason, it should be unchecked.
|
||||
const [checked, setChecked] = useState(!!disabled && !chainItem.reason);
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
if (chainItem.reason) {
|
||||
setChecked(false);
|
||||
} else {
|
||||
setChecked(true);
|
||||
}
|
||||
}
|
||||
}, [chainItem, disabled]);
|
||||
|
||||
const checkHandler = () => {
|
||||
if (!disabled) {
|
||||
@@ -46,7 +62,12 @@ export const ChainItem: FunctionComponent<Props> = (props) => {
|
||||
/>
|
||||
<ChainInfoContainer>
|
||||
<ChainName>{`.${chainItem.prefix}`}</ChainName>
|
||||
<WalletAddress>{chainItem.address}</WalletAddress>
|
||||
{chainItem.address ? (
|
||||
<WalletAddress>{chainItem.address}</WalletAddress>
|
||||
) : null}
|
||||
{disabled && chainItem.reason ? (
|
||||
<DisabledReason>{chainItem.reason.message}</DisabledReason>
|
||||
) : null}
|
||||
</ChainInfoContainer>
|
||||
|
||||
<Flex1 />
|
||||
@@ -117,3 +138,10 @@ export const WalletAddress = styled.div`
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
|
||||
export const DisabledReason = styled.div`
|
||||
color: ${color.grey["200"]};
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
`;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Dispatch, FunctionComponent, SetStateAction, useEffect } from "react";
|
||||
import { ChainItemType } from "../../types";
|
||||
import { ChainItemType, DisabledChainItemType } from "../../types";
|
||||
import color from "../../styles/color";
|
||||
import styled from "styled-components";
|
||||
import { ChainItem } from "./chain-item";
|
||||
|
||||
interface Props {
|
||||
chainList: ChainItemType[];
|
||||
disabledChainList: ChainItemType[];
|
||||
disabledChainList: DisabledChainItemType[];
|
||||
checkedItems: Set<unknown>;
|
||||
setCheckedItems: Dispatch<SetStateAction<Set<unknown>>>;
|
||||
}
|
||||
@@ -30,7 +30,7 @@ export const ChainList: FunctionComponent<Props> = (props) => {
|
||||
<ChainContainer color={color.grey["900"]}>
|
||||
{chainList.map((chainItem) => (
|
||||
<ChainItem
|
||||
key={chainItem.address}
|
||||
key={chainItem.chainId}
|
||||
chainItem={chainItem}
|
||||
checkedItemHandler={checkedItemHandler}
|
||||
checkedItems={checkedItems}
|
||||
@@ -38,11 +38,10 @@ export const ChainList: FunctionComponent<Props> = (props) => {
|
||||
))}
|
||||
{disabledChainList.map((chainItem) => (
|
||||
<ChainItem
|
||||
key={chainItem.address}
|
||||
key={chainItem.chainId}
|
||||
chainItem={chainItem}
|
||||
checkedItemHandler={checkedItemHandler}
|
||||
checkedItems={checkedItems}
|
||||
disabled={true}
|
||||
/>
|
||||
))}
|
||||
</ChainContainer>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { captureException } from "@sentry/nextjs";
|
||||
import Link from "next/link";
|
||||
import React, { Component, ErrorInfo, ReactNode } from "react";
|
||||
import { Component, ErrorInfo, ReactNode } from "react";
|
||||
|
||||
interface Props {
|
||||
children?: ReactNode;
|
||||
@@ -21,6 +22,8 @@ class ErrorBoundary extends Component<Props, State> {
|
||||
|
||||
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error("Uncaught error:", error, errorInfo);
|
||||
|
||||
captureException(error);
|
||||
}
|
||||
|
||||
public render() {
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
import { FunctionComponent } from "react";
|
||||
import color from "../../styles/color";
|
||||
import ReactModal from "react-modal";
|
||||
import styled from "styled-components";
|
||||
import TwitterIcon from "../../public/images/svg/twitter-modal-icon.svg";
|
||||
import Image from "next/image";
|
||||
import { PrimaryButton } from "../primary-button";
|
||||
import { SecondaryButton } from "../secondary-button";
|
||||
import { MINIMUM_OSMO_FEE } from "../../constants/wallet";
|
||||
import { useRouter } from "next/router";
|
||||
import { Bech32Address } from "@keplr-wallet/cosmos";
|
||||
|
||||
interface Props {
|
||||
twitterUserName: string | undefined;
|
||||
walletInfo:
|
||||
| { name: string; pubKey: Uint8Array; bech32Address: string }
|
||||
| undefined;
|
||||
isModalOpen: boolean;
|
||||
onCloseModal: () => void;
|
||||
onClickRegisterButton: () => Promise<void>;
|
||||
isLoadingRegistration?: boolean;
|
||||
}
|
||||
|
||||
export const FinalCheckModal: FunctionComponent<Props> = (props) => {
|
||||
const {
|
||||
twitterUserName,
|
||||
walletInfo,
|
||||
isModalOpen,
|
||||
onCloseModal,
|
||||
onClickRegisterButton,
|
||||
isLoadingRegistration,
|
||||
} = props;
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={onCloseModal}
|
||||
ariaHideApp={false}
|
||||
style={{
|
||||
overlay: { background: "#121212cc" },
|
||||
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>Final Checks</ModalTitle>
|
||||
<ModalDescription>{`You are claiming the ICNS name ${twitterUserName} on main keplr account`}</ModalDescription>
|
||||
|
||||
<NameBox
|
||||
marginTop="3.875rem"
|
||||
icon={
|
||||
<Image
|
||||
src={require("../../public/images/icons/twitter-small.png")}
|
||||
alt="twitter"
|
||||
width={24}
|
||||
height={24}
|
||||
style={{
|
||||
marginRight: "4px",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
title="Your Twitter ID"
|
||||
content={`@${twitterUserName}`}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "3.625rem",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="48"
|
||||
height="48"
|
||||
fill="none"
|
||||
viewBox="0 0 48 48"
|
||||
>
|
||||
<path
|
||||
stroke="#EBEBEB"
|
||||
strokeLinecap="round"
|
||||
strokeWidth="2"
|
||||
d="M18.44 19.008h-2.656a5.162 5.162 0 00-5.162 5.162v0a5.162 5.162 0 005.162 5.162h2.655M29.305 28.988h2.655a5.162 5.162 0 005.162-5.162v0a5.162 5.162 0 00-5.162-5.162h-2.655M19.295 24.242h9.155"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<NameBox
|
||||
marginTop="0"
|
||||
icon={
|
||||
<Image
|
||||
src={require("../../public/images/icons/keplr-small.png")}
|
||||
alt="twitter"
|
||||
width={20}
|
||||
height={20}
|
||||
style={{
|
||||
marginRight: "8px",
|
||||
}}
|
||||
/>
|
||||
}
|
||||
title="Main Keplr Account"
|
||||
content={Bech32Address.shortenAddress(
|
||||
walletInfo?.bech32Address || "",
|
||||
28,
|
||||
)}
|
||||
/>
|
||||
|
||||
<SubTextsContainer>
|
||||
<SubText>
|
||||
☑️ ICNS name can only be claimed once per Twitter account.
|
||||
<br />
|
||||
☑️ ICNS name can’t be transferred at this time.
|
||||
<br />
|
||||
☑️ Please make sure you’ve selected the right account on your
|
||||
wallet.
|
||||
</SubText>
|
||||
<br />
|
||||
<SubText>
|
||||
<SubBoldText>{MINIMUM_OSMO_FEE}</SubBoldText> will be spent as a
|
||||
spam-prevention fee.
|
||||
</SubText>
|
||||
</SubTextsContainer>
|
||||
|
||||
<ButtonContainer>
|
||||
<RegisterButton>
|
||||
<PrimaryButton
|
||||
onClick={onClickRegisterButton}
|
||||
isLoading={isLoadingRegistration}
|
||||
>
|
||||
Register
|
||||
</PrimaryButton>
|
||||
</RegisterButton>
|
||||
<CancelButton>
|
||||
<SecondaryButton
|
||||
onClick={async () => {
|
||||
await router.push("/");
|
||||
}}
|
||||
>
|
||||
Use a different account
|
||||
</SecondaryButton>
|
||||
</CancelButton>
|
||||
</ButtonContainer>
|
||||
</ModalContainer>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
const ModalContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
max-width: 43.5rem;
|
||||
|
||||
padding: 2rem 2.25rem;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
`;
|
||||
|
||||
const ModalTitle = styled.div`
|
||||
font-weight: 600;
|
||||
font-size: 1.625rem;
|
||||
line-height: 1.94rem;
|
||||
|
||||
color: ${color.white};
|
||||
|
||||
margin-bottom: 1.75rem;
|
||||
`;
|
||||
|
||||
const ModalDescription = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 1.18rem;
|
||||
|
||||
color: ${color.grey["100"]};
|
||||
`;
|
||||
|
||||
const SubTextsContainer = styled.div`
|
||||
margin-top: 1.75rem;
|
||||
|
||||
padding: 2rem 1.5rem;
|
||||
|
||||
background-color: ${color.grey["700"]};
|
||||
`;
|
||||
|
||||
const SubText = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
|
||||
color: ${color.grey["300"]};
|
||||
`;
|
||||
|
||||
const SubBoldText = styled.span`
|
||||
color: ${color.grey["100"]};
|
||||
`;
|
||||
|
||||
const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
align-items: center;
|
||||
|
||||
margin-top: 1.75rem;
|
||||
`;
|
||||
|
||||
const RegisterButton = styled.div`
|
||||
width: 60%;
|
||||
height: 4.125rem;
|
||||
`;
|
||||
|
||||
const CancelButton = styled.div`
|
||||
width: 80%;
|
||||
height: 3.8rem;
|
||||
`;
|
||||
|
||||
const NameBox: FunctionComponent<{
|
||||
title: string;
|
||||
content: string;
|
||||
|
||||
icon?: React.ReactElement;
|
||||
|
||||
marginTop: string;
|
||||
}> = ({ icon, title, content, marginTop }) => {
|
||||
return (
|
||||
<NameBoxContainer
|
||||
style={{
|
||||
marginTop,
|
||||
}}
|
||||
>
|
||||
<NameBoxTitleContainer>
|
||||
{icon ? <NameBoxIconContainer>{icon}</NameBoxIconContainer> : null}
|
||||
<NameBoxTitle>{title}</NameBoxTitle>
|
||||
</NameBoxTitleContainer>
|
||||
<NameBoxContentContainer>{content}</NameBoxContentContainer>
|
||||
</NameBoxContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const NameBoxContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
`;
|
||||
|
||||
const NameBoxTitleContainer = styled.div`
|
||||
position: absolute;
|
||||
top: -1.9rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const NameBoxIconContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
height: 1px;
|
||||
`;
|
||||
|
||||
const NameBoxTitle = styled.div`
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
line-height: 1.18rem;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
|
||||
const NameBoxContentContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
padding: 2.125rem 0;
|
||||
|
||||
background-color ${color.grey["700"]};
|
||||
border: 1px solid ${color.grey["300"]};
|
||||
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.81rem;
|
||||
|
||||
color: ${color.white}
|
||||
`;
|
||||
@@ -1,35 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
|
||||
export const PrimaryButton = styled.button`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
border: none;
|
||||
|
||||
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;
|
||||
|
||||
color: ${color.orange["50"]};
|
||||
background-color: ${color.orange["100"]};
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transition-duration: 0.5s;
|
||||
background-color: ${color.orange["200"]};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
|
||||
background-color: ${color.orange["300"]};
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,112 @@
|
||||
import { ButtonHTMLAttributes, FunctionComponent } from "react";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
|
||||
interface PrimaryButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export const PrimaryButton: FunctionComponent<PrimaryButtonProps> = ({
|
||||
children,
|
||||
isLoading,
|
||||
disabled,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<StyledPrimaryButton {...props} disabled={disabled || isLoading}>
|
||||
{isLoading ? (
|
||||
<SpinnerWrapper>
|
||||
<Spinner />
|
||||
<Spinner />
|
||||
<Spinner />
|
||||
<Spinner />
|
||||
</SpinnerWrapper>
|
||||
) : (
|
||||
<span>{children}</span>
|
||||
)}
|
||||
</StyledPrimaryButton>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledPrimaryButton = styled.button`
|
||||
display: flex;
|
||||
align-items center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
border: none;
|
||||
|
||||
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;
|
||||
|
||||
background-color: ${color.orange["100"]};
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transition-duration: 0.5s;
|
||||
background-color: ${color.orange["200"]};
|
||||
|
||||
span {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: ${color.orange["300"]};
|
||||
cursor: not-allowed;
|
||||
|
||||
span {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
transition-duration: 0.5s;
|
||||
color: ${color.orange["50"]};
|
||||
}
|
||||
`;
|
||||
|
||||
const SpinnerWrapper = styled.div`
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`;
|
||||
|
||||
const spinAnimation = keyframes`
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`;
|
||||
|
||||
const Spinner = styled.div<{ animationDelay?: string }>`
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
animation: ${spinAnimation} 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||
${({ animationDelay }) =>
|
||||
animationDelay ? `animation-delay: ${animationDelay};` : ""}
|
||||
|
||||
border-radius: 100%;
|
||||
border-style: solid;
|
||||
border-width: 3px;
|
||||
border-color: white transparent transparent transparent;
|
||||
`;
|
||||
@@ -0,0 +1,24 @@
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
|
||||
export const SecondaryButton = styled.button`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
border: none;
|
||||
|
||||
padding: 11px 30px;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
line-height: 1.025rem;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
|
||||
color: ${color.grey["200"]};
|
||||
background-color: transparent;
|
||||
|
||||
cursor: pointer;
|
||||
`;
|
||||
@@ -107,8 +107,6 @@ const SkeletonButton = styled.div`
|
||||
width: 12rem;
|
||||
height: 4rem;
|
||||
|
||||
padding-top: 1.5rem;
|
||||
|
||||
background-color: ${color.grey["800"]};
|
||||
`;
|
||||
|
||||
|
||||
@@ -70,21 +70,3 @@ export const makeSetRecordMessage = (
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
export const makeRemoveRecordMessage = (
|
||||
twitterUserName: string,
|
||||
senderAddress: string,
|
||||
removeAddress: string,
|
||||
): CosmwasmExecuteMessageResult => {
|
||||
return makeCosmwasmExecMsg(
|
||||
senderAddress,
|
||||
RESOLVER_ADDRESS,
|
||||
{
|
||||
remove_record: {
|
||||
name: twitterUserName,
|
||||
bech32_address: removeAddress,
|
||||
},
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
+14
-2
@@ -1,5 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { withSentryConfig } = require("@sentry/nextjs");
|
||||
|
||||
const nextConfig = {
|
||||
sentry: {
|
||||
hideSourceMaps: true,
|
||||
},
|
||||
reactStrictMode: false,
|
||||
swcMinify: true,
|
||||
compiler: {
|
||||
@@ -23,4 +28,11 @@ const nextConfig = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
const sentryWebpackPluginOptions = {
|
||||
silent: true,
|
||||
};
|
||||
|
||||
module.exports =
|
||||
process.env.NEXT_IS_ENABLE_USER_TRACKING === "true"
|
||||
? withSentryConfig(nextConfig, sentryWebpackPluginOptions)
|
||||
: nextConfig;
|
||||
|
||||
+6
-4
@@ -10,10 +10,12 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@keplr-wallet/common": "^0.11.23",
|
||||
"@keplr-wallet/cosmos": "^0.11.23",
|
||||
"@keplr-wallet/proto-types": "^0.11.23",
|
||||
"@keplr-wallet/types": "^0.11.23",
|
||||
"@amplitude/analytics-browser": "^1.6.6",
|
||||
"@keplr-wallet/common": "^0.11.25",
|
||||
"@keplr-wallet/cosmos": "^0.11.25",
|
||||
"@keplr-wallet/proto-types": "^0.11.25",
|
||||
"@keplr-wallet/types": "^0.11.25",
|
||||
"@sentry/nextjs": "^7.27.0",
|
||||
"axios": "^0.27.2",
|
||||
"buffer": "^6.0.3",
|
||||
"crypto": "^1.0.1",
|
||||
|
||||
+25
-1
@@ -1,7 +1,8 @@
|
||||
import * as amplitude from "@amplitude/analytics-browser";
|
||||
import type { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useMemo } from "react";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { DefaultTheme, ThemeProvider } from "styled-components";
|
||||
import ErrorBoundary from "../components/error-boundary";
|
||||
|
||||
@@ -17,6 +18,10 @@ const defaultPageTheme: DefaultTheme = {
|
||||
bgGridColor: "rgba(51, 51, 51, 0.3)",
|
||||
};
|
||||
|
||||
if (process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY !== undefined) {
|
||||
amplitude.init(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY);
|
||||
}
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -26,6 +31,25 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
|
||||
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
||||
|
||||
useEffect(() => {
|
||||
const handleRouteChangeComplete = (url: string) => {
|
||||
const pathname = url.split("?")[0];
|
||||
|
||||
amplitude.track("view page", {
|
||||
pathname,
|
||||
});
|
||||
};
|
||||
|
||||
handleRouteChangeComplete(router.pathname);
|
||||
router.events.on("routeChangeStart", handleRouteChangeComplete);
|
||||
|
||||
// If the component is unmounted, unsubscribe
|
||||
// from the event with the `off` method:
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", handleRouteChangeComplete);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={pageTheme}>
|
||||
<Head>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { flush } from "@sentry/nextjs";
|
||||
import { NextPageContext } from "next";
|
||||
import NextErrorComponent, { ErrorProps as NextErrorProps } from "next/error";
|
||||
|
||||
type ErrorProps = NextErrorProps & {
|
||||
hasGetInitialPropsRun: boolean;
|
||||
err: any;
|
||||
};
|
||||
|
||||
const MyError = ({ statusCode, hasGetInitialPropsRun, err }: ErrorProps) => {
|
||||
if (!hasGetInitialPropsRun && err) {
|
||||
// getInitialProps is not called in case of
|
||||
// https://github.com/vercel/next.js/issues/8592. As a workaround, we pass
|
||||
// err via _app.js so it can be captured
|
||||
}
|
||||
|
||||
return <NextErrorComponent statusCode={statusCode} />;
|
||||
};
|
||||
|
||||
MyError.getInitialProps = async (context: NextPageContext) => {
|
||||
const errorInitialProps = (await NextErrorComponent.getInitialProps(
|
||||
context,
|
||||
)) as ErrorProps;
|
||||
|
||||
const { res, err } = context;
|
||||
|
||||
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when
|
||||
// getInitialProps has run
|
||||
errorInitialProps.hasGetInitialPropsRun = true;
|
||||
|
||||
// Returning early because we don't want to log 404 errors to
|
||||
if (res?.statusCode === 404) {
|
||||
return errorInitialProps;
|
||||
}
|
||||
|
||||
// Running on the server, the response object (`res`) is available.
|
||||
//
|
||||
// Next.js will pass an err on the server if a page's data fetching methods
|
||||
// threw or returned a Promise that rejected
|
||||
//
|
||||
// Running on the client (browser), Next.js will provide an err if:
|
||||
//
|
||||
// - a page's `getInitialProps` threw or returned a Promise that rejected
|
||||
// - an exception was thrown somewhere in the React lifecycle (render,
|
||||
// componentDidMount, etc) that was caught by Next.js's React Error
|
||||
// Boundary. Read more about what types of exceptions are caught by Error
|
||||
// Boundaries: https://reactjs.org/docs/error-boundaries.html
|
||||
|
||||
if (err) {
|
||||
// Flushing before returning is necessary if deploying to Vercel, see
|
||||
// https://vercel.com/docs/platform/limits#streaming-responses
|
||||
await flush(2000);
|
||||
|
||||
return errorInitialProps;
|
||||
}
|
||||
|
||||
// If this point is reached, getInitialProps was called without any
|
||||
// information about what the error might be. This is unexpected and may
|
||||
// indicate a bug introduced in Next.js, so record it in Sentry
|
||||
|
||||
await flush(2000);
|
||||
|
||||
return errorInitialProps;
|
||||
};
|
||||
|
||||
export default MyError;
|
||||
@@ -1,3 +1,4 @@
|
||||
import * as amplitude from "@amplitude/analytics-browser";
|
||||
import Image from "next/image";
|
||||
import styled from "styled-components";
|
||||
|
||||
@@ -44,6 +45,8 @@ export default function CompletePage() {
|
||||
);
|
||||
|
||||
if (!result.code || result.code === 0) {
|
||||
amplitude.track("complete registration");
|
||||
|
||||
const addresses = await queryAddressesFromTwitterName(twitterUserName);
|
||||
setRegisteredAddressed(addresses.data.addresses);
|
||||
setIsSuccess(true);
|
||||
@@ -54,6 +57,8 @@ export default function CompletePage() {
|
||||
};
|
||||
|
||||
const onClickShareButton = () => {
|
||||
amplitude.track("click share button");
|
||||
|
||||
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 ▶`;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as amplitude from "@amplitude/analytics-browser";
|
||||
|
||||
// NextJs
|
||||
import Image from "next/image";
|
||||
|
||||
@@ -24,6 +26,8 @@ export default function Home() {
|
||||
const [isModalOpen, setModalOpen] = useState(false);
|
||||
|
||||
const onClickConnectWalletButton = async () => {
|
||||
amplitude.track("click connect wallet button");
|
||||
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
|
||||
+149
-143
@@ -1,9 +1,12 @@
|
||||
import * as amplitude from "@amplitude/analytics-browser";
|
||||
|
||||
// React
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
// Types
|
||||
import {
|
||||
ChainItemType,
|
||||
DisabledChainItemType,
|
||||
QueryError,
|
||||
RegisteredAddresses,
|
||||
TwitterProfileType,
|
||||
@@ -51,26 +54,34 @@ import {
|
||||
KEPLR_NOT_FOUND_ERROR,
|
||||
TWITTER_LOGIN_ERROR,
|
||||
} from "../../constants/error-message";
|
||||
import {
|
||||
makeClaimMessage,
|
||||
makeRemoveRecordMessage,
|
||||
makeSetRecordMessage,
|
||||
} from "../../messages";
|
||||
import { makeClaimMessage, makeSetRecordMessage } from "../../messages";
|
||||
import Axios from "axios";
|
||||
import { BackButton } from "../../components/back-button";
|
||||
import { FinalCheckModal } from "../../components/final-check-modal";
|
||||
|
||||
export default function VerificationPage() {
|
||||
const router = useRouter();
|
||||
const [twitterAuthInfo, setTwitterAuthInfo] = useState<TwitterProfileType>();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isLoadingInit, setIsLoadingInit] = useState(true);
|
||||
const [isLoadingRegistration, setIsLoadingRegistration] = useState(false);
|
||||
|
||||
const [wallet, setWallet] = useState<KeplrWallet>();
|
||||
const [walletKey, setWalletKey] = useState<{
|
||||
name: string;
|
||||
pubKey: Uint8Array;
|
||||
bech32Address: string;
|
||||
isLedgerNano?: boolean;
|
||||
}>();
|
||||
|
||||
const [chainList, setChainList] = useState<ChainItemType[]>([]);
|
||||
const [disabledChainList, setDisabledChainList] = useState<ChainItemType[]>(
|
||||
[],
|
||||
);
|
||||
const [chainList, setChainList] = useState<
|
||||
(ChainItemType & {
|
||||
isEthermintLike?: boolean;
|
||||
})[]
|
||||
>([]);
|
||||
const [disabledChainList, setDisabledChainList] = useState<
|
||||
DisabledChainItemType[]
|
||||
>([]);
|
||||
const [registeredChainList, setRegisteredChainList] = useState<
|
||||
RegisteredAddresses[]
|
||||
>([]);
|
||||
@@ -79,7 +90,9 @@ export default function VerificationPage() {
|
||||
const [searchValue, setSearchValue] = useState("");
|
||||
|
||||
const [isOwner, setIsOwner] = useState(false);
|
||||
const [isAgree, setIsAgree] = useState(false);
|
||||
// const [isAgree, setIsAgree] = useState(false);
|
||||
|
||||
const [isModalOpen, setModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
init();
|
||||
@@ -94,23 +107,51 @@ export default function VerificationPage() {
|
||||
}, [wallet]);
|
||||
|
||||
useEffect(() => {
|
||||
const disabledChainList = chainList.filter((chain) => {
|
||||
for (const registeredChain of registeredChainList) {
|
||||
if (
|
||||
chain.prefix === registeredChain.bech32_prefix &&
|
||||
chain.address === registeredChain.address
|
||||
) {
|
||||
const disabledChainList = chainList
|
||||
.filter((chain) => {
|
||||
if (!chain.address) {
|
||||
// Address can be "" if `getKey` failed.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
for (const registeredChain of registeredChainList) {
|
||||
if (
|
||||
chain.prefix === registeredChain.bech32_prefix &&
|
||||
chain.address === registeredChain.address
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.map<DisabledChainItemType>((chain) => {
|
||||
if (walletKey) {
|
||||
if (walletKey.isLedgerNano && chain.isEthermintLike) {
|
||||
return {
|
||||
...chain,
|
||||
disabled: true,
|
||||
reason: new Error(
|
||||
"Support for Ethereum address on Ledger is coming soon.",
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...chain,
|
||||
disabled: true,
|
||||
};
|
||||
});
|
||||
|
||||
const filteredChainList = chainList.filter((chain) => {
|
||||
return (
|
||||
disabledChainList.find(
|
||||
(disabled) => disabled.chainId === chain.chainId,
|
||||
) == null
|
||||
);
|
||||
});
|
||||
|
||||
const filteredChainList = chainList.filter(
|
||||
(chain) => !disabledChainList.includes(chain),
|
||||
);
|
||||
|
||||
setChainList(filteredChainList);
|
||||
setDisabledChainList(disabledChainList);
|
||||
|
||||
@@ -149,15 +190,15 @@ export default function VerificationPage() {
|
||||
registeredQueryResponse.data.name,
|
||||
);
|
||||
|
||||
const addressesQueryResponse = await queryAddressesFromTwitterName(
|
||||
registeredQueryResponse.data.name,
|
||||
);
|
||||
|
||||
if (keplrWallet) {
|
||||
const key = await keplrWallet.getKey(MainChainId);
|
||||
setIsOwner(ownerOfQueryResponse.data.owner === key.bech32Address);
|
||||
}
|
||||
|
||||
const addressesQueryResponse = await queryAddressesFromTwitterName(
|
||||
registeredQueryResponse.data.name,
|
||||
);
|
||||
|
||||
setRegisteredChainList(addressesQueryResponse.data.addresses);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -167,7 +208,7 @@ export default function VerificationPage() {
|
||||
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setIsLoadingInit(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -177,9 +218,11 @@ export default function VerificationPage() {
|
||||
|
||||
if (keplr) {
|
||||
const keplrWallet = new KeplrWallet(keplr);
|
||||
const key = await keplrWallet.getKey(MainChainId);
|
||||
|
||||
await fetchChainList(keplrWallet);
|
||||
setWallet(keplrWallet);
|
||||
setWalletKey(key);
|
||||
|
||||
return keplrWallet;
|
||||
} else {
|
||||
@@ -191,7 +234,7 @@ export default function VerificationPage() {
|
||||
const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map(
|
||||
(c) => c.chainId,
|
||||
);
|
||||
const chainKeys = await Promise.all(
|
||||
const chainKeys = await Promise.allSettled(
|
||||
chainIds.map((chainId) => wallet.getKey(chainId)),
|
||||
);
|
||||
|
||||
@@ -204,14 +247,20 @@ export default function VerificationPage() {
|
||||
chainImageUrl: `https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/${
|
||||
ChainIdHelper.parse(chainInfo.chainId).identifier
|
||||
}/chain.png`,
|
||||
isEthermintLike: chainInfo.isEthermintLike,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const chainArray = [];
|
||||
for (let i = 0; i < chainKeys.length; i++) {
|
||||
const chainKey = chainKeys[i];
|
||||
if (chainKey.status !== "fulfilled") {
|
||||
console.log("Failed to get key from wallet", chainKey);
|
||||
}
|
||||
chainArray.push({
|
||||
address: chainKeys[i].bech32Address,
|
||||
address:
|
||||
chainKey.status === "fulfilled" ? chainKey.value.bech32Address : "",
|
||||
...chainInfos[i],
|
||||
});
|
||||
}
|
||||
@@ -255,8 +304,20 @@ export default function VerificationPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const onClickRegistration = async () => {
|
||||
const onClickRegistration = () => {
|
||||
amplitude.track("click register button");
|
||||
|
||||
if (isOwner) {
|
||||
handleRegistration();
|
||||
} else {
|
||||
setModalOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRegistration = async () => {
|
||||
try {
|
||||
setIsLoadingRegistration(true);
|
||||
|
||||
const { state, code } = checkTwitterAuthQueryParameter(
|
||||
window.location.search,
|
||||
);
|
||||
@@ -264,16 +325,14 @@ export default function VerificationPage() {
|
||||
|
||||
const adr36Infos = await checkAdr36();
|
||||
|
||||
if (wallet && adr36Infos) {
|
||||
const key = await wallet.getKey(MainChainId);
|
||||
|
||||
if (wallet && walletKey && adr36Infos) {
|
||||
const icnsVerificationList = await verifyTwitterAccount(
|
||||
key.bech32Address,
|
||||
walletKey.bech32Address,
|
||||
twitterInfo.accessToken,
|
||||
);
|
||||
|
||||
const registerMsg = makeClaimMessage(
|
||||
key.bech32Address,
|
||||
walletKey.bech32Address,
|
||||
twitterInfo.username,
|
||||
icnsVerificationList,
|
||||
localStorage.getItem(REFERRAL_KEY) ?? undefined,
|
||||
@@ -281,7 +340,7 @@ export default function VerificationPage() {
|
||||
|
||||
const addressMsgs = adr36Infos.map((adr36Info) => {
|
||||
return makeSetRecordMessage(
|
||||
key.bech32Address,
|
||||
walletKey.bech32Address,
|
||||
twitterInfo.username,
|
||||
adr36Info,
|
||||
);
|
||||
@@ -306,7 +365,7 @@ export default function VerificationPage() {
|
||||
|
||||
const simulated = await simulateMsgs(
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
walletKey.bech32Address,
|
||||
{
|
||||
proto: protoMsgs,
|
||||
},
|
||||
@@ -318,7 +377,7 @@ export default function VerificationPage() {
|
||||
const txHash = await sendMsgs(
|
||||
wallet,
|
||||
chainInfo,
|
||||
key.bech32Address,
|
||||
walletKey.bech32Address,
|
||||
{
|
||||
amino: aminoMsgs,
|
||||
proto: protoMsgs,
|
||||
@@ -341,17 +400,15 @@ export default function VerificationPage() {
|
||||
if (Axios.isAxiosError(error)) {
|
||||
console.error((error?.response?.data as QueryError).message);
|
||||
}
|
||||
} finally {
|
||||
setIsLoadingRegistration(false);
|
||||
}
|
||||
};
|
||||
|
||||
const isRegisterButtonDisable = (() => {
|
||||
const hasCheckedItem = checkedItems.size > 0;
|
||||
|
||||
if (isOwner) {
|
||||
return !hasCheckedItem;
|
||||
} else {
|
||||
return !(isAgree && hasCheckedItem);
|
||||
}
|
||||
return !hasCheckedItem;
|
||||
})();
|
||||
|
||||
return (
|
||||
@@ -359,72 +416,11 @@ export default function VerificationPage() {
|
||||
<Logo />
|
||||
|
||||
<MainContainer>
|
||||
{isLoading ? (
|
||||
{isLoadingInit ? (
|
||||
<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>
|
||||
@@ -460,22 +456,21 @@ export default function VerificationPage() {
|
||||
setCheckedItems={setCheckedItems}
|
||||
/>
|
||||
|
||||
{!isOwner && (
|
||||
<AgreeContainer
|
||||
onClick={() => {
|
||||
setIsAgree(!isAgree);
|
||||
}}
|
||||
>
|
||||
<AgreeCheckBox type="checkbox" checked={isAgree} readOnly />I
|
||||
check that Osmo is required for this transaction
|
||||
</AgreeContainer>
|
||||
)}
|
||||
{/*<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}
|
||||
isLoading={isLoadingRegistration}
|
||||
>
|
||||
Register
|
||||
</PrimaryButton>
|
||||
@@ -484,6 +479,15 @@ export default function VerificationPage() {
|
||||
</ContentContainer>
|
||||
)}
|
||||
</MainContainer>
|
||||
|
||||
<FinalCheckModal
|
||||
twitterUserName={twitterAuthInfo?.username}
|
||||
walletInfo={walletKey}
|
||||
isModalOpen={isModalOpen}
|
||||
onCloseModal={() => setModalOpen(false)}
|
||||
onClickRegisterButton={handleRegistration}
|
||||
isLoadingRegistration={isLoadingRegistration}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -523,6 +527,8 @@ export const ButtonContainer = styled.div<{ disabled?: boolean }>`
|
||||
width: 11rem;
|
||||
height: 3.5rem;
|
||||
|
||||
margin-top: 1.5rem;
|
||||
|
||||
background-color: ${(props) =>
|
||||
props.disabled ? color.orange["300"] : color.orange["100"]};
|
||||
`;
|
||||
@@ -547,30 +553,30 @@ const ChainListTitle = styled.div`
|
||||
color: ${color.white};
|
||||
`;
|
||||
|
||||
const AgreeContainer = 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;
|
||||
|
||||
text-transform: uppercase;
|
||||
user-select: none;
|
||||
|
||||
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"]};
|
||||
`;
|
||||
// const AgreeContainer = 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;
|
||||
//
|
||||
// text-transform: uppercase;
|
||||
// user-select: none;
|
||||
//
|
||||
// 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"]};
|
||||
// `;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 912 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="32" height="33" viewBox="0 0 32 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M28 8.786C27.118 9.17667 26.1694 9.43733 25.1687 9.56067C26.186 8.95667 26.9687 7.99333 27.336 6.85533C26.384 7.41333 25.3294 7.82333 24.2074 8.03933C23.3114 7.08933 22.0334 6.5 20.6174 6.5C17.8974 6.5 15.6927 8.68533 15.6927 11.38C15.6927 11.7613 15.7374 12.1327 15.8214 12.4933C11.7294 12.288 8.10005 10.3427 5.67205 7.39067C5.24538 8.112 5.00538 8.95667 5.00538 9.848C5.00538 11.542 5.87271 13.0333 7.19538 13.912C6.38805 13.8873 5.62805 13.6627 4.96271 13.3027C4.96271 13.3173 4.96271 13.3393 4.96271 13.36C4.96271 15.7273 6.66071 17.6987 8.91138 18.1473C8.50005 18.26 8.06538 18.3227 7.61738 18.3227C7.29938 18.3227 6.98938 18.2867 6.68938 18.2327C7.31605 20.1673 9.13405 21.5813 11.288 21.6233C9.60271 22.93 7.48005 23.7127 5.17205 23.7127C4.77338 23.7127 4.38338 23.69 3.99805 23.6433C6.17871 25.024 8.76805 25.8333 11.5474 25.8333C20.604 25.8333 25.5587 18.396 25.5587 11.944C25.5587 11.7327 25.552 11.522 25.542 11.314C26.5087 10.6313 27.342 9.77 28 8.786Z" fill="#03A9F4"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,28 @@
|
||||
// This file configures the initialization of Sentry on the browser.
|
||||
// The config you add here will be used whenever a page is visited.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
const IS_ENABLE_USER_TRACKING = process.env.NEXT_PUBLIC_IS_ENABLE_USER_TRACKING;
|
||||
|
||||
Sentry.init({
|
||||
enabled: IS_ENABLE_USER_TRACKING === "true",
|
||||
dsn:
|
||||
SENTRY_DSN ||
|
||||
"https://78c91641e90f4f7cad28f50aaec9fb95@o4504343701946368.ingest.sentry.io/4504343708827648",
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1.0,
|
||||
// ...
|
||||
// Note: if you want to override the automatic release value, do not set a
|
||||
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
|
||||
// that it will also get attached to your source maps
|
||||
|
||||
denyUrls: [
|
||||
// deny all chrome extension
|
||||
"chrome-extension://",
|
||||
// deny all firefox extension
|
||||
"moz-extension://",
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
defaults.url=https://sentry.io/
|
||||
defaults.org=interchain-name-service
|
||||
defaults.project=icns-frontend
|
||||
@@ -0,0 +1,23 @@
|
||||
// This file configures the initialization of Sentry on the server.
|
||||
// The config you add here will be used whenever the server handles a request.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
||||
const IS_ENABLE_USER_TRACKING =
|
||||
process.env.NEXT_PUBLIC_IS_ENABLE_USER_TRACKING ||
|
||||
process.env.NEXT_IS_ENABLE_USER_TRACKING;
|
||||
|
||||
Sentry.init({
|
||||
enabled: IS_ENABLE_USER_TRACKING === "true",
|
||||
dsn:
|
||||
SENTRY_DSN ||
|
||||
"https://78c91641e90f4f7cad28f50aaec9fb95@o4504343701946368.ingest.sentry.io/4504343708827648",
|
||||
// Adjust this value in production, or use tracesSampler for greater control
|
||||
tracesSampleRate: 1.0,
|
||||
// ...
|
||||
// Note: if you want to override the automatic release value, do not set a
|
||||
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
|
||||
// that it will also get attached to your source maps
|
||||
});
|
||||
@@ -5,3 +5,10 @@ export interface ChainItemType {
|
||||
chainImageUrl: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export interface DisabledChainItemType extends ChainItemType {
|
||||
disabled: true;
|
||||
|
||||
// Show reason why this chain is disabled to user if needed.
|
||||
reason?: Error;
|
||||
}
|
||||
|
||||
+19
-6
@@ -45,18 +45,32 @@ export class KeplrWallet implements Wallet {
|
||||
}
|
||||
|
||||
getChainInfosWithoutEndpoints(): Promise<
|
||||
Omit<ChainInfo, "rest" | "rpc" | "nodeProvider">[]
|
||||
(Pick<ChainInfo, "chainId" | "chainName" | "bech32Config"> & {
|
||||
readonly isEthermintLike?: boolean;
|
||||
})[]
|
||||
> {
|
||||
// TODO: Update @keplr-wallet/types
|
||||
return (this.keplr as any).getChainInfosWithoutEndpoints();
|
||||
return this.keplr.getChainInfosWithoutEndpoints().then((chainInfos) => {
|
||||
return chainInfos.map((chainInfo) => {
|
||||
return {
|
||||
...chainInfo,
|
||||
isEthermintLike: chainInfo.features?.includes("eth-address-gen"),
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getKey(chainId: string): Promise<{
|
||||
readonly name: string;
|
||||
readonly pubKey: Uint8Array;
|
||||
readonly bech32Address: string;
|
||||
readonly isLedgerNano?: boolean;
|
||||
}> {
|
||||
return this.keplr.getKey(chainId);
|
||||
return this.keplr.getKey(chainId).then((key) => {
|
||||
return {
|
||||
...key,
|
||||
isLedgerNano: key.isNanoLedger,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
init(chainIds: string[]): Promise<void> {
|
||||
@@ -88,8 +102,7 @@ export class KeplrWallet implements Wallet {
|
||||
signature: Uint8Array;
|
||||
}[]
|
||||
> {
|
||||
// TODO: Update @keplr-wallet/types
|
||||
return (this.keplr as any).signICNSAdr36(
|
||||
return this.keplr.signICNSAdr36(
|
||||
chainId,
|
||||
contractAddress,
|
||||
owner,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user