diff --git a/package.json b/package.json index 7ef0e43..34ee711 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.scss b/src/App.scss index 9526a9f..90af14e 100644 --- a/src/App.scss +++ b/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; - display: flex; - align-items: center; - flex-direction: column; + 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; - height: 20px; + 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; } @@ -94,34 +114,4 @@ &__footer{ 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; - } - } } \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 20a6067..bf7f998 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,155 +1,194 @@ -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 - }, - { - title: 'USDT', - img: usdt - }, - { - title: 'ETH', - img: eth - }, - ] + const valuations: IValutions[] = [ + { + title: 'BTC', + img: btc, + alt: 'Bitcoin' + }, + { + title: 'USDT', + img: usdt, + alt: 'Tether' + }, + { + title: 'ETH', + img: eth, + alt: 'Ethereum' + }, + ] - const ref = useRef(null); + const ref = useRef(null); - const [firstValue, setFirstValue] = useState(0) - const [secondValue, setSecondValue] = useState(0) - const [firstValuation, setFirstValuation] = useState(valuations[0]) - const [secondValuation, setSecondValuation] = useState(valuations[1]) - const [showFirstValuation, setShowFirstValuation] = useState() - const [showSecondValuation, setShowSecondValuation] = useState() - const [isSwap, setSwap] = useState(false) + const [firstValue, setFirstValue] = useState('1') + const [secondValue, setSecondValue] = useState('1') + const [firstValuation, setFirstValuation] = useState(valuations[0]) + const [secondValuation, setSecondValuation] = useState(valuations[1]) + const [showFirstValuation, setShowFirstValuation] = useState() + const [showSecondValuation, setShowSecondValuation] = useState() + const [isSwap, setSwap] = useState(true) - const handlerInputOne = (e: ChangeEvent) => { - setFirstValue(+e.target.value) - getSecondValuation(+e.target.value) - } + const regexInput = (e: ChangeEvent) => { - const handlerInputTwo = (e: ChangeEvent) => { - setSecondValue(+e.target.value) - getFirstValuation(+e.target.value) - } + let [_, sign, integer, decimals]: any = e.target.value.replace(/[^\d\.\-]/g, "") + .replace(/(\..*?)\./g, "$1") + .replace(/(.+)-/g, "$1") + .match(/^(-?)(.*?)((?:\.\d*)?)$/); - const openFirstListValuation = () => setShowFirstValuation(true) + let pos: number = Number(e.target.selectionStart) - 1; + if (!integer && decimals) pos += 2; - const openSecondListValuation = () => setShowSecondValuation(true) + if (integer || decimals) { + integer = +integer; + } - const getSecondValuation = async (value: number) => { - await convert.ready() - setSecondValue(convert[firstValuation.title][secondValuation.title](value)) + const formatted = sign + integer + decimals; - } - const getFirstValuation = async (value: number) => { - await convert.ready() - setFirstValue(convert[secondValuation.title][firstValuation.title](value)) - } - - const swap = () => { - setSwap(true) - setSecondValuation(firstValuation) - setFirstValuation(secondValuation) - } - - useOnClickOutside(ref, () => { - setShowSecondValuation(false) - setShowFirstValuation(false) - }); - - useEffect(() => { - getSecondValuation(firstValue) - }, [firstValuation]) - - useEffect(() => { - if(isSwap){ - setSwap(false) - return + if (formatted !== e.target.value) { + e.target.value = formatted; + e.target.setSelectionRange(pos, pos); + } } - getFirstValuation(secondValue) - }, [secondValuation]) + + const handlerInputOne = (e: ChangeEvent) => { + regexInput(e) + setFirstValue(e.target.value) + getSecondValuation(+e.target.value || 0) + } + + const handlerInputTwo = (e: ChangeEvent) => { + 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) + }, []) - return ( -
-
- Тестовое задание. React. Typescript -
-
- { } -
- - - - {firstValuation.title} + return ( +
+
+
+ + +
+ + {firstValuation.title} +
+
+ +
+
+ {showFirstValuation &&
+ {valuations.map((item, index) => + - {showFirstValuation &&
- {valuations.map((item, index) => - )} + />)} -
- } +
+ } +
+
+
+
+
+ + +
+ + {secondValuation.title} +
+
+ +
+
+ {showSecondValuation &&
+ {valuations.map((item, index) => + )} +
+ } +
+
+
+ {(firstValue || 0) + ' ' + firstValuation.title + ' = ' + (secondValue || 0) + ' ' + secondValuation.title} +
+ Данные носят ознакомительный характер {'\t'} + + { + new Date(convert.lastUpdated).toLocaleDateString() + ' ' + + new Date(convert.lastUpdated).getHours() + ':' + + new Date(convert.lastUpdated).getMinutes().toString().padStart(2, '0') + } + +
-
- -
-
- - - - {secondValuation.title} - - {showSecondValuation &&
- {valuations.map((item, index) => - )} -
- } -
-
-
- {(firstValue || 0) + ' ' + firstValuation.title + ' = ' + (secondValue || 0) + ' ' + secondValuation.title}
- Данные носят ознакомительный характер {'\t'} - - { - new Date(convert.lastUpdated).toLocaleDateString() + ' ' + - new Date(convert.lastUpdated).getHours() + ':' + - new Date(convert.lastUpdated).getMinutes().toString().padStart(2,'0') - } - -
-
- ); + ); } export default App; diff --git a/src/Components/FirstValutionItem/FirstValutionItem.tsx b/src/Components/FirstValutionItem/FirstValutionItem.tsx deleted file mode 100644 index d927ff0..0000000 --- a/src/Components/FirstValutionItem/FirstValutionItem.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Dispatch, FC, MouseEvent } from "react" -import { IListValuationsName } from "../../models/IListValutionsName" - - - -export const FirstValuationItem: FC = ( - { - item, - firstValuation, - secondValuation, - setFirstValuation, - setSecondValuation - } -) => { - - return ( - - ) -} - diff --git a/src/Components/SecondValutionItem/SecondValutionItem.tsx b/src/Components/SecondValutionItem/SecondValutionItem.tsx deleted file mode 100644 index 0452660..0000000 --- a/src/Components/SecondValutionItem/SecondValutionItem.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { Dispatch, FC, MouseEvent } from "react" -import { IListValuationsName } from "../../models/IListValutionsName" - - -export const SecondValuationItem: FC = ({ - item, - firstValuation, - secondValuation, - setFirstValuation, - setSecondValuation -} -) => { - - return ( - - ) -} diff --git a/src/Components/popup-converter/Popup-converter.scss b/src/Components/popup-converter/Popup-converter.scss new file mode 100644 index 0000000..34d563e --- /dev/null +++ b/src/Components/popup-converter/Popup-converter.scss @@ -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); + } + } +} \ No newline at end of file diff --git a/src/Components/popup-converter/Popup-converter.tsx b/src/Components/popup-converter/Popup-converter.tsx new file mode 100644 index 0000000..a3fe188 --- /dev/null +++ b/src/Components/popup-converter/Popup-converter.tsx @@ -0,0 +1,42 @@ +import {Dispatch, FC, MouseEvent} from "react" +import {IListValuationsName} from "../../models/IListValutionsName" +import './Popup-converter.scss' + + +export const PopupConverter: FC = ( + { + item, + valuation, + setValuation, + } +) => { + + const setActiveValuation = (e: MouseEvent) => { + setValuation({ + img: item.img, + title: item.title + }) + } + + return ( +
+
+ {valuation === item.title && + + + } +
+
+ icon + + +
+ +
+ + ) +} + diff --git a/src/index.tsx b/src/index.tsx index cb5ba65..42a9dd0 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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 diff --git a/src/models/IListValutionsName.ts b/src/models/IListValutionsName.ts index 12baeb7..e2f7b55 100644 --- a/src/models/IListValutionsName.ts +++ b/src/models/IListValutionsName.ts @@ -3,8 +3,6 @@ import { IValutions } from "./IValutions"; export interface IListValuationsName { item: IValutions, - setFirstValuation: Dispatch, - setSecondValuation: Dispatch, - secondValuation: string, - firstValuation: string + setValuation: Dispatch, + valuation: string } \ No newline at end of file diff --git a/src/models/IValutions.ts b/src/models/IValutions.ts index 850c144..c36267d 100644 --- a/src/models/IValutions.ts +++ b/src/models/IValutions.ts @@ -1,4 +1,5 @@ export interface IValutions { title: string, - img: string + img: string, + alt?: string }