Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: display warning when backup not done #22

Merged
merged 2 commits into from
Feb 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 60 additions & 17 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import CssBaseline from "@material-ui/core/CssBaseline";
import { Theme, withStyles } from "@material-ui/core/styles";
import { ThemeProvider } from "@material-ui/styles";
import React, { ReactElement } from "react";
import React, { ReactElement, useEffect } from "react";
import {
BrowserRouter as Router,
Redirect,
Expand All @@ -13,6 +13,14 @@ import NotFound from "./common/NotFound";
import Dashboard from "./dashboard/Dashboard";
import { Path } from "./router/Path";
import { darkTheme } from "./themes";
import { useElectronStore } from "./stores/electronStore";
import { Provider } from "mobx-react";
import { isElectron, logError, sendMessageToParent } from "./common/appUtil";
import { useBackupStore } from "./stores/backupStore";
import api from "./api";
import { getErrorMsg } from "./common/errorUtil";
import { timer } from "rxjs";
import { exhaustMap, retry } from "rxjs/operators";

const GlobalCss = withStyles((theme: Theme) => {
const background = theme.palette.background;
Expand Down Expand Up @@ -42,27 +50,62 @@ const GlobalCss = withStyles((theme: Theme) => {
};
})(() => null);

const electronStore = useElectronStore({});
const backupStore = useBackupStore({ backupInfoLoaded: false });

function App(): ReactElement {
useEffect(() => {
if (!isElectron()) {
return;
}
sendMessageToParent("getConnectionType");
const messageListenerHandler = (event: MessageEvent) => {
if (event.data.startsWith("connectionType")) {
electronStore.setConnectionType(
event.data.substr(event.data.indexOf(":") + 2)
);
}
};
window.addEventListener("message", messageListenerHandler);
return () => window.removeEventListener("message", messageListenerHandler);
}, []);

useEffect(() => {
const sub = timer(0, 60000)
.pipe(
exhaustMap(() => api.getBackupInfo$()),
retry(3)
)
.subscribe({
next: (resp) => backupStore.setInfo(resp),
error: (err) =>
logError(`Failed to retrieve backup info: ${getErrorMsg(err)}`),
});
return () => sub.unsubscribe();
}, []);

return (
<ThemeProvider theme={darkTheme}>
<CssBaseline />
<GlobalCss />
<Router>
<Switch>
<Route path={Path.CONNECTION_FAILED}>
<ConnectionFailed />
</Route>
<Route path={Path.DASHBOARD}>
<Dashboard />
</Route>
<Route exact path={Path.HOME}>
<Redirect to={Path.DASHBOARD} />
</Route>
<Route>
<NotFound />
</Route>
</Switch>
</Router>
<Provider electronStore={electronStore} backupStore={backupStore}>
<Router>
<Switch>
<Route path={Path.CONNECTION_FAILED}>
<ConnectionFailed />
</Route>
<Route path={Path.DASHBOARD}>
<Dashboard />
</Route>
<Route exact path={Path.HOME}>
<Redirect to={Path.DASHBOARD} />
</Route>
<Route>
<NotFound />
</Route>
</Switch>
</Router>
</Provider>
</ThemeProvider>
);
}
Expand Down
58 changes: 42 additions & 16 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { from, fromEvent, Observable, of, throwError } from "rxjs";
import { catchError, mergeMap } from "rxjs/operators";
import { isElectron, sendMessageToParent } from "./common/appUtil";
import io from "socket.io-client";
import { logError } from "./common/appUtil";
import { BackupInfo } from "./models/BackupInfo";
import { CreateReverseSwapRequest } from "./models/CreateReverseSwapRequest";
import { CreateReverseSwapResponse } from "./models/CreateReverseSwapResponse";
import { DepositResponse } from "./models/DepositResponse";
import { GetbalanceResponse } from "./models/GetbalanceResponse";
import { GetMnemonicResponse } from "./models/GetMnemonicResponse";
import { GetServiceInfoResponse } from "./models/GetServiceInfoResponse";
import { Info } from "./models/Info";
import { ListordersParams } from "./models/ListordersParams";
import { ListordersResponse } from "./models/ListordersResponse";
import { ListpairsResponse } from "./models/ListpairsResponse";
import { OrderBookParams } from "./models/OrderBookParams";
import { OrderBookResponse } from "./models/OrderBookResponse";
import { PlaceOrderParams } from "./models/PlaceOrderParams";
import { RemoveOrderParams } from "./models/RemoveOrderParams";
import { SetupStatusResponse } from "./models/SetupStatusResponse";
import { Status } from "./models/Status";
import { TradehistoryResponse } from "./models/TradehistoryResponse";
import { TradinglimitsResponse } from "./models/TradinglimitsResponse";
import io from "socket.io-client";
import { ListpairsResponse } from "./models/ListpairsResponse";
import { PlaceOrderParams } from "./models/PlaceOrderParams";
import { ListordersResponse } from "./models/ListordersResponse";
import { ListordersParams } from "./models/ListordersParams";
import { RemoveOrderParams } from "./models/RemoveOrderParams";
import { OrderBookParams } from "./models/OrderBookParams";
import { OrderBookResponse } from "./models/OrderBookResponse";

const url =
process.env.NODE_ENV === "development"
Expand All @@ -28,15 +30,13 @@ const path = `${url}/api/v1`;
const xudPath = `${path}/xud`;
const boltzPath = `${path}/boltz`;

const logError = (url: string, err: string) => {
if (isElectron()) {
const errorMsg = typeof err === "string" ? err : JSON.stringify(err);
sendMessageToParent(`logError: requestUrl: ${url}; error: ${errorMsg}`);
}
const logErr = (url: string, err: string): void => {
const errorMsg = typeof err === "string" ? err : JSON.stringify(err);
logError(`requestUrl: ${url}; error: ${errorMsg}`);
};

const logAndThrow = (url: string, err: string) => {
logError(url, err);
logErr(url, err);
throw err;
};

Expand Down Expand Up @@ -101,7 +101,7 @@ const fetchStreamResponse = <T>(url: string): Observable<T | null> => {
reader.read().then(processText);
})
.catch((e) => {
logError(url, e);
logErr(url, e);
subscriber.error(e);
});
});
Expand Down Expand Up @@ -216,6 +216,32 @@ export default {
);
},

