forked from LaconicNetwork/icns-frontend
Compare commits
91
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc2f59cd02 | ||
|
|
26c95bfbe5 | ||
|
|
dfaf78f9d3 | ||
|
|
40e3522c2a | ||
|
|
279a372052 | ||
|
|
adbc02ee8b | ||
|
|
061f2a2bed | ||
|
|
d50f491b1b | ||
|
|
6f3fe64125 | ||
|
|
13a5c37226 | ||
|
|
53e3ec81ee | ||
|
|
da89c51b4c | ||
|
|
68dfe40ee5 | ||
|
|
28a44a9195 | ||
|
|
d23090f9f8 | ||
|
|
6eb3eb885c | ||
|
|
6a49b69a02 | ||
|
|
53ecf825a0 | ||
|
|
5d48012133 | ||
|
|
5fac2f9156 | ||
|
|
3de363af0f | ||
|
|
eead90d4b9 | ||
|
|
a61856557c | ||
|
|
7e72ab82b6 | ||
|
|
68d8ba31aa | ||
|
|
e0151666b2 | ||
|
|
b41ef751fb | ||
|
|
e884fc1d86 | ||
|
|
537e650c5e | ||
|
|
278ff37129 | ||
|
|
76b74d2c96 | ||
|
|
7b8f4910bb | ||
|
|
1abc475354 | ||
|
|
d488d31345 | ||
|
|
80907781ad | ||
|
|
11a17e64b8 | ||
|
|
75c281fea5 | ||
|
|
70a58e9bdf | ||
|
|
1194ccb375 | ||
|
|
02b0cde9b1 | ||
|
|
618442b58e | ||
|
|
913e5d2193 | ||
|
|
148a94e4ea | ||
|
|
d1221475a7 | ||
|
|
52e585f83c | ||
|
|
a35e7bb9af | ||
|
|
75656943a1 | ||
|
|
357b65b587 | ||
|
|
2efe20f0a0 | ||
|
|
11b79b7c40 | ||
|
|
0944e5c175 | ||
|
|
0eba7c4cf9 | ||
|
|
dd8f108cb7 | ||
|
|
b4bf1f4ee8 | ||
|
|
41c4ed39ec | ||
|
|
3fc581aad9 | ||
|
|
3d72c4d959 | ||
|
|
4cc4213f8c | ||
|
|
0dd50d218b | ||
|
|
bcf8827186 | ||
|
|
bb58fb54ea | ||
|
|
7de3767d03 | ||
|
|
13de62028d | ||
|
|
a3dc47b5eb | ||
|
|
d14eead3ba | ||
|
|
d0ba550eaf | ||
|
|
8897eac973 | ||
|
|
2655c35c32 | ||
|
|
75d50bbe89 | ||
|
|
01be54ee28 | ||
|
|
8504c84d3c | ||
|
|
febde1e7c4 | ||
|
|
1602ef92c8 | ||
|
|
1dd9e31911 | ||
|
|
10e129baad | ||
|
|
921ec5d7e6 | ||
|
|
8319467125 | ||
|
|
71abd3560e | ||
|
|
97161e0a48 | ||
|
|
b80273c1cc | ||
|
|
cdd7c797f5 | ||
|
|
b6926ecf3b | ||
|
|
07de65b265 | ||
|
|
b47c09fb6f | ||
|
|
9db9a0478b | ||
|
|
564b401331 | ||
|
|
f16159d189 | ||
|
|
b33309493a | ||
|
|
8771fd1880 | ||
|
|
cd81a19f00 | ||
|
|
fc58cad6a8 |
+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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -12,7 +12,8 @@ TWITTER_AUTH_CALLBACK_URI=...
|
||||
|
||||
IRON_PASSWORD=...
|
||||
|
||||
ICNS_VERIFIER_URI=...
|
||||
# must be separated by commas
|
||||
ICNS_VERIFIER_ORIGIN_LIST=http:localhost:8080,http://localhost:8081
|
||||
```
|
||||
|
||||
Run the development server:
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { FunctionComponent } from "react";
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
|
||||
import ArrowLeftIcon from "../../public/images/svg/arrow-left.svg";
|
||||
import Image from "next/image";
|
||||
|
||||
export const BackButton: FunctionComponent = () => {
|
||||
return (
|
||||
<Container
|
||||
onClick={() => {
|
||||
location.href = "/";
|
||||
}}
|
||||
>
|
||||
<ContentContainer>
|
||||
<IconContainer>
|
||||
<Image
|
||||
src={ArrowLeftIcon}
|
||||
fill={true}
|
||||
sizes="1rem"
|
||||
alt="arrow left icon"
|
||||
/>
|
||||
</IconContainer>
|
||||
<div>BACK</div>
|
||||
</ContentContainer>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
width: 100%;
|
||||
padding: 0.7rem 0.5rem;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
line-height: 0.8rem;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
gap: 0.25rem;
|
||||
`;
|
||||
|
||||
const IconContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
`;
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ChainItemType } from "../../types";
|
||||
import {
|
||||
Dispatch,
|
||||
FunctionComponent,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import { ChainImage } from "./chain-image";
|
||||
import { Flex1 } from "../../styles/flex-1";
|
||||
import {
|
||||
ChainInfoContainer,
|
||||
ChainItemContainer,
|
||||
ChainName,
|
||||
WalletAddress,
|
||||
} from "./chain-item";
|
||||
import color from "../../styles/color";
|
||||
import styled from "styled-components";
|
||||
import { Checkbox } from "../checkbox";
|
||||
import AllChainsIcon from "../../public/images/svg/all-chains-icon.svg";
|
||||
|
||||
interface Props {
|
||||
chainList: ChainItemType[];
|
||||
checkedItems: Set<unknown>;
|
||||
setCheckedItems: Dispatch<SetStateAction<Set<unknown>>>;
|
||||
}
|
||||
|
||||
export const AllChainsItem: FunctionComponent<Props> = (props) => {
|
||||
const { chainList, checkedItems, setCheckedItems } = props;
|
||||
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
const checkHandler = () => {
|
||||
if (checked) {
|
||||
setCheckedItems(new Set());
|
||||
} else if (chainList.length !== checkedItems.size) {
|
||||
setCheckedItems(new Set(chainList));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (chainList.length === checkedItems.size && checkedItems.size !== 0) {
|
||||
setChecked(true);
|
||||
} else {
|
||||
setChecked(false);
|
||||
}
|
||||
}, [checkedItems]);
|
||||
|
||||
return (
|
||||
<AllChainsContainer>
|
||||
<ChainItemContainer
|
||||
key="all chains"
|
||||
isLoading={false}
|
||||
checked={checked}
|
||||
onClick={checkHandler}
|
||||
>
|
||||
<ChainImage src={AllChainsIcon} fill={true} alt={`all chain images`} />
|
||||
<ChainInfoContainer>
|
||||
<ChainName>{`.all chains(${chainList.length})`}</ChainName>
|
||||
<WalletAddress>
|
||||
{chainList.map((chain) => chain.chainName).join(", ")}
|
||||
</WalletAddress>
|
||||
</ChainInfoContainer>
|
||||
|
||||
<Flex1 />
|
||||
|
||||
<Checkbox checked={checked} />
|
||||
</ChainItemContainer>
|
||||
</AllChainsContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const AllChainsContainer = styled.div`
|
||||
width: 100%;
|
||||
|
||||
background-color: ${color.grey["900"]};
|
||||
`;
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useState } from "react";
|
||||
import Image, { ImageProps } from "next/image";
|
||||
|
||||
import ICNSLogo from "../../public/images/icns-logo-120x120.png";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const ChainImage = ({ src, ...props }: ImageProps) => {
|
||||
const [srcState, setSrcState] = useState(src);
|
||||
|
||||
return (
|
||||
<ImageWrapper>
|
||||
<Image
|
||||
{...props}
|
||||
src={srcState}
|
||||
alt="chain image"
|
||||
sizes="3rem"
|
||||
onError={() => setSrcState(ICNSLogo)}
|
||||
/>
|
||||
</ImageWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const ImageWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,147 @@
|
||||
import { ChainItemType, DisabledChainItemType } from "../../types";
|
||||
import { FunctionComponent, useEffect, useState } from "react";
|
||||
|
||||
import color from "../../styles/color";
|
||||
import { Flex1 } from "../../styles/flex-1";
|
||||
import styled from "styled-components";
|
||||
import { ChainImage } from "./chain-image";
|
||||
import { Checkbox } from "../checkbox";
|
||||
|
||||
interface Props {
|
||||
chainItem: ChainItemType | DisabledChainItemType;
|
||||
checkedItemHandler: (chainItem: ChainItemType, isChecked: boolean) => void;
|
||||
checkedItems: Set<unknown>;
|
||||
}
|
||||
|
||||
export const ChainItem: FunctionComponent<Props> = (props) => {
|
||||
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) {
|
||||
setChecked(!checked);
|
||||
checkedItemHandler(chainItem, !checked);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!disabled) {
|
||||
setChecked(checkedItems.has(chainItem));
|
||||
}
|
||||
}, [checkedItems]);
|
||||
|
||||
return (
|
||||
<ChainItemContainer
|
||||
key={chainItem.prefix}
|
||||
isLoading={false}
|
||||
disabled={disabled}
|
||||
checked={checked}
|
||||
onClick={checkHandler}
|
||||
>
|
||||
<ChainImage
|
||||
src={chainItem.chainImageUrl}
|
||||
fill={true}
|
||||
alt={`${chainItem.prefix} chain image`}
|
||||
/>
|
||||
<ChainInfoContainer>
|
||||
<ChainName>{`.${chainItem.prefix}`}</ChainName>
|
||||
{chainItem.address ? (
|
||||
<WalletAddress>{chainItem.address}</WalletAddress>
|
||||
) : null}
|
||||
{disabled && chainItem.reason ? (
|
||||
<DisabledReason>{chainItem.reason.message}</DisabledReason>
|
||||
) : null}
|
||||
</ChainInfoContainer>
|
||||
|
||||
<Flex1 />
|
||||
|
||||
<Checkbox checked={checked} />
|
||||
</ChainItemContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChainItemContainer = styled.div<{
|
||||
isLoading: boolean;
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
isSkeleton?: boolean;
|
||||
}>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
gap: 1rem;
|
||||
|
||||
padding: 1.5rem;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
opacity: ${(props) => (props.disabled ? "0.5" : "1")};
|
||||
|
||||
background-color: ${(props) =>
|
||||
props.disabled
|
||||
? color.black
|
||||
: props.checked
|
||||
? color.grey["800"]
|
||||
: props.isSkeleton
|
||||
? color.grey["800"]
|
||||
: color.grey["900"]};
|
||||
|
||||
&:hover {
|
||||
background: ${(props) => (props.isLoading ? null : color.grey["700"])};
|
||||
}
|
||||
`;
|
||||
|
||||
export const ChainInfoContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
export const ChainName = styled.div`
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
|
||||
color: ${color.white};
|
||||
`;
|
||||
|
||||
export const WalletAddress = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1rem;
|
||||
|
||||
max-height: 2rem;
|
||||
max-width: 27rem;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
|
||||
export const DisabledReason = styled.div`
|
||||
color: ${color.grey["200"]};
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
`;
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Dispatch, FunctionComponent, SetStateAction, useEffect } from "react";
|
||||
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: DisabledChainItemType[];
|
||||
checkedItems: Set<unknown>;
|
||||
setCheckedItems: Dispatch<SetStateAction<Set<unknown>>>;
|
||||
}
|
||||
|
||||
export const ChainList: FunctionComponent<Props> = (props) => {
|
||||
const { chainList, disabledChainList, checkedItems, setCheckedItems } = props;
|
||||
|
||||
const checkedItemHandler = (chainItem: ChainItemType, isChecked: boolean) => {
|
||||
const tempSet = new Set(checkedItems);
|
||||
|
||||
if (isChecked) {
|
||||
tempSet.add(chainItem);
|
||||
} else if (!isChecked && checkedItems.has(chainItem)) {
|
||||
tempSet.delete(chainItem);
|
||||
}
|
||||
|
||||
setCheckedItems(tempSet);
|
||||
};
|
||||
|
||||
return (
|
||||
<ChainContainer color={color.grey["900"]}>
|
||||
{chainList.map((chainItem) => (
|
||||
<ChainItem
|
||||
key={chainItem.chainId}
|
||||
chainItem={chainItem}
|
||||
checkedItemHandler={checkedItemHandler}
|
||||
checkedItems={checkedItems}
|
||||
/>
|
||||
))}
|
||||
{disabledChainList.map((chainItem) => (
|
||||
<ChainItem
|
||||
key={chainItem.chainId}
|
||||
chainItem={chainItem}
|
||||
checkedItemHandler={checkedItemHandler}
|
||||
checkedItems={checkedItems}
|
||||
/>
|
||||
))}
|
||||
</ChainContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const ChainContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
//max-height: 33rem;
|
||||
overflow-y: scroll;
|
||||
|
||||
flex: 1;
|
||||
|
||||
background-color: ${(props) => props.color};
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./chain-list";
|
||||
export * from "./chain-item";
|
||||
@@ -0,0 +1,70 @@
|
||||
import { FunctionComponent } from "react";
|
||||
import color from "../../styles/color";
|
||||
import styled from "styled-components";
|
||||
|
||||
export const Checkbox: FunctionComponent<{ checked: boolean }> = ({
|
||||
checked,
|
||||
}) => {
|
||||
return (
|
||||
<ChainCheckBoxContainer>
|
||||
<ChainCheckBoxHidden checked={checked} readOnly={true} />
|
||||
<ChainCheckBoxStyled checked={checked}>
|
||||
<Center>
|
||||
<Icon width="18" height="14" viewBox="0 0 15 12">
|
||||
<path
|
||||
strokeLinecap="square"
|
||||
strokeWidth="2"
|
||||
d="M2.25 4.426l4.083 5.011 6.417-7.874"
|
||||
/>
|
||||
</Icon>
|
||||
</Center>
|
||||
</ChainCheckBoxStyled>
|
||||
</ChainCheckBoxContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const Center = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const Icon = styled.svg`
|
||||
fill: none;
|
||||
stroke: ${color.orange["50"]};
|
||||
`;
|
||||
|
||||
const ChainCheckBoxContainer = styled.div`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
`;
|
||||
|
||||
const ChainCheckBoxStyled = styled.div<{ checked: boolean }>`
|
||||
display: inline-block;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
border: ${(props) => (props.checked ? "none" : "2px solid #868686")};
|
||||
background: ${(props) =>
|
||||
props.checked ? color.orange["200"] : color.grey["400"]};
|
||||
|
||||
${Icon} {
|
||||
visibility: ${(props) => (props.checked ? "visible" : "hidden")};
|
||||
}
|
||||
`;
|
||||
|
||||
const ChainCheckBoxHidden = styled.input.attrs({ type: "checkbox" })`
|
||||
border: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
`;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./modal";
|
||||
export * from "./wallet-item";
|
||||
@@ -0,0 +1,77 @@
|
||||
import { FunctionComponent } from "react";
|
||||
import ReactModal from "react-modal";
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
import { WalletList } from "../../constants/wallet";
|
||||
import { WalletItem } from "./wallet-item";
|
||||
|
||||
interface Props {
|
||||
isModalOpen: boolean;
|
||||
onCloseModal: () => void;
|
||||
}
|
||||
|
||||
export const ConnectWalletModal: FunctionComponent<Props> = (props) => {
|
||||
const { isModalOpen, onCloseModal } = props;
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={onCloseModal}
|
||||
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>Connect Wallet</ModalTitle>
|
||||
<ModalDescription>
|
||||
Plz check which account is selected after you connect it
|
||||
</ModalDescription>
|
||||
|
||||
{WalletList.map((walletItem) => {
|
||||
return <WalletItem wallet={walletItem} key={walletItem.name} />;
|
||||
})}
|
||||
</ModalContainer>
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
const ModalContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.9rem;
|
||||
padding: 2.2rem;
|
||||
|
||||
min-width: 28rem;
|
||||
`;
|
||||
|
||||
const ModalTitle = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.8rem;
|
||||
|
||||
color: ${color.white};
|
||||
`;
|
||||
|
||||
const ModalDescription = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.1rem;
|
||||
|
||||
color: ${color.grey["400"]};
|
||||
`;
|
||||
@@ -0,0 +1,144 @@
|
||||
import { FunctionComponent, useEffect, useState } from "react";
|
||||
import ArrowRightIcon from "../../public/images/svg/arrow-right.svg";
|
||||
import color from "../../styles/color";
|
||||
import { Flex1 } from "../../styles/flex-1";
|
||||
import styled from "styled-components";
|
||||
import Image from "next/image";
|
||||
import {
|
||||
MINIMUM_VERSION,
|
||||
SELECTED_WALLET_KEY,
|
||||
WALLET_INSTALL_URL,
|
||||
WalletType,
|
||||
} from "../../constants/wallet";
|
||||
import { getKeplrFromWindow, KeplrWallet } from "../../wallets";
|
||||
import { loginWithTwitter } from "../../queries";
|
||||
import {
|
||||
KEPLR_NOT_FOUND_ERROR,
|
||||
KEPLR_VERSION_ERROR,
|
||||
} from "../../constants/error-message";
|
||||
import semver from "semver/preload";
|
||||
|
||||
interface Props {
|
||||
wallet: WalletType;
|
||||
}
|
||||
|
||||
export const WalletItem: FunctionComponent<Props> = (props: Props) => {
|
||||
const { wallet } = props;
|
||||
const [isInstalled, setIsInstalled] = useState<boolean>();
|
||||
|
||||
useEffect(() => {
|
||||
setIsInstalled(!!window.keplr);
|
||||
}, []);
|
||||
|
||||
const onClickWalletItem = async () => {
|
||||
try {
|
||||
if (wallet.name === "Keplr") {
|
||||
await connectKeplr();
|
||||
localStorage.setItem(SELECTED_WALLET_KEY, wallet.name);
|
||||
}
|
||||
|
||||
await loginWithTwitter();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const connectKeplr = async () => {
|
||||
const keplr = await getKeplrFromWindow();
|
||||
|
||||
if (keplr === undefined) {
|
||||
window.location.href = WALLET_INSTALL_URL;
|
||||
throw new Error(KEPLR_NOT_FOUND_ERROR);
|
||||
}
|
||||
|
||||
if (semver.lt(keplr.version, MINIMUM_VERSION)) {
|
||||
throw new Error(KEPLR_VERSION_ERROR);
|
||||
}
|
||||
|
||||
if (keplr) {
|
||||
const wallet = new KeplrWallet(keplr);
|
||||
const chainIds = (await wallet.getChainInfosWithoutEndpoints()).map(
|
||||
(c) => c.chainId,
|
||||
);
|
||||
|
||||
await wallet.init(chainIds);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<WalletContainer isReady={wallet.isReady} onClick={onClickWalletItem}>
|
||||
<WalletIcon>
|
||||
<Image
|
||||
src={wallet.image}
|
||||
fill={true}
|
||||
sizes="3.75rem"
|
||||
alt="wallet Icon"
|
||||
/>
|
||||
</WalletIcon>
|
||||
<WalletContentContainer>
|
||||
<WalletName>{wallet.name}</WalletName>
|
||||
{wallet.isReady ? (
|
||||
isInstalled ? null : (
|
||||
<WalletDescription>Go to install Keplr Extension</WalletDescription>
|
||||
)
|
||||
) : (
|
||||
<WalletDescription>Comming soon</WalletDescription>
|
||||
)}
|
||||
</WalletContentContainer>
|
||||
|
||||
<Flex1 />
|
||||
|
||||
<Image src={ArrowRightIcon} alt="arrow right icon" />
|
||||
</WalletContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const WalletContainer = styled.div<{ isReady: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
height: 5.8rem;
|
||||
padding: 1rem;
|
||||
|
||||
background-color: ${color.grey["600"]};
|
||||
opacity: ${(props) => (props.isReady ? 1 : 0.3)};
|
||||
|
||||
cursor: ${(props) => (props.isReady ? "pointer" : "default")};
|
||||
`;
|
||||
|
||||
const WalletIcon = styled.div`
|
||||
position: relative;
|
||||
|
||||
width: 3.75rem;
|
||||
height: 3.75rem;
|
||||
`;
|
||||
|
||||
const WalletContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
padding-left: 1rem;
|
||||
`;
|
||||
|
||||
const WalletName = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 1.3rem;
|
||||
line-height: 1.5rem;
|
||||
text-align: center;
|
||||
|
||||
color: ${color.white};
|
||||
`;
|
||||
|
||||
const WalletDescription = styled.div`
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.1rem;
|
||||
|
||||
color: ${color.grey["300"]};
|
||||
`;
|
||||
@@ -0,0 +1,43 @@
|
||||
import { captureException } from "@sentry/nextjs";
|
||||
import Link from "next/link";
|
||||
import { Component, ErrorInfo, ReactNode } from "react";
|
||||
|
||||
interface Props {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
class ErrorBoundary extends Component<Props, State> {
|
||||
public state: State = {
|
||||
hasError: false,
|
||||
};
|
||||
|
||||
public static getDerivedStateFromError(): State {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
console.error("Uncaught error:", error, errorInfo);
|
||||
|
||||
captureException(error);
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<h1>
|
||||
Oops.. there is something wrong.
|
||||
<br /> Please try again. <Link href="/">Go to home</Link>
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
@@ -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;
|
||||
`;
|
||||
@@ -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 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}
|
||||
`;
|
||||
@@ -6,15 +6,38 @@ import Link from "next/link";
|
||||
import LogoIcon from "../../public/images/svg/logo.svg";
|
||||
|
||||
// Styles
|
||||
import { LogoContainer } from "./styled";
|
||||
import styled from "styled-components";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
export const Logo: FunctionComponent = () => {
|
||||
return (
|
||||
<Link href="/">
|
||||
<LogoContainer>
|
||||
<Image src={LogoIcon} fill={true} alt="Home Logo" />
|
||||
<Image
|
||||
src={LogoIcon}
|
||||
fill={true}
|
||||
sizes="10rem"
|
||||
alt="Home Logo"
|
||||
priority
|
||||
/>
|
||||
</LogoContainer>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
export const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: absolute;
|
||||
width: 10rem;
|
||||
height: 5rem;
|
||||
|
||||
margin-top: 5rem;
|
||||
margin-left: 5rem;
|
||||
|
||||
@media screen and (max-width: 1280px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
// Styles
|
||||
import styled from "styled-components";
|
||||
|
||||
export const LogoContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: absolute;
|
||||
|
||||
width: 160px;
|
||||
height: 80px;
|
||||
|
||||
margin-top: 80px;
|
||||
margin-left: 80px;
|
||||
`;
|
||||
@@ -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,81 @@
|
||||
import { Dispatch, FunctionComponent, SetStateAction } from "react";
|
||||
|
||||
import Image from "next/image";
|
||||
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
|
||||
import SearchIcon from "../../public/images/svg/search-icon.svg";
|
||||
|
||||
interface Props {
|
||||
searchValue: string;
|
||||
setSearchValue: Dispatch<SetStateAction<string>>;
|
||||
}
|
||||
|
||||
export const SearchInput: FunctionComponent<Props> = (props) => {
|
||||
const { searchValue, setSearchValue } = props;
|
||||
|
||||
return (
|
||||
<SearchContainer>
|
||||
<SearchIconContainer>
|
||||
<Image src={SearchIcon} fill={true} sizes="1.3rem" alt="search icon" />
|
||||
</SearchIconContainer>
|
||||
|
||||
<SearchText
|
||||
type="text"
|
||||
placeholder="search"
|
||||
value={searchValue}
|
||||
onChange={(event) => {
|
||||
setSearchValue(event.target.value);
|
||||
}}
|
||||
/>
|
||||
</SearchContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
|
||||
height: 2.3rem;
|
||||
|
||||
padding: 0.6rem 1.4rem;
|
||||
|
||||
border-radius: 3rem;
|
||||
background-color: ${color.grey["700"]};
|
||||
`;
|
||||
|
||||
const SearchIconContainer = styled.div`
|
||||
position: relative;
|
||||
|
||||
width: 1.3rem;
|
||||
height: 1.3rem;
|
||||
`;
|
||||
|
||||
const SearchText = styled.input`
|
||||
border: none;
|
||||
|
||||
color: ${color.white};
|
||||
background-color: ${color.grey["700"]};
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
line-height: 1.2rem;
|
||||
|
||||
width: 5.5rem;
|
||||
transition: width 0.2s ease-out;
|
||||
|
||||
::placeholder,
|
||||
::-webkit-input-placeholder {
|
||||
color: ${color.grey["400"]};
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
|
||||
width: 10rem;
|
||||
}
|
||||
`;
|
||||
@@ -1,22 +1,24 @@
|
||||
import styled from "styled-components";
|
||||
import color from "../../styles/color";
|
||||
|
||||
export const PrimaryButton = styled.button`
|
||||
export const SecondaryButton = styled.button`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
border: none;
|
||||
|
||||
background-color: ${color.primary};
|
||||
padding: 11px 30px;
|
||||
|
||||
font-family: "Inter", serif;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
line-height: 20px;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
line-height: 1.025rem;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
|
||||
color: ${color.orange};
|
||||
color: ${color.grey["200"]};
|
||||
background-color: transparent;
|
||||
|
||||
cursor: pointer;
|
||||
`;
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from "./skeleton-animation";
|
||||
export * from "./skeleton-circle";
|
||||
export * from "./skeleton-text";
|
||||
|
||||
export * from "./skeleton-chain-list";
|
||||
|
||||
@@ -9,13 +9,13 @@ export const SkeletonAnimation = styled.div`
|
||||
${color.grey["400"]},
|
||||
transparent
|
||||
),
|
||||
linear-gradient(${color.grey["500"]}, ${color.grey["500"]}),
|
||||
linear-gradient(${color.grey["600"]}, ${color.grey["600"]}),
|
||||
radial-gradient(
|
||||
38px circle at 19px 19px,
|
||||
${color.grey["500"]} 50%,
|
||||
${color.grey["600"]} 50%,
|
||||
transparent 51%
|
||||
),
|
||||
linear-gradient(${color.grey["500"]}, ${color.grey["500"]});
|
||||
linear-gradient(${color.grey["600"]}, ${color.grey["600"]});
|
||||
background-repeat: no-repeat;
|
||||
background-size: 315px 250px, 315px 180px, 100px 100px, 225px 30px;
|
||||
background-position: -315px 0, 0 0, 0px 190px, 50px 195px;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user