Merge remote-tracking branch 'thunderhub-client/master'

This commit is contained in:
AP 2020-02-26 18:39:18 +01:00
commit 55854eb69c
217 changed files with 31084 additions and 0 deletions

1
.env Normal file
View file

@ -0,0 +1 @@
REACT_APP_VERSION=$npm_package_version

23
.gitignore vendored Normal file
View file

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

7
.prettierrc Normal file
View file

@ -0,0 +1,7 @@
{
"printWidth": 80,
"trailingComma": "all",
"tabWidth": 4,
"semi": true,
"singleQuote": true
}

25
.storybook/main.js Normal file
View file

@ -0,0 +1,25 @@
module.exports = {
stories: ['../src/**/*.stories.tsx'],
addons: [
'@storybook/addon-knobs/register',
'@storybook/addon-actions',
'@storybook/preset-create-react-app',
'@storybook/addon-links',
'@storybook/addon-viewport/register',
],
webpackFinal: async config => {
config.module.rules.push({
test: /\.(ts|tsx)$/,
use: [
{
loader: require.resolve('awesome-typescript-loader'),
},
{
loader: require.resolve('react-docgen-typescript-loader'),
},
],
});
config.resolve.extensions.push('.ts', '.tsx');
return config;
},
};

7
.storybook/manager.js Normal file
View file

@ -0,0 +1,7 @@
import { addons } from '@storybook/addons';
import { themes } from '@storybook/theming';
addons.setConfig({
theme: themes.dark,
panelPosition: 'right',
});

22
.storybook/preview.js Normal file
View file

@ -0,0 +1,22 @@
import { addDecorator, addParameters } from '@storybook/react';
import themeDecorator from './themeDecorator';
import { withKnobs } from '@storybook/addon-knobs';
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
const customViewports = {
smallScreen: {
name: 'Small Screen',
styles: {
width: '578px',
height: '100%',
},
},
};
addParameters({
viewport: {
viewports: { ...INITIAL_VIEWPORTS, ...customViewports },
},
});
addDecorator(themeDecorator);
addDecorator(withKnobs);

View file

@ -0,0 +1,37 @@
import React from 'react';
import styled, { ThemeProvider, css } from 'styled-components';
import { select, boolean } from '@storybook/addon-knobs';
import { backgroundColor, cardColor } from '../src/styles/Themes';
const StyledBackground = styled.div`
width: 100%;
height: 100%;
padding: 100px 0;
${({ withBackground, cardBackground }) =>
withBackground &&
css`
background: ${cardBackground ? cardColor : backgroundColor};
`}
display: flex;
justify-content: center;
align-items: center;
`;
const ThemeDecorator = storyFn => {
const background = boolean('No Background', false);
const cardBackground = boolean('Card Background', true);
return (
<ThemeProvider
theme={{ mode: select('Theme', ['dark', 'light'], 'dark') }}
>
<StyledBackground
withBackground={!background}
cardBackground={cardBackground}
>
{storyFn()}
</StyledBackground>
</ThemeProvider>
);
};
export default ThemeDecorator;

100
package.json Normal file
View file

