refactoring
108
src/App.scss
@ -1,111 +1,3 @@
|
||||
body {
|
||||
background: #e5e5e5;
|
||||
}
|
||||
|
||||
.converter {
|
||||
max-width: 630px;
|
||||
margin: 50px auto;
|
||||
background: #fff;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
|
||||
&__head {
|
||||
font-weight: 700;
|
||||
font-size: 25px;
|
||||
text-align: center;
|
||||
margin: 0 0 50px 0;
|
||||
}
|
||||
|
||||
&__body {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 0 0 25px 0;
|
||||
justify-content: center;
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__row {
|
||||
border-radius: 10px;
|
||||
height: 100%;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
@media (max-width: 908px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&__crypto-name {
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
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: 40px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
height: 35px;
|
||||
object-fit: contain;
|
||||
cursor: pointer;
|
||||
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;
|
||||
position: relative;
|
||||
top: 0;
|
||||
padding: 0 10px 0 0;
|
||||
outline: none;
|
||||
@media (max-width: 768px) {
|
||||
border-width: 0;
|
||||
}
|
||||
}
|
||||
&__footer {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
232
src/App.tsx
@ -1,237 +1,9 @@
|
||||
import { ChangeEvent, useEffect, useRef, useState } from 'react'
|
||||
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 { PopupConverter } from './Components/popup-converter/Popup-converter'
|
||||
import { convert } from '.'
|
||||
import { IValutions } from './models/IValutions'
|
||||
import { Converter } from './Components/Converter/Converter'
|
||||
|
||||
import './App.scss'
|
||||
|
||||
function App() {
|
||||
const valuations: IValutions[] = [
|
||||
{
|
||||
title: 'BTC',
|
||||
img: btc,
|
||||
alt: 'Bitcoin'
|
||||
},
|
||||
{
|
||||
title: 'USDT',
|
||||
img: usdt,
|
||||
alt: 'Tether'
|
||||
},
|
||||
{
|
||||
title: 'ETH',
|
||||
img: eth,
|
||||
alt: 'Ethereum'
|
||||
}
|
||||
]
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
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(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>) => {
|
||||
regexInput(e)
|
||||
setFirstValue(e.target.value)
|
||||
getSecondValuation(+e.target.value || 0)
|
||||
}
|
||||
|
||||
const handlerInputTwo = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
regexInput(e)
|
||||
setSecondValue(e.target.value)
|
||||
getFirstValuation(+e.target.value || 0)
|
||||
}
|
||||
|
||||
const openFirstListValuation = () => setShowFirstValuation(true)
|
||||
|
||||
const openSecondListValuation = () => setShowSecondValuation(true)
|
||||
|
||||
const getSecondValuation = async (value: number) => {
|
||||
await convert.ready()
|
||||
setSecondValue(
|
||||
convert[firstValuation.title][secondValuation.title](value) || 0
|
||||
)
|
||||
}
|
||||
const getFirstValuation = async (value: number) => {
|
||||
await convert.ready()
|
||||
setFirstValue(
|
||||
convert[secondValuation.title][firstValuation.title](value) || 0
|
||||
)
|
||||
}
|
||||
|
||||
const swap = () => {
|
||||
setSwap(true)
|
||||
setSecondValuation(firstValuation)
|
||||
setFirstValuation(secondValuation)
|
||||
}
|
||||
|
||||
useOnClickOutside(ref, () => {
|
||||
setShowSecondValuation(false)
|
||||
setShowFirstValuation(false)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
getSecondValuation(+firstValue)
|
||||
}, [firstValuation])
|
||||
|
||||
useEffect(() => {
|
||||
if (isSwap) {
|
||||
setSwap(false)
|
||||
return
|
||||
}
|
||||
getFirstValuation(+secondValue)
|
||||
}, [secondValuation])
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
getSecondValuation(+firstValue)
|
||||
}, 25000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const getValuations = () =>
|
||||
`${firstValue || 0} ${firstValuation.title} = ${secondValue || 0} ${
|
||||
secondValuation.title
|
||||
}`
|
||||
|
||||
return (
|
||||
<div className='converter'>
|
||||
<div className='converter__body'>
|
||||
<div className='converter__row converter__row_border'>
|
||||
<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) => (
|
||||
<PopupConverter
|
||||
key={index}
|
||||
item={item}
|
||||
setValuation={setFirstValuation}
|
||||
valuation={firstValuation.title}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='converter__row'>
|
||||
<div className={'converter__arrow'} onClick={swap} />
|
||||
</div>
|
||||
<div className='converter__row converter__row_border'>
|
||||
<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) => (
|
||||
<PopupConverter
|
||||
key={index}
|
||||
item={item}
|
||||
valuation={secondValuation.title}
|
||||
setValuation={setSecondValuation}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<footer className='converter__footer'>
|
||||
<b>{getValuations()}</b>
|
||||
<br />
|
||||
Данные носят ознакомительный характер {'\t'}
|
||||
<b>
|
||||
{new Date(convert.lastUpdated).toLocaleDateString() +
|
||||
' ' +
|
||||
new Date(convert.lastUpdated).getHours() +
|
||||
':' +
|
||||
new Date(convert.lastUpdated)
|
||||
.getMinutes()
|
||||
.toString()
|
||||
.padStart(2, '0')}
|
||||
</b>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
return <Converter />
|
||||
}
|
||||
|
||||
export default App
|
||||
|
111
src/Components/Converter/Converter.scss
Normal file
@ -0,0 +1,111 @@
|
||||
.converter {
|
||||
max-width: 640px;
|
||||
margin: 50px auto;
|
||||
background: #fff;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
|
||||
&__head {
|
||||
font-weight: 700;
|
||||
font-size: 25px;
|
||||
text-align: center;
|
||||
margin: 0 0 50px 0;
|
||||
}
|
||||
|
||||
&__body {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 0 0 25px 0;
|
||||
justify-content: center;
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__row {
|
||||
border-radius: 10px;
|
||||
height: 100%;
|
||||
font-size: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
@media (max-width: 908px) {
|
||||
font-size: 20px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&__crypto-name {
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
justify-content: center;
|
||||
padding: 0 15px;
|
||||
font-weight: 700;
|
||||
border: 2px solid rgba(7, 28, 71, 0.12);
|
||||
border-left: none;
|
||||
border-radius: 0 10px 10px 0;
|
||||
gap: 5px;
|
||||
&-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&__crypto-icon {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
height: 35px;
|
||||
object-fit: contain;
|
||||
cursor: pointer;
|
||||
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;
|
||||
position: relative;
|
||||
top: 0;
|
||||
padding: 0 10px 0 0;
|
||||
outline: none;
|
||||
@media (max-width: 768px) {
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
&__footer {
|
||||
font-size: 14px;
|
||||
}
|
||||
&__title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
108
src/Components/Converter/Converter.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { IValutions } from '../../models/IValutions'
|
||||
import { valuations } from '../../utils/Constants'
|
||||
import { ConverterInput } from './ConverterInput/ConverterInput'
|
||||
import { actualDate, getRelationToCurrency } from '../../utils/HelperFunctions'
|
||||
|
||||
import './Converter.scss'
|
||||
|
||||
export const Converter = () => {
|
||||
const [isFocus, setFocus] = useState<1 | 2>(1)
|
||||
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 swap = () => {
|
||||
setSecondValuation(firstValuation)
|
||||
setFirstValuation(secondValuation)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isFocus === 1) {
|
||||
getRelationToCurrency(
|
||||
firstValuation.title,
|
||||
secondValuation.title,
|
||||
+firstValue,
|
||||
setSecondValue
|
||||
)
|
||||
} else if (isFocus === 2) {
|
||||
getRelationToCurrency(
|
||||
secondValuation.title,
|
||||
firstValuation.title,
|
||||
+secondValue,
|
||||
setFirstValue
|
||||
)
|
||||
}
|
||||
}, [isFocus, firstValue, firstValuation, secondValue, secondValuation])
|
||||
|
||||
useEffect(() => {
|
||||
if (firstValuation.title === secondValuation.title) {
|
||||
let index = valuations.findIndex(
|
||||
(item) => item.title !== firstValuation.title
|
||||
)
|
||||
setSecondValuation(valuations[index])
|
||||
}
|
||||
}, [firstValuation])
|
||||
|
||||
useEffect(() => {
|
||||
if (secondValuation.title === firstValuation.title) {
|
||||
let index = valuations.findIndex(
|
||||
(item) => item.title !== secondValuation.title
|
||||
)
|
||||
setFirstValuation(valuations[index])
|
||||
}
|
||||
}, [secondValuation])
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
getRelationToCurrency(
|
||||
firstValuation.title,
|
||||
secondValuation.title,
|
||||
+firstValue,
|
||||
setSecondValue
|
||||
)
|
||||
}, 25000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
const outputResult = () =>
|
||||
`${firstValue || 0} ${firstValuation.title} = ${secondValue || 0} ${
|
||||
secondValuation.title
|
||||
}`
|
||||
|
||||
return (
|
||||
<div className='converter'>
|
||||
<div className='converter__body'>
|
||||
<ConverterInput
|
||||
valuation={firstValuation}
|
||||
value={firstValue}
|
||||
setValuation={setFirstValuation}
|
||||
setValue={setFirstValue}
|
||||
index={1}
|
||||
setFocus={setFocus}
|
||||
/>
|
||||
<div className='converter__row'>
|
||||
<div className={'converter__arrow'} onClick={swap} />
|
||||
</div>
|
||||
<ConverterInput
|
||||
valuation={secondValuation}
|
||||
value={secondValue}
|
||||
setValuation={setSecondValuation}
|
||||
setValue={setSecondValue}
|
||||
index={2}
|
||||
setFocus={setFocus}
|
||||
/>
|
||||
</div>
|
||||
<footer className='converter__footer'>
|
||||
<div className='converter__title'>{outputResult()}</div>
|
||||
Данные носят ознакомительный характер · {'\t'}
|
||||
<b>{actualDate()}</b>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
87
src/Components/Converter/ConverterInput/ConverterInput.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import { ChangeEvent, Dispatch, FC, useEffect, useRef, useState } from 'react'
|
||||
import { ConverterPopup } from '../ConverterPopup/ConverterPopup'
|
||||
import arrowDoubleVertical from '../../../assets/images/arrow-double-vertical.svg'
|
||||
import { IValutions } from '../../../models/IValutions'
|
||||
import { valuations } from '../../../utils/Constants'
|
||||
import useOnClickOutside from '../../../hooks/useOnClickOutside'
|
||||
import { regexInput } from '../../../utils/HelperFunctions'
|
||||
|
||||
interface IConverterInput {
|
||||
index: 1 | 2
|
||||
value: string
|
||||
setValue: Dispatch<string>
|
||||
valuation: IValutions
|
||||
setValuation: Dispatch<IValutions>
|
||||
setFocus: Dispatch<1 | 2>
|
||||
}
|
||||
|
||||
export const ConverterInput: FC<IConverterInput> = ({
|
||||
index,
|
||||
setValue,
|
||||
value,
|
||||
valuation,
|
||||
setValuation,
|
||||
setFocus
|
||||
}) => {
|
||||
const [showPopup, setShowPopup] = useState<boolean>()
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
const handlerInput = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
regexInput(e)
|
||||
setValue(e.target.value)
|
||||
}
|
||||
|
||||
useOnClickOutside(ref, () => {
|
||||
setShowPopup(false)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setShowPopup(false)
|
||||
}, [valuation])
|
||||
|
||||
return (
|
||||
<div className='converter__row converter__row_border'>
|
||||
<input
|
||||
className='converter__input'
|
||||
type='text'
|
||||
maxLength={20}
|
||||
value={value}
|
||||
onFocus={() => setFocus(index)}
|
||||
onChange={handlerInput}
|
||||
/>
|
||||
<span
|
||||
className='converter__crypto-name'
|
||||
onClick={() => setShowPopup((prev) => !prev)}
|
||||
>
|
||||
<div className={'converter__crypto-name-title'}>
|
||||
<img
|
||||
src={valuation.img}
|
||||
alt=''
|
||||
className={'converter__crypto-icon'}
|
||||
/>
|
||||
{valuation.title}
|
||||
</div>
|
||||
<div>
|
||||
<img
|
||||
src={arrowDoubleVertical}
|
||||
alt=''
|
||||
className={'converter__crypto-name-arrow'}
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
{showPopup && (
|
||||
<div className='converter-popup' ref={ref}>
|
||||
{valuations.map((item, index) => (
|
||||
<ConverterPopup
|
||||
key={index}
|
||||
item={item}
|
||||
valuation={valuation}
|
||||
setValuation={setValuation}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
.popup-converter {
|
||||
.converter-popup {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
inset: 75px auto auto 0;
|
||||
@ -37,6 +37,8 @@
|
||||
}
|
||||
}
|
||||
&__button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
transition: 0.3s all ease;
|
||||
// padding: 10px;
|
36
src/Components/Converter/ConverterPopup/ConverterPopup.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { FC, MouseEvent } from 'react'
|
||||
import { IListValuationsName } from '../../../models/IListValutionsName'
|
||||
import checkIcon from '../../../assets/images/check.svg'
|
||||
|
||||
import './ConverterPopup.scss'
|
||||
|
||||
export const ConverterPopup: FC<IListValuationsName> = ({
|
||||
item,
|
||||
valuation,
|
||||
setValuation
|
||||
}) => {
|
||||
const setActiveValuation = (e: MouseEvent<HTMLDivElement>) => {
|
||||
setValuation({
|
||||
img: item.img,
|
||||
title: item.title
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={'converter-popup__container'} onClick={setActiveValuation}>
|
||||
<div className={'converter-popup__check'}>
|
||||
{valuation.title === item.title && <img src={checkIcon} alt='check' />}
|
||||
</div>
|
||||
<div className={'converter-popup__body'}>
|
||||
<img src={item.img} alt='' className={'converter-popup__img'} />
|
||||
<button
|
||||
type='button'
|
||||
className={'converter-popup__button'}
|
||||
value={item.title}
|
||||
>
|
||||
{item.title} <span>{item.alt}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
import { 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>
|
||||
)
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.2427 5.6568C16.6332 5.26627 16.6332 4.63311 16.2427 4.24258L13.4142 1.41416C12.6332 0.633107 11.3669 0.633107 10.5858 1.41416L7.75737 4.24268C7.36685 4.6332 7.36685 5.26637 7.75737 5.65689C8.1479 6.04741 8.78106 6.04741 9.17158 5.65689L11 3.82847V8.99998C11 9.55227 11.4477 9.99998 12 9.99998C12.5523 9.99998 13 9.55227 13 8.99998V3.82835L14.8284 5.6568C15.219 6.04732 15.8521 6.04732 16.2427 5.6568Z" fill="#0F0F0F"/>
|
||||
<path d="M7.7574 18.3429C7.36688 18.7334 7.36688 19.3666 7.7574 19.7571L10.5858 22.5856C11.3669 23.3666 12.6332 23.3668 13.4143 22.5857L16.2427 19.7573C16.6332 19.3668 16.6332 18.7336 16.2427 18.3431C15.8522 17.9526 15.219 17.9526 14.8285 18.3431L13 20.1716L13 15C13 14.4477 12.5523 14 12 14C11.4477 14 11 14.4477 11 15L11 20.1713L9.17161 18.3429C8.78109 17.9524 8.14792 17.9524 7.7574 18.3429Z" fill="#0F0F0F"/>
|
||||
<svg
|
||||
focusable='false'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width='8'
|
||||
height='14'
|
||||
>
|
||||
<path d='M4 0l4 6H0l4-6zm0 14l4-6H0l4 6z'/>
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 175 B |
@ -1,8 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<circle cx="16" cy="16" r="16" fill="#F7931A"/>
|
||||
<path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
|
||||
<circle cx="16" cy="16" r="16" fill="#F7931A"/>
|
||||
|
||||
<path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/>
|
||||
|
||||
</g>
|
||||
|
||||
</svg>
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
8
src/assets/images/check.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<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>
|
After Width: | Height: | Size: 216 B |
@ -1,15 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<circle cx="16" cy="16" r="16" fill="#627EEA"/>
|
||||
<g fill="#FFF" fill-rule="nonzero">
|
||||
<path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/>
|
||||
<path d="M16.498 4L9 16.22l7.498-3.35z"/>
|
||||
<path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/>
|
||||
<path d="M16.498 27.995v-6.028L9 17.616z"/>
|
||||
<path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/>
|
||||
<path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
|
||||
<circle cx="16" cy="16" r="16" fill="#627EEA"/>
|
||||
|
||||
<g fill="#FFF" fill-rule="nonzero">
|
||||
|
||||
<path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/>
|
||||
|
||||
<path d="M16.498 4L9 16.22l7.498-3.35z"/>
|
||||
|
||||
<path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/>
|
||||
|
||||
<path d="M16.498 27.995v-6.028L9 17.616z"/>
|
||||
|
||||
<path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/>
|
||||
|
||||
<path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/>
|
||||
|
||||
</g>
|
||||
|
||||
</g>
|
||||
|
||||
</svg>
|
Before Width: | Height: | Size: 699 B After Width: | Height: | Size: 699 B |
@ -1,8 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<circle cx="16" cy="16" r="16" fill="#26A17B"/>
|
||||
<path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
|
||||
<circle cx="16" cy="16" r="16" fill="#26A17B"/>
|
||||
|
||||
<path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/>
|
||||
|
||||
</g>
|
||||
|
||||
</svg>
|
Before Width: | Height: | Size: 808 B After Width: | Height: | Size: 808 B |
@ -1,21 +1,14 @@
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import './index.css'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
const CryptoConvert = require('crypto-convert').default
|
||||
import App from './App'
|
||||
import reportWebVitals from './reportWebVitals'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
|
||||
const CryptoConvert = require('crypto-convert').default
|
||||
import { convertConfig } from './utils/Constants'
|
||||
|
||||
export const convert = new CryptoConvert({
|
||||
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
|
||||
bitfinex: true, //Use bitfinex rates
|
||||
coinbase: true, //Use coinbase rates
|
||||
kraken: true, //Use kraken rates
|
||||
HTTPAgent: null //HTTP Agent for server-side proxies (Node.js only)
|
||||
})
|
||||
import './index.css'
|
||||
|
||||
export const convert = new CryptoConvert(convertConfig)
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||
const queryClient = new QueryClient()
|
||||
|
@ -4,5 +4,5 @@ import { IValutions } from './IValutions'
|
||||
export interface IListValuationsName {
|
||||
item: IValutions
|
||||
setValuation: Dispatch<IValutions>
|
||||
valuation: string
|
||||
valuation: IValutions
|
||||
}
|
||||
|
34
src/utils/Constants.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { IValutions } from '../models/IValutions'
|
||||
|
||||
import btc from '../assets/images/btc.svg'
|
||||
import usdt from '../assets/images/usdt.svg'
|
||||
import eth from '../assets/images/eth.svg'
|
||||
|
||||
export const valuations: IValutions[] = [
|
||||
{
|
||||
title: 'BTC',
|
||||
img: btc,
|
||||
alt: 'Bitcoin'
|
||||
},
|
||||
{
|
||||
title: 'USDT',
|
||||
img: usdt,
|
||||
alt: 'Tether'
|
||||
},
|
||||
{
|
||||
title: 'ETH',
|
||||
img: eth,
|
||||
alt: 'Ethereum'
|
||||
}
|
||||
]
|
||||
|
||||
export const convertConfig = {
|
||||
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
|
||||
bitfinex: true, // Use bitfinex rates
|
||||
coinbase: true, // Use coinbase rates
|
||||
kraken: true, // Use kraken rates
|
||||
HTTPAgent: null // HTTP Agent for server-side proxies (Node.js only)
|
||||
}
|
45
src/utils/HelperFunctions.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { ChangeEvent, Dispatch } from 'react'
|
||||
import { convert } from '../index'
|
||||
|
||||
export 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)
|
||||
}
|
||||
}
|
||||
|
||||
export const getRelationToCurrency = async (
|
||||
from: string,
|
||||
to: string,
|
||||
value: number,
|
||||
setValue: Dispatch<string>
|
||||
) => {
|
||||
await convert.ready()
|
||||
setValue(convert[from][to](value) || 0)
|
||||
}
|
||||
|
||||
export const actualDate = () => `
|
||||
${new Date(convert.lastUpdated).toLocaleDateString()}
|
||||
${new Date(convert.lastUpdated)
|
||||
.getHours()
|
||||
.toString()
|
||||
.padStart(2, '0')}:${new Date(convert.lastUpdated)
|
||||
.getMinutes()
|
||||
.toString()
|
||||
.padStart(2, '0')}
|
||||
`
|