changePassword$(newPassword: string, oldPassword: string): Observable<void> {
return fetchJsonResponse(
`${xudPath}/changepass`,
JSON.stringify({ newPassword, oldPassword }),
undefined,
"POST"
);
},

getMnemonic$(): Observable<GetMnemonicResponse> {
return fetchJsonResponse(`${xudPath}/getmnemonic`);
},

updateBackupDirectory$(location: string): Observable<void> {
return fetchJsonResponse(
`${path}/backup`,
JSON.stringify({ location }),
undefined,
"PUT"
);
},

getBackupInfo$(): Observable<BackupInfo> {
return fetchJsonResponse(`${path}/info`);
},

sio: {
io$,
console$(id: string): Observable<any> {
Expand Down
4 changes: 2 additions & 2 deletions src/common/ButtonWithLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import React, { ReactElement } from "react";
type ButtonWithLoadingProps = {
onClick: () => void;
text: string;
disabled: boolean;
loading: boolean;
disabled?: boolean;
loading?: boolean;
submitButton?: boolean;
fullWidth?: boolean;
size?: "small" | "medium" | "large";
Expand Down
52 changes: 52 additions & 0 deletions src/common/Password.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
FormControl,
IconButton,
InputAdornment,
InputLabel,
OutlinedInput,
} from "@material-ui/core";
import Visibility from "@material-ui/icons/Visibility";
import VisibilityOff from "@material-ui/icons/VisibilityOff";
import React, { ChangeEvent, ReactElement, useState } from "react";

type PasswordProps = {
label?: string;
value: string;
onChange: (
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => void;
};

const Password = (props: PasswordProps): ReactElement => {
const { value, onChange } = props;
const label = props.label || "Password";
const [showPassword, setShowPassword] = useState(false);

return (
<FormControl variant="outlined">
<InputLabel htmlFor={label}>{label}</InputLabel>
<OutlinedInput
id={label}
labelWidth={label.length * 9}
value={value}
onChange={(event) => {
onChange(event);
}}
type={showPassword ? "text" : "password"}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setShowPassword(!showPassword)}
edge="end"
>
{showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
/>
</FormControl>
);
};

export default Password;
99 changes: 99 additions & 0 deletions src/common/WarningMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import {
Card,
createStyles,
Grid,
IconButton,
makeStyles,
Theme,
Typography,
} from "@material-ui/core";
import WarningIcon from "@material-ui/icons/Warning";
import React, { ReactElement } from "react";
import CloseIcon from "@material-ui/icons/Close";

type WarningMessageProps = {
message: string;
showCloseIcon?: boolean;
onClose?: () => void;
alignToStart?: boolean;
additionalButtons?: { button: ReactElement; key: string }[];
};

const useStyles = makeStyles((theme: Theme) =>
createStyles({
warningMessage: {
backgroundColor: theme.palette.warning.dark,
color: theme.palette.warning.contrastText,
marginBottom: theme.spacing(2),
padding: theme.spacing(1),
},
iconContainer: {
display: "flex",
},
})
);

const WarningMessage = (props: WarningMessageProps): ReactElement => {
const {
message,
showCloseIcon,
onClose,
alignToStart,
additionalButtons,
} = props;
const classes = useStyles();

return (
<Grid item>
<Card elevation={0} className={classes.warningMessage}>
<Grid
item
container
wrap="nowrap"
justify="space-between"
alignItems="center"
>
<Grid
item
container
spacing={1}
justify={alignToStart ? "flex-start" : "center"}
alignItems="center"
wrap="nowrap"
>
<Grid item className={classes.iconContainer}>
<WarningIcon fontSize="small" />
</Grid>
<Grid item>
<Typography variant="body2" align="center">
{message}
</Typography>
</Grid>
</Grid>
{(showCloseIcon || additionalButtons?.length) && (
<Grid item container justify="flex-end" spacing={1}>
{additionalButtons?.map((button) => (
<Grid item key={button.key}>
{button.button}
</Grid>
))}
{showCloseIcon && (
<Grid item>
<IconButton
color="inherit"
size="small"
onClick={() => (onClose ? onClose() : void 0)}
>
<CloseIcon />
</IconButton>
</Grid>
)}
</Grid>
)}
</Grid>
</Card>
</Grid>
);
};

export default WarningMessage;
8 changes: 8 additions & 0 deletions src/common/appUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ export const openLink = (url: string): void => {
window.open(url, "_blank", "noopener");
};

export const logError = (err: string): void => {
if (isElectron()) {
sendMessageToParent(`logError: ${err}`);
return;
}
console.log(err);
};

export const sendMessageToParent = (message: string): void => {
window.parent.postMessage(message, "*");
};
9 changes: 7 additions & 2 deletions src/common/serviceUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import { Status } from "../models/Status";
export const isServiceReady = (status: Status): boolean => {
return (
status.status.startsWith("Ready") ||
(status.service === "xud" &&
!XUD_NOT_READY.some((str) => status.status.startsWith(str))) ||
(status.service === "xud" && isXudReady(status)) ||
(status.service === "boltz" &&
[...status.status.matchAll(new RegExp("down", "g"))].length === 1)
);
};

export const isXudLocked = (status: Status): boolean =>
status.status.startsWith("Wallet locked");

export const isXudReady = (status: Status): boolean =>
!XUD_NOT_READY.some((str) => status.status.startsWith(str));
Loading