@ -0,0 +1,100 @@
{
"name": "app",
"version": "0.1.6.2",
"private": true,
"dependencies": {
"@apollo/react-hooks": "^3.1.3",
"@types/crypto-js": "^3.1.43",
"@types/jest": "25.1.3",
"@types/lodash.debounce": "^4.0.6",
"@types/lodash.merge": "^4.6.6",
"@types/lodash.sortby": "^4.7.6",
"@types/node": "13.7.4",
"@types/numeral": "^0.0.26",
"@types/qrcode.react": "^1.0.0",
"@types/react": "16.9.21",
"@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",
"@types/styled-react-modal": "^1.2.0",
"@types/styled-theming": "^2.2.2",
"@types/victory": "^33.1.4",
"@types/zxcvbn": "^4.4.0",
"apollo-boost": "^0.4.4",
"base64url": "^3.0.1",
"crypto-js": "^4.0.0",
"date-fns": "^2.8.0",
"graphql": "^14.6.0",
"lodash.debounce": "^4.0.8",
"lodash.merge": "^4.6.2",
"lodash.sortby": "^4.7.0",
"node-sass": "^4.13.0",
"numeral": "^2.0.6",
"qrcode.react": "^1.0.0",
"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",
"react-spring": "^8.0.27",
"react-toastify": "^5.4.1",
"react-tooltip": "^4.0.3",
"snyk": "^1.294.1",
"styled-components": "^5.0.1",
"styled-react-modal": "^2.0.0",
"styled-theming": "^2.2.0",
"typescript": "^3.7.2",
"victory": "^34.1.1",
"zxcvbn": "^4.4.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public",
"deploy": "yarn build && aws s3 --profile EBFullAccess sync build/ s3://thunderhub-client"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@storybook/addon-actions": "^5.3.13",
"@storybook/addon-info": "^5.3.13",
"@storybook/addon-knobs": "^5.3.13",
"@storybook/addon-links": "^5.3.13",
"@storybook/addon-viewport": "^5.3.13",
"@storybook/addons": "^5.3.13",
"@storybook/preset-create-react-app": "^1.5.2",
"@storybook/react": "^5.3.13",
"awesome-typescript-loader": "^5.2.1",
"husky": "^4.2.3",
"prettier": "1.19.1",
"pretty-quick": "^2.0.1",
"react-docgen-typescript-loader": "^3.6.0"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

60
public/index.html Normal file
View file

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
property="og:description"
content="Manage and monitor your lightning network node right inside your browser"
/>
<meta
property="og:title"
content="ThunderHub - Lightning Node Manager"
/>
<meta name="robots" content="index, follow" />
<link rel="apple-touch-icon" href="apple-touch-icon-152x152.png" />
<link rel="canonical" href="https://thunderhub.io/" />
<meta property="og:url" content="https://thunderhub.io" />
<meta
name="twitter:title"
content="ThunderHub - Lightning Node Manager"
/>
<meta
name="twitter:description"
content="Manage and monitor your lightning network node right inside your browser"
/>
<meta name="twitter:site" content="@thunderhubio" />
<meta name="twitter:creator" content="@thunderhubio" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>ThunderHub - Lightning Node Manager</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
--></body>
</html>

20
public/manifest.json Normal file
View file

@ -0,0 +1,20 @@
{
"short_name": "ThunderHub",
"name": "ThunderHub - Lightning Node Manager",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "apple-touch-icon-152x152.png",
"type": "image/png",
"sizes": "152x152"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

2
public/robots.txt Normal file
View file

@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *

9
src/App.test.tsx Normal file
View file

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});

80
src/App.tsx Normal file
View file

@ -0,0 +1,80 @@
import React, { Suspense } from 'react';
import { ThemeProvider } from 'styled-components';
import { GlobalStyles } from './styles/GlobalStyle';
import { ApolloProvider } from '@apollo/react-hooks';
import { BrowserRouter } from 'react-router-dom';
import ApolloClient from 'apollo-boost';
import { useSettings } from './context/SettingsContext';
import { ModalProvider } from 'styled-react-modal';
import { useAccount } from './context/AccountContext';
import { toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { Header } from './sections/header/Header';
import { Footer } from './sections/footer/Footer';
import { LoadingCard } from './components/loading/LoadingCard';
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'));
toast.configure({ draggable: false });
const client = new ApolloClient({
uri:
process.env.NODE_ENV === 'development'
? 'http://localhost:3001'
: 'https://api.thunderhub.io/',
});
const ContextApp: React.FC = () => {
const { theme } = useSettings();
const { loggedIn, admin, viewOnly, sessionAdmin } = useAccount();
const renderContent = () => (
<Suspense
fallback={<LoadingCard noCard={true} loadingHeight={'240px'} />}
>
{!loggedIn && admin === '' ? (
<EntryView />
) : admin !== '' && viewOnly === '' && sessionAdmin === '' ? (
<EntryView session={true} />
) : (
<>
<ConnectionCheck />
<StatusCheck />
<ContentView />
</>
)}
</Suspense>
);
return (
<ThemeProvider theme={{ mode: theme }}>
<ModalProvider backgroundComponent={BaseModalBackground}>
<ScrollToTop />
<GlobalStyles />
<Header />
{renderContent()}
<Footer />
</ModalProvider>
</ThemeProvider>
);
};
const App: React.FC = () => {
return (
<BrowserRouter>
<ApolloProvider client={client}>
<ContextProvider>
<ContextApp />
</ContextProvider>
</ApolloProvider>
</BrowserRouter>
);
};
export default App;

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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-alert-triangle"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>

After

Width:  |  Height:  |  Size: 424 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-anchor"><circle cx="12" cy="5" r="3"></circle><line x1="12" y1="22" x2="12" y2="8"></line><path d="M5 12H2a10 10 0 0 0 20 0h-3"></path></svg>

After

Width:  |  Height:  |  Size: 345 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-arrow-down"><line x1="12" y1="5" x2="12" y2="19"></line><polyline points="19 12 12 19 5 12"></polyline></svg>

After

Width:  |  Height:  |  Size: 313 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-arrow-up"><line x1="12" y1="19" x2="12" y2="5"></line><polyline points="5 12 12 5 19 12"></polyline></svg>

After

Width:  |  Height:  |  Size: 310 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

@ -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-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>

After

Width:  |  Height:  |  Size: 269 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-chevron-left"><polyline points="15 18 9 12 15 6"></polyline></svg>

After

Width:  |  Height:  |  Size: 270 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-chevron-right"><polyline points="9 18 15 12 9 6"></polyline></svg>

After

Width:  |  Height:  |  Size: 270 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-chevron-up"><polyline points="18 15 12 9 6 15"></polyline></svg>

After

Width:  |  Height:  |  Size: 268 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-chevrons-down"><polyline points="7 13 12 18 17 13"></polyline><polyline points="7 6 12 11 17 6"></polyline></svg>

After

Width:  |  Height:  |  Size: 317 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-chevrons-up"><polyline points="17 11 12 6 7 11"></polyline><polyline points="17 18 12 13 7 18"></polyline></svg>

After

Width:  |  Height:  |  Size: 316 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-circle"><circle cx="12" cy="12" r="10"></circle></svg>

After

Width:  |  Height:  |  Size: 258 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-copy"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>

After

Width:  |  Height:  |  Size: 351 B

1
src/assets/icons/cpu.svg Normal file
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-cpu"><rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect><rect x="9" y="9" width="6" height="6"></rect><line x1="9" y1="1" x2="9" y2="4"></line><line x1="15" y1="1" x2="15" y2="4"></line><line x1="9" y1="20" x2="9" y2="23"></line><line x1="15" y1="20" x2="15" y2="23"></line><line x1="20" y1="9" x2="23" y2="9"></line><line x1="20" y1="14" x2="23" y2="14"></line><line x1="1" y1="9" x2="4" y2="9"></line><line x1="1" y1="14" x2="4" y2="14"></line></svg>

After

Width:  |  Height:  |  Size: 667 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-crosshair"><circle cx="12" cy="12" r="10"></circle><line x1="22" y1="12" x2="18" y2="12"></line><line x1="6" y1="12" x2="2" y2="12"></line><line x1="12" y1="6" x2="12" y2="2"></line><line x1="12" y1="22" x2="12" y2="18"></line></svg>

After

Width:  |  Height:  |  Size: 437 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-edit"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>

After

Width:  |  Height:  |  Size: 365 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-eye-off"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"></path><line x1="1" y1="1" x2="23" y2="23"></line></svg>

After

Width:  |  Height:  |  Size: 460 B

1
src/assets/icons/eye.svg Normal file
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-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>

After

Width:  |  Height:  |  Size: 316 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-git-branch"><line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path></svg>

After

Width:  |  Height:  |  Size: 377 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-git-commit"><circle cx="12" cy="12" r="4"></circle><line x1="1.05" y1="12" x2="7" y2="12"></line><line x1="17.01" y1="12" x2="22.96" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 358 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="1" stroke-linecap="round" stroke-linejoin="round" class="feather feather-git-pull-request"><circle cx="18" cy="18" r="3"></circle><circle cx="6" cy="6" r="3"></circle><path d="M13 6h3a2 2 0 0 1 2 2v7"></path><line x1="6" y1="9" x2="6" y2="21"></line></svg>

After

Width:  |  Height:  |  Size: 387 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-github"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>

After

Width:  |  Height:  |  Size: 527 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-globe"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>

After

Width:  |  Height:  |  Size: 409 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-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>

After

Width:  |  Height:  |  Size: 365 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-home"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path><polyline points="9 22 9 12 15 12 15 22"></polyline></svg>

After

Width:  |  Height:  |  Size: 332 B

1
src/assets/icons/key.svg Normal file
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="1" stroke-linecap="round" stroke-linejoin="round" class="feather feather-key"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg>

After

Width:  |  Height:  |  Size: 352 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-layers"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>

After

Width:  |  Height:  |  Size: 365 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="1" stroke-linecap="round" stroke-linejoin="round" class="feather feather-link"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>

After

Width:  |  Height:  |  Size: 371 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-loader"><line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line></svg>

After

Width:  |  Height:  |  Size: 614 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-mail"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>

After

Width:  |  Height:  |  Size: 354 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-menu"><line x1="3" y1="12" x2="21" y2="12"></line><line x1="3" y1="6" x2="21" y2="6"></line><line x1="3" y1="18" x2="21" y2="18"></line></svg>

After

Width:  |  Height:  |  Size: 346 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-moon"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>

After

Width:  |  Height:  |  Size: 281 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-more-vertical"><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>

After

Width:  |  Height:  |  Size: 341 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-pocket"><path d="M4 3h16a2 2 0 0 1 2 2v6a10 10 0 0 1-10 10A10 10 0 0 1 2 11V5a2 2 0 0 1 2-2z"></path><polyline points="8 10 12 14 16 10"></polyline></svg>

After

Width:  |  Height:  |  Size: 358 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-radio"><circle cx="12" cy="12" r="2"></circle><path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path></svg>

After

Width:  |  Height:  |  Size: 389 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-repeat"><polyline points="17 1 21 5 17 9"></polyline><path d="M3 11V9a4 4 0 0 1 4-4h14"></path><polyline points="7 23 3 19 7 15"></polyline><path d="M21 13v2a4 4 0 0 1-4 4H3"></path></svg>

After

Width:  |  Height:  |  Size: 392 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-send"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>

After

Width:  |  Height:  |  Size: 314 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-server"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line></svg>

After

Width:  |  Height:  |  Size: 431 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-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>

After

Width:  |  Height:  |  Size: 1,011 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-shield"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>

After

Width:  |  Height:  |  Size: 279 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="1" stroke-linecap="round" stroke-linejoin="round" class="feather feather-sliders"><line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line></svg>

After

Width:  |  Height:  |  Size: 611 B

1
src/assets/icons/sun.svg Normal file
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-sun"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>

After

Width:  |  Height:  |  Size: 650 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="1" stroke-linecap="round" stroke-linejoin="round" class="feather feather-users"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>

After

Width:  |  Height:  |  Size: 400 B

1
src/assets/icons/x.svg Normal file
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-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>

After

Width:  |  Height:  |  Size: 299 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-zap-off"><polyline points="12.41 6.75 13 2 10.57 4.92"></polyline><polyline points="18.57 12.91 21 10 15.66 10"></polyline><polyline points="8 8 3 14 12 14 11 22 16 16"></polyline><line x1="1" y1="1" x2="23" y2="23"></line></svg>

After

Width:  |  Height:  |  Size: 433 B

1
src/assets/icons/zap.svg Normal file
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-zap"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View file

@ -0,0 +1,15 @@
import { useAccount } from '../../context/AccountContext';
interface AdminSwitchProps {
children: any;
}
export const AdminSwitch = ({ children }: AdminSwitchProps) => {
const { admin, sessionAdmin } = useAccount();
if (!admin && !sessionAdmin) {
return null;
}
return children;
};

View file

@ -0,0 +1,10 @@
import React from 'react';
import { AnimatedNumber } from './AnimatedNumber';
export default {
title: 'Animated/Number',
};
export const Default = () => {
return <AnimatedNumber amount={100} />;
};

View file

@ -0,0 +1,49 @@
import React from 'react';
import { useSpring, animated } from 'react-spring';
import { getValue } from '../../helpers/Helpers';
import { useSettings } from '../../context/SettingsContext';
import { usePriceState } from 'context/PriceContext';
type PriceProps = {
price: number;
symbol: string;
currency: string;
};
type AnimatedProps = {
amount: number;
};
export const AnimatedNumber = ({ amount }: AnimatedProps) => {
const { value } = useSpring({
from: { value: 0 },
value: amount,
});
const { currency } = useSettings();
const { prices } = usePriceState();
let priceProps: PriceProps = {
price: 0,
symbol: '',
currency: currency !== 'btc' && currency !== 'sat' ? 'sat' : currency,
};
if (prices) {
const current: { last: number; symbol: string } = prices[currency] ?? {
last: 0,
symbol: '',
};
priceProps = {
price: current.last,
symbol: current.symbol,
currency,
};
}
return (
<animated.div>
{value.interpolate(amount => getValue({ amount, ...priceProps }))}
</animated.div>
);
};

View file

@ -0,0 +1,43 @@
import styled from 'styled-components';
import { Sub4Title } from '../generic/Styled';
import { fontColors, textColor } from 'styles/Themes';
export const Line = styled.div`
margin: 16px 0;
`;
export const StyledTitle = styled(Sub4Title)`
text-align: left;
width: 100%;
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;
`;
export const QRTextWrapper = styled.div`
display: flex;
margin: 16px 0;
flex-direction: column;
align-items: center;
justify-content: center;
`;

View file

@ -0,0 +1,44 @@
import React from 'react';
import { GET_CAN_ADMIN } from 'graphql/query';
import { useQuery } from '@apollo/react-hooks';
import { getAuthString } from 'utils/auth';
import { SingleLine, Sub4Title } from 'components/generic/Styled';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { themeColors } from 'styles/Themes';
import { XSvg, Check } from 'components/generic/Icons';
type AdminProps = {
host: string;
admin: string;
cert: string;
setChecked: (state: boolean) => void;
};
export const AdminCheck = ({ host, admin, cert, setChecked }: AdminProps) => {
const { data, loading } = useQuery(GET_CAN_ADMIN, {
skip: !admin,
variables: { auth: getAuthString(host, admin, cert) },
onError: () => {
setChecked(false);
},
onCompleted: () => {
setChecked(true);
},
});
const content = () => {
if (loading) {
return <ScaleLoader height={20} color={themeColors.blue3} />;
} else if (data?.adminCheck) {
return <Check />;
}
return <XSvg />;
};
return (
<SingleLine>
<Sub4Title>Admin Macaroon</Sub4Title>
{content()}
</SingleLine>
);
};

View file

@ -0,0 +1,150 @@
import React, { useState } from 'react';
import { useQuery } from '@apollo/react-hooks';
import { GET_CAN_CONNECT } from 'graphql/query';
import { getAuthString } from 'utils/auth';
import { SingleLine, Sub4Title, Separation } from 'components/generic/Styled';
import ScaleLoader from 'react-spinners/ScaleLoader';
import { themeColors } from 'styles/Themes';
import { Check, XSvg } from 'components/generic/Icons';
import { ColorButton } from 'components/buttons/colorButton/ColorButton';
import { AdminCheck } from './AdminCheck';
import { Text } from 'views/other/OtherViews.styled';
type ViewProps = {
host: string;
admin: string;
viewOnly: string;
cert: string;
adminChecked: boolean;
callback: () => void;
setAdminChecked: (state: boolean) => void;
handleConnect: () => void;
};
export const ViewCheck = ({
host,
admin,
viewOnly,
cert,
adminChecked,
setAdminChecked,
handleConnect,
callback,
}: ViewProps) => {
const [confirmed, setConfirmed] = useState(false);
const { data, loading } = useQuery(GET_CAN_CONNECT, {
variables: { auth: getAuthString(host, viewOnly ?? admin, cert) },
onCompleted: () => setConfirmed(true),
onError: () => setConfirmed(false),
});
const content = () => {
if (loading) {
return <ScaleLoader height={20} color={themeColors.blue3} />;
} else if (data?.getNodeInfo.alias && viewOnly) {
return <Check />;
}
return <XSvg />;
};
const renderInfo = () => {
if (!loading && data && data.getNodeInfo) {
return (
<>
<SingleLine>
<Sub4Title>Alias</Sub4Title>
<Sub4Title>{data.getNodeInfo.alias}</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Synced To Chain</Sub4Title>
<Sub4Title>
{data.getNodeInfo.is_synced_to_chain ? 'Yes' : 'No'}
</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Version</Sub4Title>
<Sub4Title>
{data.getNodeInfo.version.split(' ')[0]}
</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Active Channels</Sub4Title>
<Sub4Title>
{data.getNodeInfo.active_channels_count}
</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Pending Channels</Sub4Title>
<Sub4Title>
{data.getNodeInfo.pending_channels_count}
</Sub4Title>
</SingleLine>
<SingleLine>
<Sub4Title>Closed Channels</Sub4Title>
<Sub4Title>
{data.getNodeInfo.closed_channels_count}
</Sub4Title>
</SingleLine>
<Separation />
</>
);
}
return null;
};
const renderTitle = () => {
if (!confirmed) {
return 'Go Back';
} else if (adminChecked && !viewOnly && admin) {
return 'Connect (Admin-Only)';
} else if (!adminChecked && viewOnly) {
return 'Connect (View-Only)';
} else {
return 'Connect';
}
};
const renderButton = () => (
<ColorButton
fullWidth={true}
withMargin={'16px 0 0'}
disabled={loading}
loading={loading}
arrow={confirmed}
onClick={() => {
if (confirmed) {
handleConnect();
} else {
callback();
}
}}
>
{renderTitle()}
</ColorButton>
);
const renderText = () => (
<Text>
Failed to connect to node. Please verify the information provided.
</Text>
);
return (
<>
{renderInfo()}
{!confirmed && !loading && renderText()}
<SingleLine>
<Sub4Title>View-Only Macaroon</Sub4Title>
{content()}
</SingleLine>
<AdminCheck
host={host}
admin={admin}
cert={cert}
setChecked={setAdminChecked}
/>
{renderButton()}
</>
);
};

View file

@ -0,0 +1,166 @@
import React, { useState } from 'react';
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';
import { useHistory } from 'react-router-dom';
import { saveUserAuth } from 'utils/auth';
import { PasswordInput } from './views/Password';
import { useConnectionDispatch } from 'context/ConnectionContext';
import { useStatusDispatch } from 'context/StatusContext';
type AuthProps = {
type: string;
status: string;
callback: () => void;
setStatus: (state: string) => void;
};
export const Auth = ({ type, status, callback, setStatus }: AuthProps) => {
const next = getNextAvailable();
const { changeAccount } = useAccount();
const { push } = useHistory();
const dispatch = useConnectionDispatch();
const dispatchState = useStatusDispatch();
const [name, setName] = useState();
const [host, setHost] = useState();
const [admin, setAdmin] = useState();
const [viewOnly, setViewOnly] = useState();
const [cert, setCert] = useState();
const [password, setPassword] = useState();
const [adminChecked, setAdminChecked] = useState(false);
const handleSet = ({
name,
host,
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;
admin?: string;
viewOnly?: string;
cert?: string;
}) => {
saveUserAuth({
available: next,
name,
host: host || '',
admin,
viewOnly,
cert,
});
dispatch({ type: 'disconnected' });
dispatchState({ type: 'disconnected' });
changeAccount(next);
push('/');
};
const handleSave = () => {
const encryptedAdmin =
admin && password
? CryptoJS.AES.encrypt(admin, password).toString()
: undefined;
saveUserAuth({
available: next,
name,
host,
admin: encryptedAdmin,
viewOnly,
cert,
});
dispatch({ type: 'disconnected' });
dispatchState({ type: 'disconnected' });
changeAccount(next);
push('/');
};
const handleConnect = () => {
if (adminChecked) {
setStatus('password');
} else {
handleSave();
}
};
const renderView = () => {
switch (type) {
case 'login':
return <LoginForm handleSet={handleSet} />;
case 'qrcode':
return <QRLogin handleSet={handleSet} />;
case 'connect':
return <ConnectLoginForm handleSet={handleSet} />;
default:
return <BTCLoginForm handleSet={handleSet} />;
}
};
return (
<>
{status === 'none' && renderView()}
{status === 'confirmNode' && (
<ViewCheck
host={host}
admin={admin}
viewOnly={viewOnly}
cert={cert}
adminChecked={adminChecked}
setAdminChecked={setAdminChecked}
handleConnect={handleConnect}
callback={callback}
/>
)}
{status === 'password' && (
<PasswordInput
isPass={password}
setPass={setPassword}
callback={handleSave}
loading={false}
/>
)}
</>
);
};

View file

@ -0,0 +1,58 @@
import React, { useState } from 'react';
import { getConfigLnd } from '../../../utils/auth';
import { toast } from 'react-toastify';
import { Input } from 'components/input/Input';
import { Line, StyledTitle } from '../Auth.styled';
import { RiskCheckboxAndConfirm } from './Checkboxes';
interface AuthProps {
handleSet: ({
name,
host,
admin,
viewOnly,
cert,
}: {
name?: string;
host?: string;
admin?: string;
viewOnly?: string;
cert?: string;
}) => void;
}
export const BTCLoginForm = ({ handleSet }: AuthProps) => {
const [name, setName] = useState('');
const [json, setJson] = useState('');
const [checked, setChecked] = useState(false);
const handleClick = () => {
try {
JSON.parse(json);
const { cert, admin, viewOnly, host } = getConfigLnd(json);
handleSet({ name, host, admin, viewOnly, cert });
} catch (error) {
toast.error('Invalid JSON');
}
};
const canConnect = json !== '' && checked;
return (
<>
<Line>
<StyledTitle>Name:</StyledTitle>
<Input onChange={e => setName(e.target.value)} />
</Line>
<Line>
<StyledTitle>BTCPayServer Connect JSON:</StyledTitle>
<Input onChange={e => setJson(e.target.value)} />
</Line>
<RiskCheckboxAndConfirm
disabled={!canConnect}
handleClick={handleClick}
checked={checked}
onChange={setChecked}
/>
</>
);
};

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) => (
<>
<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>
<WarningBox />
</>
);
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

@ -0,0 +1,60 @@
import React, { useState } from 'react';
import { getAuthLnd, getBase64CertfromDerFormat } from '../../../utils/auth';
import { Input } from 'components/input/Input';
import { Line, StyledTitle } from '../Auth.styled';
import { RiskCheckboxAndConfirm } from './Checkboxes';
interface AuthProps {
handleSet: ({
name,
host,
admin,
viewOnly,
cert,
}: {
name?: string;
host?: string;
admin?: string;
viewOnly?: string;
cert?: string;
}) => void;
}
export const ConnectLoginForm = ({ handleSet }: AuthProps) => {
const [name, setName] = useState('');
const [url, setUrl] = useState('');
const [checked, setChecked] = useState(false);
const handleClick = () => {
const { cert, macaroon, socket } = getAuthLnd(url);
const base64Cert = getBase64CertfromDerFormat(cert) || '';
handleSet({
name,
host: socket,
admin: macaroon,
cert: base64Cert,
});
};
const canConnect = url !== '' && checked;
return (
<>
<Line>
<StyledTitle>Name:</StyledTitle>
<Input onChange={e => setName(e.target.value)} />
</Line>
<Line>
<StyledTitle>LND Connect Url:</StyledTitle>
<Input onChange={e => setUrl(e.target.value)} />
</Line>
<RiskCheckboxAndConfirm
disabled={!canConnect}
handleClick={handleClick}
checked={checked}
onChange={setChecked}
/>
</>
);
};

View file

@ -0,0 +1,110 @@
import React, { useState } from 'react';
import { Input } from 'components/input/Input';
import { Line, StyledTitle } from '../Auth.styled';
import { SingleLine, Sub4Title } from 'components/generic/Styled';
import {
MultiButton,
SingleButton,
} from 'components/buttons/multiButton/MultiButton';
import { RiskCheckboxAndConfirm } from './Checkboxes';
interface AuthProps {
handleSet: ({
name,
host,
admin,
viewOnly,
cert,
}: {
name?: string;
host?: string;
admin?: string;
viewOnly?: string;
cert?: string;
}) => void;
}
export const LoginForm = ({ handleSet }: AuthProps) => {
const [isViewOnly, setIsViewOnly] = useState(true);
const [checked, setChecked] = useState(false);
const [name, setName] = useState('');
const [host, setHost] = useState('');
const [admin, setAdmin] = useState('');
const [viewOnly, setRead] = useState('');
const [cert, setCert] = useState('');
const handleClick = () => {
handleSet({ name, host, admin, viewOnly, cert });
};
const canConnect =
name !== '' &&
host !== '' &&
(admin !== '' || viewOnly !== '') &&
checked;
return (
<>
<SingleLine>
<Sub4Title>Type of Account:</Sub4Title>
<MultiButton>
<SingleButton
selected={isViewOnly}
onClick={() => setIsViewOnly(true)}
>
ViewOnly
</SingleButton>
<SingleButton
selected={!isViewOnly}
onClick={() => setIsViewOnly(false)}
>
Admin
</SingleButton>
</MultiButton>
</SingleLine>
<Line>
<StyledTitle>Name:</StyledTitle>
<Input
placeholder={'Name for this node (e.g.: My Awesome Node)'}
onChange={e => setName(e.target.value)}
/>
</Line>
<Line>
<StyledTitle>Host:</StyledTitle>
<Input
placeholder={'Url and port (e.g.: www.node.com:443)'}
onChange={e => setHost(e.target.value)}
/>
</Line>
{!isViewOnly && (
<Line>
<StyledTitle>Admin:</StyledTitle>
<Input
placeholder={'Base64 or HEX Admin macaroon'}
onChange={e => setAdmin(e.target.value)}
/>
</Line>
)}
<Line>
<StyledTitle>Readonly:</StyledTitle>
<Input
placeholder={'Base64 or HEX Readonly macaroon'}
onChange={e => setRead(e.target.value)}
/>
</Line>
<Line>
<StyledTitle>Certificate:</StyledTitle>
<Input
placeholder={'Base64 or HEX TLS Certificate'}
onChange={e => setCert(e.target.value)}
/>
</Line>
<RiskCheckboxAndConfirm
disabled={!canConnect}
handleClick={handleClick}
checked={checked}
onChange={setChecked}
/>
</>
);
};

View file

@ -0,0 +1,47 @@
import React from 'react';
import { Sub4Title, SubTitle } from '../../generic/Styled';
import zxcvbn from 'zxcvbn';
import { ColorButton } from '../../buttons/colorButton/ColorButton';
import { Input } from 'components/input/Input';
import { Line } from '../Auth.styled';
import { LoadingBar } from 'components/loadingBar/LoadingBar';
interface PasswordProps {
isPass: string;
setPass: (pass: string) => void;
callback: () => void;
loading: boolean;
}
export const PasswordInput = ({
isPass = '',
setPass,
callback,
loading = false,
}: PasswordProps) => {
const strength = (100 * Math.min(zxcvbn(isPass).guesses_log10, 40)) / 40;
const needed = 20;
return (
<>
<SubTitle>Please Input a Password</SubTitle>
<Line>
<Sub4Title>Password:</Sub4Title>
<Input onChange={e => setPass(e.target.value)} />
</Line>
<Line>
<Sub4Title>Strength:</Sub4Title>
<LoadingBar percent={strength} />
</Line>
<ColorButton
disabled={strength < needed}
onClick={callback}
withMargin={'32px 0 0'}
fullWidth={true}
arrow={true}
loading={loading}
>
Connect
</ColorButton>
</>
);
};

View file

@ -0,0 +1,163 @@
import React, { useState, useEffect } from 'react';
import QrReader from 'react-qr-reader';
import Modal from '../../../components/modal/ReactModal';
import { toast } from 'react-toastify';
import { getQRConfig } from 'utils/auth';
import { Line, QRTextWrapper } from '../Auth.styled';
import sortBy from 'lodash.sortby';
import { LoadingBar } from 'components/loadingBar/LoadingBar';
import { SubTitle } from 'components/generic/Styled';
import { ColorButton } from 'components/buttons/colorButton/ColorButton';
type QRLoginProps = {
handleSet: ({
name,
host,
admin,
viewOnly,
cert,
skipCheck,
}: {
name?: string;
host?: string;
admin?: string;
viewOnly?: string;
cert?: string;
skipCheck?: boolean;
}) => void;
};
export const QRLogin = ({ handleSet }: QRLoginProps) => {
const [qrData, setQrData] = useState<any>([]);
const [modalOpen, setModalOpen] = useState(true);
const [modalClosed, setModalClosed] = useState('none');
const [total, setTotal] = useState(0);
const [missing, setMissing] = useState<number[]>();
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 (
<>
<QRTextWrapper>
<SubTitle>
No information read from QR Codes.
</SubTitle>
</QRTextWrapper>
<ColorButton
fullWidth={true}
onClick={() => {
setModalClosed('none');
setModalOpen(true);
}}
>
Try Again
</ColorButton>
</>
);
case 'error':
return (
<QRTextWrapper>
<SubTitle>
Make sure you have a camara available and that you
have given ThunderHub the correct permissions to use
it.
</SubTitle>
</QRTextWrapper>
);
default:
return null;
}
};
return (
<>
{renderInfo()}
<Modal isOpen={modalOpen} closeCallback={handleClose}>
<Line>
<LoadingBar
percent={
missing
? 100 * ((total - missing.length) / total)
: 0
}
/>
</Line>
<QrReader
delay={500}
onError={handleError}
onScan={handleScan}
style={{ width: '100%' }}
/>
</Modal>
</>
);
};

