chore: add risk checkbox

This commit is contained in:
AP 2020-02-12 08:26:18 +01:00
parent cf1e579c1d
commit 420cbbfa8f
15 changed files with 233 additions and 85 deletions

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-alert-circle"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>

After

Width:  |  Height:  |  Size: 356 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>

After

Width:  |  Height:  |  Size: 262 B

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { useSpring, animated } from 'react-spring'; import { useSpring, animated } from 'react-spring';
import { getValue } from 'helpers/Helpers'; import { getValue } from '../../helpers/Helpers';
import { useSettings } from 'context/SettingsContext'; import { useSettings } from '../../context/SettingsContext';
interface AnimatedProps { interface AnimatedProps {
amount: number; amount: number;

View file

@ -1,5 +1,6 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { Sub4Title } from '../generic/Styled'; import { Sub4Title } from '../generic/Styled';
import { fontColors, textColor } from 'styles/Themes';
export const Line = styled.div` export const Line = styled.div`
margin: 16px 0; margin: 16px 0;
@ -10,3 +11,25 @@ export const StyledTitle = styled(Sub4Title)`
width: 100%; width: 100%;
margin-bottom: 0px; margin-bottom: 0px;
`; `;
export const CheckboxText = styled.div`
font-size: 13px;
color: ${fontColors.grey7};
text-align: justify;
`;
export const StyledContainer = styled.div`
color: ${textColor};
display: flex;
justify-content: flex-start;
align-items: center;
padding-right: 32px;
margin: 32px 0 8px;
`;
export const FixedWidth = styled.div`
height: 18px;
width: 18px;
margin: 0px;
margin-right: 8px;
`;

View file

@ -12,6 +12,7 @@ import { ColorButton } from '../buttons/colorButton/ColorButton';
import { Input } from 'components/input/Input'; import { Input } from 'components/input/Input';
import { Line, StyledTitle } from './Auth.styled'; import { Line, StyledTitle } from './Auth.styled';
import { ChevronLeft } from 'components/generic/Icons'; import { ChevronLeft } from 'components/generic/Icons';
import { RiskCheckboxAndConfirm } from './Checkboxes';
interface AuthProps { interface AuthProps {
available: number; available: number;
@ -31,6 +32,7 @@ export const BTCLoginForm = ({
const [isName, setName] = useState(''); const [isName, setName] = useState('');
const [isJson, setJson] = useState(''); const [isJson, setJson] = useState('');
const [checked, setChecked] = useState(false);
const [hasInfo, setHasInfo] = useState(false); const [hasInfo, setHasInfo] = useState(false);
const [isPass, setPass] = useState(''); const [isPass, setPass] = useState('');
@ -113,7 +115,7 @@ export const BTCLoginForm = ({
}; };
const renderContent = () => { const renderContent = () => {
const canConnect = isJson !== '' && !!available; const canConnect = isJson !== '' && !!available && checked;
return ( return (
<> <>
{goBack && ( {goBack && (
@ -129,17 +131,12 @@ export const BTCLoginForm = ({
<StyledTitle>BTCPayServer Connect JSON:</StyledTitle> <StyledTitle>BTCPayServer Connect JSON:</StyledTitle>
<Input onChange={e => setJson(e.target.value)} /> <Input onChange={e => setJson(e.target.value)} />
</Line> </Line>
{canConnect && ( <RiskCheckboxAndConfirm
<ColorButton disabled={!canConnect}
disabled={!canConnect} handleClick={handleClick}
onClick={handleClick} checked={checked}
withMargin={'16px 0 0'} onChange={setChecked}
fullWidth={true} />
arrow={true}
>
Connect
</ColorButton>
)}
</> </>
); );
}; };

View file

@ -0,0 +1,55 @@
import React from 'react';
import { Checkbox } from 'components/checkbox/Checkbox';
import { CheckboxText, StyledContainer, FixedWidth } from './Auth.styled';
import { AlertCircle } from 'components/generic/Icons';
import { fontColors } from 'styles/Themes';
import { ColorButton } from 'components/buttons/colorButton/ColorButton';
type CheckboxProps = {
handleClick: () => void;
disabled: boolean;
checked: boolean;
onChange: (state: boolean) => void;
};
export const RiskCheckboxAndConfirm = ({
handleClick,
disabled,
checked,
onChange,
}: CheckboxProps) => (
<>
<WarningBox />
<Checkbox checked={checked} onChange={onChange}>
<CheckboxText>
I'm feeling reckless - I understand that Lightning, LND and
ThunderHub are under constant development and that there is
always a risk of losing funds.
</CheckboxText>
</Checkbox>
<ColorButton
disabled={disabled}
onClick={handleClick}
withMargin={'32px 0 0'}
fullWidth={true}
arrow={true}
>
Connect
</ColorButton>
</>
);
export const WarningBox = () => {
return (
<StyledContainer>
<FixedWidth>
<AlertCircle color={fontColors.grey7} />
</FixedWidth>
<CheckboxText>
Macaroons are handled by the ThunderHub server to connect to
your LND node but are never stored. Still, this involves a
certain degree of trust you must be aware of.
</CheckboxText>
</StyledContainer>
);
};

View file

@ -18,6 +18,7 @@ import { ColorButton } from '../buttons/colorButton/ColorButton';
import { Input } from 'components/input/Input'; import { Input } from 'components/input/Input';
import { Line, StyledTitle } from './Auth.styled'; import { Line, StyledTitle } from './Auth.styled';
import { ChevronLeft } from 'components/generic/Icons'; import { ChevronLeft } from 'components/generic/Icons';
import { RiskCheckboxAndConfirm } from './Checkboxes';
interface AuthProps { interface AuthProps {
available: number; available: number;
@ -37,6 +38,7 @@ export const ConnectLoginForm = ({
const [isName, setName] = useState(''); const [isName, setName] = useState('');
const [isUrl, setUrl] = useState(''); const [isUrl, setUrl] = useState('');
const [checked, setChecked] = useState(false);
const [hasInfo, setHasInfo] = useState(false); const [hasInfo, setHasInfo] = useState(false);
const [isPass, setPass] = useState(''); const [isPass, setPass] = useState('');
@ -106,7 +108,7 @@ export const ConnectLoginForm = ({
}; };
const renderContent = () => { const renderContent = () => {
const canConnect = isUrl !== '' && !!available; const canConnect = isUrl !== '' && !!available && checked;
return ( return (
<> <>
{goBack && ( {goBack && (
@ -122,17 +124,12 @@ export const ConnectLoginForm = ({
<StyledTitle>LND Connect Url:</StyledTitle> <StyledTitle>LND Connect Url:</StyledTitle>
<Input onChange={e => setUrl(e.target.value)} /> <Input onChange={e => setUrl(e.target.value)} />
</Line> </Line>
{canConnect && ( <RiskCheckboxAndConfirm
<ColorButton disabled={!canConnect}
disabled={!canConnect} handleClick={() => setHasInfo(true)}
onClick={() => setHasInfo(true)} checked={checked}
withMargin={'16px 0 0'} onChange={setChecked}
fullWidth={true} />
arrow={true}
>
Connect
</ColorButton>
)}
</> </>
); );
}; };

View file

@ -17,6 +17,7 @@ import {
SingleButton, SingleButton,
} from 'components/buttons/multiButton/MultiButton'; } from 'components/buttons/multiButton/MultiButton';
import { ChevronLeft } from 'components/generic/Icons'; import { ChevronLeft } from 'components/generic/Icons';
import { RiskCheckboxAndConfirm } from './Checkboxes';
interface AuthProps { interface AuthProps {
available: number; available: number;
@ -35,6 +36,7 @@ export const LoginForm = ({
const { push } = useHistory(); const { push } = useHistory();
const [viewOnly, setViewOnly] = useState(true); const [viewOnly, setViewOnly] = useState(true);
const [checked, setChecked] = useState(false);
const [isName, setName] = useState(''); const [isName, setName] = useState('');
const [isHost, setHost] = useState(''); const [isHost, setHost] = useState('');
@ -131,7 +133,8 @@ export const LoginForm = ({
isName !== '' && isName !== '' &&
isHost !== '' && isHost !== '' &&
(isAdmin !== '' || isRead !== '') && (isAdmin !== '' || isRead !== '') &&
!!available; !!available &&
checked;
return ( return (
<> <>
<SingleLine> <SingleLine>
@ -193,17 +196,12 @@ export const LoginForm = ({
onChange={e => setCert(e.target.value)} onChange={e => setCert(e.target.value)}
/> />
</Line> </Line>
{canConnect && ( <RiskCheckboxAndConfirm
<ColorButton disabled={!canConnect}
disabled={!canConnect} handleClick={handleClick}
onClick={handleClick} checked={checked}
withMargin={'16px 0 0'} onChange={setChecked}
fullWidth={true} />
arrow={true}
>
Connect
</ColorButton>
)}
</> </>
); );
}; };

View file

@ -1,20 +1,15 @@
import React from 'react'; import React from 'react';
import { SingleLine, Sub4Title, SubTitle } from '../generic/Styled'; import { Sub4Title, SubTitle } from '../generic/Styled';
import zxcvbn from 'zxcvbn'; import zxcvbn from 'zxcvbn';
import styled from 'styled-components'; import styled from 'styled-components';
import { progressBackground } from '../../styles/Themes'; import { progressBackground } from '../../styles/Themes';
import { Loader } from '../generic/Icons';
import { ColorButton } from '../buttons/colorButton/ColorButton'; import { ColorButton } from '../buttons/colorButton/ColorButton';
import { Input } from 'components/input/Input'; import { Input } from 'components/input/Input';
import { Line } from './Auth.styled';
const Progress = styled.div` const Progress = styled.div`
width: 80%; width: 100%;
margin: 10px 10px 10px 15px;
padding: 3px;
border-radius: 15px;
background: ${progressBackground}; background: ${progressBackground};
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.25),
0 1px rgba(255, 255, 255, 0.08);
`; `;
interface ProgressBar { interface ProgressBar {
@ -24,12 +19,6 @@ interface ProgressBar {
const ProgressBar = styled.div` const ProgressBar = styled.div`
height: 10px; height: 10px;
border-radius: 15px;
background-image: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.3),
rgba(0, 0, 0, 0.05)
);
background-color: ${({ barColor }: ProgressBar) => background-color: ${({ barColor }: ProgressBar) =>
barColor ? barColor : 'blue'}; barColor ? barColor : 'blue'};
width: ${({ percent }: ProgressBar) => `${percent}%`}; width: ${({ percent }: ProgressBar) => `${percent}%`};
@ -66,15 +55,15 @@ export const PasswordInput = ({
loading = false, loading = false,
}: PasswordProps) => { }: PasswordProps) => {
const strength = (100 * Math.min(zxcvbn(isPass).guesses_log10, 40)) / 40; const strength = (100 * Math.min(zxcvbn(isPass).guesses_log10, 40)) / 40;
const needed = 1; const needed = 20;
return ( return (
<> <>
<SubTitle>Please Input a Password</SubTitle> <SubTitle>Please Input a Password</SubTitle>
<SingleLine> <Line>
<Sub4Title>Password:</Sub4Title> <Sub4Title>Password:</Sub4Title>
<Input onChange={e => setPass(e.target.value)} /> <Input onChange={e => setPass(e.target.value)} />
</SingleLine> </Line>
<SingleLine> <Line>
<Sub4Title>Strength:</Sub4Title> <Sub4Title>Strength:</Sub4Title>
<Progress> <Progress>
<ProgressBar <ProgressBar
@ -82,23 +71,17 @@ export const PasswordInput = ({
barColor={getColor(strength)} barColor={getColor(strength)}
/> />
</Progress> </Progress>
</SingleLine> </Line>
{strength >= needed && !loading && ( <ColorButton
<ColorButton disabled={strength < needed}
disabled={strength < needed} onClick={callback}
onClick={callback} withMargin={'32px 0 0'}
withMargin={'16px 0 0'} fullWidth={true}
fullWidth={true} arrow={true}
arrow={true} loading={loading}
> >
Connect Connect
</ColorButton> </ColorButton>
)}
{loading && (
<ColorButton disabled={true} color={'grey'}>
<Loader />
</ColorButton>
)}
</> </>
); );
}; };

View file

@ -0,0 +1,16 @@
import React, { useState } from 'react';
import { Checkbox } from './Checkbox';
export default {
title: 'Checkbox',
};
export const Default = () => {
const [checked, set] = useState<boolean>(false);
return (
<Checkbox checked={checked} onChange={set}>
This is a checkbox
</Checkbox>
);
};

View file

@ -0,0 +1,58 @@
import React from 'react';
import styled from 'styled-components';
import {
colorButtonBackground,
buttonBorderColor,
themeColors,
} from '../../styles/Themes';
const StyledContainer = styled.div`
display: flex;
justify-content: flex-start;
align-items: center;
padding-right: 32px;
cursor: pointer;
`;
const FixedWidth = styled.div`
height: 18px;
width: 18px;
margin: 0px;
margin-right: 8px;
`;
const StyledCheckbox = styled.div`
height: 16px;
width: 16px;
margin: 0;
border: 1px solid ${buttonBorderColor};
border-radius: 4px;
outline: none;
transition-duration: 0.3s;
background-color: ${colorButtonBackground};
box-sizing: border-box;
border-radius: 50%;
${({ checked }: { checked: boolean }) =>
checked && `background-color: ${themeColors.blue2}`}
`;
type CheckboxProps = {
checked: boolean;
onChange: (state: boolean) => void;
};
export const Checkbox: React.FC<CheckboxProps> = ({
children,
checked,
onChange,
}) => {
return (
<StyledContainer onClick={() => onChange(!checked)}>
<FixedWidth>
<StyledCheckbox checked={checked} />
</FixedWidth>
{children}
</StyledContainer>
);
};

View file

@ -30,6 +30,7 @@ import { ReactComponent as LayersIcon } from '../../assets/icons/layers.svg';
import { ReactComponent as LoaderIcon } from '../../assets/icons/loader.svg'; import { ReactComponent as LoaderIcon } from '../../assets/icons/loader.svg';
import { ReactComponent as CircleIcon } from '../../assets/icons/circle.svg'; import { ReactComponent as CircleIcon } from '../../assets/icons/circle.svg';
import { ReactComponent as AlertTriangleIcon } from '../../assets/icons/alert-triangle.svg'; import { ReactComponent as AlertTriangleIcon } from '../../assets/icons/alert-triangle.svg';
import { ReactComponent as AlertCircleIcon } from '../../assets/icons/alert-circle.svg';
import { ReactComponent as GitCommitIcon } from '../../assets/icons/git-commit.svg'; import { ReactComponent as GitCommitIcon } from '../../assets/icons/git-commit.svg';
import { ReactComponent as GitBranchIcon } from '../../assets/icons/git-branch.svg'; import { ReactComponent as GitBranchIcon } from '../../assets/icons/git-branch.svg';
import { ReactComponent as RadioIcon } from '../../assets/icons/radio.svg'; import { ReactComponent as RadioIcon } from '../../assets/icons/radio.svg';
@ -45,6 +46,7 @@ import { ReactComponent as Menu } from '../../assets/icons/menu.svg';
import { ReactComponent as Mail } from '../../assets/icons/mail.svg'; import { ReactComponent as Mail } from '../../assets/icons/mail.svg';
import { ReactComponent as Github } from '../../assets/icons/github.svg'; import { ReactComponent as Github } from '../../assets/icons/github.svg';
import { ReactComponent as Repeat } from '../../assets/icons/repeat.svg'; import { ReactComponent as Repeat } from '../../assets/icons/repeat.svg';
import { ReactComponent as CheckIcon } from '../../assets/icons/check.svg';
interface IconProps { interface IconProps {
color?: string; color?: string;
@ -97,6 +99,7 @@ export const Layers = styleIcon(LayersIcon);
export const Loader = styleIcon(LoaderIcon); export const Loader = styleIcon(LoaderIcon);
export const Circle = styleIcon(CircleIcon); export const Circle = styleIcon(CircleIcon);
export const AlertTriangle = styleIcon(AlertTriangleIcon); export const AlertTriangle = styleIcon(AlertTriangleIcon);
export const AlertCircle = styleIcon(AlertCircleIcon);
export const GitCommit = styleIcon(GitCommitIcon); export const GitCommit = styleIcon(GitCommitIcon);
export const GitBranch = styleIcon(GitBranchIcon); export const GitBranch = styleIcon(GitBranchIcon);
export const Radio = styleIcon(RadioIcon); export const Radio = styleIcon(RadioIcon);
@ -112,3 +115,4 @@ export const MenuIcon = styleIcon(Menu);
export const MailIcon = styleIcon(Mail); export const MailIcon = styleIcon(Mail);
export const GithubIcon = styleIcon(Github); export const GithubIcon = styleIcon(Github);
export const RepeatIcon = styleIcon(Repeat); export const RepeatIcon = styleIcon(Repeat);
export const Check = styleIcon(CheckIcon);

View file

@ -68,7 +68,18 @@ export const getAuthParams = (available: string) => {
export const getAuthLnd = (lndconnect: string) => { export const getAuthLnd = (lndconnect: string) => {
const auth = lndconnect.replace('lndconnect', 'https'); const auth = lndconnect.replace('lndconnect', 'https');
const url = new URL(auth);
let url;
try {
url = new URL(auth);
} catch (error) {
return {
cert: '',
macaroon: '',
socket: '',
};
}
const cert = url.searchParams.get('cert') || ''; const cert = url.searchParams.get('cert') || '';
const macaroon = url.searchParams.get('macaroon') || ''; const macaroon = url.searchParams.get('macaroon') || '';

View file

@ -24,8 +24,8 @@ export const FaqView = () => {
<Section color={themeColors.grey} padding={'60px 0 16px'}> <Section color={themeColors.grey} padding={'60px 0 16px'}>
<Question>What is ThunderHub?</Question> <Question>What is ThunderHub?</Question>
<Text> <Text>
ThunderHub is a <b>LND node manager</b> that you can open on ThunderHub is a <b>LND node manager</b> that you can open in
any browser and any device. any browser and on any device.
</Text> </Text>
</Section> </Section>
<Section color={themeColors.grey} padding={'19px 0 16px'}> <Section color={themeColors.grey} padding={'19px 0 16px'}>
@ -38,7 +38,7 @@ export const FaqView = () => {
<Section color={themeColors.grey} padding={'19px 0 16px'}> <Section color={themeColors.grey} padding={'19px 0 16px'}>
<Question>What is the value of ThunderHub?</Question> <Question>What is the value of ThunderHub?</Question>
<Text> <Text>
ThunderHub brings a <b>full LND lightning node manager </b> ThunderHub brings a <b>full lightning node manager </b>
directly to your device without the need of installing directly to your device without the need of installing
plugins, extensions or apps, having specific browsers or plugins, extensions or apps, having specific browsers or
operating systems and is completely <b>open-source.</b> operating systems and is completely <b>open-source.</b>
@ -51,10 +51,7 @@ export const FaqView = () => {
with a password only you know. with a password only you know.
</Text> </Text>
<Text> <Text>
<b> <b>The code is public and available for anyone to audit.</b>
No need to trust us, the code is public and available
for anyone to audit.
</b>
</Text> </Text>
</Section> </Section>
<Section color={themeColors.grey} padding={'19px 0 16px'}> <Section color={themeColors.grey} padding={'19px 0 16px'}>
@ -72,9 +69,15 @@ export const FaqView = () => {
<b> stored only in your browser. </b> <b> stored only in your browser. </b>
The password is only known by you and you need to unlock The password is only known by you and you need to unlock
your account everytime you want to perform an admin only your account everytime you want to perform an admin only
change such as managing channels or sending and recieving change such as managing channels or sending and receiving
bitcoin or lightning payments. bitcoin or lightning payments.
</Text> </Text>
<Text>
The ThunderHub server uses your credentials to connect to
your node but they are never stored outside of your browser.
Still, this involves a certain degree of trust you must be
aware of.
</Text>
<Text> <Text>
If you want a more secure alternative, you can connect using If you want a more secure alternative, you can connect using
a view-only macaroon and use ThunderHub only for monitoring a view-only macaroon and use ThunderHub only for monitoring

View file

@ -26,7 +26,7 @@ export const PrivacyView = () => {
<Title>Privacy Policy</Title> <Title>Privacy Policy</Title>
</Section> </Section>
<Section color={themeColors.grey} padding={'60px 0 16px'}> <Section color={themeColors.grey} padding={'60px 0 16px'}>
<Text>Last Updated: January 30, 2020</Text> <Text>Last Updated: February 12, 2020</Text>
</Section> </Section>
<Section color={themeColors.grey} padding={'19px 0 16px'}> <Section color={themeColors.grey} padding={'19px 0 16px'}>
<Text> <Text>
@ -69,8 +69,9 @@ export const PrivacyView = () => {
Lightning node, we ask for sensitive information to do so. Lightning node, we ask for sensitive information to do so.
This information is stored using your browser's own storage This information is stored using your browser's own storage
APIs, and is encrypted using a password only known to the APIs, and is encrypted using a password only known to the
user. This information is never recorded outside of the user. This information is used by the server to connect to
user's browser storage. your node but is never recorded outside of the user's
browser storage.
</Text> </Text>
<Text> <Text>
<b>Error Reporting / Usage Statistics</b> - No information <b>Error Reporting / Usage Statistics</b> - No information