add styles

This commit is contained in:
UserX 2023-08-09 15:32:19 +03:00
parent 3fc789bb02
commit e693462f0f
10 changed files with 309 additions and 238 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "cryptocurrency-converter", "name": "cryptocurrency-converter",
"version": "0.1.0", "version": "0.1.0",
"homepage": "https://dmitry220.github.io/crypto-converter", "homepage": "https://vbatischev1.github.io/crypto-converter",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@tanstack/react-query": "^5.0.0-beta.9", "@tanstack/react-query": "^5.0.0-beta.9",

View File

@ -1,8 +1,16 @@
body{
background: #e5e5e5;
}
.converter{ .converter{
max-width: 812px; max-width: 610px;
margin: 50px auto; margin: 50px auto;
padding: 0 15px; background: #fff;
padding: 15px;
border-radius: 10px;
&__head{ &__head{
font-weight: 700; font-weight: 700;
@ -26,6 +34,7 @@
} }
&__row{ &__row{
border-radius: 10px;
height: 100%; height: 100%;
font-size: 24px; font-size: 24px;
display: flex; display: flex;
@ -39,10 +48,11 @@
font-size: inherit; font-size: inherit;
} }
&_border{ &_border{
border: 3px solid gray;
} }
} }
&__crypto-name{ &__crypto-name{
height: 100%; height: 100%;
vertical-align: middle; vertical-align: middle;
@ -50,42 +60,52 @@
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 12px;
line-height: 15px;
justify-content: center; justify-content: center;
padding: 0 15px; 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; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
gap: 3px;
}
img{ img{
width: 50px; width: 40px;
height: 20px; height: 20px;
} }
} }
&__arrow{ &__arrow{
width: 50px;
height: 35px; height: 35px;
object-fit: contain; object-fit: contain;
cursor: pointer; cursor: pointer;
@media (max-width:768px) { width: 20px;
height: 50px; background: #999;
width: 50px; -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;
@media (max-width:576px) {
height: 25px;
width: 25px;
}
} }
&__input{ &__input{
border: 2px solid rgba(7, 28, 71, 0.12);
border-radius: 10px 0 0 10px;
height: 100%; height: 100%;
font-family: HelveticaNeue-Light,"Helvetica Neue Light",Helvetica,Arial,sans-serif; font-family: HelveticaNeue-Light,"Helvetica Neue Light",Helvetica,Arial,sans-serif;
font-size: inherit; font-size: inherit;
text-align: right; text-align: right;
width: 201px;
color: #000; color: #000;
border-width: 0 5px 0px 0px; position: relative;
border-color: rgb(107 107 107); top: 0;
padding: 0 10px 0 0;
outline: none;
@media (max-width:768px) { @media (max-width:768px) {
border-width: 0; border-width: 0;
} }
@ -95,33 +115,3 @@
font-size: 18px; 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;
}
}
}

View File