View file

@ -0,0 +1,27 @@
import { useEffect } from 'react';
import { useQuery } from '@apollo/react-hooks';
import { GET_BITCOIN_FEES } from '../../graphql/query';
import { useBitcoinDispatch } from '../../context/BitcoinContext';
export const BitcoinFees = () => {
const setInfo = useBitcoinDispatch();
const { loading, data, stopPolling } = useQuery(GET_BITCOIN_FEES, {
onError: () => {
setInfo({ type: 'error' });
stopPolling();
},
pollInterval: 60000,
});
useEffect(() => {
if (!loading && data && data.getBitcoinFees) {
const { fast, halfHour, hour } = data.getBitcoinFees;
setInfo({
type: 'fetched',
state: { loading: false, error: false, fast, halfHour, hour },
});
}
}, [data, loading, setInfo]);
return null;
};

View file

@ -0,0 +1,29 @@
import { useEffect } from 'react';
import { useQuery } from '@apollo/react-hooks';
import { GET_BITCOIN_PRICE } from '../../graphql/query';
import { usePriceDispatch } from '../../context/PriceContext';
export const BitcoinPrice = () => {
const setPrices = usePriceDispatch();
const { loading, data, stopPolling } = useQuery(GET_BITCOIN_PRICE, {
onError: () => setPrices({ type: 'error' }),
pollInterval: 60000,
});
useEffect(() => {
if (!loading && data && data.getBitcoinPrice) {
try {
const prices = JSON.parse(data.getBitcoinPrice);
setPrices({
type: 'fetched',
state: { loading: false, error: false, prices },
});
} catch (error) {
stopPolling();
setPrices({ type: 'error' });
}
}
}, [data, loading, setPrices, stopPolling]);
return null;
};

