diff --git a/package.json b/package.json index 492ae417..1c4337d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "app", - "version": "0.1.5", + "version": "0.1.6", "private": true, "dependencies": { "@apollo/react-hooks": "^3.1.3", @@ -16,6 +16,7 @@ "@types/react-copy-to-clipboard": "^4.3.0", "@types/react-dom": "16.9.5", "@types/react-modal": "^3.10.5", + "@types/react-qr-reader": "^2.1.2", "@types/react-router-dom": "^5.1.2", "@types/react-tooltip": "^3.11.0", "@types/styled-components": "^4.4.3", @@ -37,6 +38,7 @@ "react": "^16.11.0", "react-copy-to-clipboard": "^5.0.2", "react-dom": "^16.11.0", + "react-qr-reader": "^2.2.1", "react-router-dom": "^5.1.2", "react-scripts": "3.4.0", "react-spinners": "^0.8.0", diff --git a/src/App.tsx b/src/App.tsx index 59a91802..1b9c1133 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,6 @@ import { useSettings } from './context/SettingsContext'; import { ModalProvider } from 'styled-react-modal'; import { useAccount } from './context/AccountContext'; import { toast } from 'react-toastify'; -import { FadingBackground } from './components/modal/ReactModal'; import 'react-toastify/dist/ReactToastify.css'; import { Header } from './sections/header/Header'; import { Footer } from './sections/footer/Footer'; @@ -17,6 +16,7 @@ import { ScrollToTop } from 'components/scrollToTop/ScrollToTop'; import { ContextProvider } from 'context/ContextProvider'; import { ConnectionCheck } from 'components/connectionCheck/ConnectionCheck'; import { StatusCheck } from 'components/statusCheck/StatusCheck'; +import { BaseModalBackground } from 'styled-react-modal'; const EntryView = React.lazy(() => import('./views/entry/Entry')); const ContentView = React.lazy(() => import('./sections/content/Content')); @@ -54,7 +54,7 @@ const ContextApp: React.FC = () => { return ( - +
diff --git a/src/components/auth/Auth.styled.tsx b/src/components/auth/Auth.styled.tsx index edc43078..43af3a5f 100644 --- a/src/components/auth/Auth.styled.tsx +++ b/src/components/auth/Auth.styled.tsx @@ -33,3 +33,11 @@ export const FixedWidth = styled.div` margin: 0px; margin-right: 8px; `; + +export const QRTextWrapper = styled.div` + display: flex; + margin: 16px 0; + flex-direction: column; + align-items: center; + justify-content: center; +`; diff --git a/src/components/auth/index.tsx b/src/components/auth/index.tsx index d99006af..9d8072ac 100644 --- a/src/components/auth/index.tsx +++ b/src/components/auth/index.tsx @@ -3,6 +3,7 @@ import { getNextAvailable } from 'utils/storage'; import { LoginForm } from './views/NormalLogin'; import { ConnectLoginForm } from './views/ConnectLogin'; import { BTCLoginForm } from './views/BTCLogin'; +import { QRLogin } from './views/QRLogin'; import { ViewCheck } from './checks/ViewCheck'; import CryptoJS from 'crypto-js'; import { useAccount } from 'context/AccountContext'; @@ -15,18 +16,11 @@ import { useStatusDispatch } from 'context/StatusContext'; type AuthProps = { type: string; status: string; - withRedirect?: boolean; callback: () => void; setStatus: (state: string) => void; }; -export const Auth = ({ - type, - status, - withRedirect, - callback, - setStatus, -}: AuthProps) => { +export const Auth = ({ type, status, callback, setStatus }: AuthProps) => { const next = getNextAvailable(); const { changeAccount } = useAccount(); @@ -50,6 +44,34 @@ export const Auth = ({ admin, viewOnly, cert, + skipCheck, + }: { + name?: string; + host?: string; + admin?: string; + viewOnly?: string; + cert?: string; + skipCheck?: boolean; + }) => { + if (skipCheck) { + quickSave({ name, cert, admin, viewOnly, host }); + } else { + name && setName(name); + host && setHost(host); + admin && setAdmin(admin); + viewOnly && setViewOnly(viewOnly); + cert && setCert(cert); + + setStatus('confirmNode'); + } + }; + + const quickSave = ({ + name, + host, + admin, + viewOnly, + cert, }: { name?: string; host?: string; @@ -57,13 +79,20 @@ export const Auth = ({ viewOnly?: string; cert?: string; }) => { - name && setName(name); - host && setHost(host); - admin && setAdmin(admin); - viewOnly && setViewOnly(viewOnly); - cert && setCert(cert); + saveUserAuth({ + available: next, + name, + host: host || '', + admin, + viewOnly, + cert, + }); - setStatus('confirmNode'); + dispatch({ type: 'disconnected' }); + dispatchState({ type: 'disconnected' }); + changeAccount(next); + + push('/'); }; const handleSave = () => { @@ -85,7 +114,7 @@ export const Auth = ({ dispatchState({ type: 'disconnected' }); changeAccount(next); - withRedirect && push('/'); + push('/'); }; const handleConnect = () => { @@ -99,13 +128,13 @@ export const Auth = ({ const renderView = () => { switch (type) { case 'login': - return ; + return ; + case 'qrcode': + return ; case 'connect': - return ( - - ); + return ; default: - return ; + return ; } }; diff --git a/src/components/auth/views/BTCLogin.tsx b/src/components/auth/views/BTCLogin.tsx index 48b2c59d..392788e6 100644 --- a/src/components/auth/views/BTCLogin.tsx +++ b/src/components/auth/views/BTCLogin.tsx @@ -6,8 +6,7 @@ import { Line, StyledTitle } from '../Auth.styled'; import { RiskCheckboxAndConfirm } from './Checkboxes'; interface AuthProps { - available: number; - handleSet?: ({ + handleSet: ({ name, host, admin, @@ -22,7 +21,7 @@ interface AuthProps { }) => void; } -export const BTCLoginForm = ({ available, handleSet }: AuthProps) => { +export const BTCLoginForm = ({ handleSet }: AuthProps) => { const [name, setName] = useState(''); const [json, setJson] = useState(''); const [checked, setChecked] = useState(false); @@ -31,13 +30,13 @@ export const BTCLoginForm = ({ available, handleSet }: AuthProps) => { try { JSON.parse(json); const { cert, admin, viewOnly, host } = getConfigLnd(json); - handleSet && handleSet({ name, host, admin, viewOnly, cert }); + handleSet({ name, host, admin, viewOnly, cert }); } catch (error) { - toast.error('Invalid JSON Object'); + toast.error('Invalid JSON'); } }; - const canConnect = json !== '' && !!available && checked; + const canConnect = json !== '' && checked; return ( <> diff --git a/src/components/auth/views/ConnectLogin.tsx b/src/components/auth/views/ConnectLogin.tsx index ba6706c3..1f4bbf21 100644 --- a/src/components/auth/views/ConnectLogin.tsx +++ b/src/components/auth/views/ConnectLogin.tsx @@ -5,8 +5,7 @@ import { Line, StyledTitle } from '../Auth.styled'; import { RiskCheckboxAndConfirm } from './Checkboxes'; interface AuthProps { - available: number; - handleSet?: ({ + handleSet: ({ name, host, admin, @@ -21,7 +20,7 @@ interface AuthProps { }) => void; } -export const ConnectLoginForm = ({ available, handleSet }: AuthProps) => { +export const ConnectLoginForm = ({ handleSet }: AuthProps) => { const [name, setName] = useState(''); const [url, setUrl] = useState(''); const [checked, setChecked] = useState(false); @@ -30,16 +29,15 @@ export const ConnectLoginForm = ({ available, handleSet }: AuthProps) => { const { cert, macaroon, socket } = getAuthLnd(url); const base64Cert = getBase64CertfromDerFormat(cert) || ''; - handleSet && - handleSet({ - name, - host: socket, - admin: macaroon, - cert: base64Cert, - }); + handleSet({ + name, + host: socket, + admin: macaroon, + cert: base64Cert, + }); }; - const canConnect = url !== '' && !!available && checked; + const canConnect = url !== '' && checked; return ( <> diff --git a/src/components/auth/views/NormalLogin.tsx b/src/components/auth/views/NormalLogin.tsx index d11285a7..cd58aad6 100644 --- a/src/components/auth/views/NormalLogin.tsx +++ b/src/components/auth/views/NormalLogin.tsx @@ -9,8 +9,7 @@ import { import { RiskCheckboxAndConfirm } from './Checkboxes'; interface AuthProps { - available: number; - handleSet?: ({ + handleSet: ({ name, host, admin, @@ -25,7 +24,7 @@ interface AuthProps { }) => void; } -export const LoginForm = ({ available, handleSet }: AuthProps) => { +export const LoginForm = ({ handleSet }: AuthProps) => { const [isViewOnly, setIsViewOnly] = useState(true); const [checked, setChecked] = useState(false); @@ -36,14 +35,13 @@ export const LoginForm = ({ available, handleSet }: AuthProps) => { const [cert, setCert] = useState(''); const handleClick = () => { - handleSet && handleSet({ name, host, admin, viewOnly, cert }); + handleSet({ name, host, admin, viewOnly, cert }); }; const canConnect = name !== '' && host !== '' && (admin !== '' || viewOnly !== '') && - !!available && checked; return ( <> diff --git a/src/components/auth/views/Password.tsx b/src/components/auth/views/Password.tsx index d8a76ae8..a569988f 100644 --- a/src/components/auth/views/Password.tsx +++ b/src/components/auth/views/Password.tsx @@ -1,45 +1,10 @@ import React from 'react'; import { Sub4Title, SubTitle } from '../../generic/Styled'; import zxcvbn from 'zxcvbn'; -import styled from 'styled-components'; -import { progressBackground } from '../../../styles/Themes'; import { ColorButton } from '../../buttons/colorButton/ColorButton'; import { Input } from 'components/input/Input'; import { Line } from '../Auth.styled'; - -const Progress = styled.div` - width: 100%; - background: ${progressBackground}; -`; - -interface ProgressBar { - percent: number; - barColor?: string; -} - -const ProgressBar = styled.div` - height: 10px; - background-color: ${({ barColor }: ProgressBar) => - barColor ? barColor : 'blue'}; - width: ${({ percent }: ProgressBar) => `${percent}%`}; -`; - -const getColor = (percent: number) => { - switch (true) { - case percent < 20: - return '#ff4d4f'; - case percent < 40: - return '#ff7a45'; - case percent < 60: - return '#ffa940'; - case percent < 80: - return '#bae637'; - case percent <= 100: - return '#73d13d'; - default: - return ''; - } -}; +import { LoadingBar } from 'components/loadingBar/LoadingBar'; interface PasswordProps { isPass: string; @@ -65,12 +30,7 @@ export const PasswordInput = ({ Strength: - - - + void; +}; + +export const QRLogin = ({ handleSet }: QRLoginProps) => { + const [qrData, setQrData] = useState([]); + const [modalOpen, setModalOpen] = useState(true); + const [modalClosed, setModalClosed] = useState('none'); + + const [total, setTotal] = useState(0); + const [missing, setMissing] = useState(); + + useEffect(() => { + if (qrData.length >= total && total !== 0) { + setModalOpen(false); + + const sorted = sortBy(qrData, 'index'); + const strings = sorted.map((code: { auth: string }) => code.auth); + const completeString = strings.join(''); + + try { + const { name, cert, admin, viewOnly, host } = getQRConfig( + completeString, + ); + + handleSet({ + name, + host, + admin, + viewOnly, + cert, + skipCheck: true, + }); + } catch (error) { + toast.error('Error reading QR codes.'); + } + } + }, [qrData, handleSet, total]); + + const handleScan = (data: string | null) => { + if (data) { + try { + const parsed = JSON.parse(data); + !total && setTotal(parsed.total); + !missing && setMissing([...Array(parsed.total).keys()]); + + if ( + missing && + missing.length >= 0 && + missing.includes(parsed.index) + ) { + const remaining = missing.filter((value: number) => { + const number = parseInt(parsed.index); + return value !== number; + }); + const data = [...qrData, parsed]; + setQrData(data); + setMissing(remaining); + } + } catch (error) { + setModalOpen(false); + toast.error('Error reading QR codes.'); + } + } + }; + + const handleError = () => { + setModalOpen(false); + setModalClosed('error'); + }; + + const handleClose = () => { + setModalClosed('forced'); + setModalOpen(false); + setMissing(undefined); + setTotal(0); + setQrData([]); + }; + + const renderInfo = () => { + switch (modalClosed) { + case 'forced': + return ( + <> + + + No information read from QR Codes. + + + { + setModalClosed('none'); + setModalOpen(true); + }} + > + Try Again + + + ); + case 'error': + return ( + + + Make sure you have given ThunderHub the correct + permissions to use the camara. + + + ); + default: + return null; + } + }; + + return ( + <> + {renderInfo()} + + + + + + + + ); +}; diff --git a/src/components/buttons/secureButton/SecureButton.tsx b/src/components/buttons/secureButton/SecureButton.tsx index ef3d845d..41c3944e 100644 --- a/src/components/buttons/secureButton/SecureButton.tsx +++ b/src/components/buttons/secureButton/SecureButton.tsx @@ -50,7 +50,7 @@ export const SecureButton = ({ > {children} - + setModalOpen(false)}> + barColor ? barColor : 'blue'}; + width: ${({ percent }: ProgressBar) => `${percent}%`}; +`; + +const getColor = (percent: number) => { + switch (true) { + case percent < 20: + return '#ff4d4f'; + case percent < 40: + return '#ff7a45'; + case percent < 60: + return '#ffa940'; + case percent < 80: + return '#bae637'; + case percent <= 100: + return '#73d13d'; + default: + return ''; + } +}; + +export const LoadingBar = ({ percent }: { percent: number }) => ( + + + +); diff --git a/src/components/modal/ReactModal.tsx b/src/components/modal/ReactModal.tsx index 921e28b2..77284d3d 100644 --- a/src/components/modal/ReactModal.tsx +++ b/src/components/modal/ReactModal.tsx @@ -1,45 +1,59 @@ import React, { ReactNode } from 'react'; -import styled from 'styled-components'; +import { css } from 'styled-components'; import { cardColor, mediaWidths } from '../../styles/Themes'; -import ReactModal, { BaseModalBackground } from 'styled-react-modal'; - -export const FadingBackground = styled(BaseModalBackground)``; +import ReactModal from 'styled-react-modal'; interface ModalProps { children: ReactNode; isOpen: boolean; - setIsOpen: (set: boolean) => void; + noMinWidth?: boolean; + closeCallback: () => void; } -const StyleModal = ReactModal.styled` - position: absolute; - top: 50%; - left: 50%; - transform: translateY(-50%) translateX(-50%); - background-color: ${cardColor}; - padding: 20px; - border-radius: 5px; - outline: none; - min-width: 578px; +const generalCSS = css` + position: absolute; + top: 50%; + left: 50%; + transform: translateY(-50%) translateX(-50%); + background-color: ${cardColor}; + padding: 20px; + border-radius: 5px; + outline: none; - @media (${mediaWidths.mobile}) { - top: 100%; - border-radius: 0px; - transform: translateY(-100%) translateX(-50%); - width: 100%; - min-width: 325px; - } + @media (${mediaWidths.mobile}) { + top: 100%; + border-radius: 0px; + transform: translateY(-100%) translateX(-50%); + width: 100%; + min-width: 325px; + } `; -const Modal = ({ children, isOpen, setIsOpen }: ModalProps) => { +const StyleModal = ReactModal.styled` + ${generalCSS} + min-width: 578px; +`; + +const StyleModalSmall = ReactModal.styled` +${generalCSS} +`; + +const Modal = ({ + children, + isOpen, + noMinWidth = false, + closeCallback, +}: ModalProps) => { + const Styled = noMinWidth ? StyleModalSmall : StyleModal; + return ( - setIsOpen(!isOpen)} - onEscapeKeydown={() => setIsOpen(!isOpen)} + onBackgroundClick={closeCallback} + onEscapeKeydown={closeCallback} > {children} - + ); }; diff --git a/src/hooks/UseInterval.tsx b/src/hooks/UseInterval.tsx new file mode 100644 index 00000000..fa27fb71 --- /dev/null +++ b/src/hooks/UseInterval.tsx @@ -0,0 +1,17 @@ +import { useEffect, useRef } from 'react'; + +export const useInterval = (callback: any, delay: number) => { + const savedCallback = useRef(callback); + + useEffect(() => { + savedCallback.current = callback; + }, [callback]); + + useEffect(() => { + const tick = () => { + savedCallback.current(); + }; + let id = setInterval(tick, delay); + return () => clearInterval(id); + }, [delay]); +}; diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 0ac20bdd..fd379fcb 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -134,3 +134,21 @@ export const getConfigLnd = (json: string) => { return emptyObject; }; + +export const getQRConfig = (json: string) => { + const config = JSON.parse(json); + + if (config) { + const { name = '', cert = '', admin, viewOnly, host } = config; + + return { + name, + cert, + admin, + viewOnly, + host, + }; + } + + return { ...emptyObject, name: undefined }; +}; diff --git a/src/views/channels/channels/ChannelCard.tsx b/src/views/channels/channels/ChannelCard.tsx index 640af0ba..3589540f 100644 --- a/src/views/channels/channels/ChannelCard.tsx +++ b/src/views/channels/channels/ChannelCard.tsx @@ -242,7 +242,7 @@ export const ChannelCard = ({
{`Received: ${formatReceived}`}
{`Sent: ${formatSent}`}
- + setModalOpen(false)}> { > BTCPayServer + setIsType('qrcode')} + > + QR Code + ); const renderText = () => { switch (isType) { - case 'login': - return null; - case 'connect': - return ( - <> - - - To connect via LNDConnect paste the LNDConnectUrl - down below. - {' Find the url format specification '} - - here. - - - - - ); - default: + case 'btcpay': return ( <> @@ -88,6 +73,27 @@ export const LoginView = () => { ); + case 'connect': + return ( + <> + + + To connect via LNDConnect paste the LNDConnectUrl + down below. + {' Find the url format specification '} + + here. + + + + + ); + default: + return null; } }; @@ -101,7 +107,6 @@ export const LoginView = () => { type={isType} status={status} setStatus={setStatus} - withRedirect={true} callback={() => setStatus('none')} /> diff --git a/src/views/home/account/pay/pay.tsx b/src/views/home/account/pay/pay.tsx index 9aeabab8..1a4a2a7b 100644 --- a/src/views/home/account/pay/pay.tsx +++ b/src/views/home/account/pay/pay.tsx @@ -105,7 +105,7 @@ export const PayCard = ({ setOpen }: { setOpen: () => void }) => { Send Sats - + setModalOpen(false)}> {renderData()} void }) => { > Send - + setModalOpen(false)}> Send to Address diff --git a/src/views/settings/Account.tsx b/src/views/settings/Account.tsx index b9f37edf..4ac79a7c 100644 --- a/src/views/settings/Account.tsx +++ b/src/views/settings/Account.tsx @@ -110,7 +110,6 @@ export const AccountSettings = () => { type={isType} status={status} setStatus={setStatus} - withRedirect={true} callback={() => setStatus('none')} /> diff --git a/src/views/settings/Danger.tsx b/src/views/settings/Danger.tsx index cd2da8ca..a650d63d 100644 --- a/src/views/settings/Danger.tsx +++ b/src/views/settings/Danger.tsx @@ -44,27 +44,49 @@ export const SettingsButton = styled(SimpleButton)` export const DangerView = () => { const { refreshAccount } = useAccount(); + const renderButton = () => { + const saved = getStorageSaved(); + if (saved.length > 1) { + return ( + + {saved.map((entry, index) => { + return ( + { + deleteAuth(entry.index); + refreshAccount(); + }} + > + {entry.name} + + ); + })} + + ); + } else if (saved.length === 1) { + return ( + { + deleteAuth(saved[0].index); + refreshAccount(); + }} + > + {saved[0].name} + + ); + } + return null; + }; + return ( Danger Zone Delete Account: - - {getStorageSaved().map((entry, index) => { - return ( - { - deleteAuth(entry.index); - refreshAccount(); - }} - > - {entry.name} - - ); - })} - + {renderButton()} Delete all Accounts and Settings: diff --git a/src/views/settings/Settings.tsx b/src/views/settings/Settings.tsx index f5a320a6..b1e42957 100644 --- a/src/views/settings/Settings.tsx +++ b/src/views/settings/Settings.tsx @@ -6,6 +6,7 @@ import { textColor } from '../../styles/Themes'; import { AccountSettings } from './Account'; import { DangerView } from './Danger'; import { CurrentSettings } from './Current'; +import { SyncSettings } from './Sync'; export const ButtonRow = styled.div` width: auto; @@ -13,11 +14,11 @@ export const ButtonRow = styled.div` `; export const SettingsLine = styled(SingleLine)` - margin: 10px 0; + margin: 8px 0; `; export const SettingsButton = styled(SimpleButton)` - padding: 10px; + padding: 8px; &:hover { border: 1px solid ${textColor}; @@ -28,6 +29,7 @@ export const SettingsView = () => { return ( <> + diff --git a/src/views/settings/Sync.tsx b/src/views/settings/Sync.tsx new file mode 100644 index 00000000..cd0c1789 --- /dev/null +++ b/src/views/settings/Sync.tsx @@ -0,0 +1,169 @@ +import React, { useState } from 'react'; +import { + CardWithTitle, + SubTitle, + Card, + Sub4Title, + Separation, +} from '../../components/generic/Styled'; +import { SettingsLine } from './Settings'; +import { + MultiButton, + SingleButton, +} from 'components/buttons/multiButton/MultiButton'; +import { ColorButton } from 'components/buttons/colorButton/ColorButton'; +import { XSvg } from 'components/generic/Icons'; +import { useAccount } from 'context/AccountContext'; +import QRCode from 'qrcode.react'; +import styled from 'styled-components'; +import { useInterval } from 'hooks/UseInterval'; +import Modal from 'components/modal/ReactModal'; + +const QRWrapper = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; +`; + +export const SyncSettings = () => { + const { name, host, admin, viewOnly, cert } = useAccount(); + + const getValue = () => { + switch (true) { + case !!viewOnly: + return 'viewOnly'; + default: + return 'adminOnly'; + } + }; + + const [state, setState] = useState('none'); + const [type, setType] = useState(getValue()); + + const getObject = () => { + switch (type) { + case 'complete': + return { viewOnly, admin }; + case 'adminOnly': + return { admin }; + default: + return { viewOnly }; + } + }; + + const renderSettings = () => ( + <> + + + Sync Type + + {viewOnly && ( + setType('viewOnly')} + > + View-Only + + )} + {admin && ( + setType('adminOnly')} + > + Admin-Only + + )} + {viewOnly && admin && ( + setType('complete')} + > + Admin and View + + )} + + + + setState('finish')} + > + Generate QR + + + + ); + + const renderQRCode = () => { + const connection = JSON.stringify({ + name, + host, + cert, + ...getObject(), + }); + + return ( + setState('none')} + > + + Scan with ThunderHub + + + + ); + }; + + return ( + + Sync + + + Sync account to another device + + setState((prev: string) => + prev !== 'none' ? 'none' : 'generate', + ) + } + > + {state !== 'none' ? : 'Sync'} + + + {state === 'generate' && renderSettings()} + {state === 'finish' && renderQRCode()} + + + ); +}; + +const QRLoop = ({ connection }: { connection: string }) => { + const textArray = connection.match(/.{1,100}/g) ?? []; + const length = textArray.length; + + const objectArray = textArray.map((value: string, index: number) => + JSON.stringify({ + index: index, + total: length, + auth: value, + }), + ); + + const [count, setCount] = useState(0); + + useInterval(() => { + setCount((prev: number) => { + if (prev < length - 1) { + return prev + 1; + } + return 0; + }); + }, 1000); + + return ; +}; diff --git a/yarn.lock b/yarn.lock index 9cc494ba..17b0609c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2383,6 +2383,13 @@ dependencies: "@types/react" "*" +"@types/react-qr-reader@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@types/react-qr-reader/-/react-qr-reader-2.1.2.tgz#da7a674cf9e3ceb1c026e89d5aa41fb1587fc66b" + integrity sha512-tDLqqiQMZlTKBKtWedcf7QZ3L9fyz8nPHNq/j+1mcJoEvrdM3Y89i3/FKwdmZ4H7G6uUk4JqShpWXLCuYpZK+w== + dependencies: + "@types/react" "*" + "@types/react-router-dom@^5.1.2": version "5.1.3" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" @@ -9117,6 +9124,11 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jsqr@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/jsqr/-/jsqr-1.2.0.tgz#f93fc65fa7d1ded78b1bcb020fa044352b04261a" + integrity sha512-wKcQS9QC2VHGk7aphWCp1RrFyC0CM6fMgC5prZZ2KV/Lk6OKNoCod9IR6bao+yx3KPY0gZFC5dc+h+KFzCI0Wg== + jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" @@ -12403,6 +12415,15 @@ react-popper@^1.3.6: typed-styles "^0.0.7" warning "^4.0.2" +react-qr-reader@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-qr-reader/-/react-qr-reader-2.2.1.tgz#dc89046d1c1a1da837a683dd970de5926817d55b" + integrity sha512-EL5JEj53u2yAOgtpAKAVBzD/SiKWn0Bl7AZy6ZrSf1lub7xHwtaXe6XSx36Wbhl1VMGmvmrwYMRwO1aSCT2fwA== + dependencies: + jsqr "^1.2.0" + prop-types "^15.7.2" + webrtc-adapter "^7.2.1" + react-router-dom@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" @@ -13090,6 +13111,13 @@ rsvp@^4.8.4: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== +rtcpeerconnection-shim@^1.2.15: + version "1.2.15" + resolved "https://registry.yarnpkg.com/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz#e7cc189a81b435324c4949aa3dfb51888684b243" + integrity sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw== + dependencies: + sdp "^2.6.0" + run-async@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" @@ -13224,6 +13252,11 @@ scss-tokenizer@^0.2.3: js-base64 "^2.1.8" source-map "^0.4.2" +sdp@^2.12.0, sdp@^2.6.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/sdp/-/sdp-2.12.0.tgz#338a106af7560c86e4523f858349680350d53b22" + integrity sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw== + secure-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/secure-keys/-/secure-keys-1.0.0.tgz#f0c82d98a3b139a8776a8808050b824431087fca" @@ -15618,6 +15651,14 @@ webpack@4.41.5, webpack@^4.33.0, webpack@^4.38.0: watchpack "^1.6.0" webpack-sources "^1.4.1" +webrtc-adapter@^7.2.1: + version "7.5.0" + resolved "https://registry.yarnpkg.com/webrtc-adapter/-/webrtc-adapter-7.5.0.tgz#b870f6ff5e87191276e66f9521ac457c81801954" + integrity sha512-cUqlw310uLLSYvO8FTNCVmGWSMlMt6vuSDkcYL1nW+RUvAILJ3jEIvAUgFQU5EFGnU+mf9/No14BFv3U+hoxBQ== + dependencies: + rtcpeerconnection-shim "^1.2.15" + sdp "^2.12.0" + websocket-driver@>=0.5.1: version "0.7.3" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9"