mirror of
https://github.com/apotdevin/thunderhub.git
synced 2025-02-22 14:22:33 +01:00
feat: refactor login and entry
This commit is contained in:
parent
a45da9d63d
commit
650c178cee
44 changed files with 307 additions and 123 deletions
|
@ -15,6 +15,7 @@ import { Header } from './sections/header/Header';
|
|||
import { Footer } from './sections/footer/Footer';
|
||||
import { BitcoinInfoProvider } from './context/BitcoinContext';
|
||||
import { BitcoinFees } from './components/bitcoinInfo/BitcoinFees';
|
||||
import { LoadingCard } from './components/loading/LoadingCard';
|
||||
|
||||
const EntryView = React.lazy(() => import('./views/entry/Entry'));
|
||||
const MainView = React.lazy(() => import('./views/main/Main'));
|
||||
|
@ -32,13 +33,15 @@ const client = new ApolloClient({
|
|||
|
||||
const ContextApp: React.FC = () => {
|
||||
const { theme } = useSettings();
|
||||
const { loggedIn, admin, read } = useAccount();
|
||||
const { loggedIn, admin, read, sessionAdmin } = useAccount();
|
||||
|
||||
const renderContent = () => (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Suspense
|
||||
fallback={<LoadingCard noCard={true} loadingHeight={'240px'} />}
|
||||
>
|
||||
{!loggedIn && admin === '' ? (
|
||||
<EntryView />
|
||||
) : admin !== '' && read === '' ? (
|
||||
) : admin !== '' && read === '' && sessionAdmin === '' ? (
|
||||
<EntryView session={true} />
|
||||
) : (
|
||||
<MainView />
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { useAccount } from '../../context/AccountContext';
|
||||
|
||||
interface AdminSwitchProps {
|
||||
children: any;
|
||||
}
|
||||
|
||||
export const AdminSwitch = ({ children }: AdminSwitchProps) => {
|
||||
const currentAuth = localStorage.getItem('account') || 'auth1';
|
||||
const adminMacaroon = localStorage.getItem(`${currentAuth}-admin`) || '';
|
||||
const sessionAdmin = sessionStorage.getItem('session') || '';
|
||||
const { admin, sessionAdmin } = useAccount();
|
||||
|
||||
if (!adminMacaroon && !sessionAdmin) {
|
||||
if (!admin && !sessionAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,14 +8,21 @@ import { toast } from 'react-toastify';
|
|||
import { useLazyQuery } from '@apollo/react-hooks';
|
||||
import { GET_CAN_CONNECT } from '../../graphql/query';
|
||||
import { getErrorContent } from '../../utils/error';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
interface AuthProps {
|
||||
available: number;
|
||||
callback?: () => void;
|
||||
withRedirect?: boolean;
|
||||
}
|
||||
|
||||
export const BTCLoginForm = ({ available, callback }: AuthProps) => {
|
||||
export const BTCLoginForm = ({
|
||||
available,
|
||||
callback,
|
||||
withRedirect,
|
||||
}: AuthProps) => {
|
||||
const { setAccount } = useAccount();
|
||||
const { push } = useHistory();
|
||||
|
||||
const [isName, setName] = useState('');
|
||||
const [isJson, setJson] = useState('');
|
||||
|
@ -63,6 +70,7 @@ export const BTCLoginForm = ({ available, callback }: AuthProps) => {
|
|||
|
||||
toast.success('Connected!');
|
||||
callback && callback();
|
||||
withRedirect && push('/');
|
||||
}
|
||||
}, [
|
||||
data,
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
getBase64CertfromDerFormat,
|
||||
saveUserAuth,
|
||||
getAuthString,
|
||||
saveSessionAuth,
|
||||
} from '../../utils/auth';
|
||||
import { LoginButton, PasswordInput } from './Password';
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
@ -13,14 +14,21 @@ import { useLazyQuery } from '@apollo/react-hooks';
|
|||
import { GET_CAN_CONNECT } from '../../graphql/query';
|
||||
import { toast } from 'react-toastify';
|
||||
import { getErrorContent } from '../../utils/error';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
interface AuthProps {
|
||||
available: number;
|
||||
callback?: () => void;
|
||||
withRedirect?: boolean;
|
||||
}
|
||||
|
||||
export const ConnectLoginForm = ({ available, callback }: AuthProps) => {
|
||||
export const ConnectLoginForm = ({
|
||||
available,
|
||||
callback,
|
||||
withRedirect,
|
||||
}: AuthProps) => {
|
||||
const { setAccount } = useAccount();
|
||||
const { push } = useHistory();
|
||||
|
||||
const [isName, setName] = useState('');
|
||||
const [isUrl, setUrl] = useState('');
|
||||
|
@ -54,18 +62,20 @@ export const ConnectLoginForm = ({ available, callback }: AuthProps) => {
|
|||
cert: base64Cert,
|
||||
});
|
||||
|
||||
sessionStorage.setItem('session', macaroon);
|
||||
saveSessionAuth(macaroon);
|
||||
|
||||
setAccount({
|
||||
loggedIn: true,
|
||||
host: socket,
|
||||
admin: encryptedAdmin,
|
||||
sessionAdmin: macaroon,
|
||||
read: macaroon,
|
||||
cert: base64Cert,
|
||||
});
|
||||
|
||||
toast.success('Connected!');
|
||||
callback && callback();
|
||||
withRedirect && push('/');
|
||||
}
|
||||
}, [data, loading, available, callback, isName, isPass, isUrl, setAccount]);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Input, SingleLine, Sub4Title } from '../generic/Styled';
|
||||
import { useAccount } from '../../context/AccountContext';
|
||||
import { saveUserAuth, getAuthString } from '../../utils/auth';
|
||||
import { saveUserAuth, getAuthString, saveSessionAuth } from '../../utils/auth';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import base64url from 'base64url';
|
||||
import { PasswordInput, LoginButton } from './Password';
|
||||
|
@ -9,14 +9,17 @@ import { useLazyQuery } from '@apollo/react-hooks';
|
|||
import { GET_CAN_CONNECT } from '../../graphql/query';
|
||||
import { getErrorContent } from '../../utils/error';
|
||||
import { toast } from 'react-toastify';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
interface AuthProps {
|
||||
available: number;
|
||||
callback?: () => void;
|
||||
withRedirect?: boolean;
|
||||
}
|
||||
|
||||
export const LoginForm = ({ available, callback }: AuthProps) => {
|
||||
export const LoginForm = ({ available, callback, withRedirect }: AuthProps) => {
|
||||
const { setAccount } = useAccount();
|
||||
const { push } = useHistory();
|
||||
|
||||
const [isName, setName] = useState('');
|
||||
const [isHost, setHost] = useState('');
|
||||
|
@ -62,16 +65,21 @@ export const LoginForm = ({ available, callback }: AuthProps) => {
|
|||
cert,
|
||||
});
|
||||
|
||||
saveSessionAuth(admin);
|
||||
|
||||
setAccount({
|
||||
loggedIn: true,
|
||||
name: isName,
|
||||
host: isHost,
|
||||
admin: encryptedAdmin,
|
||||
...(read === '' && { sessionAdmin: admin }),
|
||||
read,
|
||||
cert,
|
||||
});
|
||||
|
||||
toast.success('Connected!');
|
||||
callback && callback();
|
||||
withRedirect && push('/');
|
||||
}
|
||||
}, [
|
||||
data,
|
||||
|
@ -103,7 +111,10 @@ export const LoginForm = ({ available, callback }: AuthProps) => {
|
|||
|
||||
const renderContent = () => {
|
||||
const canConnect =
|
||||
isName !== '' && isHost !== '' && isRead !== '' && !!available;
|
||||
isName !== '' &&
|
||||
isHost !== '' &&
|
||||
(isAdmin !== '' || isRead !== '') &&
|
||||
!!available;
|
||||
return (
|
||||
<>
|
||||
<SingleLine>
|
||||
|
|
|
@ -83,8 +83,8 @@ export const CloseChannel = ({
|
|||
const [hour, setHour] = useState(0);
|
||||
|
||||
const { theme } = useSettings();
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { data: feeData } = useQuery(GET_BITCOIN_FEES, {
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
|
|
|
@ -7,6 +7,7 @@ import { ReactComponent as ZapOffIcon } from '../../icons/zap-off.svg';
|
|||
import { ReactComponent as HelpIcon } from '../../icons/help-circle.svg';
|
||||
import { ReactComponent as SunIcon } from '../../icons/sun.svg';
|
||||
import { ReactComponent as MoonIcon } from '../../icons/moon.svg';
|
||||
import { ReactComponent as EyeIcon } from '../../icons/eye.svg';
|
||||
import { ReactComponent as EyeOffIcon } from '../../icons/eye-off.svg';
|
||||
import { ReactComponent as ChevronsUpIcon } from '../../icons/chevrons-up.svg';
|
||||
import { ReactComponent as ChevronsDownIcon } from '../../icons/chevrons-down.svg';
|
||||
|
@ -35,11 +36,15 @@ import { ReactComponent as RadioIcon } from '../../icons/radio.svg';
|
|||
import { ReactComponent as CopyIcon } from '../../icons/copy.svg';
|
||||
import { ReactComponent as ShieldIcon } from '../../icons/shield.svg';
|
||||
import { ReactComponent as CrosshairIcon } from '../../icons/crosshair.svg';
|
||||
import { ReactComponent as KeyIcon } from '../../icons/key.svg';
|
||||
import { ReactComponent as SlidersIcon } from '../../icons/sliders.svg';
|
||||
import { ReactComponent as UsersIcon } from '../../icons/users.svg';
|
||||
|
||||
interface IconProps {
|
||||
color?: string;
|
||||
size?: string;
|
||||
fillcolor?: string;
|
||||
strokeWidth?: string;
|
||||
}
|
||||
|
||||
const GenericStyles = css`
|
||||
|
@ -47,6 +52,8 @@ const GenericStyles = css`
|
|||
width: ${({ size }: IconProps) => (size ? size : '18px')};
|
||||
color: ${({ color }: IconProps) => (color ? color : '')};
|
||||
fill: ${({ fillcolor }: IconProps) => (fillcolor ? fillcolor : '')};
|
||||
stroke-width: ${({ strokeWidth }: IconProps) =>
|
||||
strokeWidth ? strokeWidth : '2px'};
|
||||
`;
|
||||
|
||||
export const IconCircle = styled.div`
|
||||
|
@ -77,6 +84,7 @@ export const UpArrow = styleIcon(UpIcon);
|
|||
export const DownArrow = styleIcon(DownIcon);
|
||||
export const Sun = styleIcon(SunIcon);
|
||||
export const Moon = styleIcon(MoonIcon);
|
||||
export const Eye = styleIcon(EyeIcon);
|
||||
export const EyeOff = styleIcon(EyeOffIcon);
|
||||
export const ChevronsDown = styleIcon(ChevronsDownIcon);
|
||||
export const ChevronsUp = styleIcon(ChevronsUpIcon);
|
||||
|
@ -102,3 +110,6 @@ export const Radio = styleIcon(RadioIcon);
|
|||
export const Copy = styleIcon(CopyIcon);
|
||||
export const Shield = styleIcon(ShieldIcon);
|
||||
export const Crosshair = styleIcon(CrosshairIcon);
|
||||
export const Key = styleIcon(KeyIcon);
|
||||
export const Sliders = styleIcon(SlidersIcon);
|
||||
export const Users = styleIcon(UsersIcon);
|
||||
|
|
|
@ -56,7 +56,7 @@ interface SubCardProps {
|
|||
|
||||
export const SubCard = styled.div`
|
||||
margin-bottom: 10px;
|
||||
padding: ${({ padding }) => (padding ? padding : '10px')};
|
||||
padding: ${({ padding }) => (padding ? padding : '16px')};
|
||||
background: ${subCardColor};
|
||||
border: 1px solid ${cardBorderColor};
|
||||
border-left: ${({ color }: SubCardProps) =>
|
||||
|
@ -128,6 +128,13 @@ export const SingleLine = styled.div`
|
|||
align-items: center;
|
||||
`;
|
||||
|
||||
export const ColumnLine = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
`;
|
||||
|
||||
export const SimpleButton = styled.button`
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
|
|
|
@ -7,6 +7,8 @@ import { textColorMap } from '../../styles/Themes';
|
|||
|
||||
const Loading = styled.div`
|
||||
width: 100%;
|
||||
height: ${({ loadingHeight }: { loadingHeight?: string }) =>
|
||||
loadingHeight ? loadingHeight : 'auto'};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
@ -17,6 +19,7 @@ interface LoadingCardProps {
|
|||
noCard?: boolean;
|
||||
color?: string;
|
||||
noTitle?: boolean;
|
||||
loadingHeight?: string;
|
||||
}
|
||||
|
||||
export const LoadingCard = ({
|
||||
|
@ -24,6 +27,7 @@ export const LoadingCard = ({
|
|||
color,
|
||||
noCard = false,
|
||||
noTitle = false,
|
||||
loadingHeight,
|
||||
}: LoadingCardProps) => {
|
||||
const { theme } = useSettings();
|
||||
|
||||
|
@ -31,7 +35,7 @@ export const LoadingCard = ({
|
|||
|
||||
if (noCard) {
|
||||
return (
|
||||
<Loading>
|
||||
<Loading loadingHeight={loadingHeight}>
|
||||
<ScaleLoader height={20} color={loadingColor} />
|
||||
</Loading>
|
||||
);
|
||||
|
@ -40,7 +44,7 @@ export const LoadingCard = ({
|
|||
if (noTitle) {
|
||||
return (
|
||||
<Card>
|
||||
<Loading>
|
||||
<Loading loadingHeight={loadingHeight}>
|
||||
<ScaleLoader height={20} color={loadingColor} />
|
||||
</Loading>
|
||||
</Card>
|
||||
|
@ -53,7 +57,7 @@ export const LoadingCard = ({
|
|||
<SubTitle>{title}</SubTitle>
|
||||
</CardTitle>
|
||||
<Card>
|
||||
<Loading>
|
||||
<Loading loadingHeight={loadingHeight}>
|
||||
<ScaleLoader height={20} color={loadingColor} />
|
||||
</Loading>
|
||||
</Card>
|
||||
|
|
|
@ -13,7 +13,7 @@ import { LoginButton } from '../../components/auth/Password';
|
|||
import { Circle, ChevronRight } from '../generic/Icons';
|
||||
import styled from 'styled-components';
|
||||
import { useAccount } from '../../context/AccountContext';
|
||||
import { getAuthString } from '../../utils/auth';
|
||||
import { getAuthString, saveSessionAuth } from '../../utils/auth';
|
||||
import { useSettings } from '../../context/SettingsContext';
|
||||
import { textColorMap } from '../../styles/Themes';
|
||||
|
||||
|
@ -46,7 +46,7 @@ export const LoginModal = ({
|
|||
const { theme } = useSettings();
|
||||
const [pass, setPass] = useState<string>('');
|
||||
const [storeSession, setStoreSession] = useState<boolean>(false);
|
||||
const { host, cert } = useAccount();
|
||||
const { host, cert, refreshAccount } = useAccount();
|
||||
|
||||
const handleClick = () => {
|
||||
try {
|
||||
|
@ -54,7 +54,8 @@ export const LoginModal = ({
|
|||
const decrypted = bytes.toString(CryptoJS.enc.Utf8);
|
||||
|
||||
if (storeSession) {
|
||||
sessionStorage.setItem('session', decrypted);
|
||||
saveSessionAuth(decrypted);
|
||||
refreshAccount();
|
||||
}
|
||||
const auth = getAuthString(host, decrypted, cert);
|
||||
callback({ variables: { ...variables, auth } });
|
||||
|
|
|
@ -24,13 +24,9 @@ export const SecureButton = ({
|
|||
}: SecureButtonProps) => {
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false);
|
||||
|
||||
const { host, cert } = useAccount();
|
||||
const { host, cert, admin, sessionAdmin } = useAccount();
|
||||
|
||||
const currentAuth = localStorage.getItem('account') || 'auth1';
|
||||
const adminMacaroon = localStorage.getItem(`${currentAuth}-admin`) || '';
|
||||
const sessionAdmin = sessionStorage.getItem('session') || '';
|
||||
|
||||
if (!adminMacaroon && !sessionAdmin) {
|
||||
if (!admin && !sessionAdmin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -55,7 +51,7 @@ export const SecureButton = ({
|
|||
<Modal isOpen={modalOpen} setIsOpen={setModalOpen}>
|
||||
<LoginModal
|
||||
color={color}
|
||||
macaroon={adminMacaroon}
|
||||
macaroon={admin}
|
||||
setModalOpen={setModalOpen}
|
||||
callback={callback}
|
||||
variables={variables}
|
||||
|
|
|
@ -42,14 +42,14 @@ const AccountProvider = ({ children }: any) => {
|
|||
const activeAccount = localStorage.getItem('account') || 'auth1';
|
||||
const sessionAdmin = sessionStorage.getItem('session') || '';
|
||||
const { name, host, admin, read, cert } = getAuthParams(activeAccount);
|
||||
const readMacaroon = read ? read : sessionAdmin;
|
||||
const loggedIn = host !== '' && readMacaroon !== '';
|
||||
const loggedIn = host !== '' && (read !== '' || sessionAdmin !== '');
|
||||
|
||||
const setAccount = ({
|
||||
loggedIn,
|
||||
name,
|
||||
host,
|
||||
admin,
|
||||
sessionAdmin,
|
||||
read,
|
||||
cert,
|
||||
}: ChangeProps) => {
|
||||
|
@ -60,6 +60,7 @@ const AccountProvider = ({ children }: any) => {
|
|||
name,
|
||||
host,
|
||||
admin,
|
||||
sessionAdmin,
|
||||
read,
|
||||
cert,
|
||||
});
|
||||
|
@ -90,8 +91,7 @@ const AccountProvider = ({ children }: any) => {
|
|||
const activeAccount = localStorage.getItem('account') || 'auth1';
|
||||
const sessionAdmin = sessionStorage.getItem('session') || '';
|
||||
const { name, host, admin, read, cert } = getAuthParams(activeAccount);
|
||||
const readMacaroon = read ? read : sessionAdmin;
|
||||
const loggedIn = host !== '' && readMacaroon !== '';
|
||||
const loggedIn = host !== '' && (read !== '' || sessionAdmin !== '');
|
||||
|
||||
updateAccount((prevState: any) => {
|
||||
const newState = { ...prevState };
|
||||
|
@ -101,7 +101,7 @@ const AccountProvider = ({ children }: any) => {
|
|||
host,
|
||||
admin,
|
||||
sessionAdmin,
|
||||
read: readMacaroon,
|
||||
read,
|
||||
cert,
|
||||
});
|
||||
});
|
||||
|
@ -113,7 +113,7 @@ const AccountProvider = ({ children }: any) => {
|
|||
host,
|
||||
admin,
|
||||
sessionAdmin,
|
||||
read: readMacaroon,
|
||||
read,
|
||||
cert,
|
||||
setAccount,
|
||||
changeAccount,
|
||||
|
|
1
src/icons/eye.svg
Normal file
1
src/icons/eye.svg
Normal 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 |
1
src/icons/key.svg
Normal file
1
src/icons/key.svg
Normal 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 |
1
src/icons/sliders.svg
Normal file
1
src/icons/sliders.svg
Normal 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/icons/users.svg
Normal file
1
src/icons/users.svg
Normal 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,6 +1,9 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { textColor, cardColor } from '../../styles/Themes';
|
||||
import { HomeButton } from '../../views/entry/HomePage.styled';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useAccount } from '../../context/AccountContext';
|
||||
|
||||
const HeaderStyle = styled.div`
|
||||
display: flex;
|
||||
|
@ -28,11 +31,17 @@ const HeaderTitle = styled.div`
|
|||
`;
|
||||
|
||||
export const Header = () => {
|
||||
const { loggedIn } = useAccount();
|
||||
|
||||
return (
|
||||
<HeaderStyle>
|
||||
<Wrapper>
|
||||
<HeaderTitle>ThunderHub</HeaderTitle>
|
||||
<button>Login</button>
|
||||
{!loggedIn && (
|
||||
<Link to="/login" style={{ textDecoration: 'none' }}>
|
||||
<HomeButton>Login</HomeButton>
|
||||
</Link>
|
||||
)}
|
||||
</Wrapper>
|
||||
</HeaderStyle>
|
||||
);
|
||||
|
|
|
@ -45,8 +45,8 @@ const Alias = styled.div`
|
|||
`;
|
||||
|
||||
export const NodeInfo = () => {
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { loading, data } = useQuery(GET_NODE_INFO, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -37,6 +37,9 @@ export const saveUserAuth = ({
|
|||
cert && localStorage.setItem(`auth${available}-cert`, cert);
|
||||
};
|
||||
|
||||
export const saveSessionAuth = (sessionAdmin: string) =>
|
||||
sessionStorage.setItem('session', sessionAdmin);
|
||||
|
||||
export const getAuthString = (
|
||||
host: string,
|
||||
macaroon: string,
|
||||
|
|
|
@ -14,8 +14,8 @@ import { getErrorContent } from '../../utils/error';
|
|||
import ScaleLoader from 'react-spinners/ScaleLoader';
|
||||
|
||||
export const DownloadBackups = ({ color }: { color: string }) => {
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const [getBackups, { data, loading }] = useLazyQuery(GET_BACKUPS, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -20,8 +20,8 @@ export const RecoverFunds = ({ color }: { color: string }) => {
|
|||
const [backupString, setBackupString] = useState<string>('');
|
||||
const [isPasting, setIsPasting] = useState<boolean>(false);
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const [recoverFunds, { data, loading }] = useLazyQuery(RECOVER_FUNDS, {
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
|
|
|
@ -24,8 +24,8 @@ export const VerifyBackups = ({ color }: { color: string }) => {
|
|||
const [backupString, setBackupString] = useState<string>('');
|
||||
const [isPasting, setIsPasting] = useState<boolean>(false);
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const [verifyBackup, { data, loading }] = useLazyQuery(VERIFY_BACKUPS, {
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
|
|
|
@ -25,8 +25,8 @@ export const ChannelView = () => {
|
|||
});
|
||||
|
||||
const { theme } = useSettings();
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { data } = useQuery(GET_CHANNEL_AMOUNT_INFO, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -12,8 +12,8 @@ import { LoadingCard } from '../../../components/loading/LoadingCard';
|
|||
export const Channels = () => {
|
||||
const [indexOpen, setIndexOpen] = useState(0);
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { loading, data } = useQuery(GET_CHANNELS, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -12,8 +12,8 @@ import { LoadingCard } from '../../../components/loading/LoadingCard';
|
|||
export const ClosedChannels = () => {
|
||||
const [indexOpen, setIndexOpen] = useState(0);
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { loading, data } = useQuery(GET_CLOSED_CHANNELS, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -12,8 +12,8 @@ import { LoadingCard } from '../../../components/loading/LoadingCard';
|
|||
export const PendingChannels = () => {
|
||||
const [indexOpen, setIndexOpen] = useState(0);
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { loading, data } = useQuery(GET_PENDING_CHANNELS, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { LoginView } from './login/Login';
|
||||
import { SessionLogin } from './login/SessionLogin';
|
||||
import { useHistory } from 'react-router';
|
||||
import { useHistory, Switch, Route, useLocation } from 'react-router';
|
||||
import { HomePageView } from './HomePage';
|
||||
|
||||
interface HomeProps {
|
||||
|
@ -9,21 +9,24 @@ interface HomeProps {
|
|||
}
|
||||
|
||||
const EntryView = ({ session }: HomeProps) => {
|
||||
const [login, setLogin] = useState<boolean>(false);
|
||||
const history = useHistory();
|
||||
const { push } = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
if (history.location.pathname !== '/') {
|
||||
history.push('/');
|
||||
if (location.pathname !== '/' && location.pathname !== '/login') {
|
||||
push('/');
|
||||
}
|
||||
}, [history]);
|
||||
}, [location, push]);
|
||||
|
||||
return session ? (
|
||||
<SessionLogin />
|
||||
) : login ? (
|
||||
<LoginView />
|
||||
) : (
|
||||
<HomePageView setLogin={setLogin} />
|
||||
const getView = () => {
|
||||
return session ? <SessionLogin /> : <HomePageView />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/login" render={() => <LoginView />} />
|
||||
<Route path="/" render={() => getView()} />
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
36
src/views/entry/HomePage.styled.ts
Normal file
36
src/views/entry/HomePage.styled.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import styled from 'styled-components';
|
||||
import { ReactComponent as HeadlineImage } from '../../images/MoshingDoodle.svg';
|
||||
|
||||
export const Headline = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px 0;
|
||||
`;
|
||||
|
||||
export const LeftHeadline = styled.div`
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const StyledImage = styled(HeadlineImage)`
|
||||
width: 500px;
|
||||
`;
|
||||
|
||||
export const HomeButton = styled.button`
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
padding: 8px 24px;
|
||||
text-decoration: 2px solid blue;
|
||||
font-size: 16px;
|
||||
background-image: linear-gradient(to right, #fe4928, #ffc700);
|
||||
color: white;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 24px;
|
||||
white-space: nowrap;
|
||||
min-width: 100px;
|
||||
`;
|
|
@ -1,30 +1,65 @@
|
|||
import React from 'react';
|
||||
import {
|
||||
Wrapper,
|
||||
Card,
|
||||
SubTitle,
|
||||
SingleLine,
|
||||
ColumnLine,
|
||||
Sub4Title,
|
||||
} from '../../components/generic/Styled';
|
||||
import {
|
||||
Headline,
|
||||
LeftHeadline,
|
||||
StyledImage,
|
||||
HomeButton,
|
||||
} from './HomePage.styled';
|
||||
import styled from 'styled-components';
|
||||
import { ReactComponent as HeadlineImage } from '../../images/MoshingDoodle.svg';
|
||||
import { Wrapper } from '../../components/generic/Styled';
|
||||
import {
|
||||
Zap,
|
||||
Eye,
|
||||
Send,
|
||||
Key,
|
||||
Server,
|
||||
Sliders,
|
||||
Users,
|
||||
} from '../../components/generic/Icons';
|
||||
import { backgroundColor } from '../../styles/Themes';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const Headline = styled.div`
|
||||
const DetailCard = styled(Card)`
|
||||
width: 33%;
|
||||
background-color: ${backgroundColor};
|
||||
margin-bottom: 0;
|
||||
margin: 8px 16px;
|
||||
`;
|
||||
|
||||
const DetailLine = styled.div`
|
||||
margin: 0 -16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32px 0;
|
||||
`;
|
||||
|
||||
const LeftHeadline = styled.div`
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
const Padding = styled.div`
|
||||
padding-right: ${({ padding }: { padding?: string }) =>
|
||||
padding ? padding : '16px'};
|
||||
`;
|
||||
|
||||
const StyledImage = styled(HeadlineImage)`
|
||||
width: 500px;
|
||||
`;
|
||||
const detailCardContent = (title: string, text: string, Icon: any) => (
|
||||
<DetailCard>
|
||||
<SingleLine>
|
||||
<Padding>
|
||||
<Icon size={'40px'} strokeWidth={'1px'} />
|
||||
</Padding>
|
||||
<ColumnLine>
|
||||
<SubTitle>{title}</SubTitle>
|
||||
<Sub4Title>{text}</Sub4Title>
|
||||
</ColumnLine>
|
||||
</SingleLine>
|
||||
</DetailCard>
|
||||
);
|
||||
|
||||
interface HomePageProps {
|
||||
setLogin: (login: boolean) => void;
|
||||
}
|
||||
|
||||
export const HomePageView = ({ setLogin }: HomePageProps) => {
|
||||
export const HomePageView = () => {
|
||||
return (
|
||||
<>
|
||||
<Wrapper>
|
||||
|
@ -36,16 +71,56 @@ export const HomePageView = ({ setLogin }: HomePageProps) => {
|
|||
something else to place here. Think Think Think
|
||||
</h4>
|
||||
<h5>Available everywhere you can open a website.</h5>
|
||||
<button onClick={() => setLogin(true)}>Login</button>
|
||||
<Link to="/login" style={{ textDecoration: 'none' }}>
|
||||
<HomeButton>
|
||||
<Padding padding={'4px'}>
|
||||
<Zap />
|
||||
</Padding>
|
||||
Control The Lightning
|
||||
</HomeButton>
|
||||
</Link>
|
||||
</LeftHeadline>
|
||||
<StyledImage />
|
||||
</Headline>
|
||||
</Wrapper>
|
||||
<Wrapper withColor={true}>
|
||||
<div>Hello</div>
|
||||
<DetailLine>
|
||||
{detailCardContent(
|
||||
'Make Payments',
|
||||
'Send and receive both Lightning and On-Chain payments.',
|
||||
Send,
|
||||
)}
|
||||
{detailCardContent(
|
||||
'Multiple Nodes',
|
||||
'Connect to multiple nodes and quickly switch between them.',
|
||||
Server,
|
||||
)}
|
||||
{detailCardContent(
|
||||
'View-Only Mode',
|
||||
`Check the status of your node any time without risk.`,
|
||||
Eye,
|
||||
)}
|
||||
</DetailLine>
|
||||
<DetailLine>
|
||||
{detailCardContent(
|
||||
'AES Encryption',
|
||||
'Your Macaroon is AES encrypted with a password only you know.',
|
||||
Key,
|
||||
)}
|
||||
{detailCardContent(
|
||||
'Open Source',
|
||||
`Don't trust anyone. Verify the code yourself.`,
|
||||
Users,
|
||||
)}
|
||||
{detailCardContent(
|
||||
'Manage Channels',
|
||||
'Open, close and monitor channel status and liquidity',
|
||||
Sliders,
|
||||
)}
|
||||
</DetailLine>
|
||||
</Wrapper>
|
||||
<Wrapper>
|
||||
<div>Hello</div>
|
||||
<div>Another Line</div>
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -54,9 +54,15 @@ export const LoginView = () => {
|
|||
<SubTitle> How do you want to connect?</SubTitle>
|
||||
)}
|
||||
{isType === 'none' && renderButtons()}
|
||||
{isType === 'login' && <LoginForm available={next} />}
|
||||
{isType === 'connect' && <ConnectLoginForm available={next} />}
|
||||
{isType === 'btcpay' && <BTCLoginForm available={next} />}
|
||||
{isType === 'login' && (
|
||||
<LoginForm available={next} withRedirect={true} />
|
||||
)}
|
||||
{isType === 'connect' && (
|
||||
<ConnectLoginForm available={next} withRedirect={true} />
|
||||
)}
|
||||
{isType === 'btcpay' && (
|
||||
<BTCLoginForm available={next} withRedirect={true} />
|
||||
)}
|
||||
</Card>
|
||||
</Wrapper>
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
import { LoginButton } from '../../../components/auth/Password';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import { toast } from 'react-toastify';
|
||||
import { saveSessionAuth } from '../../../utils/auth';
|
||||
|
||||
export const SessionLogin = () => {
|
||||
const { name, admin, refreshAccount } = useAccount();
|
||||
|
@ -22,7 +23,7 @@ export const SessionLogin = () => {
|
|||
const bytes = CryptoJS.AES.decrypt(admin, pass);
|
||||
const decrypted = bytes.toString(CryptoJS.enc.Utf8);
|
||||
|
||||
sessionStorage.setItem('session', decrypted);
|
||||
saveSessionAuth(decrypted);
|
||||
refreshAccount();
|
||||
} catch (error) {
|
||||
toast.error('Wrong Password');
|
||||
|
|
|
@ -37,8 +37,8 @@ export const FeesView = () => {
|
|||
const [feeRate, setFeeRate] = useState(0);
|
||||
|
||||
const { theme } = useSettings();
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { loading, data } = useQuery(CHANNEL_FEES, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -48,8 +48,8 @@ const sectionColor = '#FFD300';
|
|||
|
||||
export const AccountInfo = () => {
|
||||
const [state, setState] = useState<string>('none');
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { data, loading } = useQuery(GET_BALANCES, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -43,7 +43,7 @@ export const SendOnChainCard = ({ color }: { color: string }) => {
|
|||
const [sendAll, setSendAll] = useState(false);
|
||||
const [isSent, setIsSent] = useState(false);
|
||||
|
||||
const canSend = address !== '' && tokens > 0 && amount > 0;
|
||||
const canSend = address !== '' && (sendAll || tokens > 0) && amount > 0;
|
||||
|
||||
const { theme } = useSettings();
|
||||
const { price, symbol, currency } = useSettings();
|
||||
|
|
|
@ -40,8 +40,8 @@ const TextPadding = styled.span`
|
|||
const sectionColor = '#fa541c';
|
||||
|
||||
export const ConnectCard = () => {
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { loading, data } = useQuery(GET_CONNECT_INFO, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -38,8 +38,8 @@ const Title = styled.div`
|
|||
`;
|
||||
|
||||
export const NetworkInfo = () => {
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { loading, data } = useQuery(GET_NETWORK_INFO, {
|
||||
variables: { auth },
|
||||
|
|
|
@ -26,8 +26,8 @@ export const DecodeCard = ({ color }: { color: string }) => {
|
|||
const { price, symbol, currency } = useSettings();
|
||||
const priceProps = { price, symbol, currency };
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const [decode, { data, loading }] = useMutation(DECODE_REQUEST, {
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
|
|
|
@ -57,8 +57,8 @@ export const OpenChannelCard = ({ color, setOpenCard }: OpenChannelProps) => {
|
|||
const { price, symbol, currency, theme } = useSettings();
|
||||
const priceProps = { price, symbol, currency };
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const [openChannel] = useMutation(OPEN_CHANNEL, {
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
|
|
|
@ -61,8 +61,8 @@ export const FlowBox = () => {
|
|||
const [isTime, setIsTime] = useState<string>('month');
|
||||
const [isType, setIsType] = useState<string>('amount');
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
const { data, loading } = useQuery(GET_IN_OUT, {
|
||||
variables: { time: isTime, auth },
|
||||
onError: error => toast.error(getErrorContent(error)),
|
||||
|
|
|
@ -64,8 +64,8 @@ export const ForwardChannelsReport = ({ isTime, isType, color }: Props) => {
|
|||
|
||||
const { price, symbol, currency } = useSettings();
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { data, loading } = useQuery(GET_FORWARD_CHANNELS_REPORT, {
|
||||
variables: { time: isTime, order: isType, auth, type },
|
||||
|
|
|
@ -31,8 +31,8 @@ interface Props {
|
|||
export const ForwardReport = ({ isTime, isType }: Props) => {
|
||||
const { theme, price, symbol, currency } = useSettings();
|
||||
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { data, loading } = useQuery(GET_FORWARD_REPORT, {
|
||||
variables: { time: isTime, auth },
|
||||
|
|
|
@ -25,8 +25,8 @@ import {
|
|||
import { LoadingCard } from '../../../../components/loading/LoadingCard';
|
||||
|
||||
export const LiquidReport = () => {
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { theme, price, symbol, currency } = useSettings();
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from '../../components/generic/Styled';
|
||||
import styled from 'styled-components';
|
||||
import { unSelectedNavButton, chartLinkColor } from '../../styles/Themes';
|
||||
import { useAccount } from '../../context/AccountContext';
|
||||
|
||||
const CurrentField = styled.div`
|
||||
color: ${chartLinkColor};
|
||||
|
@ -21,12 +22,7 @@ const CurrentField = styled.div`
|
|||
`;
|
||||
|
||||
export const CurrentSettings = () => {
|
||||
const currentAuth = localStorage.getItem('account') || 'auth1';
|
||||
const currentName = localStorage.getItem(`${currentAuth}-name`);
|
||||
const currentAdmin = localStorage.getItem(`${currentAuth}-admin`);
|
||||
const currentRead = localStorage.getItem(`${currentAuth}-read`);
|
||||
const currentCert = localStorage.getItem(`${currentAuth}-cert`);
|
||||
const currentHost = localStorage.getItem(`${currentAuth}-host`);
|
||||
const { name, host, admin, read, cert } = useAccount();
|
||||
|
||||
const renderField = (title: string, field: string | null) => {
|
||||
if (!field) return null;
|
||||
|
@ -43,11 +39,11 @@ export const CurrentSettings = () => {
|
|||
<CardWithTitle>
|
||||
<SubTitle>Current Account:</SubTitle>
|
||||
<Card>
|
||||
{renderField('Name:', currentName)}
|
||||
{renderField('AES Encrypted Admin Macaroon:', currentAdmin)}
|
||||
{renderField('Read-only Macaroon:', currentRead)}
|
||||
{renderField('Certificate:', currentCert)}
|
||||
{renderField('Host:', currentHost)}
|
||||
{renderField('Name:', name)}
|
||||
{renderField('Host:', host)}
|
||||
{renderField('AES Encrypted Admin Macaroon:', admin)}
|
||||
{renderField('Read-only Macaroon:', read)}
|
||||
{renderField('Certificate:', cert)}
|
||||
</Card>
|
||||
</CardWithTitle>
|
||||
);
|
||||
|
|
|
@ -29,8 +29,8 @@ export const TransactionList = () => {
|
|||
const [fetching, setFetching] = useState(false);
|
||||
|
||||
const { theme } = useSettings();
|
||||
const { host, read, cert } = useAccount();
|
||||
const auth = getAuthString(host, read, cert);
|
||||
const { host, read, cert, sessionAdmin } = useAccount();
|
||||
const auth = getAuthString(host, read !== '' ? read : sessionAdmin, cert);
|
||||
|
||||
const { loading, data, fetchMore } = useQuery(GET_RESUME, {
|
||||
variables: { auth, token: '' },
|
||||
|
|
Loading…
Add table
Reference in a new issue