add styles
This commit is contained in:
parent
3fc789bb02
commit
e693462f0f
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cryptocurrency-converter",
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://dmitry220.github.io/crypto-converter",
|
||||
"homepage": "https://vbatischev1.github.io/crypto-converter",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.0.0-beta.9",
|
||||
|
80
src/App.scss
80
src/App.scss
@ -1,8 +1,16 @@
|
||||
body{
|
||||
background: #e5e5e5;
|
||||
}
|
||||
|
||||
|
||||
.converter{
|
||||
|
||||
max-width: 812px;
|
||||
max-width: 610px;
|
||||
margin: 50px auto;
|
||||
padding: 0 15px;
|
||||
background: #fff;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
|
||||
|
||||
&__head{
|
||||
font-weight: 700;
|
||||
@ -26,6 +34,7 @@
|
||||
}
|
||||
|
||||
&__row{
|
||||
border-radius: 10px;
|
||||
height: 100%;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
@ -39,10 +48,11 @@
|
||||
font-size: inherit;
|
||||
}
|
||||
&_border{
|
||||
border: 3px solid gray;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&__crypto-name{
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
@ -50,42 +60,52 @@
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
justify-content: center;
|
||||
padding: 0 15px;
|
||||
border-left: 2px solid rgba(7, 28, 71, 0.12);
|
||||
font-weight: 700;
|
||||
border: 2px solid rgba(7, 28, 71, 0.12);
|
||||
border-left: none;
|
||||
border-radius: 0 10px 10px 0;
|
||||
&-title{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
img{
|
||||
width: 50px;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&__arrow{
|
||||
width: 50px;
|
||||
height: 35px;
|
||||
object-fit: contain;
|
||||
cursor: pointer;
|
||||
@media (max-width:768px) {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
@media (max-width:576px) {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
}
|
||||
width: 20px;
|
||||
background: #999;
|
||||
-webkit-mask: url(//yastatic.net/s3/web4static/_/v2/static/media/Swap_16.c0236c02.svg) no-repeat center;
|
||||
mask: url(//yastatic.net/s3/web4static/_/v2/static/media/Swap_16.c0236c02.svg) no-repeat center;
|
||||
}
|
||||
|
||||
&__input{
|
||||
border: 2px solid rgba(7, 28, 71, 0.12);
|
||||
border-radius: 10px 0 0 10px;
|
||||
height: 100%;
|
||||
font-family: HelveticaNeue-Light,"Helvetica Neue Light",Helvetica,Arial,sans-serif;
|
||||
font-size: inherit;
|
||||
text-align: right;
|
||||
width: 201px;
|
||||
color: #000;
|
||||
border-width: 0 5px 0px 0px;
|
||||
border-color: rgb(107 107 107);
|
||||
position: relative;
|
||||
top: 0;
|
||||
padding: 0 10px 0 0;
|
||||
outline: none;
|
||||
@media (max-width:768px) {
|
||||
border-width: 0;
|
||||
}
|
||||
@ -95,33 +115,3 @@
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-converter{
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
inset: 75px auto auto 0;
|
||||
width: 95%;
|
||||
box-shadow: 0 4px 24px rgba(0,0,0,.25);
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
gap: 12px;
|
||||
border-radius: 5px;
|
||||
&__button{
|
||||
background: transparent;
|
||||
transition: 0.3s all ease;
|
||||
padding: 10px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
&_active{
|
||||
background: green;
|
||||
color: #fff;
|
||||
}
|
||||
&:hover{
|
||||
background: green;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
121
src/App.tsx
121
src/App.tsx
@ -1,51 +1,76 @@
|
||||
import React, {ChangeEvent, useEffect, useRef, useState} from 'react';
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||
import './App.scss';
|
||||
import arrowSwap from '../src/assets/images/arrow-right-left.svg'
|
||||
import btc from '../src/assets/images/btc.svg'
|
||||
import usdt from '../src/assets/images/usdt.svg'
|
||||
import eth from '../src/assets/images/eth.svg'
|
||||
import useOnClickOutside from './hooks/useOnClickOutside';
|
||||
import {SecondValuationItem} from './Components/SecondValutionItem/SecondValutionItem';
|
||||
import {FirstValuationItem} from './Components/FirstValutionItem/FirstValutionItem';
|
||||
import {convert} from '.';
|
||||
import {IValutions} from './models/IValutions';
|
||||
import { PopupConverter } from './Components/popup-converter/Popup-converter';
|
||||
import { convert } from '.';
|
||||
import { IValutions } from './models/IValutions';
|
||||
|
||||
function App() {
|
||||
|
||||
const valuations: IValutions[] = [
|
||||
{
|
||||
title: 'BTC',
|
||||
img: btc
|
||||
img: btc,
|
||||
alt: 'Bitcoin'
|
||||
},
|
||||
{
|
||||
title: 'USDT',
|
||||
img: usdt
|
||||
img: usdt,
|
||||
alt: 'Tether'
|
||||
},
|
||||
{
|
||||
title: 'ETH',
|
||||
img: eth
|
||||
img: eth,
|
||||
alt: 'Ethereum'
|
||||
},
|
||||
]
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [firstValue, setFirstValue] = useState<number>(0)
|
||||
const [secondValue, setSecondValue] = useState<number>(0)
|
||||
const [firstValue, setFirstValue] = useState<string>('1')
|
||||
const [secondValue, setSecondValue] = useState<string>('1')
|
||||
const [firstValuation, setFirstValuation] = useState<IValutions>(valuations[0])
|
||||
const [secondValuation, setSecondValuation] = useState<IValutions>(valuations[1])
|
||||
const [showFirstValuation, setShowFirstValuation] = useState<boolean>()
|
||||
const [showSecondValuation, setShowSecondValuation] = useState<boolean>()
|
||||
const [isSwap, setSwap] = useState(false)
|
||||
const [isSwap, setSwap] = useState(true)
|
||||
|
||||
|
||||
const regexInput = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
|
||||
let [_, sign, integer, decimals]: any = e.target.value.replace(/[^\d\.\-]/g, "")
|
||||
.replace(/(\..*?)\./g, "$1")
|
||||
.replace(/(.+)-/g, "$1")
|
||||
.match(/^(-?)(.*?)((?:\.\d*)?)$/);
|
||||
|
||||
let pos: number = Number(e.target.selectionStart) - 1;
|
||||
if (!integer && decimals) pos += 2;
|
||||
|
||||
if (integer || decimals) {
|
||||
integer = +integer;
|
||||
}
|
||||
|
||||
const formatted = sign + integer + decimals;
|
||||
|
||||
if (formatted !== e.target.value) {
|
||||
e.target.value = formatted;
|
||||
e.target.setSelectionRange(pos, pos);
|
||||
}
|
||||
}
|
||||
|
||||
const handlerInputOne = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setFirstValue(+e.target.value)
|
||||
getSecondValuation(+e.target.value)
|
||||
regexInput(e)
|
||||
setFirstValue(e.target.value)
|
||||
getSecondValuation(+e.target.value || 0)
|
||||
}
|
||||
|
||||
const handlerInputTwo = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setSecondValue(+e.target.value)
|
||||
getFirstValuation(+e.target.value)
|
||||
regexInput(e)
|
||||
setSecondValue(e.target.value)
|
||||
getFirstValuation(+e.target.value || 0)
|
||||
}
|
||||
|
||||
const openFirstListValuation = () => setShowFirstValuation(true)
|
||||
@ -54,12 +79,12 @@ function App() {
|
||||
|
||||
const getSecondValuation = async (value: number) => {
|
||||
await convert.ready()
|
||||
setSecondValue(convert[firstValuation.title][secondValuation.title](value))
|
||||
setSecondValue(convert[firstValuation.title][secondValuation.title](value) || 0)
|
||||
|
||||
}
|
||||
const getFirstValuation = async (value: number) => {
|
||||
await convert.ready()
|
||||
setFirstValue(convert[secondValuation.title][firstValuation.title](value))
|
||||
setFirstValue(convert[secondValuation.title][firstValuation.title](value) || 0)
|
||||
}
|
||||
|
||||
const swap = () => {
|
||||
@ -74,77 +99,91 @@ function App() {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
getSecondValuation(firstValue)
|
||||
getSecondValuation(+firstValue)
|
||||
}, [firstValuation])
|
||||
|
||||
useEffect(() => {
|
||||
if(isSwap){
|
||||
if (isSwap) {
|
||||
setSwap(false)
|
||||
return
|
||||
}
|
||||
getFirstValuation(secondValue)
|
||||
getFirstValuation(+secondValue)
|
||||
}, [secondValuation])
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
getSecondValuation(+firstValue)
|
||||
}, 25000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<div className="converter">
|
||||
<div className="converter__head">
|
||||
Тестовое задание. React. Typescript
|
||||
</div>
|
||||
<div className="converter__body">
|
||||
{ }
|
||||
<div className="converter__row converter__row_border">
|
||||
<input className='converter__input' min={0} type="number" value={firstValue} onChange={handlerInputOne} />
|
||||
<input className='converter__input' type="text" value={firstValue}
|
||||
onChange={handlerInputOne} />
|
||||
<span className='converter__crypto-name' onClick={openFirstListValuation}>
|
||||
<div className={'converter__crypto-name-title'}>
|
||||
<img src={firstValuation.img} alt="" />
|
||||
{firstValuation.title}
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" width="8" height="14"><path
|
||||
d="M4 0l4 6H0l4-6zm0 14l4-6H0l4 6z" /></svg>
|
||||
</div>
|
||||
</span>
|
||||
{showFirstValuation && <div className='converter__popup popup-converter' ref={ref}>
|
||||
{valuations.map((item, index) =>
|
||||
<FirstValuationItem
|
||||
<PopupConverter
|
||||
key={index}
|
||||
item={item}
|
||||
firstValuation={firstValuation.title}
|
||||
setSecondValuation={setSecondValuation}
|
||||
secondValuation={secondValuation.title}
|
||||
setFirstValuation={setFirstValuation}
|
||||
setValuation={setFirstValuation}
|
||||
valuation={firstValuation.title}
|
||||
|
||||
/>)}
|
||||
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div className="converter__row">
|
||||
<img className='converter__arrow' src={arrowSwap} alt="" onClick={swap} />
|
||||
<div className={'converter__arrow'} onClick={swap} />
|
||||
</div>
|
||||
<div className="converter__row converter__row_border">
|
||||
<input className='converter__input' min={0} type="number" value={secondValue} onChange={handlerInputTwo} />
|
||||
<input className='converter__input' min={0} type="text" maxLength={20} value={secondValue}
|
||||
onChange={handlerInputTwo} />
|
||||
<span className='converter__crypto-name' onClick={openSecondListValuation}>
|
||||
<div className={'converter__crypto-name-title'}>
|
||||
<img src={secondValuation.img} alt="" />
|
||||
{secondValuation.title}
|
||||
</div>
|
||||
<div>
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" width="8" height="14"><path
|
||||
d="M4 0l4 6H0l4-6zm0 14l4-6H0l4 6z" /></svg>
|
||||
</div>
|
||||
</span>
|
||||
{showSecondValuation && <div className='converter__popup popup-converter' ref={ref}>
|
||||
{valuations.map((item, index) =>
|
||||
<SecondValuationItem
|
||||
<PopupConverter
|
||||
key={index}
|
||||
item={item}
|
||||
firstValuation={firstValuation.title}
|
||||
setSecondValuation={setSecondValuation}
|
||||
secondValuation={secondValuation.title}
|
||||
setFirstValuation={setFirstValuation}
|
||||
valuation={secondValuation.title}
|
||||
setValuation={setSecondValuation}
|
||||
/>)}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<footer className="converter__footer">
|
||||
<b>{(firstValue || 0) + ' ' + firstValuation.title + ' = ' + (secondValue || 0) + ' ' + secondValuation.title}</b> <br />
|
||||
<b>{(firstValue || 0) + ' ' + firstValuation.title + ' = ' + (secondValue || 0) + ' ' + secondValuation.title}</b>
|
||||
<br />
|
||||
Данные носят ознакомительный характер {'\t'}
|
||||
<b>
|
||||
{
|
||||
new Date(convert.lastUpdated).toLocaleDateString() + ' ' +
|
||||
new Date(convert.lastUpdated).getHours() + ':' +
|
||||
new Date(convert.lastUpdated).getMinutes().toString().padStart(2,'0')
|
||||
new Date(convert.lastUpdated).getMinutes().toString().padStart(2, '0')
|
||||
}
|
||||
</b>
|
||||
</footer>
|
||||
|
@ -1,28 +0,0 @@
|
||||
import { Dispatch, FC, MouseEvent } from "react"
|
||||
import { IListValuationsName } from "../../models/IListValutionsName"
|
||||
|
||||
|
||||
|
||||
export const FirstValuationItem: FC<IListValuationsName> = (
|
||||
{
|
||||
item,
|
||||
firstValuation,
|
||||
secondValuation,
|
||||
setFirstValuation,
|
||||
setSecondValuation
|
||||
}
|
||||
) => {
|
||||
|
||||
return (
|
||||
<button type='button'
|
||||
className={firstValuation != item.title ? 'popup-converter__button' : 'popup-converter__button popup-converter__button_active'}
|
||||
value={item.title}
|
||||
onClick={(e: MouseEvent<HTMLButtonElement>) => {
|
||||
setFirstValuation({
|
||||
img: item.img,
|
||||
title: e.currentTarget.value
|
||||
})
|
||||
}}>{item.title}</button>
|
||||
)
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { Dispatch, FC, MouseEvent } from "react"
|
||||
import { IListValuationsName } from "../../models/IListValutionsName"
|
||||
|
||||
|
||||
export const SecondValuationItem: FC<IListValuationsName> = ({
|
||||
item,
|
||||
firstValuation,
|
||||
secondValuation,
|
||||
setFirstValuation,
|
||||
setSecondValuation
|
||||
}
|
||||
) => {
|
||||
|
||||
return (
|
||||
<button type='button'
|
||||
className={secondValuation != item.title ? 'popup-converter__button' : 'popup-converter__button popup-converter__button_active'}
|
||||
value={item.title} onClick={(e: MouseEvent<HTMLButtonElement>) => {
|
||||
setSecondValuation({
|
||||
img: item.img,
|
||||
title: e.currentTarget.value
|
||||
})
|
||||
|
||||
}}>{item.title}</button>
|
||||
)
|
||||
}
|
54
src/Components/popup-converter/Popup-converter.scss
Normal file
54
src/Components/popup-converter/Popup-converter.scss
Normal file
@ -0,0 +1,54 @@
|
||||
.popup-converter{
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
inset: 75px auto auto 0;
|
||||
width: 100%;
|
||||
box-shadow: 0 4px 24px rgba(0,0,0,.25);
|
||||
background: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 0;
|
||||
border-radius: 5px;
|
||||
&__container{
|
||||
padding: 4px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
background: #ffeca1;
|
||||
}
|
||||
}
|
||||
&__check{
|
||||
display: flex;
|
||||
flex: 0 0 16px;
|
||||
}
|
||||
&__body{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
&__img{
|
||||
flex: 0 0 20px;
|
||||
height: 20px;
|
||||
img{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
&__button{
|
||||
background: transparent;
|
||||
transition: 0.3s all ease;
|
||||
// padding: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
span{
|
||||
margin: 0 4px;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: rgba(84,96,122,.68);
|
||||
}
|
||||
}
|
||||
}
|
42
src/Components/popup-converter/Popup-converter.tsx
Normal file
42
src/Components/popup-converter/Popup-converter.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import {Dispatch, FC, MouseEvent} from "react"
|
||||
import {IListValuationsName} from "../../models/IListValutionsName"
|
||||
import './Popup-converter.scss'
|
||||
|
||||
|
||||
export const PopupConverter: FC<IListValuationsName> = (
|
||||
{
|
||||
item,
|
||||
valuation,
|
||||
setValuation,
|
||||
}
|
||||
) => {
|
||||
|
||||
const setActiveValuation = (e: MouseEvent<HTMLDivElement>) => {
|
||||
setValuation({
|
||||
img: item.img,
|
||||
title: item.title
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'popup-converter__container'} onClick={setActiveValuation}>
|
||||
<div className={'popup-converter__check'}>
|
||||
{valuation === item.title &&
|
||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" width="16" height="10">
|
||||
<path d="M7.207 7.506L3.629 3.81 2.343 4.939l4.841 5.002 8.462-8.428L14.382.362z"/>
|
||||
</svg>}
|
||||
</div>
|
||||
<div className={'popup-converter__body'}>
|
||||
<img className={'popup-converter__img'} src={item.img} alt="icon"/>
|
||||
<button type='button'
|
||||
className={'popup-converter__button'}
|
||||
value={item.title}
|
||||
>{item.title} <span>{item.alt}</span></button>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
|
||||
const CryptoConvert = require("crypto-convert").default;
|
||||
|
||||
export const convert = new CryptoConvert({
|
||||
cryptoInterval: 600000, //Crypto prices update interval in ms (default 5 seconds on Node.js & 15 seconds on Browsers)
|
||||
cryptoInterval: 20000, //Crypto prices update interval in ms (default 5 seconds on Node.js & 15 seconds on Browsers)
|
||||
fiatInterval: (60 * 1e3 * 60), //Fiat prices update interval (default every 1 hour)
|
||||
calculateAverage: true, //Calculate the average crypto price from exchanges
|
||||
binance: true, //Use binance rates
|
||||
|
@ -3,8 +3,6 @@ import { IValutions } from "./IValutions";
|
||||
|
||||
export interface IListValuationsName {
|
||||
item: IValutions,
|
||||
setFirstValuation: Dispatch<IValutions>,
|
||||
setSecondValuation: Dispatch<IValutions>,
|
||||
secondValuation: string,
|
||||
firstValuation: string
|
||||
setValuation: Dispatch<IValutions>,
|
||||
valuation: string
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
export interface IValutions {
|
||||
title: string,
|
||||
img: string
|
||||
img: string,
|
||||
alt?: string
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user