View file

@ -0,0 +1,32 @@
import React from 'react';
import styled, { css } from 'styled-components';
import { burgerColor } from 'styles/Themes';
import { NodeInfo } from 'sections/navigation/nodeInfo/NodeInfo';
import { Navigation } from 'sections/navigation/Navigation';
import { SideSettings } from 'sections/navigation/sideSettings/SideSettings';
const StyledBurger = styled.div`
padding: 16px 16px 0;
background-color: ${burgerColor};
box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.1);
${({ open }: { open: boolean }) =>
open &&
css`
margin-bottom: 16px;
`}
`;
interface BurgerProps {
open: boolean;
setOpen: (state: boolean) => void;
}
export const BurgerMenu = ({ open, setOpen }: BurgerProps) => {
return (
<StyledBurger open={open}>
<NodeInfo isBurger={true} />
<SideSettings isBurger={true} />
<Navigation isBurger={true} setOpen={setOpen} />
</StyledBurger>
);
};

View file

@ -0,0 +1,31 @@
import React from 'react';
import { action } from '@storybook/addon-actions';
import { ColorButton } from './ColorButton';
import { text, boolean, color } from '@storybook/addon-knobs';
export default {
title: 'Color Button',
};
export const Default = () => {
const withColor = boolean('With Color', false);
const buttonColor = withColor ? { color: color('Color', 'yellow') } : {};
return (
<ColorButton
{...buttonColor}
loading={boolean('Loading', false)}
disabled={boolean('Disabled', false)}
arrow={boolean('With Arrow', false)}
selected={boolean('Selected', false)}
onClick={action('clicked')}
withMargin={text('Margin', '')}
withBorder={boolean('With Border', false)}
fullWidth={boolean('Full Width', false)}
width={text('Width', '')}
>
{text('Text', 'Button')}
</ColorButton>
);
};