@ -1,51 +1,76 @@
import React, {ChangeEvent, useEffect, useRef, useState} from 'react'; import { ChangeEvent, useEffect, useRef, useState } from 'react';
import './App.scss'; import './App.scss';
import arrowSwap from '../src/assets/images/arrow-right-left.svg'
import btc from '../src/assets/images/btc.svg' import btc from '../src/assets/images/btc.svg'
import usdt from '../src/assets/images/usdt.svg' import usdt from '../src/assets/images/usdt.svg'
import eth from '../src/assets/images/eth.svg' import eth from '../src/assets/images/eth.svg'
import useOnClickOutside from './hooks/useOnClickOutside'; import useOnClickOutside from './hooks/useOnClickOutside';
import {SecondValuationItem} from './Components/SecondValutionItem/SecondValutionItem'; import { PopupConverter } from './Components/popup-converter/Popup-converter';
import {FirstValuationItem} from './Components/FirstValutionItem/FirstValutionItem'; import { convert } from '.';
import {convert} from '.'; import { IValutions } from './models/IValutions';
import {IValutions} from './models/IValutions';
function App() { function App() {
const valuations: IValutions[] = [ const valuations: IValutions[] = [
{ {
title: 'BTC', title: 'BTC',
img: btc img: btc,
alt: 'Bitcoin'
}, },
{ {
title: 'USDT', title: 'USDT',
img: usdt img: usdt,
alt: 'Tether'
}, },
{ {
title: 'ETH', title: 'ETH',
img: eth img: eth,
alt: 'Ethereum'
}, },
] ]
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [firstValue, setFirstValue] = useState<number>(0) const [firstValue, setFirstValue] = useState<string>('1')
const [secondValue, setSecondValue] = useState<number>(0) const [secondValue, setSecondValue] = useState<string>('1')
const [firstValuation, setFirstValuation] = useState<IValutions>(valuations[0]) const [firstValuation, setFirstValuation] = useState<IValutions>(valuations[0])
const [secondValuation, setSecondValuation] = useState<IValutions>(valuations[1]) const [secondValuation, setSecondValuation] = useState<IValutions>(valuations[1])
const [showFirstValuation, setShowFirstValuation] = useState<boolean>() const [showFirstValuation, setShowFirstValuation] = useState<boolean>()
const [showSecondValuation, setShowSecondValuation] = 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>) => { const handlerInputOne = (e: ChangeEvent<HTMLInputElement>) => {
setFirstValue(+e.target.value) regexInput(e)
getSecondValuation(+e.target.value) setFirstValue(e.target.value)
getSecondValuation(+e.target.value || 0)
} }
const handlerInputTwo = (e: ChangeEvent<HTMLInputElement>) => { const handlerInputTwo = (e: ChangeEvent<HTMLInputElement>) => {
setSecondValue(+e.target.value) regexInput(e)
getFirstValuation(+e.target.value) setSecondValue(e.target.value)
getFirstValuation(+e.target.value || 0)
} }
const openFirstListValuation = () => setShowFirstValuation(true) const openFirstListValuation = () => setShowFirstValuation(true)
@ -54,12 +79,12 @@ function App() {
const getSecondValuation = async (value: number) => { const getSecondValuation = async (value: number) => {
await convert.ready() await convert.ready()
setSecondValue(convert[firstValuation.title][secondValuation.title](value)) setSecondValue(convert[firstValuation.title][secondValuation.title](value) || 0)
} }
const getFirstValuation = async (value: number) => { const getFirstValuation = async (value: number) => {
await convert.ready() await convert.ready()
setFirstValue(convert[secondValuation.title][firstValuation.title](value)) setFirstValue(convert[secondValuation.title][firstValuation.title](value) || 0)
} }
const swap = () => { const swap = () => {
@ -74,77 +99,91 @@ function App() {
}); });
useEffect(() => { useEffect(() => {
getSecondValuation(firstValue) getSecondValuation(+firstValue)
}, [firstValuation]) }, [firstValuation])
useEffect(() => { useEffect(() => {
if(isSwap){ if (isSwap) {
setSwap(false) setSwap(false)
return return
} }
getFirstValuation(secondValue) getFirstValuation(+secondValue)
}, [secondValuation]) }, [secondValuation])
useEffect(() => {
const interval = setInterval(() => {
getSecondValuation(+firstValue)
}, 25000)
return () => clearInterval(interval)
}, [])
return ( return (
<div className="converter"> <div className="converter">
<div className="converter__head">
Тестовое задание. React. Typescript
</div>
<div className="converter__body"> <div className="converter__body">
{ }
<div className="converter__row converter__row_border"> <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}> <span className='converter__crypto-name' onClick={openFirstListValuation}>
<div className={'converter__crypto-name-title'}>
<img src={firstValuation.img} alt="" /> <img src={firstValuation.img} alt="" />
{firstValuation.title} {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> </span>
{showFirstValuation && <div className='converter__popup popup-converter' ref={ref}> {showFirstValuation && <div className='converter__popup popup-converter' ref={ref}>
{valuations.map((item, index) => {valuations.map((item, index) =>
<FirstValuationItem <PopupConverter
key={index} key={index}
item={item} item={item}
firstValuation={firstValuation.title} setValuation={setFirstValuation}
setSecondValuation={setSecondValuation} valuation={firstValuation.title}
secondValuation={secondValuation.title}
setFirstValuation={setFirstValuation}
/>)} />)}
</div> </div>
} }
</div> </div>
<div className="converter__row"> <div className="converter__row">
<img className='converter__arrow' src={arrowSwap} alt="" onClick={swap} /> <div className={'converter__arrow'} onClick={swap} />
</div> </div>
<div className="converter__row converter__row_border"> <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}> <span className='converter__crypto-name' onClick={openSecondListValuation}>
<div className={'converter__crypto-name-title'}>
<img src={secondValuation.img} alt="" /> <img src={secondValuation.img} alt="" />
{secondValuation.title} {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> </span>
{showSecondValuation && <div className='converter__popup popup-converter' ref={ref}> {showSecondValuation && <div className='converter__popup popup-converter' ref={ref}>
{valuations.map((item, index) => {valuations.map((item, index) =>
<SecondValuationItem <PopupConverter
key={index} key={index}
item={item} item={item}
firstValuation={firstValuation.title} valuation={secondValuation.title}
setSecondValuation={setSecondValuation} setValuation={setSecondValuation}
secondValuation={secondValuation.title}
setFirstValuation={setFirstValuation}
/>)} />)}
</div> </div>
} }
</div> </div>
</div> </div>
<footer className="converter__footer"> <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'} Данные носят ознакомительный характер {'\t'}
<b> <b>
{ {
new Date(convert.lastUpdated).toLocaleDateString() + ' ' + new Date(convert.lastUpdated).toLocaleDateString() + ' ' +
new Date(convert.lastUpdated).getHours() + ':' + new Date(convert.lastUpdated).getHours() + ':' +
new Date(convert.lastUpdated).getMinutes().toString().padStart(2,'0') new Date(convert.lastUpdated).getMinutes().toString().padStart(2, '0')
} }
</b> </b>
</footer> </footer>

View File

@ -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>
)
}

View File

@ -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>
)
}

View 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);
}
}
}

View 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>
)
}

View File

@ -8,7 +8,7 @@ import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
const CryptoConvert = require("crypto-convert").default; const CryptoConvert = require("crypto-convert").default;
export const convert = new CryptoConvert({ 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) fiatInterval: (60 * 1e3 * 60), //Fiat prices update interval (default every 1 hour)
calculateAverage: true, //Calculate the average crypto price from exchanges calculateAverage: true, //Calculate the average crypto price from exchanges
binance: true, //Use binance rates binance: true, //Use binance rates

View File

@ -3,8 +3,6 @@ import { IValutions } from "./IValutions";
export interface IListValuationsName { export interface IListValuationsName {
item: IValutions, item: IValutions,
setFirstValuation: Dispatch<IValutions>, setValuation: Dispatch<IValutions>,
setSecondValuation: Dispatch<IValutions>, valuation: string
secondValuation: string,
firstValuation: string
} }

View File

@ -1,4 +1,5 @@
export interface IValutions { export interface IValutions {
title: string, title: string,
img: string img: string,
alt?: string
} }