mirror of
https://github.com/haveno-dex/haveno-ui.git
synced 2024-10-01 07:35:39 -04:00
feat: node settings wired to haveno daemon
- wired node settings to daemon and storage - updated tests and storybook - refactored for simplicity and code quality --- Reviewed-by: localredhead
This commit is contained in:
parent
8eb4694ca2
commit
250d742d48
@ -117,15 +117,21 @@ export function registerStoreHandlers() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle(IpcChannels.SetMoneroNode, async (_, value: string) => {
|
// set or clear remote node url; empty indicates local node
|
||||||
store.set(StorageKeys.Preferences_MoneroNode, value);
|
ipcMain.handle(IpcChannels.SetMoneroNode, async (_, uri?: string) => {
|
||||||
|
if (!uri) {
|
||||||
|
store.delete(StorageKeys.Preferences_SelectedNode);
|
||||||
|
} else {
|
||||||
|
store.set(StorageKeys.Preferences_SelectedNode, uri);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// fetch the complete set of user preferences
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
IpcChannels.GetPreferences,
|
IpcChannels.GetPreferences,
|
||||||
async (): Promise<IPreferences> => {
|
async (): Promise<IPreferences> => {
|
||||||
return {
|
return {
|
||||||
moneroNode: store.get(StorageKeys.Preferences_MoneroNode),
|
selectedNode: store.get(StorageKeys.Preferences_SelectedNode),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -21,7 +21,7 @@ export enum IpcChannels {
|
|||||||
VerifyPassword = "store:accountinfo.verifyPassword",
|
VerifyPassword = "store:accountinfo.verifyPassword",
|
||||||
SetPrimaryFiat = "store:accountinfo.primaryFiat",
|
SetPrimaryFiat = "store:accountinfo.primaryFiat",
|
||||||
GetPreferences = "store:preferences",
|
GetPreferences = "store:preferences",
|
||||||
SetMoneroNode = "store:preferences.moneroNode",
|
SetMoneroNode = "store:preferences.setMoneroNode",
|
||||||
|
|
||||||
VerifyAuthToken = "verifyAuthToken",
|
VerifyAuthToken = "verifyAuthToken",
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,14 @@ import type { Schema } from "electron-store";
|
|||||||
export enum StorageKeys {
|
export enum StorageKeys {
|
||||||
AccountInfo_Password = "accounInfo.password",
|
AccountInfo_Password = "accounInfo.password",
|
||||||
AccountInfo_PrimaryFiat = "accounInfo.primaryFiat",
|
AccountInfo_PrimaryFiat = "accounInfo.primaryFiat",
|
||||||
Preferences_MoneroNode = "preferences.moneroNode",
|
Preferences_SelectedNode = "preferences.selectedNode",
|
||||||
}
|
}
|
||||||
|
|
||||||
// TS types for StoreSchema
|
// TS types for StoreSchema
|
||||||
export interface IStoreSchema {
|
export interface IStoreSchema {
|
||||||
[StorageKeys.AccountInfo_Password]: IAccountInfo["password"];
|
[StorageKeys.AccountInfo_Password]: IAccountInfo["password"];
|
||||||
[StorageKeys.AccountInfo_PrimaryFiat]: IAccountInfo["primaryFiat"];
|
[StorageKeys.AccountInfo_PrimaryFiat]: IAccountInfo["primaryFiat"];
|
||||||
[StorageKeys.Preferences_MoneroNode]: IPreferences["moneroNode"]; // TODO: change to object {url, password}
|
[StorageKeys.Preferences_SelectedNode]: IPreferences["selectedNode"];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAccountInfo {
|
export interface IAccountInfo {
|
||||||
@ -39,7 +39,7 @@ export interface AccountInfoDto extends Omit<IAccountInfo, "password"> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IPreferences {
|
export interface IPreferences {
|
||||||
moneroNode: string;
|
selectedNode?: string; // empty for local; id for remote
|
||||||
}
|
}
|
||||||
|
|
||||||
// this schema is used by electron-store
|
// this schema is used by electron-store
|
||||||
@ -51,7 +51,7 @@ export const StoreSchema: Schema<IStoreSchema> = {
|
|||||||
[StorageKeys.AccountInfo_PrimaryFiat]: {
|
[StorageKeys.AccountInfo_PrimaryFiat]: {
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
[StorageKeys.Preferences_MoneroNode]: {
|
[StorageKeys.Preferences_SelectedNode]: {
|
||||||
type: "string",
|
type: "string",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -46,8 +46,9 @@ export const store = {
|
|||||||
getAccountInfo: async (): Promise<AccountInfoDto> =>
|
getAccountInfo: async (): Promise<AccountInfoDto> =>
|
||||||
ipcRenderer.invoke(IpcChannels.GetAccountInfo),
|
ipcRenderer.invoke(IpcChannels.GetAccountInfo),
|
||||||
|
|
||||||
setMoneroNode: async (value: string): Promise<void> =>
|
// sets the selected monero node url; empty indicates local node
|
||||||
ipcRenderer.invoke(IpcChannels.SetMoneroNode, value),
|
setMoneroNode: async (uri?: string): Promise<void> =>
|
||||||
|
ipcRenderer.invoke(IpcChannels.SetMoneroNode, uri),
|
||||||
|
|
||||||
getPreferences: async (): Promise<IPreferences> =>
|
getPreferences: async (): Promise<IPreferences> =>
|
||||||
ipcRenderer.invoke(IpcChannels.GetPreferences),
|
ipcRenderer.invoke(IpcChannels.GetPreferences),
|
||||||
|
@ -21,11 +21,11 @@ import { Home } from "@pages/Home";
|
|||||||
import { Login } from "@pages/Login";
|
import { Login } from "@pages/Login";
|
||||||
import { CreateAccount, Welcome } from "@pages/Onboarding";
|
import { CreateAccount, Welcome } from "@pages/Onboarding";
|
||||||
import {
|
import {
|
||||||
AccountBackup,
|
Backup,
|
||||||
AccountNodeSettings,
|
Settings,
|
||||||
AccountPaymentAccounts,
|
PaymentAccounts,
|
||||||
AccountSecurity,
|
Security,
|
||||||
AccountWallet,
|
Wallet,
|
||||||
AddPaymentAccount,
|
AddPaymentAccount,
|
||||||
PaymentMethods,
|
PaymentMethods,
|
||||||
} from "@pages/Account";
|
} from "@pages/Account";
|
||||||
@ -38,47 +38,47 @@ export function AppRoutes() {
|
|||||||
<Route path={ROUTES.Welcome} element={<Welcome />} />
|
<Route path={ROUTES.Welcome} element={<Welcome />} />
|
||||||
<Route path={ROUTES.CreateAccount} element={<CreateAccount />} />
|
<Route path={ROUTES.CreateAccount} element={<CreateAccount />} />
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.AccountPaymentAccounts}
|
path={ROUTES.PaymentAccounts}
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<AccountPaymentAccounts />
|
<PaymentAccounts />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.AccountNodeSettings}
|
path={ROUTES.NodeSettings}
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<AccountNodeSettings />
|
<Settings />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.AccountBackup}
|
path={ROUTES.Backup}
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<AccountBackup />
|
<Backup />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.AccountWallet}
|
path={ROUTES.Wallet}
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<AccountWallet />
|
<Wallet />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.AccountSecurity}
|
path={ROUTES.Security}
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<AccountSecurity />
|
<Security />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.AccountPaymentAccounts}
|
path={ROUTES.PaymentAccounts}
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<PaymentMethods />
|
<PaymentMethods />
|
||||||
@ -86,7 +86,7 @@ export function AppRoutes() {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.AccountAddPaymentAccount}
|
path={ROUTES.AddPaymentAccount}
|
||||||
element={
|
element={
|
||||||
<ProtectedRoute>
|
<ProtectedRoute>
|
||||||
<AddPaymentAccount />
|
<AddPaymentAccount />
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import type { ReactText } from "react";
|
import type { ReactNode } from "react";
|
||||||
import type { UnstyledButtonProps } from "@mantine/core";
|
import type { UnstyledButtonProps } from "@mantine/core";
|
||||||
import { UnstyledButton } from "@mantine/core";
|
import { UnstyledButton } from "@mantine/core";
|
||||||
import { BodyText } from "@atoms/Typography";
|
import { BodyText } from "@atoms/Typography";
|
||||||
|
|
||||||
interface TextButtonProps extends UnstyledButtonProps<"button"> {
|
interface TextButtonProps extends UnstyledButtonProps<"button"> {
|
||||||
children: ReactText;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TextButton(props: TextButtonProps) {
|
export function TextButton(props: TextButtonProps) {
|
||||||
|
@ -16,24 +16,33 @@
|
|||||||
|
|
||||||
import { Stack } from "@mantine/core";
|
import { Stack } from "@mantine/core";
|
||||||
import type { ComponentStory, ComponentMeta } from "@storybook/react";
|
import type { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||||
import { NodeStatus, NodeStatusType } from ".";
|
import { MoneroNodeListItem, NodeStatus } from ".";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "atoms/NodeStatus",
|
title: "atoms/MoneroNodeListItem",
|
||||||
component: NodeStatus,
|
component: MoneroNodeListItem,
|
||||||
} as ComponentMeta<typeof NodeStatus>;
|
} as ComponentMeta<typeof MoneroNodeListItem>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof NodeStatus> = () => {
|
const Template: ComponentStory<typeof MoneroNodeListItem> = () => {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Stack>
|
||||||
<NodeStatus
|
<MoneroNodeListItem
|
||||||
|
isSelected={true}
|
||||||
title="node.moneroworldcom:18089"
|
title="node.moneroworldcom:18089"
|
||||||
status={NodeStatusType.Active}
|
status={NodeStatus.Active}
|
||||||
|
onClick={() => console.log("clicked")}
|
||||||
/>
|
/>
|
||||||
<NodeStatus title="node.xmr.pt:18081" status={NodeStatusType.Inactive} />
|
<MoneroNodeListItem
|
||||||
<NodeStatus
|
isSelected={false}
|
||||||
|
title="node.xmr.pt:18081"
|
||||||
|
status={NodeStatus.Inactive}
|
||||||
|
onClick={() => console.log("clicked")}
|
||||||
|
/>
|
||||||
|
<MoneroNodeListItem
|
||||||
|
isSelected={false}
|
||||||
title="node.monero.net:18081"
|
title="node.monero.net:18081"
|
||||||
status={NodeStatusType.Active}
|
status={NodeStatus.Active}
|
||||||
|
onClick={() => console.log("clicked")}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
@ -17,19 +17,23 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { render } from "@testing-library/react";
|
import { render } from "@testing-library/react";
|
||||||
import { AppProviders } from "@atoms/AppProviders";
|
import { AppProviders } from "@atoms/AppProviders";
|
||||||
import { NodeStatus, NodeStatusType } from "./NodeStatus";
|
import { MoneroNodeListItem, NodeStatus } from "./MoneroNodeListItem";
|
||||||
|
|
||||||
describe("atoms::NodeStatus", () => {
|
describe("atoms::MoneroNodeListItem", () => {
|
||||||
it("renders without exploding", () => {
|
it("renders without exploding", () => {
|
||||||
const { asFragment } = render(
|
const { asFragment } = render(
|
||||||
<AppProviders>
|
<AppProviders>
|
||||||
<NodeStatus
|
<MoneroNodeListItem
|
||||||
|
isSelected={true}
|
||||||
title="node.moneroworldcom:18089:active"
|
title="node.moneroworldcom:18089:active"
|
||||||
status={NodeStatusType.Active}
|
status={NodeStatus.Active}
|
||||||
|
onClick={() => console.log("clicked")}
|
||||||
/>
|
/>
|
||||||
<NodeStatus
|
<MoneroNodeListItem
|
||||||
|
isSelected={false}
|
||||||
title="node.moneroworldcom:18089:inactive"
|
title="node.moneroworldcom:18089:inactive"
|
||||||
status={NodeStatusType.Inactive}
|
status={NodeStatus.Inactive}
|
||||||
|
onClick={() => console.log("clicked")}
|
||||||
/>
|
/>
|
||||||
</AppProviders>
|
</AppProviders>
|
||||||
);
|
);
|
@ -0,0 +1,82 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import { createStyles, Box, Text, UnstyledButton } from "@mantine/core";
|
||||||
|
|
||||||
|
export enum NodeStatus {
|
||||||
|
Active = "active",
|
||||||
|
Inactive = "inactive",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MoneroNodeListItemProps {
|
||||||
|
isSelected?: boolean;
|
||||||
|
onClick: () => void;
|
||||||
|
status: NodeStatus;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MoneroNodeListItem(props: MoneroNodeListItemProps) {
|
||||||
|
const { isSelected = false, onClick, status, title } = props;
|
||||||
|
const { classes } = useStyles({ isSelected, status });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UnstyledButton className={classes.root} onClick={onClick}>
|
||||||
|
<Text className={classes.title}>{title}</Text>
|
||||||
|
<Box className={classes.status}>
|
||||||
|
<Box className={classes.statusInner} />
|
||||||
|
</Box>
|
||||||
|
</UnstyledButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useStyles = createStyles<
|
||||||
|
string,
|
||||||
|
{ isSelected: boolean; status: NodeStatus }
|
||||||
|
>((theme, { isSelected, status }) => {
|
||||||
|
return {
|
||||||
|
root: {
|
||||||
|
backgroundColor: isSelected
|
||||||
|
? theme.colors.blue[0]
|
||||||
|
: theme.colorScheme === "dark"
|
||||||
|
? theme.colors.dark[8]
|
||||||
|
: theme.white,
|
||||||
|
border: `1px solid ${theme.colors.gray[2]}`,
|
||||||
|
borderRadius: theme.radius.md,
|
||||||
|
padding: "0.875rem",
|
||||||
|
display: "flex",
|
||||||
|
transition: "background-color 0.1s ease-in-out",
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: theme.fontSizes.sm,
|
||||||
|
lineHeight: 1,
|
||||||
|
width: "100%",
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
display: "flex",
|
||||||
|
},
|
||||||
|
statusInner: {
|
||||||
|
height: "0.625rem",
|
||||||
|
width: "0.625rem",
|
||||||
|
borderRadius: "0.625rem",
|
||||||
|
background:
|
||||||
|
status === NodeStatus.Active
|
||||||
|
? theme.colors.green[4]
|
||||||
|
: theme.colors.gray[4],
|
||||||
|
margin: "auto",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
@ -1,9 +1,10 @@
|
|||||||
// Vitest Snapshot v1
|
// Vitest Snapshot v1
|
||||||
|
|
||||||
exports[`atoms::NodeStatus > renders without exploding 1`] = `
|
exports[`atoms::MoneroNodeListItem > renders without exploding 1`] = `
|
||||||
<DocumentFragment>
|
<DocumentFragment>
|
||||||
<div
|
<button
|
||||||
class="mantine-1vax8o0"
|
class="mantine-UnstyledButton-root mantine-pk5m2t"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mantine-Text-root mantine-14byb36"
|
class="mantine-Text-root mantine-14byb36"
|
||||||
@ -17,9 +18,10 @@ exports[`atoms::NodeStatus > renders without exploding 1`] = `
|
|||||||
class="mantine-zy87za"
|
class="mantine-zy87za"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
<div
|
<button
|
||||||
class="mantine-1vax8o0"
|
class="mantine-UnstyledButton-root mantine-1v48mh9"
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mantine-Text-root mantine-14byb36"
|
class="mantine-Text-root mantine-14byb36"
|
||||||
@ -33,6 +35,6 @@ exports[`atoms::NodeStatus > renders without exploding 1`] = `
|
|||||||
class="mantine-19v6ci5"
|
class="mantine-19v6ci5"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</DocumentFragment>
|
</DocumentFragment>
|
||||||
`;
|
`;
|
@ -0,0 +1,17 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export * from "./MoneroNodeListItem";
|
@ -1,78 +0,0 @@
|
|||||||
// =============================================================================
|
|
||||||
// Copyright 2022 Haveno
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
import { createStyles, Box, Text } from "@mantine/core";
|
|
||||||
|
|
||||||
export enum NodeStatusType {
|
|
||||||
Active = "active",
|
|
||||||
Inactive = "inactive",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NodeStatusProps {
|
|
||||||
/** Node title */
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
/** Node status */
|
|
||||||
status: NodeStatusType;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NodeStatus({ title, status }: NodeStatusProps) {
|
|
||||||
const { classes } = useStyles({ status });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box className={classes.root}>
|
|
||||||
<Text className={classes.title}>{title}</Text>
|
|
||||||
<Box className={classes.status}>
|
|
||||||
<Box className={classes.statusInner} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useStyles = createStyles<string, { status: NodeStatusType }>(
|
|
||||||
(theme, { status }) => {
|
|
||||||
return {
|
|
||||||
root: {
|
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === "dark" ? theme.colors.dark[8] : theme.white,
|
|
||||||
border: `1px solid ${theme.colors.gray[2]}`,
|
|
||||||
borderRadius: theme.radius.md,
|
|
||||||
padding: "0.875rem",
|
|
||||||
display: "flex",
|
|
||||||
transition: "background-color 0.1s ease-in-out",
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: theme.fontSizes.sm,
|
|
||||||
lineHeight: 1,
|
|
||||||
width: "100%",
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
display: "flex",
|
|
||||||
},
|
|
||||||
statusInner: {
|
|
||||||
height: "0.625rem",
|
|
||||||
width: "0.625rem",
|
|
||||||
borderRadius: "0.625rem",
|
|
||||||
background:
|
|
||||||
status === NodeStatusType.Active
|
|
||||||
? theme.colors.green[4]
|
|
||||||
: theme.colors.gray[4],
|
|
||||||
margin: "auto",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
@ -18,13 +18,13 @@ import { createStyles } from "@mantine/core";
|
|||||||
|
|
||||||
export const useTabsStyles = createStyles<string, void>(() => {
|
export const useTabsStyles = createStyles<string, void>(() => {
|
||||||
return {
|
return {
|
||||||
|
body: {
|
||||||
|
marginTop: "2.5rem",
|
||||||
|
},
|
||||||
root: {},
|
root: {},
|
||||||
tabsListWrapper: {
|
tabsListWrapper: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
},
|
},
|
||||||
body: {
|
|
||||||
marginTop: "2.5rem",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -38,19 +38,21 @@ export const useControlStyles = createStyles<
|
|||||||
tabControl: {
|
tabControl: {
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.white,
|
theme.colorScheme === "dark" ? theme.colors.dark[6] : theme.white,
|
||||||
color:
|
|
||||||
theme.colorScheme === "dark"
|
|
||||||
? theme.colors.dark[0]
|
|
||||||
: theme.colors.gray[9],
|
|
||||||
border: `2px solid ${
|
border: `2px solid ${
|
||||||
theme.colorScheme === "dark"
|
theme.colorScheme === "dark"
|
||||||
? theme.colors.dark[6]
|
? theme.colors.dark[6]
|
||||||
: theme.colors.gray[2]
|
: theme.colors.gray[2]
|
||||||
}`,
|
}`,
|
||||||
fontSize: theme.fontSizes.md,
|
|
||||||
padding: `${theme.spacing.lg}px ${theme.spacing.xl}px`,
|
|
||||||
borderRadius: theme.radius.lg,
|
borderRadius: theme.radius.lg,
|
||||||
|
color:
|
||||||
|
theme.colorScheme === "dark"
|
||||||
|
? theme.colors.dark[0]
|
||||||
|
: theme.colors.gray[9],
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: theme.fontSizes.md,
|
||||||
height: "8.45rem",
|
height: "8.45rem",
|
||||||
|
padding: `${theme.spacing.lg}px ${theme.spacing.xl}px`,
|
||||||
|
position: "relative",
|
||||||
width: "13.85rem",
|
width: "13.85rem",
|
||||||
|
|
||||||
"&:not(:first-of-type)": {
|
"&:not(:first-of-type)": {
|
||||||
@ -59,13 +61,11 @@ export const useControlStyles = createStyles<
|
|||||||
[`&.${tabActive.ref}`]: {
|
[`&.${tabActive.ref}`]: {
|
||||||
color: theme.colorScheme === "dark" ? theme.black : theme.white,
|
color: theme.colorScheme === "dark" ? theme.black : theme.white,
|
||||||
},
|
},
|
||||||
cursor: "pointer",
|
|
||||||
position: "relative",
|
|
||||||
},
|
},
|
||||||
tabIcon: {
|
tabIcon: {
|
||||||
|
display: "flex",
|
||||||
fill: "currentColor",
|
fill: "currentColor",
|
||||||
minHeight: "3.8rem",
|
minHeight: "3.8rem",
|
||||||
display: "flex",
|
|
||||||
|
|
||||||
svg: {
|
svg: {
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
@ -85,18 +85,18 @@ export const useControlStyles = createStyles<
|
|||||||
color: theme.white,
|
color: theme.white,
|
||||||
},
|
},
|
||||||
tabCurrent: {
|
tabCurrent: {
|
||||||
display: "inline-block",
|
|
||||||
position: "absolute",
|
|
||||||
fontSize: theme.fontSizes.xs,
|
|
||||||
lineHeight: 1,
|
|
||||||
padding: "0.38rem 1.15rem",
|
|
||||||
borderRadius: theme.radius.sm,
|
|
||||||
top: "0.6rem",
|
|
||||||
left: "0.7rem",
|
|
||||||
background: active
|
background: active
|
||||||
? theme.fn.rgba(theme.white, 0.15)
|
? theme.fn.rgba(theme.white, 0.15)
|
||||||
: theme.fn.rgba(theme.colors.blue[5], 0.15),
|
: theme.fn.rgba(theme.colors.blue[5], 0.15),
|
||||||
color: active ? theme.white : theme.black,
|
color: active ? theme.white : theme.black,
|
||||||
|
borderRadius: theme.radius.sm,
|
||||||
|
display: "inline-block",
|
||||||
|
fontSize: theme.fontSizes.xs,
|
||||||
|
left: "0.7rem",
|
||||||
|
lineHeight: 1,
|
||||||
|
padding: "0.38rem 1.15rem",
|
||||||
|
position: "absolute",
|
||||||
|
top: "0.6rem",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -42,11 +42,11 @@ interface NodeConnectSwitchProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function NodeConnectSwitch({
|
export function NodeConnectSwitch({
|
||||||
className,
|
|
||||||
onTabChange,
|
|
||||||
active,
|
active,
|
||||||
children,
|
children,
|
||||||
|
className,
|
||||||
initialTab,
|
initialTab,
|
||||||
|
onTabChange,
|
||||||
}: NodeConnectSwitchProps) {
|
}: NodeConnectSwitchProps) {
|
||||||
const { classes, cx } = useTabsStyles();
|
const { classes, cx } = useTabsStyles();
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ exports[`molecules::NodeConnectSwitch > renders without exploding 1`] = `
|
|||||||
class="mantine-1d0mff5"
|
class="mantine-1d0mff5"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="mantine-1lhe3fe"
|
class="mantine-m2616v"
|
||||||
role="tab"
|
role="tab"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
type="button"
|
type="button"
|
||||||
@ -18,12 +18,12 @@ exports[`molecules::NodeConnectSwitch > renders without exploding 1`] = `
|
|||||||
class="mantine-199rwtt"
|
class="mantine-199rwtt"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mantine-1jn9p7a"
|
class="mantine-b2d3ff"
|
||||||
>
|
>
|
||||||
Current
|
Current
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="mantine-9bd5vi"
|
class="mantine-prfd4k"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
height="62px"
|
height="62px"
|
||||||
@ -57,7 +57,7 @@ exports[`molecules::NodeConnectSwitch > renders without exploding 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="mantine-1lhe3fe"
|
class="mantine-m2616v"
|
||||||
role="tab"
|
role="tab"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
type="button"
|
type="button"
|
||||||
@ -66,7 +66,7 @@ exports[`molecules::NodeConnectSwitch > renders without exploding 1`] = `
|
|||||||
class="mantine-199rwtt"
|
class="mantine-199rwtt"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mantine-9bd5vi"
|
class="mantine-prfd4k"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
fill="none"
|
fill="none"
|
||||||
|
@ -31,7 +31,7 @@ import {
|
|||||||
getPaymentAccountLogo,
|
getPaymentAccountLogo,
|
||||||
getPaymentAccountName,
|
getPaymentAccountName,
|
||||||
getPaymentAccountNumber,
|
getPaymentAccountNumber,
|
||||||
} from "@utils/payment-account";
|
} from "@src/utils/paymentAccount";
|
||||||
|
|
||||||
interface PaymentMethodCardProps {
|
interface PaymentMethodCardProps {
|
||||||
data: PaymentAccount;
|
data: PaymentAccount;
|
||||||
|
@ -34,35 +34,35 @@ export const useGetAccountSidebarMenu = () => {
|
|||||||
id: LangKeys.AccountSidebarPaymentAccounts,
|
id: LangKeys.AccountSidebarPaymentAccounts,
|
||||||
defaultMessage: "Payment Accounts",
|
defaultMessage: "Payment Accounts",
|
||||||
}),
|
}),
|
||||||
route: ROUTES.AccountPaymentAccounts,
|
route: ROUTES.PaymentAccounts,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
id: LangKeys.AccountSidebarNodeSettings,
|
id: LangKeys.AccountSidebarNodeSettings,
|
||||||
defaultMessage: "Node Settings",
|
defaultMessage: "Node Settings",
|
||||||
}),
|
}),
|
||||||
route: ROUTES.AccountNodeSettings,
|
route: ROUTES.NodeSettings,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
id: LangKeys.AccountSidebarSecurity,
|
id: LangKeys.AccountSidebarSecurity,
|
||||||
defaultMessage: "Security",
|
defaultMessage: "Security",
|
||||||
}),
|
}),
|
||||||
route: ROUTES.AccountSecurity,
|
route: ROUTES.Security,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
id: LangKeys.AccountSidebarWallet,
|
id: LangKeys.AccountSidebarWallet,
|
||||||
defaultMessage: "Wallet",
|
defaultMessage: "Wallet",
|
||||||
}),
|
}),
|
||||||
route: ROUTES.AccountWallet,
|
route: ROUTES.Wallet,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: intl.formatMessage({
|
label: intl.formatMessage({
|
||||||
id: LangKeys.AccountSidebarBackup,
|
id: LangKeys.AccountSidebarBackup,
|
||||||
defaultMessage: "Backup",
|
defaultMessage: "Backup",
|
||||||
}),
|
}),
|
||||||
route: ROUTES.AccountBackup,
|
route: ROUTES.Backup,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[]
|
[]
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import type { ComponentStory, ComponentMeta } from "@storybook/react";
|
||||||
|
import { AddNode } from ".";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "organisms/Add Node",
|
||||||
|
component: AddNode,
|
||||||
|
} as ComponentMeta<typeof AddNode>;
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof AddNode> = (args) => {
|
||||||
|
return <AddNode {...args} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default = Template.bind({});
|
||||||
|
Default.args = {
|
||||||
|
isLoading: false,
|
||||||
|
onSubmit: (values) => console.log(values),
|
||||||
|
showTitle: true,
|
||||||
|
};
|
@ -0,0 +1,65 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { AddNode } from ".";
|
||||||
|
|
||||||
|
describe("organisms::AddNode", () => {
|
||||||
|
it("renders without exploding", () => {
|
||||||
|
const submitSpy = vi.fn();
|
||||||
|
const { asFragment, unmount } = render(<AddNode onSubmit={submitSpy} />);
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can render the form without the title", () => {
|
||||||
|
const submitSpy = vi.fn();
|
||||||
|
const { asFragment, unmount } = render(
|
||||||
|
<AddNode showTitle={false} onSubmit={submitSpy} />
|
||||||
|
);
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("blocks onSubmit on validation failure", async () => {
|
||||||
|
const submitSpy = vi.fn();
|
||||||
|
const { unmount } = render(
|
||||||
|
<AddNode showTitle={false} onSubmit={submitSpy} />
|
||||||
|
);
|
||||||
|
fireEvent.submit(screen.getByRole("button", { name: "Save" }));
|
||||||
|
expect(submitSpy).not.toHaveBeenCalled();
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls onSubmit on successful validation", async () => {
|
||||||
|
const submitSpy = vi.fn();
|
||||||
|
const user = userEvent.setup();
|
||||||
|
const { unmount } = render(
|
||||||
|
<AddNode showTitle={false} onSubmit={submitSpy} />
|
||||||
|
);
|
||||||
|
expect(submitSpy).to.not.toHaveBeenCalled();
|
||||||
|
await user.type(
|
||||||
|
screen.getByLabelText("Node address"),
|
||||||
|
"http://haveno.network"
|
||||||
|
);
|
||||||
|
await user.type(screen.getByLabelText("Port"), "58080");
|
||||||
|
fireEvent.submit(screen.getByRole("button", { name: "Save" }));
|
||||||
|
expect(submitSpy).to.toHaveBeenCalledTimes(1);
|
||||||
|
unmount();
|
||||||
|
});
|
||||||
|
});
|
100
packages/renderer/src/components/organisms/AddNode/AddNode.tsx
Normal file
100
packages/renderer/src/components/organisms/AddNode/AddNode.tsx
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import { Group, Space, Stack } from "@mantine/core";
|
||||||
|
import Joi from "joi";
|
||||||
|
import { joiResolver, useForm } from "@mantine/form";
|
||||||
|
import { Heading } from "@atoms/Typography";
|
||||||
|
import { TextInput } from "@atoms/TextInput";
|
||||||
|
import { PasswordInput } from "@atoms/PasswordInput";
|
||||||
|
import { Button } from "@atoms/Buttons";
|
||||||
|
|
||||||
|
interface AddNodeProps {
|
||||||
|
isLoading?: boolean;
|
||||||
|
onSubmit: (values: AddNodeFormValues) => void;
|
||||||
|
showTitle?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddNode(props: AddNodeProps) {
|
||||||
|
const { isLoading = false, onSubmit, showTitle = true } = props;
|
||||||
|
const { getInputProps, onSubmit: onFormSubmit } = useForm<AddNodeFormValues>({
|
||||||
|
initialValues: {
|
||||||
|
address: "",
|
||||||
|
port: "",
|
||||||
|
user: "",
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
|
validate: joiResolver(validation),
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = (values: AddNodeFormValues) => {
|
||||||
|
onSubmit(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack spacing="sm">
|
||||||
|
{showTitle && <Heading order={3}>Add a new node</Heading>}
|
||||||
|
<form onSubmit={onFormSubmit(handleSubmit)}>
|
||||||
|
<Stack>
|
||||||
|
<TextInput
|
||||||
|
aria-label="Node address"
|
||||||
|
id="address"
|
||||||
|
label="Node address"
|
||||||
|
required
|
||||||
|
{...getInputProps("address")}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
aria-label="Port"
|
||||||
|
id="port"
|
||||||
|
label="Port"
|
||||||
|
required
|
||||||
|
{...getInputProps("port")}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
id="user"
|
||||||
|
label="Login (optional)"
|
||||||
|
{...getInputProps("user")}
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
id="password"
|
||||||
|
label="Password (optional)"
|
||||||
|
{...getInputProps("password")}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
<Group position="right">
|
||||||
|
<Button loaderPosition="right" loading={isLoading} type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</form>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AddNodeFormValues {
|
||||||
|
address: string;
|
||||||
|
port: string;
|
||||||
|
user?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validation = Joi.object<AddNodeFormValues>({
|
||||||
|
address: Joi.string().uri({ allowRelative: false }),
|
||||||
|
port: Joi.number().port(),
|
||||||
|
user: Joi.string().allow("").optional(),
|
||||||
|
password: Joi.string().allow("").optional(),
|
||||||
|
});
|
@ -0,0 +1,340 @@
|
|||||||
|
// Vitest Snapshot v1
|
||||||
|
|
||||||
|
exports[`organisms::AddNode > can render the form without the title 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
class="mantine-Stack-root mantine-ngu3vw"
|
||||||
|
>
|
||||||
|
<form>
|
||||||
|
<div
|
||||||
|
class="mantine-Stack-root mantine-lfk3cq"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-root mantine-18udhi"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mantine-TextInput-label mantine-7802ha"
|
||||||
|
for="address"
|
||||||
|
id="address-label"
|
||||||
|
>
|
||||||
|
Node address
|
||||||
|
<span
|
||||||
|
class="mantine-1m203yh mantine-TextInput-required"
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-wrapper mantine-12sbrde"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
aria-label="Node address"
|
||||||
|
class="mantine-TextInput-defaultVariant mantine-TextInput-input mantine-dagq8e"
|
||||||
|
id="address"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-root mantine-18udhi"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mantine-TextInput-label mantine-7802ha"
|
||||||
|
for="port"
|
||||||
|
id="port-label"
|
||||||
|
>
|
||||||
|
Port
|
||||||
|
<span
|
||||||
|
class="mantine-1m203yh mantine-TextInput-required"
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-wrapper mantine-12sbrde"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
aria-label="Port"
|
||||||
|
class="mantine-TextInput-defaultVariant mantine-TextInput-input mantine-dagq8e"
|
||||||
|
id="port"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-root mantine-18udhi"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mantine-TextInput-label mantine-7802ha"
|
||||||
|
for="user"
|
||||||
|
id="user-label"
|
||||||
|
>
|
||||||
|
Login (optional)
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-wrapper mantine-12sbrde"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="mantine-TextInput-defaultVariant mantine-TextInput-input mantine-dagq8e"
|
||||||
|
id="user"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-PasswordInput-root mantine-18udhi"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mantine-PasswordInput-label mantine-7802ha"
|
||||||
|
for="password"
|
||||||
|
id="password-label"
|
||||||
|
>
|
||||||
|
Password (optional)
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="mantine-PasswordInput-wrapper mantine-12sbrde"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-invalid="false"
|
||||||
|
class="mantine-PasswordInput-defaultVariant mantine-PasswordInput-input mantine-PasswordInput-input mantine-1i2duzg"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="mantine-PasswordInput-innerInput mantine-1bj8gkk"
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-o3oqoy mantine-PasswordInput-rightSection"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mantine-ActionIcon-hover mantine-ActionIcon-root mantine-PasswordInput-visibilityToggle mantine-vao037"
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="15"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
width="15"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M7.5 11C4.80285 11 2.52952 9.62184 1.09622 7.50001C2.52952 5.37816 4.80285 4 7.5 4C10.1971 4 12.4705 5.37816 13.9038 7.50001C12.4705 9.62183 10.1971 11 7.5 11ZM7.5 3C4.30786 3 1.65639 4.70638 0.0760002 7.23501C-0.0253338 7.39715 -0.0253334 7.60288 0.0760014 7.76501C1.65639 10.2936 4.30786 12 7.5 12C10.6921 12 13.3436 10.2936 14.924 7.76501C15.0253 7.60288 15.0253 7.39715 14.924 7.23501C13.3436 4.70638 10.6921 3 7.5 3ZM7.5 9.5C8.60457 9.5 9.5 8.60457 9.5 7.5C9.5 6.39543 8.60457 5.5 7.5 5.5C6.39543 5.5 5.5 6.39543 5.5 7.5C5.5 8.60457 6.39543 9.5 7.5 9.5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-1ne3752"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="mantine-Group-root mantine-147cgkf"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mantine-Button-filled mantine-Button-root mantine-Group-child mantine-1bqp2m7"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mantine-3xbgk5 mantine-Button-inner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mantine-qo1k2 mantine-Button-label"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`organisms::AddNode > renders without exploding 1`] = `
|
||||||
|
<DocumentFragment>
|
||||||
|
<div
|
||||||
|
class="mantine-Stack-root mantine-ngu3vw"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="mantine-Title-root mantine-ha7ih2"
|
||||||
|
>
|
||||||
|
Add a new node
|
||||||
|
</h3>
|
||||||
|
<form>
|
||||||
|
<div
|
||||||
|
class="mantine-Stack-root mantine-lfk3cq"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-root mantine-18udhi"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mantine-TextInput-label mantine-7802ha"
|
||||||
|
for="address"
|
||||||
|
id="address-label"
|
||||||
|
>
|
||||||
|
Node address
|
||||||
|
<span
|
||||||
|
class="mantine-1m203yh mantine-TextInput-required"
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-wrapper mantine-12sbrde"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
aria-label="Node address"
|
||||||
|
class="mantine-TextInput-defaultVariant mantine-TextInput-input mantine-dagq8e"
|
||||||
|
id="address"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-root mantine-18udhi"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mantine-TextInput-label mantine-7802ha"
|
||||||
|
for="port"
|
||||||
|
id="port-label"
|
||||||
|
>
|
||||||
|
Port
|
||||||
|
<span
|
||||||
|
class="mantine-1m203yh mantine-TextInput-required"
|
||||||
|
>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-wrapper mantine-12sbrde"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
aria-label="Port"
|
||||||
|
class="mantine-TextInput-defaultVariant mantine-TextInput-input mantine-dagq8e"
|
||||||
|
id="port"
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-root mantine-18udhi"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mantine-TextInput-label mantine-7802ha"
|
||||||
|
for="user"
|
||||||
|
id="user-label"
|
||||||
|
>
|
||||||
|
Login (optional)
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="mantine-TextInput-wrapper mantine-12sbrde"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-invalid="false"
|
||||||
|
class="mantine-TextInput-defaultVariant mantine-TextInput-input mantine-dagq8e"
|
||||||
|
id="user"
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-PasswordInput-root mantine-18udhi"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="mantine-PasswordInput-label mantine-7802ha"
|
||||||
|
for="password"
|
||||||
|
id="password-label"
|
||||||
|
>
|
||||||
|
Password (optional)
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
class="mantine-PasswordInput-wrapper mantine-12sbrde"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
aria-invalid="false"
|
||||||
|
class="mantine-PasswordInput-defaultVariant mantine-PasswordInput-input mantine-PasswordInput-input mantine-1i2duzg"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="mantine-PasswordInput-innerInput mantine-1bj8gkk"
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-o3oqoy mantine-PasswordInput-rightSection"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mantine-ActionIcon-hover mantine-ActionIcon-root mantine-PasswordInput-visibilityToggle mantine-vao037"
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="15"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
width="15"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M7.5 11C4.80285 11 2.52952 9.62184 1.09622 7.50001C2.52952 5.37816 4.80285 4 7.5 4C10.1971 4 12.4705 5.37816 13.9038 7.50001C12.4705 9.62183 10.1971 11 7.5 11ZM7.5 3C4.30786 3 1.65639 4.70638 0.0760002 7.23501C-0.0253338 7.39715 -0.0253334 7.60288 0.0760014 7.76501C1.65639 10.2936 4.30786 12 7.5 12C10.6921 12 13.3436 10.2936 14.924 7.76501C15.0253 7.60288 15.0253 7.39715 14.924 7.23501C13.3436 4.70638 10.6921 3 7.5 3ZM7.5 9.5C8.60457 9.5 9.5 8.60457 9.5 7.5C9.5 6.39543 8.60457 5.5 7.5 5.5C6.39543 5.5 5.5 6.39543 5.5 7.5C5.5 8.60457 6.39543 9.5 7.5 9.5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-1ne3752"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="mantine-Group-root mantine-147cgkf"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="mantine-Button-filled mantine-Button-root mantine-Group-child mantine-1bqp2m7"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mantine-3xbgk5 mantine-Button-inner"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mantine-qo1k2 mantine-Button-label"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DocumentFragment>
|
||||||
|
`;
|
@ -14,4 +14,4 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
export * from "./NodeStatus";
|
export * from "./AddNode";
|
@ -14,11 +14,15 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
import type { FormEvent } from "react";
|
||||||
import { Stack, Space, Group } from "@mantine/core";
|
import { Stack, Space, Group } from "@mantine/core";
|
||||||
import { BodyText, Heading } from "@atoms/Typography";
|
import { BodyText, Heading } from "@atoms/Typography";
|
||||||
import { Button, TextButton } from "@atoms/Buttons";
|
import { Button, TextButton } from "@atoms/Buttons";
|
||||||
import { Select } from "@atoms/Select";
|
import { Select } from "@atoms/Select";
|
||||||
import type { FormEvent } from "react";
|
import {
|
||||||
|
HAVENO_DAEMON_PASSWORD,
|
||||||
|
HAVENO_DAEMON_URL,
|
||||||
|
} from "@constants/haveno-daemon";
|
||||||
|
|
||||||
interface SelectMoneroNodeProps {
|
interface SelectMoneroNodeProps {
|
||||||
onGoBack: () => void;
|
onGoBack: () => void;
|
||||||
@ -32,8 +36,8 @@ export function SelectMoneroNode(props: SelectMoneroNodeProps) {
|
|||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
// TODO: fix
|
// TODO: fix
|
||||||
onNext({
|
onNext({
|
||||||
url: "http://192.168.29.59:8080",
|
url: HAVENO_DAEMON_URL,
|
||||||
password: "apitest",
|
password: HAVENO_DAEMON_PASSWORD,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export enum LangKeys {
|
|||||||
AccountNodeSettingsLocal = "account.nodeSecurity.local.title",
|
AccountNodeSettingsLocal = "account.nodeSecurity.local.title",
|
||||||
AccountNodeSettingsRemote = "account.nodeSecurity.remote.title",
|
AccountNodeSettingsRemote = "account.nodeSecurity.remote.title",
|
||||||
AccountNodeFieldBlockchainLocation = "account.nodeSecurity.blockchainLocation",
|
AccountNodeFieldBlockchainLocation = "account.nodeSecurity.blockchainLocation",
|
||||||
AccountNodeFieldDaemonAddress = "account.nodeSecurity.daemonAddress",
|
AccountNodeFieldBootstrapUrl = "account.nodeSecurity.bootstrapUrl",
|
||||||
AccountNodeFieldDaemonFlags = "account.nodeSecurity.daemonFlags",
|
AccountNodeFieldDaemonFlags = "account.nodeSecurity.daemonFlags",
|
||||||
AccountNodeFieldPort = "account.nodeSecurity.port",
|
AccountNodeFieldPort = "account.nodeSecurity.port",
|
||||||
AccountNodeStopDaemon = "account.nodeSecurity.stopDaemon",
|
AccountNodeStopDaemon = "account.nodeSecurity.stopDaemon",
|
||||||
|
@ -39,7 +39,7 @@ const LangPackEN: { [key in LangKeys]: string } = {
|
|||||||
[LangKeys.AccountNodeSettingsLocal]: "Local Node",
|
[LangKeys.AccountNodeSettingsLocal]: "Local Node",
|
||||||
[LangKeys.AccountNodeSettingsRemote]: "Remote Node",
|
[LangKeys.AccountNodeSettingsRemote]: "Remote Node",
|
||||||
[LangKeys.AccountNodeFieldBlockchainLocation]: "Blockchain location",
|
[LangKeys.AccountNodeFieldBlockchainLocation]: "Blockchain location",
|
||||||
[LangKeys.AccountNodeFieldDaemonAddress]: "Daemon Address",
|
[LangKeys.AccountNodeFieldBootstrapUrl]: "Bootstrap URL",
|
||||||
[LangKeys.AccountNodeFieldPort]: "Port",
|
[LangKeys.AccountNodeFieldPort]: "Port",
|
||||||
[LangKeys.AccountNodeFieldDaemonFlags]: "Daemon startup flags",
|
[LangKeys.AccountNodeFieldDaemonFlags]: "Daemon startup flags",
|
||||||
[LangKeys.AccountNodeStopDaemon]: "Stop daemon",
|
[LangKeys.AccountNodeStopDaemon]: "Stop daemon",
|
||||||
|
@ -40,7 +40,7 @@ const LangPackES: { [key in LangKeys]: string } = {
|
|||||||
[LangKeys.AccountNodeSettingsRemote]: "Nodo Remoto",
|
[LangKeys.AccountNodeSettingsRemote]: "Nodo Remoto",
|
||||||
[LangKeys.AccountNodeFieldBlockchainLocation]:
|
[LangKeys.AccountNodeFieldBlockchainLocation]:
|
||||||
"Ubicación de cadena de bloques",
|
"Ubicación de cadena de bloques",
|
||||||
[LangKeys.AccountNodeFieldDaemonAddress]: "Dirección del demonio",
|
[LangKeys.AccountNodeFieldBootstrapUrl]: "Dirección URL de arranque",
|
||||||
[LangKeys.AccountNodeFieldPort]: "Puerto",
|
[LangKeys.AccountNodeFieldPort]: "Puerto",
|
||||||
[LangKeys.AccountNodeFieldDaemonFlags]: "Indicadores de inicio de daemon",
|
[LangKeys.AccountNodeFieldDaemonFlags]: "Indicadores de inicio de daemon",
|
||||||
[LangKeys.AccountNodeStopDaemon]: "Detener demonio",
|
[LangKeys.AccountNodeStopDaemon]: "Detener demonio",
|
||||||
|
@ -18,9 +18,10 @@ export enum QueryKeys {
|
|||||||
// Haveno
|
// Haveno
|
||||||
Balances = "Haveno.Balances",
|
Balances = "Haveno.Balances",
|
||||||
HavenoVersion = "Haveno.Version",
|
HavenoVersion = "Haveno.Version",
|
||||||
|
MoneroConnection = "Haveno.MoneroConnection",
|
||||||
|
MoneroConnections = "Haveno.MoneroConnections",
|
||||||
MoneroNodeIsRunning = "Haveno.MoneroNodeIsRunning",
|
MoneroNodeIsRunning = "Haveno.MoneroNodeIsRunning",
|
||||||
MoneroNodeSettings = "Haveno.MoneroNodeSettings",
|
MoneroNodeSettings = "Haveno.MoneroNodeSettings",
|
||||||
MoneroRemoteNodes = "Haveno.MoneroRemoteNodes",
|
|
||||||
PaymentAccounts = "Haveno.PaymentAccounts",
|
PaymentAccounts = "Haveno.PaymentAccounts",
|
||||||
Prices = "Haveno.Prices",
|
Prices = "Haveno.Prices",
|
||||||
PrimaryAddress = "Haveno.PrimaryAddress",
|
PrimaryAddress = "Haveno.PrimaryAddress",
|
||||||
@ -29,6 +30,7 @@ export enum QueryKeys {
|
|||||||
// Storage
|
// Storage
|
||||||
StorageAccountInfo = "Storage.AccountInfo",
|
StorageAccountInfo = "Storage.AccountInfo",
|
||||||
StoragePreferences = "Storage.Preferences",
|
StoragePreferences = "Storage.Preferences",
|
||||||
|
StorageRemoteMoneroNode = "Storage.RemoteMoneroNode",
|
||||||
|
|
||||||
// Others
|
// Others
|
||||||
AuthSession = "AuthSession",
|
AuthSession = "AuthSession",
|
||||||
|
@ -23,10 +23,10 @@ export const ROUTES = {
|
|||||||
RestoreBackup: "/onboarding/restore-backup",
|
RestoreBackup: "/onboarding/restore-backup",
|
||||||
|
|
||||||
// Account routes
|
// Account routes
|
||||||
AccountPaymentAccounts: "/account/payment-accounts",
|
PaymentAccounts: "/account/payment-accounts",
|
||||||
AccountAddPaymentAccount: "/account/payment-accounts/add",
|
AddPaymentAccount: "/account/payment-accounts/add",
|
||||||
AccountNodeSettings: "/account/node-settings",
|
NodeSettings: "/account/node-settings",
|
||||||
AccountBackup: "/account/backup",
|
Backup: "/account/backup",
|
||||||
AccountWallet: "/account/wallet",
|
Wallet: "/account/wallet",
|
||||||
AccountSecurity: "/account/security",
|
Security: "/account/security",
|
||||||
};
|
};
|
||||||
|
@ -15,31 +15,39 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "react-query";
|
||||||
|
import { UrlConnection } from "haveno-ts";
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
interface SetMeneroNodeSettingsVariables {
|
interface Variables {
|
||||||
blockchainPath?: string;
|
address: string;
|
||||||
bootstrapUrl?: string;
|
port: string;
|
||||||
startupFlags?: Array<string>;
|
user?: string;
|
||||||
|
password?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSetMoneroNodeSettings() {
|
export function useAddMoneroNode() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const client = useHavenoClient();
|
const client = useHavenoClient();
|
||||||
|
|
||||||
return useMutation(
|
return useMutation<void, Error, Variables>(
|
||||||
async (data: SetMeneroNodeSettingsVariables) => {
|
async (data: Variables) => {
|
||||||
const nodeSettings = await client.getMoneroNodeSettings();
|
const url = new URL(data.address);
|
||||||
|
if (data.port) {
|
||||||
data.blockchainPath &&
|
url.port = data.port + "";
|
||||||
nodeSettings?.setBlockchainPath(data.blockchainPath);
|
}
|
||||||
data.startupFlags && nodeSettings?.setStartupFlagsList(data.startupFlags);
|
const conn = new UrlConnection().setUrl(url.toString()).setPriority(1);
|
||||||
data.bootstrapUrl && nodeSettings?.setBootstrapUrl(data.bootstrapUrl);
|
if (data.user) {
|
||||||
|
conn.setUsername(data.user);
|
||||||
|
}
|
||||||
|
if (data.password) {
|
||||||
|
conn.setPassword(data.password);
|
||||||
|
}
|
||||||
|
client.addMoneroConnection(conn);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(QueryKeys.MoneroNodeSettings);
|
queryClient.invalidateQueries(QueryKeys.MoneroConnections);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
@ -14,8 +14,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
export function useAddress() {
|
export function useAddress() {
|
||||||
|
26
packages/renderer/src/hooks/haveno/useGetMoneroConnection.ts
Normal file
26
packages/renderer/src/hooks/haveno/useGetMoneroConnection.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import { useQuery } from "react-query";
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
|
export function useGetMoneroConnection() {
|
||||||
|
const client = useHavenoClient();
|
||||||
|
return useQuery(QueryKeys.MoneroConnection, async () =>
|
||||||
|
client.getMoneroConnection()
|
||||||
|
);
|
||||||
|
}
|
@ -14,8 +14,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { HavenoClient } from "haveno-ts";
|
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
|
import { HavenoClient } from "haveno-ts";
|
||||||
import {
|
import {
|
||||||
HAVENO_DAEMON_PASSWORD,
|
HAVENO_DAEMON_PASSWORD,
|
||||||
HAVENO_DAEMON_URL,
|
HAVENO_DAEMON_URL,
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
export function useHavenoVersion() {
|
export function useHavenoVersion() {
|
||||||
|
@ -20,13 +20,18 @@ import { useHavenoClient } from "./useHavenoClient";
|
|||||||
|
|
||||||
export function useIsMoneroNodeRunning() {
|
export function useIsMoneroNodeRunning() {
|
||||||
const client = useHavenoClient();
|
const client = useHavenoClient();
|
||||||
|
return useQuery<boolean, Error>(
|
||||||
return useQuery<boolean, Error>(QueryKeys.MoneroNodeIsRunning, async () => {
|
QueryKeys.MoneroNodeIsRunning,
|
||||||
try {
|
async () => {
|
||||||
const value = await client.isMoneroNodeRunning();
|
try {
|
||||||
return value;
|
const value = await client.isMoneroNodeRunning();
|
||||||
} catch {
|
return value;
|
||||||
return false;
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
staleTime: 10_000, // 10 sec
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,25 +15,17 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
|
import type { UrlConnection } from "haveno-ts";
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
// import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
interface MoneroRemoteNodes {
|
export function useMoneroConnections() {
|
||||||
title: string;
|
const client = useHavenoClient();
|
||||||
isActive: boolean;
|
return useQuery<Array<UrlConnection.AsObject>, Error>(
|
||||||
}
|
QueryKeys.MoneroConnections,
|
||||||
|
|
||||||
export function useMoneroRemoteNodes() {
|
|
||||||
// const client = useHavenoClient();
|
|
||||||
|
|
||||||
return useQuery<MoneroRemoteNodes[], Error>(
|
|
||||||
QueryKeys.MoneroRemoteNodes,
|
|
||||||
async () => {
|
async () => {
|
||||||
return Promise.resolve([
|
const connections = await client.getMoneroConnections();
|
||||||
{ title: "node.moneroworldcom:18089", isActive: true },
|
return connections.map((conn) => conn.toObject());
|
||||||
{ title: "node.xmr.pt:18081", isActive: true },
|
|
||||||
{ title: "node.monero.net:18081", isActive: true },
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -14,18 +14,15 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
|
||||||
import type { MoneroNodeSettings } from "haveno-ts";
|
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
|
import type { MoneroNodeSettings } from "haveno-ts";
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
export function useMoneroNodeSettings() {
|
export function useMoneroNodeSettings() {
|
||||||
const client = useHavenoClient();
|
const client = useHavenoClient();
|
||||||
|
|
||||||
return useQuery<MoneroNodeSettings | undefined, Error>(
|
return useQuery<MoneroNodeSettings | undefined, Error>(
|
||||||
QueryKeys.MoneroNodeSettings,
|
QueryKeys.MoneroNodeSettings,
|
||||||
async () => {
|
async () => client.getMoneroNodeSettings()
|
||||||
return client.getMoneroNodeSettings();
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
|
||||||
import type { PaymentAccount } from "haveno-ts";
|
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
|
import type { PaymentAccount } from "haveno-ts";
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
export function usePaymentAccounts() {
|
export function usePaymentAccounts() {
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
// =============================================================================
|
|
||||||
// Copyright 2022 Haveno
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
|
||||||
import { useQuery } from "react-query";
|
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
|
||||||
|
|
||||||
export function usePaymentMethods() {
|
|
||||||
const client = useHavenoClient();
|
|
||||||
return useQuery(QueryKeys.PaymentMethods, async () => {
|
|
||||||
// TODO: replace with getSupportedAssets(): TradeCurrency[]
|
|
||||||
// const mns = await client.getMoneroNodeSettings();
|
|
||||||
const mns = await client._moneroNodeClient.getMoneroNodeSettings({}, {});
|
|
||||||
console.log("monero node settings: ", mns?.toObject());
|
|
||||||
if (mns) {
|
|
||||||
const mns2 = mns.setStartupFlagsList(["foo1"]);
|
|
||||||
mns;
|
|
||||||
}
|
|
||||||
const assetCodes = await client.getSupportedAssetCodes();
|
|
||||||
return await Promise.all(
|
|
||||||
assetCodes.map((assetCode) => client.getPaymentMethods(assetCode))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
@ -14,9 +14,9 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
import { useQuery } from "react-query";
|
||||||
import type { MarketPriceInfo } from "haveno-ts";
|
import type { MarketPriceInfo } from "haveno-ts";
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { useQuery } from "react-query";
|
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
export function usePrices() {
|
export function usePrices() {
|
||||||
|
61
packages/renderer/src/hooks/haveno/useSaveLocalMoneroNode.ts
Normal file
61
packages/renderer/src/hooks/haveno/useSaveLocalMoneroNode.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import { useMutation, useQueryClient } from "react-query";
|
||||||
|
import { MoneroNodeSettings } from "haveno-ts";
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
|
import { useSaveRemoteNode } from "@hooks/storage/useSaveRemoteNode";
|
||||||
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
|
interface Variables {
|
||||||
|
blockchainPath: string;
|
||||||
|
bootstrapUrl: string;
|
||||||
|
startupFlags: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSaveLocalMoneroNode() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const client = useHavenoClient();
|
||||||
|
const { mutateAsync: saveRemoteNode } = useSaveRemoteNode();
|
||||||
|
|
||||||
|
return useMutation<void, Error, Variables>(
|
||||||
|
async (data: Variables) => {
|
||||||
|
const nodeSettings = new MoneroNodeSettings();
|
||||||
|
nodeSettings.setBlockchainPath(data.blockchainPath);
|
||||||
|
nodeSettings.setStartupFlagsList(data.startupFlags);
|
||||||
|
nodeSettings.setBootstrapUrl(data.bootstrapUrl);
|
||||||
|
|
||||||
|
if (await client.isMoneroNodeRunning()) {
|
||||||
|
// stop the node if it's running
|
||||||
|
await client.stopMoneroNode();
|
||||||
|
}
|
||||||
|
// start the node with new settings
|
||||||
|
try {
|
||||||
|
await client.startMoneroNode(nodeSettings);
|
||||||
|
await saveRemoteNode({}); // clear the saved remote node
|
||||||
|
} catch (ex) {
|
||||||
|
console.log(ex);
|
||||||
|
throw new Error("Failed to start the monero node");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(QueryKeys.MoneroConnections);
|
||||||
|
queryClient.invalidateQueries(QueryKeys.MoneroNodeIsRunning);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
@ -14,16 +14,30 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { useMutation } from "react-query";
|
import { useMutation, useQueryClient } from "react-query";
|
||||||
|
import { useSaveRemoteNode } from "@hooks/storage/useSaveRemoteNode";
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
interface Variables {
|
interface Variables {
|
||||||
connection: string;
|
uri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSetMoneroConnection() {
|
export function useSetMoneroConnection() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { mutateAsync: saveRemoteNode } = useSaveRemoteNode();
|
||||||
const client = useHavenoClient();
|
const client = useHavenoClient();
|
||||||
return useMutation(async (variables: Variables) =>
|
|
||||||
client.setMoneroConnection(variables.connection)
|
return useMutation<void, Error, Variables>(
|
||||||
|
async (variables: Variables) => {
|
||||||
|
await client.setMoneroConnection(variables.uri);
|
||||||
|
// save to storage
|
||||||
|
await saveRemoteNode({ uri: variables.uri });
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(QueryKeys.MoneroConnection);
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -14,17 +14,17 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
|
||||||
import type { MoneroNodeSettings } from "haveno-ts";
|
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "react-query";
|
||||||
|
import type { MoneroNodeSettings } from "haveno-ts";
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
export function useStartMoneroNode() {
|
export function useStartMoneroNode() {
|
||||||
const client = useHavenoClient();
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const client = useHavenoClient();
|
||||||
|
|
||||||
return useMutation<void, Error, MoneroNodeSettings>(
|
return useMutation<void, Error, MoneroNodeSettings>(
|
||||||
(data: MoneroNodeSettings) => client.startMoneroNode(data),
|
async (data: MoneroNodeSettings) => client.startMoneroNode(data),
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(QueryKeys.MoneroNodeIsRunning);
|
queryClient.invalidateQueries(QueryKeys.MoneroNodeIsRunning);
|
||||||
|
@ -14,15 +14,15 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "react-query";
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { useHavenoClient } from "./useHavenoClient";
|
import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
export function useStopMoneroNode() {
|
export function useStopMoneroNode() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const client = useHavenoClient();
|
const client = useHavenoClient();
|
||||||
|
|
||||||
return useMutation(() => client.stopMoneroNode(), {
|
return useMutation(async () => client.stopMoneroNode(), {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries(QueryKeys.MoneroNodeIsRunning);
|
queryClient.invalidateQueries(QueryKeys.MoneroNodeIsRunning);
|
||||||
},
|
},
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
import { useQuery } from "react-query";
|
import { useQuery } from "react-query";
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
// import { useHavenoClient } from "./useHavenoClient";
|
|
||||||
import { SyncStatus } from "@constants/sync-status";
|
import { SyncStatus } from "@constants/sync-status";
|
||||||
|
// import { useHavenoClient } from "./useHavenoClient";
|
||||||
|
|
||||||
export function useSyncStatus() {
|
export function useSyncStatus() {
|
||||||
// const client = useHavenoClient();
|
// const client = useHavenoClient();
|
||||||
@ -28,7 +28,7 @@ export function useSyncStatus() {
|
|||||||
return SyncStatus.NotSynced;
|
return SyncStatus.NotSynced;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
staleTime: 10000,
|
staleTime: 10_000,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { useMutation, useQueryClient } from "react-query";
|
import { useMutation, useQueryClient } from "react-query";
|
||||||
import { QueryKeys } from "@constants/query-keys";
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
import { getIpcError } from "@utils/get-ipc-error";
|
import { getIpcError } from "@src/utils/getIpcError";
|
||||||
import { createSession } from "@utils/session";
|
import { createSession } from "@utils/session";
|
||||||
|
|
||||||
interface Variables {
|
interface Variables {
|
||||||
|
@ -20,7 +20,6 @@ import { useMutation, useQueryClient } from "react-query";
|
|||||||
interface Variables {
|
interface Variables {
|
||||||
password: string;
|
password: string;
|
||||||
primaryFiat: string;
|
primaryFiat: string;
|
||||||
moneroNode: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateAccount() {
|
export function useCreateAccount() {
|
||||||
@ -31,7 +30,6 @@ export function useCreateAccount() {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
window.electronStore.setPassword({ newPassword: variables.password }),
|
window.electronStore.setPassword({ newPassword: variables.password }),
|
||||||
window.electronStore.setPrimaryFiat(variables.primaryFiat),
|
window.electronStore.setPrimaryFiat(variables.primaryFiat),
|
||||||
window.electronStore.setMoneroNode(variables.moneroNode),
|
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { usePreferences } from "./usePreferences";
|
||||||
|
|
||||||
|
export function useIsLocalNodeSelected() {
|
||||||
|
const [data, setData] = useState(false);
|
||||||
|
const { data: preferences, ...rest } = usePreferences();
|
||||||
|
useEffect(() => {
|
||||||
|
setData(!preferences?.selectedNode);
|
||||||
|
}, [preferences]);
|
||||||
|
return { data, ...rest };
|
||||||
|
}
|
37
packages/renderer/src/hooks/storage/useSaveRemoteNode.ts
Normal file
37
packages/renderer/src/hooks/storage/useSaveRemoteNode.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import { QueryKeys } from "@constants/query-keys";
|
||||||
|
import { useMutation, useQueryClient } from "react-query";
|
||||||
|
|
||||||
|
interface Variables {
|
||||||
|
uri?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSaveRemoteNode() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation<void, Error, Variables>(
|
||||||
|
async (variables: Variables) => {
|
||||||
|
return window.electronStore.setMoneroNode(variables.uri);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(QueryKeys.StoragePreferences);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { AccountLayout } from "@templates/AccountLayout";
|
import { AccountLayout } from "@templates/AccountLayout";
|
||||||
|
|
||||||
export function AccountBackup() {
|
export function Backup() {
|
||||||
return (
|
return (
|
||||||
<AccountLayout>
|
<AccountLayout>
|
||||||
<h1>Account Backup</h1>
|
<h1>Account Backup</h1>
|
@ -1,79 +0,0 @@
|
|||||||
// =============================================================================
|
|
||||||
// Copyright 2022 Haveno
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
import { Stack, createStyles, Group } from "@mantine/core";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
import { Button } from "@atoms/Buttons";
|
|
||||||
import { NodeStatus, NodeStatusType } from "@atoms/NodeStatus";
|
|
||||||
import { LangKeys } from "@constants/lang";
|
|
||||||
import { useMoneroRemoteNodes } from "@hooks/haveno/useMoneroRemoteNodes";
|
|
||||||
|
|
||||||
export function NodeRemoteStatus() {
|
|
||||||
const { data: remoteNodes } = useMoneroRemoteNodes();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack>
|
|
||||||
{remoteNodes?.map((node) => (
|
|
||||||
<NodeStatus
|
|
||||||
key={node.title}
|
|
||||||
title={node.title}
|
|
||||||
status={
|
|
||||||
node.isActive ? NodeStatusType.Active : NodeStatusType.Inactive
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<AddNewNodeButton />
|
|
||||||
|
|
||||||
<Group position="right" mt="sm">
|
|
||||||
<Button size="md">
|
|
||||||
<FormattedMessage id={LangKeys.Save} defaultMessage="Save" />
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AddNewNodeButton({ ...rest }) {
|
|
||||||
const { classes } = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="subtle"
|
|
||||||
color="dark"
|
|
||||||
classNames={{
|
|
||||||
root: classes.root,
|
|
||||||
inner: classes.inner,
|
|
||||||
}}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
+{" "}
|
|
||||||
<FormattedMessage
|
|
||||||
id={LangKeys.AccountSettingsAddNode}
|
|
||||||
defaultMessage="Add a new node"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = createStyles(() => ({
|
|
||||||
root: {
|
|
||||||
padding: "0.8rem",
|
|
||||||
height: 45,
|
|
||||||
},
|
|
||||||
inner: {
|
|
||||||
justifyContent: "start",
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,56 +0,0 @@
|
|||||||
// =============================================================================
|
|
||||||
// Copyright 2022 Haveno
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
import { Stack, Box, createStyles } from "@mantine/core";
|
|
||||||
import { AccountLayout } from "@templates/AccountLayout";
|
|
||||||
import { LangKeys } from "@constants/lang";
|
|
||||||
import { NodeSettingsSwitch } from "./NodeSettingsSwitch";
|
|
||||||
import { BodyText, Heading } from "@atoms/Typography";
|
|
||||||
|
|
||||||
export function AccountNodeSettings() {
|
|
||||||
const { classes } = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AccountLayout>
|
|
||||||
<Box className={classes.content}>
|
|
||||||
<Stack spacing="sm">
|
|
||||||
<Heading stringId={LangKeys.AccountNodeSettingsTitle} order={3}>
|
|
||||||
Your node settings
|
|
||||||
</Heading>
|
|
||||||
<BodyText
|
|
||||||
stringId={LangKeys.AccountNodeSettingsDesc}
|
|
||||||
size="md"
|
|
||||||
className={classes.paragraph}
|
|
||||||
>
|
|
||||||
Using a local node is recommended, but does require loading the
|
|
||||||
entire blockchain. Choose ‘remote node’ if you prefer a faster but
|
|
||||||
less secure experience.
|
|
||||||
</BodyText>
|
|
||||||
<NodeSettingsSwitch />
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
</AccountLayout>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
|
||||||
content: {
|
|
||||||
maxWidth: theme.other.contentWidthMd,
|
|
||||||
},
|
|
||||||
paragraph: {
|
|
||||||
marginBottom: theme.spacing.xl,
|
|
||||||
},
|
|
||||||
}));
|
|
@ -1,52 +0,0 @@
|
|||||||
// =============================================================================
|
|
||||||
// Copyright 2022 Haveno
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
import type { ReactNode } from "react";
|
|
||||||
import { BodyText } from "@atoms/Typography";
|
|
||||||
import { useMoneroNodeSettings } from "@hooks/haveno/useMoneroNodeSettings";
|
|
||||||
import { useIsMoneroNodeRunning } from "@hooks/haveno/useIsMoneroNodeRunning";
|
|
||||||
import { useMoneroRemoteNodes } from "@hooks/haveno/useMoneroRemoteNodes";
|
|
||||||
|
|
||||||
interface NodeSettingsBootProps {
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LocalNodeSettingsBoot({ children }: NodeSettingsBootProps) {
|
|
||||||
const { isLoading: isNodeSettingsLoading } = useMoneroNodeSettings();
|
|
||||||
const { isLoading: isMoneroNodeIsLoading } = useIsMoneroNodeRunning();
|
|
||||||
|
|
||||||
return isNodeSettingsLoading || isMoneroNodeIsLoading ? (
|
|
||||||
<BodyText>Loading settings...</BodyText>
|
|
||||||
) : (
|
|
||||||
<>{children}</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RemoteNodeSettingsBootProps {
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RemoteNodeSettingsBoot({
|
|
||||||
children,
|
|
||||||
}: RemoteNodeSettingsBootProps) {
|
|
||||||
const { isLoading: isMoneroRemoteLoading } = useMoneroRemoteNodes();
|
|
||||||
|
|
||||||
return isMoneroRemoteLoading ? (
|
|
||||||
<BodyText>Loading settings...</BodyText>
|
|
||||||
) : (
|
|
||||||
<>{children}</>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
// =============================================================================
|
|
||||||
// Copyright 2022 Haveno
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
import { createStyles } from "@mantine/core";
|
|
||||||
import { FormattedMessage } from "react-intl";
|
|
||||||
import { LangKeys } from "@constants/lang";
|
|
||||||
import { NodeConnectSwitch } from "@molecules/NodeConnectSwitch";
|
|
||||||
import { ReactComponent as CloudIcon } from "@assets/setting-cloud.svg";
|
|
||||||
import { ReactComponent as ServerIcon } from "@assets/setting-server.svg";
|
|
||||||
import { NodeLocalForm } from "./NodeLocalForm";
|
|
||||||
import { NodeRemoteStatus } from "./NodeRemoteStatus";
|
|
||||||
import {
|
|
||||||
LocalNodeSettingsBoot,
|
|
||||||
RemoteNodeSettingsBoot,
|
|
||||||
} from "./NodeSettingsBoot";
|
|
||||||
|
|
||||||
export function NodeSettingsSwitch() {
|
|
||||||
const { classes } = useStyles();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NodeConnectSwitch
|
|
||||||
initialTab="local-node"
|
|
||||||
className={classes.connectSwitch}
|
|
||||||
>
|
|
||||||
<NodeConnectSwitch.Method
|
|
||||||
active={true}
|
|
||||||
current={true}
|
|
||||||
tabKey="local-node"
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id={LangKeys.AccountNodeSettingsLocal}
|
|
||||||
defaultMessage="Local Node"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
icon={<ServerIcon width={32} height={62} />}
|
|
||||||
>
|
|
||||||
<LocalNodeSettingsBoot>
|
|
||||||
<NodeLocalForm />
|
|
||||||
</LocalNodeSettingsBoot>
|
|
||||||
</NodeConnectSwitch.Method>
|
|
||||||
|
|
||||||
<NodeConnectSwitch.Method
|
|
||||||
tabKey="remote-node"
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id={LangKeys.AccountNodeSettingsRemote}
|
|
||||||
defaultMessage="Remote Node"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
icon={<CloudIcon width={58} height={54} />}
|
|
||||||
>
|
|
||||||
<RemoteNodeSettingsBoot>
|
|
||||||
<NodeRemoteStatus />
|
|
||||||
</RemoteNodeSettingsBoot>
|
|
||||||
</NodeConnectSwitch.Method>
|
|
||||||
</NodeConnectSwitch>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const useStyles = createStyles(() => ({
|
|
||||||
connectSwitch: {
|
|
||||||
marginBottom: "2rem",
|
|
||||||
},
|
|
||||||
}));
|
|
@ -19,13 +19,11 @@ import { ROUTES } from "@constants/routes";
|
|||||||
import { PaymentMethodList } from "@organisms/PaymentMethodList";
|
import { PaymentMethodList } from "@organisms/PaymentMethodList";
|
||||||
import { AccountLayout } from "@templates/AccountLayout";
|
import { AccountLayout } from "@templates/AccountLayout";
|
||||||
|
|
||||||
export function AccountPaymentAccounts() {
|
export function PaymentAccounts() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<AccountLayout>
|
<AccountLayout>
|
||||||
<PaymentMethodList
|
<PaymentMethodList onAdd={() => navigate(ROUTES.AddPaymentAccount)} />
|
||||||
onAdd={() => navigate(ROUTES.AccountAddPaymentAccount)}
|
|
||||||
/>
|
|
||||||
</AccountLayout>
|
</AccountLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -24,9 +24,7 @@ export function PaymentMethods() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<NavbarLayout>
|
<NavbarLayout>
|
||||||
<PaymentMethodList
|
<PaymentMethodList onAdd={() => navigate(ROUTES.AddPaymentAccount)} />
|
||||||
onAdd={() => navigate(ROUTES.AccountAddPaymentAccount)}
|
|
||||||
/>
|
|
||||||
</NavbarLayout>
|
</NavbarLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import { AccountLayout } from "@templates/AccountLayout";
|
|||||||
import { Heading, BodyText } from "@atoms/Typography";
|
import { Heading, BodyText } from "@atoms/Typography";
|
||||||
import { ChangePassword } from "@organisms/ChangePassword";
|
import { ChangePassword } from "@organisms/ChangePassword";
|
||||||
|
|
||||||
export function AccountSecurity() {
|
export function Security() {
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
@ -14,70 +14,82 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { Box, Stack, Grid, Group } from "@mantine/core";
|
import { useEffect } from "react";
|
||||||
import { joiResolver, useForm } from "@mantine/form";
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
|
import { Stack, Grid, Group } from "@mantine/core";
|
||||||
|
import { joiResolver, useForm } from "@mantine/form";
|
||||||
import { showNotification } from "@mantine/notifications";
|
import { showNotification } from "@mantine/notifications";
|
||||||
import { Button } from "@atoms/Buttons";
|
import { Button } from "@atoms/Buttons";
|
||||||
import { LangKeys } from "@constants/lang";
|
|
||||||
import { TextInput } from "@atoms/TextInput";
|
import { TextInput } from "@atoms/TextInput";
|
||||||
import { useMoneroNodeSettings } from "@hooks/haveno/useMoneroNodeSettings";
|
import { useMoneroNodeSettings } from "@hooks/haveno/useMoneroNodeSettings";
|
||||||
import { useSetMoneroNodeSettings } from "@hooks/haveno/useSetMoneroNodeSettings";
|
import { useSaveLocalMoneroNode } from "@hooks/haveno/useSaveLocalMoneroNode";
|
||||||
import { NodeLocalStopDaemon } from "./NodeLocalStopDaemon";
|
import { LangKeys } from "@constants/lang";
|
||||||
import type { NodeLocalFormValues } from "./_hooks";
|
import { StartStopDaemon } from "./StartStopDaemon";
|
||||||
import { useNodeLocalFormValidation } from "./_hooks";
|
import type { LocalSettingsFormValues } from "./_types";
|
||||||
|
import { useLocalSettingsValidation } from "./_hooks";
|
||||||
import { transformSettingsRequestToForm } from "./_utils";
|
import { transformSettingsRequestToForm } from "./_utils";
|
||||||
|
|
||||||
export function NodeLocalForm() {
|
export function LocalNode() {
|
||||||
const { data: nodeSettings } = useMoneroNodeSettings();
|
const { data: nodeSettings } = useMoneroNodeSettings();
|
||||||
const { mutateAsync: updateNodeSettings } = useSetMoneroNodeSettings();
|
const { mutate: saveLocalNode, isLoading: isSaving } =
|
||||||
|
useSaveLocalMoneroNode();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const validation = useNodeLocalFormValidation();
|
const validation = useLocalSettingsValidation();
|
||||||
|
|
||||||
const form = useForm<NodeLocalFormValues>({
|
const { getInputProps, onSubmit, setValues } =
|
||||||
initialValues: {
|
useForm<LocalSettingsFormValues>({
|
||||||
blockchainLocation: "",
|
initialValues: {
|
||||||
startupFlags: "",
|
blockchainLocation: "",
|
||||||
daemonAddress: "",
|
startupFlags: "",
|
||||||
port: "",
|
bootstrapUrl: "",
|
||||||
...(nodeSettings
|
port: "",
|
||||||
? transformSettingsRequestToForm(nodeSettings.toObject())
|
},
|
||||||
: {}),
|
validate: joiResolver(validation),
|
||||||
},
|
});
|
||||||
validate: joiResolver(validation),
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleFormSubmit = (values: NodeLocalFormValues) => {
|
const handleSubmit = (values: LocalSettingsFormValues) => {
|
||||||
updateNodeSettings({
|
saveLocalNode(
|
||||||
blockchainPath: values.blockchainLocation,
|
{
|
||||||
startupFlags: values.startupFlags.split(", "),
|
blockchainPath: values.blockchainLocation,
|
||||||
bootstrapUrl: `${values.daemonAddress}:${values.port}`,
|
startupFlags: values.startupFlags.split(/\s|=/),
|
||||||
})
|
bootstrapUrl: values.bootstrapUrl
|
||||||
.then(() => {
|
? (new URL(values.bootstrapUrl).port = values.port)
|
||||||
showNotification({
|
: "",
|
||||||
color: "green",
|
},
|
||||||
message: intl.formatMessage({
|
{
|
||||||
id: LangKeys.AccountNodeLocalSaveNotification,
|
onSuccess: () => {
|
||||||
defaultMessage: "Local node settings updated successfully",
|
showNotification({
|
||||||
}),
|
color: "green",
|
||||||
});
|
message: intl.formatMessage({
|
||||||
})
|
id: LangKeys.AccountNodeLocalSaveNotification,
|
||||||
.catch((err) => {
|
defaultMessage: "Local node settings saved successfully",
|
||||||
console.dir(err);
|
}),
|
||||||
showNotification({
|
});
|
||||||
color: "red",
|
},
|
||||||
message: err.message,
|
onError: (err: Error) => {
|
||||||
title: "Something went wrong",
|
console.dir(err);
|
||||||
});
|
showNotification({
|
||||||
});
|
color: "red",
|
||||||
|
message: err.message,
|
||||||
|
title: "Something went wrong",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<Box>
|
if (nodeSettings) {
|
||||||
<NodeLocalStopDaemon />
|
setValues(transformSettingsRequestToForm(nodeSettings.toObject()));
|
||||||
|
}
|
||||||
|
}, [nodeSettings]);
|
||||||
|
|
||||||
<form onSubmit={form.onSubmit(handleFormSubmit)}>
|
return (
|
||||||
|
<>
|
||||||
|
<StartStopDaemon />
|
||||||
|
|
||||||
|
<form onSubmit={onSubmit(handleSubmit)}>
|
||||||
<Stack spacing="lg">
|
<Stack spacing="lg">
|
||||||
<TextInput
|
<TextInput
|
||||||
id="blockchainLocation"
|
id="blockchainLocation"
|
||||||
@ -87,7 +99,7 @@ export function NodeLocalForm() {
|
|||||||
defaultMessage="Blockchain location"
|
defaultMessage="Blockchain location"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{...form.getInputProps("blockchainLocation")}
|
{...getInputProps("blockchainLocation")}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
id="startupFlags"
|
id="startupFlags"
|
||||||
@ -97,20 +109,19 @@ export function NodeLocalForm() {
|
|||||||
defaultMessage="Daemon startup flags"
|
defaultMessage="Daemon startup flags"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{...form.getInputProps("startupFlags")}
|
{...getInputProps("startupFlags")}
|
||||||
/>
|
/>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.Col span={9}>
|
<Grid.Col span={9}>
|
||||||
<TextInput
|
<TextInput
|
||||||
id="daemonAddress"
|
id="bootstrapUrl"
|
||||||
label={
|
label={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={LangKeys.AccountNodeFieldDaemonAddress}
|
id={LangKeys.AccountNodeFieldBootstrapUrl}
|
||||||
defaultMessage="Daemon Address"
|
defaultMessage="Bootstrap URL"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
required
|
{...getInputProps("bootstrapUrl")}
|
||||||
{...form.getInputProps("daemonAddress")}
|
|
||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={3}>
|
<Grid.Col span={3}>
|
||||||
@ -122,19 +133,23 @@ export function NodeLocalForm() {
|
|||||||
defaultMessage="Port"
|
defaultMessage="Port"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
required
|
{...getInputProps("port")}
|
||||||
{...form.getInputProps("port")}
|
|
||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Group position="right" mt="md">
|
<Group position="right" mt="md">
|
||||||
<Button size="md" type="submit">
|
<Button
|
||||||
|
loaderPosition="right"
|
||||||
|
loading={isSaving}
|
||||||
|
size="md"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
<FormattedMessage id={LangKeys.Save} defaultMessage="Save" />
|
<FormattedMessage id={LangKeys.Save} defaultMessage="Save" />
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</form>
|
||||||
</Box>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
144
packages/renderer/src/pages/Account/Settings/RemoteNode.tsx
Normal file
144
packages/renderer/src/pages/Account/Settings/RemoteNode.tsx
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { Stack, Group, Modal } from "@mantine/core";
|
||||||
|
import { showNotification } from "@mantine/notifications";
|
||||||
|
import { Button, TextButton } from "@atoms/Buttons";
|
||||||
|
import { MoneroNodeListItem, NodeStatus } from "@atoms/MoneroNodeListItem";
|
||||||
|
import type { AddNodeFormValues } from "@organisms/AddNode";
|
||||||
|
import { AddNode } from "@organisms/AddNode";
|
||||||
|
import { useMoneroConnections } from "@hooks/haveno/useMoneroConnections";
|
||||||
|
import { useAddMoneroNode } from "@hooks/haveno/useAddMoneroNode";
|
||||||
|
import { useSetMoneroConnection } from "@hooks/haveno/useSetMoneroConnection";
|
||||||
|
import { useGetMoneroConnection } from "@hooks/haveno/useGetMoneroConnection";
|
||||||
|
import { LangKeys } from "@constants/lang";
|
||||||
|
|
||||||
|
export function RemoteNode() {
|
||||||
|
const [isAdding, setAdding] = useState(false);
|
||||||
|
const [selectedNode, setSelectedNode] = useState<string>();
|
||||||
|
const { data: connections } = useMoneroConnections();
|
||||||
|
const { data: selectedConnection } = useGetMoneroConnection();
|
||||||
|
const { mutate: addMoneroNode } = useAddMoneroNode();
|
||||||
|
const { mutate: setMoneroConnection } = useSetMoneroConnection();
|
||||||
|
|
||||||
|
const handleAddNode = (data: AddNodeFormValues) => {
|
||||||
|
const { address, port, user, password } = data;
|
||||||
|
addMoneroNode(
|
||||||
|
{
|
||||||
|
address,
|
||||||
|
port,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (err) => {
|
||||||
|
console.log(err);
|
||||||
|
showNotification({
|
||||||
|
color: "red",
|
||||||
|
message: err.message,
|
||||||
|
title: "Something went wrong",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
setAdding(false);
|
||||||
|
showNotification({
|
||||||
|
color: "green",
|
||||||
|
message: "Saved",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (!selectedNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMoneroConnection(
|
||||||
|
{ uri: selectedNode },
|
||||||
|
{
|
||||||
|
onError: (err) => {
|
||||||
|
console.dir(err);
|
||||||
|
showNotification({
|
||||||
|
color: "red",
|
||||||
|
message: err.message,
|
||||||
|
title: "Something went wrong",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
showNotification({
|
||||||
|
color: "green",
|
||||||
|
message: "Saved",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedConnection) {
|
||||||
|
setSelectedNode(selectedConnection.getUrl());
|
||||||
|
}
|
||||||
|
}, [selectedConnection]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Stack>
|
||||||
|
{connections?.map((conn) => (
|
||||||
|
<MoneroNodeListItem
|
||||||
|
key={conn.url}
|
||||||
|
title={conn.url}
|
||||||
|
status={
|
||||||
|
conn.onlineStatus === 1 ? NodeStatus.Active : NodeStatus.Inactive
|
||||||
|
}
|
||||||
|
isSelected={conn.url === selectedNode}
|
||||||
|
onClick={() => setSelectedNode(conn.url)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Group position="apart" mt="sm">
|
||||||
|
<TextButton onClick={() => setAdding(true)}>
|
||||||
|
<FormattedMessage
|
||||||
|
id={LangKeys.AccountSettingsAddNode}
|
||||||
|
defaultMessage="Add a new node"
|
||||||
|
/>
|
||||||
|
</TextButton>
|
||||||
|
<Button
|
||||||
|
disabled={
|
||||||
|
!selectedNode || selectedNode === selectedConnection?.getUrl()
|
||||||
|
}
|
||||||
|
size="md"
|
||||||
|
onClick={handleSave}
|
||||||
|
>
|
||||||
|
<FormattedMessage id={LangKeys.Save} defaultMessage="Save" />
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
<Modal
|
||||||
|
opened={isAdding}
|
||||||
|
withCloseButton
|
||||||
|
onClose={() => setAdding(false)}
|
||||||
|
size="sm"
|
||||||
|
radius="md"
|
||||||
|
title="Add a new node"
|
||||||
|
>
|
||||||
|
<AddNode onSubmit={handleAddNode} showTitle={false} />
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
104
packages/renderer/src/pages/Account/Settings/Settings.tsx
Normal file
104
packages/renderer/src/pages/Account/Settings/Settings.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
import { Stack, createStyles } from "@mantine/core";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
import { NodeConnectSwitch } from "@molecules/NodeConnectSwitch";
|
||||||
|
import { BodyText, Heading } from "@atoms/Typography";
|
||||||
|
import { AccountLayout } from "@templates/AccountLayout";
|
||||||
|
import { ReactComponent as CloudIcon } from "@assets/setting-cloud.svg";
|
||||||
|
import { ReactComponent as ServerIcon } from "@assets/setting-server.svg";
|
||||||
|
import { useIsLocalNodeSelected } from "@hooks/storage/useIsLocalNodeSelected";
|
||||||
|
import { LangKeys } from "@constants/lang";
|
||||||
|
import { LocalNode } from "./LocalNode";
|
||||||
|
import { RemoteNode } from "./RemoteNode";
|
||||||
|
|
||||||
|
enum NodeTypes {
|
||||||
|
Local = "local",
|
||||||
|
Remote = "remote",
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Settings() {
|
||||||
|
const { classes } = useStyles();
|
||||||
|
const { data: isLocalNodeSelected, isSuccess } = useIsLocalNodeSelected();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccountLayout>
|
||||||
|
<Stack className={classes.content} spacing="sm">
|
||||||
|
<Heading stringId={LangKeys.AccountNodeSettingsTitle} order={3}>
|
||||||
|
Your node settings
|
||||||
|
</Heading>
|
||||||
|
<BodyText
|
||||||
|
stringId={LangKeys.AccountNodeSettingsDesc}
|
||||||
|
size="md"
|
||||||
|
className={classes.paragraph}
|
||||||
|
>
|
||||||
|
Using a local node is recommended, but does require loading the entire
|
||||||
|
blockchain. Choose ‘remote node’ if you prefer a faster but less
|
||||||
|
secure experience.
|
||||||
|
</BodyText>
|
||||||
|
{isSuccess && (
|
||||||
|
<NodeConnectSwitch
|
||||||
|
initialTab={
|
||||||
|
isLocalNodeSelected ? NodeTypes.Local : NodeTypes.Remote
|
||||||
|
}
|
||||||
|
className={classes.connectSwitch}
|
||||||
|
>
|
||||||
|
<NodeConnectSwitch.Method
|
||||||
|
current={isLocalNodeSelected}
|
||||||
|
tabKey={NodeTypes.Local}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id={LangKeys.AccountNodeSettingsLocal}
|
||||||
|
defaultMessage="Local Node"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
icon={<ServerIcon width={32} height={62} />}
|
||||||
|
>
|
||||||
|
<LocalNode />
|
||||||
|
</NodeConnectSwitch.Method>
|
||||||
|
|
||||||
|
<NodeConnectSwitch.Method
|
||||||
|
current={!isLocalNodeSelected}
|
||||||
|
tabKey={NodeTypes.Remote}
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id={LangKeys.AccountNodeSettingsRemote}
|
||||||
|
defaultMessage="Remote Node"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
icon={<CloudIcon height={54} width={58} />}
|
||||||
|
>
|
||||||
|
<RemoteNode />
|
||||||
|
</NodeConnectSwitch.Method>
|
||||||
|
</NodeConnectSwitch>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</AccountLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = createStyles((theme) => ({
|
||||||
|
connectSwitch: {
|
||||||
|
marginBottom: "2rem",
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
maxWidth: theme.other.contentWidthMd,
|
||||||
|
},
|
||||||
|
paragraph: {
|
||||||
|
marginBottom: theme.spacing.xl,
|
||||||
|
},
|
||||||
|
}));
|
@ -15,7 +15,7 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import { createStyles } from "@mantine/core";
|
import { Box, createStyles } from "@mantine/core";
|
||||||
import { showNotification } from "@mantine/notifications";
|
import { showNotification } from "@mantine/notifications";
|
||||||
import { Button } from "@atoms/Buttons";
|
import { Button } from "@atoms/Buttons";
|
||||||
import { LangKeys } from "@constants/lang";
|
import { LangKeys } from "@constants/lang";
|
||||||
@ -24,18 +24,19 @@ import { useStopMoneroNode } from "@hooks/haveno/useStopMoneroNode";
|
|||||||
import { useIsMoneroNodeRunning } from "@hooks/haveno/useIsMoneroNodeRunning";
|
import { useIsMoneroNodeRunning } from "@hooks/haveno/useIsMoneroNodeRunning";
|
||||||
import { useStartMoneroNode } from "@hooks/haveno/useStartMoneroNode";
|
import { useStartMoneroNode } from "@hooks/haveno/useStartMoneroNode";
|
||||||
|
|
||||||
export function NodeLocalStopDaemon() {
|
export function StartStopDaemon() {
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { mutateAsync: stopMoneroNode } = useStopMoneroNode();
|
|
||||||
const { data: isMoneroNodeRunning } = useIsMoneroNodeRunning();
|
const { data: isMoneroNodeRunning } = useIsMoneroNodeRunning();
|
||||||
const { mutateAsync: startMoneroNode } = useStartMoneroNode();
|
const { mutateAsync: stopMoneroNode, isLoading: isStopping } =
|
||||||
|
useStopMoneroNode();
|
||||||
|
const { mutateAsync: startMoneroNode, isLoading: isStarting } =
|
||||||
|
useStartMoneroNode();
|
||||||
const { isLoading: isNodeSettingsLoading, data: nodeSettings } =
|
const { isLoading: isNodeSettingsLoading, data: nodeSettings } =
|
||||||
useMoneroNodeSettings();
|
useMoneroNodeSettings();
|
||||||
|
|
||||||
// handle the stop button click.
|
const handleStop = () => {
|
||||||
const handleStopBtnClick = () => {
|
|
||||||
stopMoneroNode()
|
stopMoneroNode()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
showNotification({
|
showNotification({
|
||||||
@ -55,8 +56,8 @@ export function NodeLocalStopDaemon() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
// Handle the start button click.
|
|
||||||
const handleStartBtnClick = () => {
|
const handleStart = () => {
|
||||||
if (!nodeSettings) {
|
if (!nodeSettings) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -81,9 +82,14 @@ export function NodeLocalStopDaemon() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.actions}>
|
<Box className={classes.actions}>
|
||||||
{isMoneroNodeRunning ? (
|
{isMoneroNodeRunning ? (
|
||||||
<Button flavor="neutral" onClick={handleStopBtnClick}>
|
<Button
|
||||||
|
flavor="neutral"
|
||||||
|
loading={isStopping}
|
||||||
|
loaderPosition="right"
|
||||||
|
onClick={handleStop}
|
||||||
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={LangKeys.AccountNodeStopDaemon}
|
id={LangKeys.AccountNodeStopDaemon}
|
||||||
defaultMessage="Stop daemon"
|
defaultMessage="Stop daemon"
|
||||||
@ -91,17 +97,18 @@ export function NodeLocalStopDaemon() {
|
|||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
flavor="neutral"
|
disabled={isNodeSettingsLoading || !nodeSettings || isStarting}
|
||||||
onClick={handleStartBtnClick}
|
loading={isStarting}
|
||||||
disabled={Boolean(isNodeSettingsLoading || !nodeSettings)}
|
loaderPosition="right"
|
||||||
|
onClick={handleStart}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id={LangKeys.AccountNodeStopDaemon}
|
id={LangKeys.AccountNodeStartDaemon}
|
||||||
defaultMessage="Start daemon"
|
defaultMessage="Start daemon"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -14,20 +14,14 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
|
import type { LocalSettingsFormValues } from "./_types";
|
||||||
|
|
||||||
export interface NodeLocalFormValues {
|
export function useLocalSettingsValidation() {
|
||||||
blockchainLocation: string;
|
return Joi.object<LocalSettingsFormValues>({
|
||||||
startupFlags: string;
|
|
||||||
daemonAddress: string;
|
|
||||||
port: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useNodeLocalFormValidation() {
|
|
||||||
return Joi.object<NodeLocalFormValues>({
|
|
||||||
blockchainLocation: Joi.string().empty("").uri({ relativeOnly: true }),
|
blockchainLocation: Joi.string().empty("").uri({ relativeOnly: true }),
|
||||||
startupFlags: Joi.string().empty(""),
|
startupFlags: Joi.string().empty(""),
|
||||||
daemonAddress: Joi.string().uri({ allowRelative: false }),
|
bootstrapUrl: Joi.string().allow("").uri({ allowRelative: false }),
|
||||||
port: Joi.number().port(),
|
port: Joi.number().allow("").port(),
|
||||||
});
|
});
|
||||||
}
|
}
|
22
packages/renderer/src/pages/Account/Settings/_types.ts
Normal file
22
packages/renderer/src/pages/Account/Settings/_types.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// Copyright 2022 Haveno
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export interface LocalSettingsFormValues {
|
||||||
|
blockchainLocation: string;
|
||||||
|
startupFlags: string;
|
||||||
|
bootstrapUrl: string;
|
||||||
|
port: string;
|
||||||
|
}
|
@ -15,20 +15,20 @@
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import type { MoneroNodeSettings } from "haveno-ts";
|
import type { MoneroNodeSettings } from "haveno-ts";
|
||||||
import type { NodeLocalFormValues } from "./_hooks";
|
import type { LocalSettingsFormValues } from "./_types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transformes the settings request values to form.
|
* Transformes the settings request values to form.
|
||||||
* @param {MoneroNodeSettings.AsObject} nodeSettings
|
* @param {MoneroNodeSettings.AsObject} nodeSettings
|
||||||
* @returns {NodeLocalFormValues}
|
* @returns {LocalSettingsFormValues}
|
||||||
*/
|
*/
|
||||||
export function transformSettingsRequestToForm(
|
export function transformSettingsRequestToForm(
|
||||||
nodeSettings: MoneroNodeSettings.AsObject
|
nodeSettings: MoneroNodeSettings.AsObject
|
||||||
): NodeLocalFormValues {
|
): LocalSettingsFormValues {
|
||||||
return {
|
return {
|
||||||
blockchainLocation: nodeSettings?.blockchainPath || "",
|
blockchainLocation: nodeSettings?.blockchainPath || "",
|
||||||
startupFlags: nodeSettings?.startupFlagsList.join(", ") || "",
|
startupFlags: nodeSettings?.startupFlagsList.join(" ") || "",
|
||||||
daemonAddress: transfromBootstrapUrl(nodeSettings?.bootstrapUrl || ""),
|
bootstrapUrl: transfromBootstrapUrl(nodeSettings?.bootstrapUrl || ""),
|
||||||
port: transformPort(nodeSettings?.bootstrapUrl || ""),
|
port: transformPort(nodeSettings?.bootstrapUrl || ""),
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -14,4 +14,4 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
export * from "./NodeSettings";
|
export * from "./Settings";
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
import { AccountLayout } from "@templates/AccountLayout";
|
import { AccountLayout } from "@templates/AccountLayout";
|
||||||
|
|
||||||
export function AccountWallet() {
|
export function Wallet() {
|
||||||
return (
|
return (
|
||||||
<AccountLayout>
|
<AccountLayout>
|
||||||
<h1>Account Wallet</h1>
|
<h1>Account Wallet</h1>
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
export * from "./AddPaymentAccount";
|
export * from "./AddPaymentAccount";
|
||||||
export * from "./PaymentMethods";
|
export * from "./PaymentMethods";
|
||||||
export * from "./AccountBackup";
|
export * from "./Backup";
|
||||||
export * from "./NodeSettings";
|
export * from "./Settings";
|
||||||
export * from "./AccountPaymentAccounts";
|
export * from "./PaymentAccounts";
|
||||||
export * from "./AccountSecurity";
|
export * from "./Security";
|
||||||
export * from "./AccountWallet";
|
export * from "./Wallet";
|
||||||
|
@ -105,7 +105,7 @@ describe("pages::Login", () => {
|
|||||||
await user.type(screen.getByLabelText("Password"), PASSWORD);
|
await user.type(screen.getByLabelText("Password"), PASSWORD);
|
||||||
fireEvent.submit(screen.getByRole("button", { name: "Login" }));
|
fireEvent.submit(screen.getByRole("button", { name: "Login" }));
|
||||||
expect(navSpy).to.toHaveBeenCalledTimes(1);
|
expect(navSpy).to.toHaveBeenCalledTimes(1);
|
||||||
expect(navSpy).toHaveBeenCalledWith(ROUTES.AccountPaymentAccounts, {
|
expect(navSpy).toHaveBeenCalledWith(ROUTES.PaymentAccounts, {
|
||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
unmount();
|
unmount();
|
||||||
|
@ -23,7 +23,7 @@ import { useLogin } from "@hooks/session/useLogin";
|
|||||||
import { CenteredLayout } from "@templates/CenteredLayout";
|
import { CenteredLayout } from "@templates/CenteredLayout";
|
||||||
import { BodyText, Heading } from "@atoms/Typography";
|
import { BodyText, Heading } from "@atoms/Typography";
|
||||||
import { Button } from "@atoms/Buttons";
|
import { Button } from "@atoms/Buttons";
|
||||||
import { TextInput } from "@atoms/TextInput";
|
import { PasswordInput } from "@atoms/PasswordInput";
|
||||||
import { ROUTES } from "@constants/routes";
|
import { ROUTES } from "@constants/routes";
|
||||||
import { CONTENT_MAX_WIDTH } from "./_constants";
|
import { CONTENT_MAX_WIDTH } from "./_constants";
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ export function Login() {
|
|||||||
const handleSubmit = (values: FormValues) => {
|
const handleSubmit = (values: FormValues) => {
|
||||||
login(values, {
|
login(values, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
navigate(ROUTES.AccountPaymentAccounts, { replace: true });
|
navigate(ROUTES.PaymentAccounts, { replace: true });
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
showNotification({
|
showNotification({
|
||||||
@ -66,11 +66,10 @@ export function Login() {
|
|||||||
solely a password.
|
solely a password.
|
||||||
</BodyText>
|
</BodyText>
|
||||||
<Space h="lg" />
|
<Space h="lg" />
|
||||||
<TextInput
|
<PasswordInput
|
||||||
aria-label="Password"
|
aria-label="Password"
|
||||||
id="password"
|
id="password"
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
|
||||||
{...getInputProps("password")}
|
{...getInputProps("password")}
|
||||||
/>
|
/>
|
||||||
<Space h="lg" />
|
<Space h="lg" />
|
||||||
|
@ -42,26 +42,55 @@ exports[`pages::Login > renders without exploding 1`] = `
|
|||||||
class="mantine-63n06h"
|
class="mantine-63n06h"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="mantine-TextInput-root mantine-14qek68"
|
class="mantine-PasswordInput-root mantine-14qek68"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="mantine-TextInput-label mantine-1bjo575"
|
class="mantine-PasswordInput-label mantine-1bjo575"
|
||||||
for="password"
|
for="password"
|
||||||
id="password-label"
|
id="password-label"
|
||||||
>
|
>
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
class="mantine-TextInput-wrapper mantine-12sbrde"
|
class="mantine-PasswordInput-wrapper mantine-12sbrde"
|
||||||
>
|
>
|
||||||
<input
|
<div
|
||||||
aria-invalid="false"
|
aria-invalid="false"
|
||||||
aria-label="Password"
|
class="mantine-PasswordInput-defaultVariant mantine-PasswordInput-input mantine-PasswordInput-input mantine-nakpsh"
|
||||||
class="mantine-TextInput-defaultVariant mantine-TextInput-input mantine-nk8491"
|
>
|
||||||
id="password"
|
<input
|
||||||
type="password"
|
aria-label="Password"
|
||||||
value=""
|
class="mantine-PasswordInput-innerInput mantine-17c0t6q"
|
||||||
/>
|
id="password"
|
||||||
|
type="password"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mantine-o3oqoy mantine-PasswordInput-rightSection"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mantine-ActionIcon-hover mantine-ActionIcon-root mantine-PasswordInput-visibilityToggle mantine-910bvd"
|
||||||
|
tabindex="-1"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
height="15"
|
||||||
|
viewBox="0 0 15 15"
|
||||||
|
width="15"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
clip-rule="evenodd"
|
||||||
|
d="M7.5 11C4.80285 11 2.52952 9.62184 1.09622 7.50001C2.52952 5.37816 4.80285 4 7.5 4C10.1971 4 12.4705 5.37816 13.9038 7.50001C12.4705 9.62183 10.1971 11 7.5 11ZM7.5 3C4.30786 3 1.65639 4.70638 0.0760002 7.23501C-0.0253338 7.39715 -0.0253334 7.60288 0.0760014 7.76501C1.65639 10.2936 4.30786 12 7.5 12C10.6921 12 13.3436 10.2936 14.924 7.76501C15.0253 7.60288 15.0253 7.39715 14.924 7.23501C13.3436 4.70638 10.6921 3 7.5 3ZM7.5 9.5C8.60457 9.5 9.5 8.60457 9.5 7.5C9.5 6.39543 8.60457 5.5 7.5 5.5C6.39543 5.5 5.5 6.39543 5.5 7.5C5.5 8.60457 6.39543 9.5 7.5 9.5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -14,17 +14,17 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
import { Stack, Container } from "@mantine/core";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { CenteredLayout } from "@templates/CenteredLayout";
|
|
||||||
import { ROUTES } from "@constants/routes";
|
|
||||||
import { CONTENT_MAX_WIDTH } from "./_constants";
|
|
||||||
import { useCreateAccount } from "@src/hooks/storage/useCreateAccount";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Stack, Container } from "@mantine/core";
|
||||||
|
import { CenteredLayout } from "@templates/CenteredLayout";
|
||||||
import { SetPassword } from "@organisms/SetPassword";
|
import { SetPassword } from "@organisms/SetPassword";
|
||||||
import { SetPrimaryFiat } from "@organisms/SetPrimaryFiat";
|
import { SetPrimaryFiat } from "@organisms/SetPrimaryFiat";
|
||||||
import { SelectMoneroNode } from "@organisms/SelectMoneroNode";
|
import { SelectMoneroNode } from "@organisms/SelectMoneroNode";
|
||||||
import { ReadyToUse } from "@molecules/ReadyToUse";
|
import { ReadyToUse } from "@molecules/ReadyToUse";
|
||||||
|
import { useCreateAccount } from "@hooks/storage/useCreateAccount";
|
||||||
|
import { ROUTES } from "@constants/routes";
|
||||||
|
import { CONTENT_MAX_WIDTH } from "./_constants";
|
||||||
|
|
||||||
enum Steps {
|
enum Steps {
|
||||||
CreatePassword = "CreatePassword",
|
CreatePassword = "CreatePassword",
|
||||||
@ -50,13 +50,9 @@ export function CreateAccount() {
|
|||||||
setStep(Steps.SelectNode);
|
setStep(Steps.SelectNode);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateAccount = (moneroNode: {
|
const handleCreateAccount = () => {
|
||||||
url: string;
|
|
||||||
password: string;
|
|
||||||
}) => {
|
|
||||||
createAccount(
|
createAccount(
|
||||||
{
|
{
|
||||||
moneroNode: moneroNode.url,
|
|
||||||
password,
|
password,
|
||||||
primaryFiat: fiat,
|
primaryFiat: fiat,
|
||||||
},
|
},
|
||||||
@ -94,9 +90,7 @@ export function CreateAccount() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{step === Steps.Completed && (
|
{step === Steps.Completed && (
|
||||||
<ReadyToUse
|
<ReadyToUse onSubmit={() => navigate(ROUTES.PaymentAccounts)} />
|
||||||
onSubmit={() => navigate(ROUTES.AccountPaymentAccounts)}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
Loading…
Reference in New Issue
Block a user