View file

@ -0,0 +1,159 @@
import React from 'react';
import styled, { css } from 'styled-components';
import {
textColor,
colorButtonBackground,
disabledButtonBackground,
disabledButtonBorder,
disabledTextColor,
colorButtonBorder,
colorButtonBorderTwo,
hoverTextColor,
} from '../../../styles/Themes';
import { ChevronRight } from '../../generic/Icons';
import { themeColors } from '../../../styles/Themes';
import ScaleLoader from 'react-spinners/ScaleLoader';
interface GeneralProps {
fullWidth?: boolean;
buttonWidth?: string;
withMargin?: string;
}
const GeneralButton = styled.button`
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
outline: none;
padding: 8px 16px;
text-decoration: none;
border-radius: 4px;
white-space: nowrap;
font-size: 14px;
box-sizing: border-box;
margin: ${({ withMargin }) => (withMargin ? withMargin : '0')};
width: ${({ fullWidth, buttonWidth }: GeneralProps) =>
fullWidth ? '100%' : buttonWidth ? buttonWidth : 'auto'};
`;
const StyledArrow = styled.div`
margin: 0 -8px -5px 4px;
`;
interface BorderProps {
borderColor?: string;
selected?: boolean;
withBorder?: boolean;
}
const BorderButton = styled(GeneralButton)`
${({ selected }) => selected && `cursor: default`};
${({ selected }) => selected && `font-weight: 900`};
background-color: ${colorButtonBackground};
color: ${textColor};
border: 1px solid
${({ borderColor, selected, withBorder }: BorderProps) =>
withBorder
? borderColor
? borderColor
: colorButtonBorder
: selected
? colorButtonBorder
: colorButtonBorderTwo};
&:hover {
${({ borderColor, selected }: BorderProps) =>
!selected
? css`
border: 1px solid ${colorButtonBackground};
background-color: ${borderColor
? borderColor
: colorButtonBorder};
color: ${hoverTextColor};
`
: ''};
}
`;
const DisabledButton = styled(GeneralButton)`
border: none;
background-color: ${disabledButtonBackground};
color: ${disabledTextColor};
border: 1px solid ${disabledButtonBorder};
cursor: default;
`;
const renderArrow = () => (
<StyledArrow>
<ChevronRight size={'18px'} />
</StyledArrow>
);
export interface ColorButtonProps {
loading?: boolean;
color?: string;
disabled?: boolean;
children?: any;
selected?: boolean;
arrow?: boolean;
onClick?: any;
withMargin?: string;
withBorder?: boolean;
fullWidth?: boolean;
width?: string;
}
export const ColorButton = ({
loading,
color,
disabled,
children,
selected,
arrow,
withMargin,
withBorder,
fullWidth,
width,
onClick,
}: ColorButtonProps) => {
if (disabled && !loading) {
return (
<DisabledButton
withMargin={withMargin}
fullWidth={fullWidth}
buttonWidth={width}
>
{children}
{arrow && renderArrow()}
</DisabledButton>
);
}
if (loading) {
return (
<DisabledButton
withMargin={withMargin}
fullWidth={fullWidth}
buttonWidth={width}
>
<ScaleLoader height={16} color={themeColors.blue2} />
</DisabledButton>
);
}
return (
<BorderButton
borderColor={color}
selected={selected}
onClick={onClick}
withMargin={withMargin}
withBorder={withBorder}
fullWidth={fullWidth}
buttonWidth={width}
>
{children}
{arrow && renderArrow()}
</BorderButton>
);
};

