Compare commits

...
Author SHA1 Message Date
Thunnini cc2f59cd02 Add environment variable "NEXT_PUBLIC_CHAIN_ALLOWLIST" 2022-12-19 16:14:40 +09:00
Thunnini 26c95bfbe5 Revert "Add block list"
This reverts commit 40e3522c2a.
2022-12-19 16:00:10 +09:00
Thunnini dfaf78f9d3 Minor styling fix 2022-12-19 00:49:45 +09:00
HeesungB 40e3522c2a Add block list 2022-12-19 00:32:35 +09:00
HeesungB 279a372052 Hide logo when window size width under 1280px 2022-12-19 00:00:47 +09:00
HeesungB adbc02ee8b Add error handle modal 2022-12-18 23:58:48 +09:00
Thunnini 061f2a2bed Fix a bug that nft owner check prevents the unregistered user 2022-12-18 23:35:49 +09:00
Thunnini d50f491b1b Minor fix 2022-12-18 23:22:34 +09:00
delivan 6f3fe64125 Merge branch 'main' of https://github.com/interchain-name/icns-frontend into main 2022-12-18 23:11:30 +09:00
delivan 13a5c37226 Update style of learn more link 2022-12-18 23:11:18 +09:00
Thunnini 53e3ec81ee Merge branch 'Thunnini/owner-check' 2022-12-18 23:10:34 +09:00
Thunnini da89c51b4c Show warning message if the account is not owner of name 2022-12-18 23:10:23 +09:00
Thunnini 68dfe40ee5 Merge branch 'Thunnini/final-modal-redegisn' 2022-12-18 22:47:52 +09:00
Thunnini 28a44a9195 Redesign FinalCheckModal component 2022-12-18 22:47:24 +09:00
delivan d23090f9f8 Update style of home page 2022-12-18 22:14:10 +09:00
delivan 6eb3eb885c Update background color 2022-12-18 21:58:39 +09:00
Thunnini 6a49b69a02 Merge branch 'Thunnini/ledger-ethereum-warning' 2022-12-18 21:40:12 +09:00
Thunnini 53ecf825a0 Minor fix 2022-12-18 21:30:37 +09:00
Thunnini 5d48012133 Add DisabledChainItemType type and prevent registration from ethermint chain with ledger 2022-12-18 21:01:34 +09:00
delivan 5fac2f9156 Add loading state to register button on final check modal 2022-12-18 20:24:33 +09:00
Thunnini 3de363af0f Merge branch 'main' into Thunnini/ledger-ethereum-warning
# Conflicts:
#	.pnp.cjs
#	package.json
#	yarn.lock
2022-12-18 19:42:16 +09:00
delivan eead90d4b9 Change empty chain image to icns logo 2022-12-18 18:45:49 +09:00
delivan a61856557c Add loading state on primary button 2022-12-18 18:38:56 +09:00
delivan 7e72ab82b6 Update primary button style 2022-12-18 17:59:29 +09:00
HeesungB 68d8ba31aa Add final check modal 2022-12-18 01:51:29 +09:00
delivan e0151666b2 Merge branch 'main' of https://github.com/interchain-name/icns-frontend into main 2022-12-17 23:46:02 +09:00
delivan b41ef751fb Track registration flow 2022-12-17 23:45:50 +09:00
delivan e884fc1d86 Add Amplitude 2022-12-17 22:40:07 +09:00
delivan 1abc475354 Add Sentry 2022-12-17 20:42:56 +09:00
Thunnini d488d31345 Change typing of interface Wallet 2022-12-17 20:24:17 +09:00
Thunnini 80907781ad Bump @keplr-wallet/* 2022-12-17 20:22:03 +09:00
107 changed files with 2574 additions and 255 deletions
+3 -1
View File
@@ -49,4 +49,6 @@ build
.sentryclirc
# Intelij files
.idea
.idea
# Sentry
.sentryclirc
Generated
+776 -33
View File
File diff suppressed because it is too large Load Diff
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.
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.
+2 -2
View File
@@ -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>
);
+34 -6
View File
@@ -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;
`;
+5 -6
View File
@@ -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>
@@ -54,7 +53,7 @@ export const ChainContainer = styled.div`
flex-direction: column;
width: 100%;
//max-height: 33rem;
overflow: scroll;
overflow-y: scroll;
flex: 1;
+4 -1
View File
@@ -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() {
+151
View File
@@ -0,0 +1,151 @@
import color from "../../styles/color";
import { FunctionComponent } from "react";
import ReactModal from "react-modal";
import { ErrorMessage } from "../../types";
import styled from "styled-components";
import Image from "next/image";
interface Props {
isModalOpen: boolean;
onCloseModal: () => void;
errorMessage?: ErrorMessage;
}
import ErrorIcon from "../../public/images/svg/error-icon.svg";
import ArrowLeftIcon from "../../public/images/svg/arrow-left.svg";
import { useRouter } from "next/router";
export const ErrorModal: FunctionComponent<Props> = (props) => {
const router = useRouter();
const { isModalOpen, onCloseModal, errorMessage } = props;
const onClose = async () => {
if (errorMessage?.path) {
await router.push(errorMessage.path);
}
onCloseModal();
};
return (
<ReactModal
isOpen={isModalOpen}
onRequestClose={onClose}
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>
<ErrorTitleContainer>
<ErrorImageContainer>
<Image
src={ErrorIcon}
fill={true}
sizes="2.5rem"
alt="error icon"
/>
</ErrorImageContainer>
Error
</ErrorTitleContainer>
<ErrorDescription>{errorMessage?.message}</ErrorDescription>
{errorMessage?.path ? (
<ErrorBackButton onClick={onClose}>
<ErrorBackIconContainer>
<Image
src={ArrowLeftIcon}
fill={true}
sizes="1.5rem"
alt="back button icon"
/>
</ErrorBackIconContainer>
GO BACK TO HOME
</ErrorBackButton>
) : null}
</ModalContainer>
</ReactModal>
);
};
const ModalContainer = styled.div`
display: flex;
flex-direction: column;
width: 32rem;
padding: 2.5rem 2.25rem;
`;
const ErrorTitleContainer = styled.div`
display: flex;
flex-direction: row;
gap: 1rem;
align-items: center;
font-family: "Inter", serif;
font-style: normal;
font-weight: 600;
font-size: 2rem;
line-height: 2.5rem;
color: ${color.white};
`;
const ErrorImageContainer = styled.div`
width: 2.5rem;
height: 2.5rem;
position: relative;
`;
const ErrorDescription = styled.div`
margin-top: 1rem;
font-family: "Inter", serif;
font-style: normal;
font-weight: 400;
font-size: 1rem;
line-height: 1.25rem;
color: ${color.grey["300"]};
`;
const ErrorBackButton = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 0.3rem;
margin-top: 2.5rem;
font-family: "Inter", serif;
font-style: normal;
font-weight: 600;
font-size: 1.125rem;
line-height: 1.125rem;
color: ${color.grey["100"]};
cursor: pointer;
`;
const ErrorBackIconContainer = styled.div`
width: 1.5rem;
height: 1.5rem;
position: relative;
`;
+300
View File
@@ -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 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={walletInfo?.name || "Keplr account"}
content={Bech32Address.shortenAddress(
walletInfo?.bech32Address || "",
28,
)}
/>
<SubTextsContainer>
<SubText>
ICNS name can only be claimed once per Twitter account.
<br />
ICNS name cant be transferred at this time.
<br />
Please make sure youve 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}
`;
+4
View File
@@ -36,4 +36,8 @@ export const LogoContainer = styled.div`
margin-top: 5rem;
margin-left: 5rem;
@media screen and (max-width: 1280px) {
display: none;
}
`;
-35
View File
@@ -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"]};
}
`;
+112
View File
@@ -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;
`;
+24
View File
@@ -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"]};
`;
+14 -2
View File
@@ -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
View File
@@ -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",
+30 -5
View File
@@ -1,22 +1,28 @@
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";
import color from "../styles/color";
import { GlobalStyle } from "../styles/global";
const homePageTheme: DefaultTheme = {
bgColor: "rgba(18, 18, 18, 1)",
bgGridColor: "rgba(51, 51, 51, 1)",
bgColor: color.black,
bgGridColor: color.grey["600"],
};
const defaultPageTheme: DefaultTheme = {
bgColor: "rgba(18, 18, 18, 0.8)",
bgGridColor: "rgba(51, 51, 51, 0.3)",
bgColor: "#0B0B0B",
bgGridColor: color.grey["900"],
};
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 +32,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>
+66
View File
@@ -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;
+5
View File
@@ -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 ▶`;
+44 -5
View File
@@ -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);
};
@@ -74,11 +78,24 @@ export default function Home() {
</MainTitleImageContainer>
</MainTitleImageBackground>
<ConnectButtonContainer>
<PrimaryButton onClick={onClickConnectWalletButton}>
Connect Wallet
</PrimaryButton>
</ConnectButtonContainer>
<CTAContainer>
<ConnectButtonContainer>
<PrimaryButton onClick={onClickConnectWalletButton}>
Connect Wallet
</PrimaryButton>
</ConnectButtonContainer>
<ICNSDescription>
ICNS allows you to use easy-to-remember names instead of
addresses.
<br />
<LearnMoreLink
href="https://medium.com/@icns/announcing-icns-the-interchain-name-service-e61e0c3e2abb"
target="_blank"
>
Learn More
</LearnMoreLink>
</ICNSDescription>
</CTAContainer>
<SubContainer>
<CheckContainer>
<CheckIconContainer>
@@ -199,11 +216,33 @@ const MainLogoContainer = styled.div`
height: 25rem;
`;
const CTAContainer = styled.div`
display: flex;
`;
const ConnectButtonContainer = styled.div`
width: 19.9rem;
height: 5rem;
margin-left: 5rem;
margin-right: 26px;
`;
const ICNSDescription = styled.p`
font-weight: 500;
font-size: 16px;
line-height: 150%;
color: #6c6c6c;
`;
const LearnMoreLink = styled.a`
color: #6c6c6c;
&:link,
&:hover,
&:active &:visited {
color: #6c6c6c;
}
`;
const SubContainer = styled.div`
+191 -87
View File
@@ -1,9 +1,13 @@
import * as amplitude from "@amplitude/analytics-browser";
// React
import { useEffect, useState } from "react";
// Types
import {
ChainItemType,
DisabledChainItemType,
ErrorMessage,
QueryError,
RegisteredAddresses,
TwitterProfileType,
@@ -28,7 +32,7 @@ import {
sendMsgs,
simulateMsgs,
} from "../../wallets";
import { ChainIdHelper } from "@keplr-wallet/cosmos";
import { Bech32Address, ChainIdHelper } from "@keplr-wallet/cosmos";
import { AllChainsItem } from "../../components/chain-list/all-chains-item";
import { SearchInput } from "../../components/search-input";
@@ -54,19 +58,32 @@ import {
import { makeClaimMessage, makeSetRecordMessage } from "../../messages";
import Axios from "axios";
import { BackButton } from "../../components/back-button";
import { FinalCheckModal } from "../../components/final-check-modal";
import { ErrorModal } from "../../components/error-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[]
>([]);
@@ -74,8 +91,12 @@ export default function VerificationPage() {
const [searchValue, setSearchValue] = useState("");
const [nftOwnerAddress, setNFTOwnerAddress] = useState("");
const [isOwner, setIsOwner] = useState(false);
const [isAgree, setIsAgree] = useState(false);
const [isModalOpen, setModalOpen] = useState(false);
const [isErrorModalOpen, setErrorModalOpen] = useState(false);
const [errorMessage, setErrorMessage] = useState<ErrorMessage>();
useEffect(() => {
init();
@@ -90,23 +111,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);
@@ -145,25 +194,29 @@ export default function VerificationPage() {
registeredQueryResponse.data.name,
);
if (keplrWallet) {
const key = await keplrWallet.getKey(MainChainId);
setIsOwner(ownerOfQueryResponse.data.owner === key.bech32Address);
setNFTOwnerAddress(ownerOfQueryResponse.data.owner);
}
const addressesQueryResponse = await queryAddressesFromTwitterName(
registeredQueryResponse.data.name,
);
if (keplrWallet) {
const key = await keplrWallet.getKey(MainChainId);
setIsOwner(ownerOfQueryResponse.data.owner === key.bech32Address);
}
setRegisteredChainList(addressesQueryResponse.data.addresses);
}
} catch (error) {
if (error instanceof Error && error.message === TWITTER_LOGIN_ERROR) {
await router.push("/");
if (error instanceof Error) {
if (error.message === TWITTER_LOGIN_ERROR) {
setErrorMessage({ message: TWITTER_LOGIN_ERROR, path: "/" });
setErrorModalOpen(true);
}
}
console.error(error);
} finally {
setIsLoading(false);
setIsLoadingInit(false);
}
}
};
@@ -173,26 +226,47 @@ 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 {
ErrorHandler(KEPLR_NOT_FOUND_ERROR);
setErrorMessage({ message: KEPLR_NOT_FOUND_ERROR, path: "/" });
setErrorModalOpen(true);
}
};
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 needAllowList =
process.env.NEXT_PUBLIC_CHAIN_ALLOWLIST != null &&
process.env.NEXT_PUBLIC_CHAIN_ALLOWLIST.trim().length !== 0;
const chainAllowList = (process.env.NEXT_PUBLIC_CHAIN_ALLOWLIST || "")
.split(",")
.map((str) => str.trim())
.filter((str) => str.length > 0)
.map((str) => ChainIdHelper.parse(str).identifier);
const chainInfos = (await wallet.getChainInfosWithoutEndpoints()).map(
(chainInfo) => {
const chainAllowListMap = new Map<string, true | undefined>();
for (const allow of chainAllowList) {
chainAllowListMap.set(allow, true);
}
const chainInfos = (await wallet.getChainInfosWithoutEndpoints())
.filter((chainInfo) => {
if (!needAllowList) {
return true;
}
const chainIdentifier = ChainIdHelper.parse(
chainInfo.chainId,
).identifier;
return chainAllowListMap.get(chainIdentifier) === true;
})
.map((chainInfo) => {
return {
chainId: chainInfo.chainId,
chainName: chainInfo.chainName,
@@ -200,14 +274,24 @@ 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 chainIds = chainInfos.map((c) => c.chainId);
const chainKeys = await Promise.allSettled(
chainIds.map((chainId) => wallet.getKey(chainId)),
);
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],
});
}
@@ -251,8 +335,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,
);
@@ -260,16 +356,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,
@@ -277,7 +371,7 @@ export default function VerificationPage() {
const addressMsgs = adr36Infos.map((adr36Info) => {
return makeSetRecordMessage(
key.bech32Address,
walletKey.bech32Address,
twitterInfo.username,
adr36Info,
);
@@ -302,7 +396,7 @@ export default function VerificationPage() {
const simulated = await simulateMsgs(
chainInfo,
key.bech32Address,
walletKey.bech32Address,
{
proto: protoMsgs,
},
@@ -314,7 +408,7 @@ export default function VerificationPage() {
const txHash = await sendMsgs(
wallet,
chainInfo,
key.bech32Address,
walletKey.bech32Address,
{
amino: aminoMsgs,
proto: protoMsgs,
@@ -335,19 +429,31 @@ export default function VerificationPage() {
}
} catch (error) {
if (Axios.isAxiosError(error)) {
console.error((error?.response?.data as QueryError).message);
setErrorMessage({
message: (error?.response?.data as QueryError).message,
});
setErrorModalOpen(true);
return;
}
if (error instanceof Error) {
console.log(error.message);
setErrorMessage({ message: error.message });
setErrorModalOpen(true);
}
} finally {
setIsLoadingRegistration(false);
}
};
const isRegisterButtonDisable = (() => {
if (!isOwner && nftOwnerAddress) {
return true;
}
const hasCheckedItem = checkedItems.size > 0;
if (isOwner) {
return !hasCheckedItem;
} else {
return !(isAgree && hasCheckedItem);
}
return !hasCheckedItem;
})();
return (
@@ -355,13 +461,12 @@ export default function VerificationPage() {
<Logo />
<MainContainer>
{isLoading ? (
{isLoadingInit ? (
<SkeletonChainList />
) : (
<ContentContainer>
<BackButton />
<TwitterProfile twitterProfileInformation={twitterAuthInfo} />
<ChainListTitleContainer>
<ChainListTitle>Chain List</ChainListTitle>
<SearchInput
@@ -369,7 +474,6 @@ export default function VerificationPage() {
setSearchValue={setSearchValue}
/>
</ChainListTitleContainer>
{!searchValue ? (
<AllChainsItem
chainList={chainList}
@@ -377,7 +481,6 @@ export default function VerificationPage() {
setCheckedItems={setCheckedItems}
/>
) : null}
<ChainList
chainList={chainList.filter(
(chain) =>
@@ -395,22 +498,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>
)}
{!isOwner && nftOwnerAddress ? (
<OwnerAlert>
You are not owner of this name.
<br />
Please select the account (
{Bech32Address.shortenAddress(nftOwnerAddress, 28)})
</OwnerAlert>
) : null}
{chainList.length > 0 && (
<ButtonContainer disabled={isRegisterButtonDisable}>
<PrimaryButton
disabled={isRegisterButtonDisable}
onClick={onClickRegistration}
isLoading={isLoadingRegistration}
>
Register
</PrimaryButton>
@@ -419,6 +521,21 @@ export default function VerificationPage() {
</ContentContainer>
)}
</MainContainer>
<FinalCheckModal
twitterUserName={twitterAuthInfo?.username}
walletInfo={walletKey}
isModalOpen={isModalOpen}
onCloseModal={() => setModalOpen(false)}
onClickRegisterButton={handleRegistration}
isLoadingRegistration={isLoadingRegistration}
/>
<ErrorModal
isModalOpen={isErrorModalOpen}
onCloseModal={() => setErrorModalOpen(false)}
errorMessage={errorMessage}
/>
</Container>
);
}
@@ -458,6 +575,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"]};
`;
@@ -482,30 +601,15 @@ const ChainListTitle = styled.div`
color: ${color.white};
`;
const AgreeContainer = styled.div`
display: flex;
align-items: center;
gap: 0.5rem;
const OwnerAlert = styled.div`
font-family: "Inter", serif;
font-style: normal;
font-weight: 500;
font-size: 0.8rem;
line-height: 0.8rem;
text-transform: uppercase;
user-select: none;
font-size: 0.9rem;
line-height: 1.1rem;
text-align: center;
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"]};
padding-top: 1.25rem;
`;
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

+5
View File
@@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M35 20C35 11.7188 28.2812 5 20 5C11.7188 5 5 11.7188 5 20C5 28.2813 11.7188 35 20 35C28.2812 35 35 28.2813 35 20Z" stroke="#FBCF32" stroke-width="1.95" stroke-miterlimit="10"/>
<path d="M18.3344 13.3945L19.7441 22.5057C19.7895 22.7994 20.2125 22.7994 20.258 22.5057L21.6649 13.409C21.6751 13.1826 21.6391 12.9565 21.5591 12.7446C21.479 12.5326 21.3566 12.3391 21.1992 12.1761C21.0419 12.013 20.8529 11.8838 20.6439 11.7962C20.4349 11.7086 20.2102 11.6646 19.9837 11.6668C19.761 11.669 19.5411 11.7158 19.3368 11.8043C19.1326 11.8929 18.9481 12.0215 18.7944 12.1826C18.6406 12.3436 18.5207 12.5338 18.4417 12.742C18.3627 12.9501 18.3262 13.172 18.3344 13.3945Z" fill="#FBCF32" stroke="#FBCF32" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M19.9999 28.3333C19.6703 28.3333 19.3481 28.2356 19.074 28.0524C18.7999 27.8693 18.5863 27.609 18.4601 27.3045C18.334 26.9999 18.301 26.6648 18.3653 26.3415C18.4296 26.0182 18.5883 25.7212 18.8214 25.4882C19.0545 25.2551 19.3515 25.0963 19.6748 25.032C19.9981 24.9677 20.3332 25.0007 20.6377 25.1269C20.9423 25.253 21.2026 25.4666 21.3857 25.7407C21.5688 26.0148 21.6666 26.337 21.6666 26.6667C21.6666 27.1087 21.491 27.5326 21.1784 27.8452C20.8659 28.1577 20.442 28.3333 19.9999 28.3333Z" fill="#FBCF32"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+14 -11
View File
@@ -1,28 +1,28 @@
<svg width="920" height="202" viewBox="0 0 920 202" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M149.971 78.9997V64.5897H112.021V49.1897H147.001V34.7797H112.021V19.3797H149.311V4.96969H110.151C99.9215 4.96969 95.9615 10.3597 95.9615 18.8297V65.2497C95.9615 73.7197 99.9215 78.9997 110.151 78.9997H149.971Z" fill="#DAECD5"/>
<path d="M218.14 4.96969H158.52V19.8197H180.3V78.9997H196.36V19.8197H218.14V4.96969Z" fill="#DAECD5"/>
<path d="M272.027 46.5497C274.007 51.1697 276.977 54.3597 280.277 56.3397V78.9997H296.337V56.2297C299.637 54.2497 302.497 51.0597 304.367 46.5497L322.408 4.96969H305.027L289.958 42.1497C289.298 43.4697 288.967 44.3497 288.307 44.3497C287.647 44.3497 287.318 43.5797 286.658 42.1497L271.477 4.96969H254.207L272.027 46.5497Z" fill="#DAECD5"/>
<path d="M450.854 80.2097C472.854 80.2097 483.084 67.4497 483.084 46.1097V4.96969H467.024V45.7797C467.024 58.6497 461.744 64.9197 450.854 64.9197C439.964 64.9197 434.684 58.6497 434.684 45.7797V4.96969H418.624V46.1097C418.624 67.4497 428.744 80.2097 450.854 80.2097Z" fill="#DAECD5"/>
<path d="M518.076 78.9997L518.186 55.3497H524.456C532.816 55.3497 536.006 58.9797 539.416 65.7997L546.016 78.9997H563.396L556.246 64.3697C552.396 56.6697 549.646 51.6097 544.916 48.9697C554.376 46.4397 560.316 38.6297 560.316 28.5097C560.316 12.7797 547.446 4.96969 530.726 4.96969H502.236V78.9997H518.076ZM530.946 19.8197C538.206 19.8197 543.816 22.8997 543.816 30.1597C543.816 37.0897 538.316 41.0497 530.726 41.0497H518.186L518.296 19.8197H530.946Z" fill="#DAECD5"/>
<path d="M149.971 78.9997V64.5897H112.021V49.1897H147.001V34.7797H112.021V19.3797H149.311V4.96969H110.151C99.9213 4.96969 95.9613 10.3597 95.9613 18.8297V65.2497C95.9613 73.7197 99.9213 78.9997 110.151 78.9997H149.971Z" fill="#DAECD5"/>
<path d="M218.139 4.96969H158.519V19.8197H180.299V78.9997H196.359V19.8197H218.139V4.96969Z" fill="#DAECD5"/>
<path d="M272.027 46.5497C274.007 51.1697 276.977 54.3597 280.277 56.3397V78.9997H296.337V56.2297C299.637 54.2497 302.497 51.0597 304.367 46.5497L322.407 4.96969H305.027L289.957 42.1497C289.297 43.4697 288.967 44.3497 288.307 44.3497C287.647 44.3497 287.317 43.5797 286.657 42.1497L271.477 4.96969H254.207L272.027 46.5497Z" fill="#DAECD5"/>
<path d="M450.853 80.2097C472.853 80.2097 483.083 67.4497 483.083 46.1097V4.96969H467.023V45.7797C467.023 58.6497 461.743 64.9197 450.853 64.9197C439.963 64.9197 434.683 58.6497 434.683 45.7797V4.96969H418.623V46.1097C418.623 67.4497 428.743 80.2097 450.853 80.2097Z" fill="#DAECD5"/>
<path d="M518.075 78.9997L518.185 55.3497H524.455C532.815 55.3497 536.005 58.9797 539.415 65.7997L546.015 78.9997H563.395L556.245 64.3697C552.395 56.6697 549.645 51.6097 544.915 48.9697C554.375 46.4397 560.315 38.6297 560.315 28.5097C560.315 12.7797 547.445 4.96969 530.725 4.96969H502.235V78.9997H518.075ZM530.945 19.8197C538.205 19.8197 543.815 22.8997 543.815 30.1597C543.815 37.0897 538.315 41.0497 530.725 41.0497H518.185L518.295 19.8197H530.945Z" fill="#DAECD5"/>
<path d="M626.206 78.9997V24.2197L655.466 74.7097C657.886 78.7797 661.956 80.4297 666.686 80.4297C672.516 80.4297 677.906 76.2497 677.906 68.8797V4.96969H662.726V59.4197L633.466 9.25969C631.046 5.18969 627.196 3.42969 622.356 3.42969C616.306 3.42969 610.916 7.71969 610.916 15.0897V78.9997H626.206Z" fill="#DAECD5"/>
<path d="M689.501 78.9997H705.891L710.181 63.9297L740.211 63.8197L744.501 78.9997H760.891L741.311 15.4197C738.781 7.38969 732.181 3.53969 725.141 3.53969C718.211 3.53969 711.611 7.38969 709.081 15.4197L689.501 78.9997ZM722.831 20.1497C723.271 18.6097 724.041 17.9497 725.251 17.9497C726.571 17.9497 727.231 18.6097 727.671 20.1497L736.031 49.5197H714.361L722.831 20.1497Z" fill="#DAECD5"/>
<path d="M919.812 78.9997V64.5897H881.862V49.1897H916.842V34.7797H881.862V19.3797H919.152V4.96969H879.992C869.762 4.96969 865.802 10.3597 865.802 18.8297V65.2497C865.802 73.7197 869.762 78.9997 879.992 78.9997H919.812Z" fill="#DAECD5"/>
<path d="M69.361 200V145.22L98.621 195.71C101.041 199.78 105.111 201.43 109.841 201.43C115.671 201.43 121.061 197.25 121.061 189.88V125.97H105.881V180.42L76.621 130.26C74.201 126.19 70.351 124.43 65.511 124.43C59.461 124.43 54.071 128.72 54.071 136.09V200H69.361Z" fill="#DAECD5"/>
<path d="M69.3608 200V145.22L98.6208 195.71C101.041 199.78 105.111 201.43 109.841 201.43C115.671 201.43 121.061 197.25 121.061 189.88V125.97H105.881V180.42L76.6208 130.26C74.2008 126.19 70.3508 124.43 65.5108 124.43C59.4608 124.43 54.0708 128.72 54.0708 136.09V200H69.3608Z" fill="#DAECD5"/>
<path d="M179.247 189.55C180.897 197.36 186.177 201.54 192.997 201.54C199.377 201.54 204.877 197.36 206.527 190.21L217.747 141.26L229.077 189.88C230.837 197.36 236.117 201.54 242.717 201.54C249.867 201.54 254.707 197.03 256.357 189.22L269.557 125.97H253.497L242.277 184.6C242.167 184.93 242.167 185.15 242.057 185.15C241.837 185.15 241.837 185.04 241.727 184.6L230.837 137.63C228.747 128.72 224.347 124.87 217.747 124.87C211.147 124.87 206.637 128.72 204.657 137.74L193.987 184.6C193.877 185.04 193.767 185.15 193.657 185.15C193.547 185.15 193.437 185.04 193.327 184.6L181.667 125.97H165.607L179.247 189.55Z" fill="#DAECD5"/>
<path d="M381.671 125.97H365.611V186.25C365.611 194.72 369.571 200 379.801 200H416.211V185.15H381.671V125.97Z" fill="#DAECD5"/>
<path d="M444.4 125.97H428.34V186.25C428.34 194.72 432.299 200 442.53 200H478.94V185.15H444.4V125.97Z" fill="#DAECD5"/>
<path d="M444.399 125.97H428.339V186.25C428.339 194.72 432.299 200 442.529 200H478.939V185.15H444.399V125.97Z" fill="#DAECD5"/>
<path d="M543.978 200V185.59H506.028V170.19H541.008V155.78H506.028V140.38H543.318V125.97H504.158C493.928 125.97 489.968 131.36 489.968 139.83V186.25C489.968 194.72 493.928 200 504.158 200H543.978Z" fill="#DAECD5"/>
<path d="M612.146 125.97H552.526V140.82H574.306V200H590.366V140.82H612.146V125.97Z" fill="#DAECD5"/>
<rect y="120" width="39.4428" height="40.0003" rx="19.7214" fill="#FB5232"/>
<rect y="160" width="39.4428" height="40.0003" fill="#F4CC3E"/>
<path d="M773 3C794.784 3 812.444 20.6595 812.444 42.4435V43.001H773V3Z" fill="#00B86E"/>
<path d="M812.445 42.4435C812.445 20.6595 830.105 3 851.889 3V43.001H812.445V42.4435Z" fill="#FF77F3"/>
<rect x="773" y="42.9961" width="39.4435" height="40.001" fill="#F4CC3E"/>
<rect x="812.445" y="42.9961" width="39.4435" height="40.001" fill="#FB5232"/>
<rect x="773" y="42.9971" width="39.4435" height="40.001" fill="#F4CC3E"/>
<rect x="812.445" y="42.9971" width="39.4435" height="40.001" fill="#FB5232"/>
<path d="M314.448 121C336.233 121 353.893 138.91 353.893 161.002H334.17C323.278 161.002 314.448 152.048 314.448 141.001V121Z" fill="#FF77F3"/>
<path d="M314.448 180.997C314.448 169.951 323.278 160.996 334.17 160.996H353.893V200.999H314.448V180.997Z" fill="#00B86E"/>
<path d="M314.448 180.999C314.448 169.953 323.278 160.998 334.17 160.998H353.893V201.001H314.448V180.999Z" fill="#00B86E"/>
<path d="M275 161.002C275 138.91 292.66 121 314.445 121V141.001C314.445 152.048 305.615 161.002 294.722 161.002H275Z" fill="#F4CC3E"/>
<path d="M275 160.996H294.722C305.615 160.996 314.445 169.951 314.445 180.997V200.999H275V160.996Z" fill="#5A4CFA"/>
<path d="M275 160.998H294.722C305.615 160.998 314.445 169.953 314.445 180.999V201.001H275V160.998Z" fill="#5A4CFA"/>
<path d="M408 42C408 18.804 389.196 0 366 0V20.25C366 32.2622 375.738 42 387.75 42H408Z" fill="#5A4CFA"/>
<path d="M408 42C408 65.196 389.196 84 366 84V63.75C366 51.7378 375.738 42 387.75 42H408Z" fill="#F4CC3E"/>
<path d="M324 42C324 18.804 342.804 0 366 0V20.25C366 32.2622 356.262 42 344.25 42H324Z" fill="#FF77F3"/>
@@ -31,4 +31,7 @@
<path d="M0 43H40V83H0V43Z" fill="#5A4CFA"/>
<rect x="40" y="3" width="40" height="40" fill="#FF77F3"/>
<path d="M40 43H60C71.0457 43 80 51.9543 80 63C80 74.0457 71.0457 83 60 83H40V43Z" fill="#F4CC3E"/>
<path d="M627.496 199L623.391 184.455H626.224L628.845 195.143H628.98L631.778 184.455H634.357L637.162 195.151H637.29L639.911 184.455H642.744L638.639 199H636.04L633.128 188.794H633.014L630.095 199H627.496ZM648.606 184.455V199H645.971V184.455H648.606ZM652.22 186.663V184.455H663.826V186.663H659.33V199H656.716V186.663H652.22ZM667.463 199V184.455H670.098V190.612H676.838V184.455H679.48V199H676.838V192.821H670.098V199H667.463ZM689.112 184.455H692.088L695.646 190.889H695.788L699.347 184.455H702.322L697.031 193.574V199H694.403V193.574L689.112 184.455ZM717.765 191.727C717.765 193.295 717.472 194.637 716.885 195.754C716.302 196.867 715.507 197.719 714.498 198.311C713.495 198.903 712.356 199.199 711.082 199.199C709.809 199.199 708.667 198.903 707.659 198.311C706.655 197.714 705.86 196.86 705.273 195.747C704.69 194.63 704.399 193.29 704.399 191.727C704.399 190.16 704.69 188.82 705.273 187.707C705.86 186.59 706.655 185.735 707.659 185.143C708.667 184.552 709.809 184.256 711.082 184.256C712.356 184.256 713.495 184.552 714.498 185.143C715.507 185.735 716.302 186.59 716.885 187.707C717.472 188.82 717.765 190.16 717.765 191.727ZM715.116 191.727C715.116 190.624 714.943 189.694 714.598 188.936C714.257 188.174 713.783 187.598 713.177 187.21C712.571 186.817 711.873 186.621 711.082 186.621C710.291 186.621 709.593 186.817 708.987 187.21C708.381 187.598 707.905 188.174 707.559 188.936C707.219 189.694 707.048 190.624 707.048 191.727C707.048 192.83 707.219 193.763 707.559 194.526C707.905 195.283 708.381 195.858 708.987 196.251C709.593 196.64 710.291 196.834 711.082 196.834C711.873 196.834 712.571 196.64 713.177 196.251C713.783 195.858 714.257 195.283 714.598 194.526C714.943 193.763 715.116 192.83 715.116 191.727ZM730.826 184.455H733.461V193.957C733.461 194.999 733.214 195.915 732.722 196.706C732.234 197.497 731.548 198.115 730.662 198.56C729.777 199 728.742 199.22 727.559 199.22C726.37 199.22 725.333 199 724.448 198.56C723.562 198.115 722.876 197.497 722.388 196.706C721.9 195.915 721.657 194.999 721.657 193.957V184.455H724.291V193.737C724.291 194.343 724.424 194.883 724.689 195.357C724.959 195.83 725.338 196.202 725.826 196.472C726.313 196.737 726.891 196.869 727.559 196.869C728.226 196.869 728.804 196.737 729.291 196.472C729.784 196.202 730.163 195.83 730.428 195.357C730.693 194.883 730.826 194.343 730.826 193.737V184.455ZM737.724 199V184.455H743.179C744.296 184.455 745.234 184.649 745.992 185.037C746.754 185.425 747.329 185.97 747.717 186.67C748.11 187.366 748.307 188.179 748.307 189.107C748.307 190.039 748.108 190.849 747.71 191.536C747.317 192.217 746.737 192.745 745.97 193.119C745.203 193.489 744.261 193.673 743.144 193.673H739.259V191.486H742.788C743.442 191.486 743.977 191.396 744.394 191.216C744.81 191.031 745.118 190.764 745.317 190.413C745.52 190.058 745.622 189.623 745.622 189.107C745.622 188.59 745.52 188.15 745.317 187.786C745.113 187.416 744.803 187.137 744.386 186.947C743.97 186.753 743.432 186.656 742.774 186.656H740.359V199H737.724ZM745.239 192.409L748.84 199H745.899L742.362 192.409H745.239Z" fill="#5B5B5B"/>
<path d="M757.854 186.663V184.455H769.459V186.663H764.963V199H762.35V186.663H757.854ZM776.157 199L772.052 184.455H774.886L777.507 195.143H777.642L780.44 184.455H783.018L785.824 195.151H785.951L788.572 184.455H791.406L787.301 199H784.701L781.79 188.794H781.676L778.757 199H776.157ZM797.268 184.455V199H794.633V184.455H797.268ZM800.882 186.663V184.455H812.487V186.663H807.992V199H805.378V186.663H800.882ZM815.485 186.663V184.455H827.09V186.663H822.595V199H819.981V186.663H815.485ZM830.728 199V184.455H840.188V186.663H833.363V190.612H839.698V192.821H833.363V196.791H840.245V199H830.728ZM844.276 199V184.455H849.731C850.848 184.455 851.786 184.649 852.543 185.037C853.305 185.425 853.881 185.97 854.269 186.67C854.662 187.366 854.858 188.179 854.858 189.107C854.858 190.039 854.66 190.849 854.262 191.536C853.869 192.217 853.289 192.745 852.522 193.119C851.755 193.489 850.813 193.673 849.695 193.673H845.81V191.486H849.34C849.993 191.486 850.528 191.396 850.945 191.216C851.362 191.031 851.67 190.764 851.868 190.413C852.072 190.058 852.174 189.623 852.174 189.107C852.174 188.59 852.072 188.15 851.868 187.786C851.665 187.416 851.355 187.137 850.938 186.947C850.521 186.753 849.984 186.656 849.326 186.656H846.911V199H844.276ZM851.79 192.409L855.391 199H852.451L848.914 192.409H851.79ZM867.68 184.455V199H865.045V184.455H867.68ZM876.862 199H871.933V184.455H876.962C878.406 184.455 879.646 184.746 880.683 185.328C881.725 185.906 882.525 186.737 883.084 187.821C883.642 188.905 883.922 190.203 883.922 191.713C883.922 193.228 883.64 194.53 883.077 195.619C882.518 196.708 881.711 197.544 880.655 198.126C879.604 198.709 878.339 199 876.862 199ZM874.568 196.72H876.734C877.747 196.72 878.593 196.536 879.27 196.166C879.947 195.792 880.456 195.236 880.797 194.497C881.138 193.754 881.308 192.826 881.308 191.713C881.308 190.6 881.138 189.677 880.797 188.943C880.456 188.205 879.952 187.653 879.284 187.288C878.621 186.919 877.797 186.734 876.812 186.734H874.568V196.72Z" fill="#03A9F4"/>
<path d="M919 184.247C918.118 184.631 917.169 184.887 916.169 185.008C917.186 184.414 917.969 183.468 918.336 182.349C917.384 182.898 916.33 183.301 915.208 183.513C914.312 182.579 913.034 182 911.618 182C908.898 182 906.694 184.148 906.694 186.796C906.694 187.171 906.738 187.536 906.822 187.89C902.731 187.688 899.102 185.776 896.674 182.875C896.247 183.584 896.007 184.414 896.007 185.29C896.007 186.955 896.875 188.421 898.197 189.284C897.39 189.26 896.63 189.039 895.965 188.685C895.965 188.7 895.965 188.721 895.965 188.742C895.965 191.068 897.662 193.006 899.913 193.447C899.502 193.557 899.067 193.619 898.619 193.619C898.301 193.619 897.991 193.583 897.691 193.53C898.318 195.432 900.136 196.821 902.289 196.863C900.604 198.147 898.482 198.916 896.174 198.916C895.775 198.916 895.385 198.894 895 198.848C897.18 200.205 899.77 201 902.549 201C911.605 201 916.559 193.691 916.559 187.35C916.559 187.142 916.552 186.935 916.542 186.731C917.509 186.06 918.342 185.214 919 184.247Z" fill="#03A9F4"/>
</svg>

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+3
View File
@@ -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

+28
View File
@@ -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://",
],
});
+3
View File
@@ -0,0 +1,3 @@
defaults.url=https://sentry.io/
defaults.org=interchain-name-service
defaults.project=icns-frontend

Some files were not shown because too many files have changed in this diff Show More