View file

@ -0,0 +1,51 @@
import React, { useState } from 'react';
import { boolean, color } from '@storybook/addon-knobs';
import { MultiButton, SingleButton } from './MultiButton';
import { action } from '@storybook/addon-actions';
export default {
title: 'Multi Button',
};
export const Default = () => {
const withColor = boolean('With Color', false);
const buttonColor = withColor ? { color: color('Color', 'yellow') } : {};
const [selected, setSelected] = useState(0);
return (
<MultiButton>
<SingleButton
{...buttonColor}
selected={selected === 0}
onClick={() => {
action('Button 1 clicked')();
setSelected(0);
}}
>
Button 1
</SingleButton>
<SingleButton
{...buttonColor}
selected={selected === 1}
onClick={() => {
action('Button 2 clicked')();
setSelected(1);
}}
>
Button 2
</SingleButton>
<SingleButton
{...buttonColor}
selected={selected === 2}
onClick={() => {
action('Button 3 clicked')();
setSelected(2);
}}
>
Button 3
</SingleButton>
</MultiButton>
);
};

View file

@ -0,0 +1,85 @@
import React from 'react';
import styled, { css } from 'styled-components';
import {
multiSelectColor,
colorButtonBorder,
multiButtonColor,
} from '../../../styles/Themes';
interface StyledSingleProps {
selected?: boolean;
buttonColor?: string;
}
const StyledSingleButton = styled.button`
border-radius: 4px;
cursor: pointer;
outline: none;
border: none;
text-decoration: none;
padding: 8px 16px;
background-color: transparent;
color: ${multiSelectColor};
flex-grow: 1;
${({ selected, buttonColor }: StyledSingleProps) =>
selected
? css`
color: white;
background-color: ${buttonColor
? buttonColor
: colorButtonBorder};
`
: ''};
`;
interface SingleButtonProps {
children: any;
selected?: boolean;
color?: string;
onClick?: () => void;
}
export const SingleButton = ({
children,
selected,
color,
onClick,
}: SingleButtonProps) => {
return (
<StyledSingleButton
selected={selected}
buttonColor={color}
onClick={() => {
onClick && onClick();
}}
>
{children}
</StyledSingleButton>
);
};
interface MultiBackProps {
margin?: string;
}
const MultiBackground = styled.div`
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
padding: 4px;
background: ${multiButtonColor};
flex-wrap: wrap;
${({ margin }: MultiBackProps) => margin && `margin: ${margin}`}
`;
interface MultiButtonProps {
children: any;
margin?: string;
}
export const MultiButton = ({ children, margin }: MultiButtonProps) => {
return <MultiBackground margin={margin}>{children}</MultiBackground>;
};

View file

@ -0,0 +1,115 @@
import React, { useState } from 'react';
import CryptoJS from 'crypto-js';
import { toast } from 'react-toastify';
import {
Sub4Title,
NoWrapTitle,
SubTitle,
ResponsiveLine,
} from '../../generic/Styled';
import { Circle, ChevronRight } from '../../generic/Icons';
import styled from 'styled-components';
import { useAccount } from '../../../context/AccountContext';
import { getAuthString, saveSessionAuth } from '../../../utils/auth';
import { useSettings } from '../../../context/SettingsContext';
import { textColorMap } from '../../../styles/Themes';
import { ColorButton } from '../colorButton/ColorButton';
import { Input } from '../../input/Input';
const RadioText = styled.div`
margin-left: 10px;
`;
const ButtonRow = styled.div`
width: auto;
display: flex;
justify-content: center;
align-items: center;
`;
interface LoginProps {
macaroon: string;
color?: string;
callback: any;
variables: {};
setModalOpen: (value: boolean) => void;
}
export const LoginModal = ({
macaroon,
color,
setModalOpen,
callback,
variables,
}: LoginProps) => {
const { theme } = useSettings();
const [pass, setPass] = useState<string>('');
const [storeSession, setStoreSession] = useState<boolean>(false);
const { host, cert, refreshAccount } = useAccount();
const handleClick = () => {
try {
const bytes = CryptoJS.AES.decrypt(macaroon, pass);
const decrypted = bytes.toString(CryptoJS.enc.Utf8);
if (storeSession) {
saveSessionAuth(decrypted);
refreshAccount();
}
const auth = getAuthString(host, decrypted, cert);
callback({ variables: { ...variables, auth } });
setModalOpen(false);
} catch (error) {
toast.error('Wrong Password');
}
};
const renderButton = (
onClick: () => void,
text: string,
selected: boolean,
) => (
<ColorButton color={color} onClick={onClick}>
<Circle
size={'10px'}
fillcolor={selected ? textColorMap[theme] : ''}
/>
<RadioText>{text}</RadioText>
</ColorButton>
);
return (
<>
<SubTitle>Unlock your Account</SubTitle>
<ResponsiveLine>
<Sub4Title>Password:</Sub4Title>
<Input onChange={e => setPass(e.target.value)} />
</ResponsiveLine>
<ResponsiveLine>
<NoWrapTitle>Don't ask me again this session:</NoWrapTitle>
<ButtonRow>
{renderButton(
() => setStoreSession(true),
'Yes',
storeSession,
)}
{renderButton(
() => setStoreSession(false),
'No',
!storeSession,
)}
</ButtonRow>
</ResponsiveLine>
<ColorButton
disabled={pass === ''}
onClick={handleClick}
color={color}
fullWidth={true}
withMargin={'16px 0 0'}
>
Unlock
<ChevronRight />
</ColorButton>
</>
);
};

View file

@ -0,0 +1,64 @@
import React, { useState } from 'react';
import Modal from '../../modal/ReactModal';
import { LoginModal } from './LoginModal';
import { useAccount } from '../../../context/AccountContext';
import { getAuthString } from '../../../utils/auth';
import { ColorButton } from '../colorButton/ColorButton';
import { ColorButtonProps } from '../colorButton/ColorButton';
interface SecureButtonProps extends ColorButtonProps {
callback: any;
disabled: boolean;
children: any;
variables: {};
color?: string;
withMargin?: string;
arrow?: boolean;
}
export const SecureButton = ({
callback,
color,
disabled,
children,
variables,
...props
}: SecureButtonProps) => {
const [modalOpen, setModalOpen] = useState<boolean>(false);
const { host, cert, admin, sessionAdmin } = useAccount();
if (!admin && !sessionAdmin) {
return null;
}
const auth = getAuthString(host, sessionAdmin, cert);
const handleClick = () => setModalOpen(true);
const onClick = sessionAdmin
? () => callback({ variables: { ...variables, auth } })
: handleClick;
return (
<>
<ColorButton
color={color}
disabled={disabled}
onClick={onClick}
{...props}
>
{children}
</ColorButton>
<Modal isOpen={modalOpen} closeCallback={() => setModalOpen(false)}>
<LoginModal
color={color}
macaroon={admin}
setModalOpen={setModalOpen}
callback={callback}
variables={variables}
/>
</Modal>
</>
);
};

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

Some files were not shown because too many files have changed in this diff Show more