mirror of
https://github.com/haveno-dex/haveno-ui.git
synced 2024-12-23 14:39:45 -05:00
feat: haveno daemon integration
- create account - login - change password - haveno hooks - electron-store hooks - haveno-ts --- Authored-by: schowdhuri Reviewed-by: localredhead
This commit is contained in:
parent
7bcf36d595
commit
a0c7875391
2
.env.example
Normal file
2
.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
VITE_HAVENO_URL=http://127.0.0.1:8080
|
||||
VITE_HAVENO_PASSWORD=daemon-password
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -54,3 +54,5 @@ thumbs.db
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
/.idea/csv-plugin.xml
|
||||
|
||||
.env
|
||||
|
@ -30,7 +30,7 @@ export default {
|
||||
return Array.from(
|
||||
filenames.reduce((set, filename) => {
|
||||
const pack = filename.replace(pathToPackages, "").split(sep)[0];
|
||||
set.add(`yarn typecheck:${pack} --if-present`);
|
||||
set.add(`npm run typecheck:${pack} --if-present`);
|
||||
return set;
|
||||
}, new Set())
|
||||
);
|
||||
|
25
.vscode/tasks.json
vendored
Normal file
25
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "TS Watch",
|
||||
"type": "process",
|
||||
"command": "./.vscode/tscwatch.sh",
|
||||
"isBackground": true,
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "never",
|
||||
"echo": false,
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
},
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"runOptions": {
|
||||
"runOn": "folderOpen"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
8
.vscode/tscwatch.sh
vendored
Executable file
8
.vscode/tscwatch.sh
vendored
Executable file
@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
./node_modules/.bin/tsc --watch --noEmit --project packages/main &
|
||||
P1=$!
|
||||
./node_modules/.bin/tsc --watch --noEmit --project packages/preload &
|
||||
P2=$!
|
||||
./node_modules/.bin/tsc --watch --noEmit --project packages/renderer &
|
||||
P3=$!
|
||||
wait $P1 $P2 $P3
|
@ -46,6 +46,7 @@
|
||||
"@storybook/react": "^6.4.22",
|
||||
"@storybook/testing-library": "^0.0.10",
|
||||
"@testing-library/react": "^12",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/react": "<18.0.0",
|
||||
"@types/react-dom": "<18.0.0",
|
||||
@ -54,6 +55,7 @@
|
||||
"@vitejs/plugin-react": "^1.3.0",
|
||||
"babel-loader": "^8.2.5",
|
||||
"cross-env": "7.0.3",
|
||||
"dotenv": "^16.0.0",
|
||||
"electron": "17.1.0",
|
||||
"electron-builder": "22.14.13",
|
||||
"electron-devtools-installer": "3.2.0",
|
||||
@ -82,7 +84,9 @@
|
||||
"dayjs": "^1.11.0",
|
||||
"electron-store": "^8.0.1",
|
||||
"electron-updater": "4.6.5",
|
||||
"haveno-ts": "0.0.2",
|
||||
"joi": "^17.6.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "<18.0.0",
|
||||
"react-dom": "<18.0.0",
|
||||
|
@ -17,7 +17,7 @@
|
||||
import { app } from "electron";
|
||||
import "./security-restrictions";
|
||||
import { restoreOrCreateWindow } from "@src/mainWindow";
|
||||
import { registerStoreHandlers } from "./services/store";
|
||||
import { registerStoreHandlers } from "@src/services/store";
|
||||
|
||||
/**
|
||||
* Prevent multiple instances
|
||||
|
@ -17,44 +17,116 @@
|
||||
import { ipcMain, safeStorage } from "electron";
|
||||
import Store from "electron-store";
|
||||
import type {
|
||||
AccountInfoDto,
|
||||
ChangePasswordInput,
|
||||
IPreferences,
|
||||
IStoreSchema,
|
||||
IUserInfo,
|
||||
UserInfoInputType,
|
||||
IUserPermission,
|
||||
SetPasswordInput,
|
||||
} from "@src/types";
|
||||
import { StoreKeys, StoreSchema } from "@src/types";
|
||||
import { StorageKeys } from "@src/types";
|
||||
import { IpcChannels, StoreSchema } from "@src/types";
|
||||
import {
|
||||
createAuthToken,
|
||||
hashPassword,
|
||||
verifyAuthAuthToken,
|
||||
verifyPassword,
|
||||
} from "@src/utils/password";
|
||||
|
||||
const store = new Store<IStoreSchema>({ schema: StoreSchema });
|
||||
|
||||
export function registerStoreHandlers() {
|
||||
ipcMain.handle("store:userinfo", async (_, payload?: UserInfoInputType) => {
|
||||
const prevData = store.get(StoreKeys.UserInfo);
|
||||
// retrieve encrypted data like so:
|
||||
// safeStorage.decryptString(Buffer.from(prevData.password));
|
||||
if (!payload) {
|
||||
return prevData;
|
||||
ipcMain.handle(IpcChannels.SetPassword, async (_, data: SetPasswordInput) => {
|
||||
const encryptedPassword = store.get(StorageKeys.AccountInfo_Password);
|
||||
if (encryptedPassword) {
|
||||
throw new Error("[[Can't set password]]");
|
||||
}
|
||||
const userInfo: IUserInfo = {
|
||||
...payload,
|
||||
// encrypt sensitive data before storage
|
||||
password: safeStorage.encryptString(payload.password),
|
||||
};
|
||||
store.set(StoreKeys.UserInfo, {
|
||||
...(prevData ?? {}),
|
||||
...userInfo,
|
||||
});
|
||||
return store.get(StoreKeys.UserInfo);
|
||||
const hash = await hashPassword(data.newPassword);
|
||||
store.set(
|
||||
StorageKeys.AccountInfo_Password,
|
||||
safeStorage.encryptString(hash)
|
||||
);
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
"store:permissions",
|
||||
async (_, permissions?: Array<IUserPermission>) => {
|
||||
const prevData = store.get(StoreKeys.Permissions);
|
||||
if (!permissions) {
|
||||
return prevData;
|
||||
IpcChannels.ChangePassword,
|
||||
async (_, data: ChangePasswordInput): Promise<string> => {
|
||||
const encryptedPassword = store.get(StorageKeys.AccountInfo_Password);
|
||||
if (!encryptedPassword) {
|
||||
throw new Error("[[No password currently set]]");
|
||||
}
|
||||
store.set(StoreKeys.Permissions, [...(prevData || []), ...permissions]);
|
||||
return store.get(StoreKeys.Permissions);
|
||||
// verify old password
|
||||
const oldPassHash = safeStorage.decryptString(
|
||||
Buffer.from(encryptedPassword)
|
||||
);
|
||||
if (!("currentPassword" in data)) {
|
||||
throw new Error("[[Current password required]]");
|
||||
}
|
||||
if (!(await verifyPassword(data.currentPassword, oldPassHash))) {
|
||||
throw new Error("[[Current password doesn't match]]");
|
||||
}
|
||||
const hash = await hashPassword(data.newPassword);
|
||||
store.set(
|
||||
StorageKeys.AccountInfo_Password,
|
||||
safeStorage.encryptString(hash)
|
||||
);
|
||||
// generate and return a new authToken
|
||||
return createAuthToken(hash);
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle(IpcChannels.SetPrimaryFiat, (_, value: string) => {
|
||||
store.set(StorageKeys.AccountInfo_PrimaryFiat, value);
|
||||
});
|
||||
|
||||
ipcMain.handle(IpcChannels.GetAccountInfo, (): AccountInfoDto | null => {
|
||||
const encryptedPassword = store.get(StorageKeys.AccountInfo_Password);
|
||||
if (!encryptedPassword) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
passwordHash: safeStorage.decryptString(Buffer.from(encryptedPassword)),
|
||||
primaryFiat: store.get(StorageKeys.AccountInfo_PrimaryFiat),
|
||||
};
|
||||
});
|
||||
|
||||
// returns null if password is incorrect. returns jwt if password is correct
|
||||
ipcMain.handle(
|
||||
IpcChannels.VerifyPassword,
|
||||
async (_, plainText: string): Promise<string | null> => {
|
||||
const encryptedPassword = store.get(StorageKeys.AccountInfo_Password);
|
||||
if (!encryptedPassword) {
|
||||
return null;
|
||||
}
|
||||
const hash = safeStorage.decryptString(Buffer.from(encryptedPassword));
|
||||
if (!(await verifyPassword(plainText, hash))) {
|
||||
return null;
|
||||
}
|
||||
return createAuthToken(hash);
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
IpcChannels.VerifyAuthToken,
|
||||
async (_, token: string): Promise<boolean> => {
|
||||
const encryptedPassword = store.get(StorageKeys.AccountInfo_Password);
|
||||
if (!encryptedPassword) {
|
||||
return false;
|
||||
}
|
||||
const hash = safeStorage.decryptString(Buffer.from(encryptedPassword));
|
||||
return verifyAuthAuthToken(token, hash);
|
||||
}
|
||||
);
|
||||
|
||||
ipcMain.handle(IpcChannels.SetMoneroNode, async (_, value: string) => {
|
||||
store.set(StorageKeys.Preferences_MoneroNode, value);
|
||||
});
|
||||
|
||||
ipcMain.handle(
|
||||
IpcChannels.GetPreferences,
|
||||
async (): Promise<IPreferences> => {
|
||||
return {
|
||||
moneroNode: store.get(StorageKeys.Preferences_MoneroNode),
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -14,4 +14,5 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export * from "./ipc";
|
||||
export * from "./store";
|
||||
|
27
packages/main/src/types/ipc.ts
Normal file
27
packages/main/src/types/ipc.ts
Normal file
@ -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.
|
||||
// =============================================================================
|
||||
|
||||
export enum IpcChannels {
|
||||
GetAccountInfo = "store:accountInfo",
|
||||
SetPassword = "store:accountinfo.setPassword",
|
||||
ChangePassword = "store:accountinfo.changePassword",
|
||||
VerifyPassword = "store:accountinfo.verifyPassword",
|
||||
SetPrimaryFiat = "store:accountinfo.primaryFiat",
|
||||
GetPreferences = "store:preferences",
|
||||
SetMoneroNode = "store:preferences.moneroNode",
|
||||
|
||||
VerifyAuthToken = "verifyAuthToken",
|
||||
}
|
@ -16,49 +16,51 @@
|
||||
|
||||
import type { Schema } from "electron-store";
|
||||
|
||||
export enum StoreKeys {
|
||||
UserInfo = "UserInfo",
|
||||
Permissions = "Permissions",
|
||||
export enum StorageKeys {
|
||||
AccountInfo_Password = "accounInfo.password",
|
||||
AccountInfo_PrimaryFiat = "accounInfo.primaryFiat",
|
||||
Preferences_MoneroNode = "preferences.moneroNode",
|
||||
}
|
||||
|
||||
// TS types for StoreSchema
|
||||
export interface IStoreSchema {
|
||||
[StoreKeys.UserInfo]: IUserInfo;
|
||||
[StoreKeys.Permissions]: Array<IUserPermission>;
|
||||
[StorageKeys.AccountInfo_Password]: IAccountInfo["password"];
|
||||
[StorageKeys.AccountInfo_PrimaryFiat]: IAccountInfo["primaryFiat"];
|
||||
[StorageKeys.Preferences_MoneroNode]: IPreferences["moneroNode"]; // TODO: change to object {url, password}
|
||||
}
|
||||
|
||||
export interface IUserInfo {
|
||||
username: string;
|
||||
export interface IAccountInfo {
|
||||
password: Buffer;
|
||||
primaryFiat: string;
|
||||
}
|
||||
|
||||
export type UserInfoInputType = Omit<IUserInfo, "password"> & {
|
||||
password: string;
|
||||
};
|
||||
export interface AccountInfoDto extends Omit<IAccountInfo, "password"> {
|
||||
passwordHash: string;
|
||||
}
|
||||
|
||||
export interface IUserPermission {
|
||||
name: string;
|
||||
export interface IPreferences {
|
||||
moneroNode: string;
|
||||
}
|
||||
|
||||
// this schema is used by electron-store
|
||||
// must mirror IStoreSchema
|
||||
export const StoreSchema: Schema<IStoreSchema> = {
|
||||
[StoreKeys.UserInfo]: {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {
|
||||
username: { type: "string" },
|
||||
},
|
||||
[StorageKeys.AccountInfo_Password]: {
|
||||
type: "string",
|
||||
},
|
||||
[StoreKeys.Permissions]: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
},
|
||||
},
|
||||
[StorageKeys.AccountInfo_PrimaryFiat]: {
|
||||
type: "string",
|
||||
},
|
||||
[StorageKeys.Preferences_MoneroNode]: {
|
||||
type: "string",
|
||||
},
|
||||
};
|
||||
|
||||
export interface SetPasswordInput {
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export interface ChangePasswordInput {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
}
|
||||
|
84
packages/main/src/utils/password.ts
Normal file
84
packages/main/src/utils/password.ts
Normal file
@ -0,0 +1,84 @@
|
||||
// =============================================================================
|
||||
// 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 { randomBytes, scrypt, timingSafeEqual } from "crypto";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
const SALT_LENGTH = 32;
|
||||
const KEY_LENGTH = 64;
|
||||
|
||||
export async function hashPassword(plainText: string): Promise<string> {
|
||||
const salt = randomBytes(SALT_LENGTH).toString("hex");
|
||||
return new Promise((resolve, reject) => {
|
||||
scrypt(plainText, salt, KEY_LENGTH, (err, derivedKey) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(`${salt}:${derivedKey.toString("hex")}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyPassword(
|
||||
plainText: string,
|
||||
storedHash: string
|
||||
): Promise<boolean> {
|
||||
const [salt, key] = storedHash.split(":");
|
||||
return new Promise((resolve, reject) => {
|
||||
const origKey = Buffer.from(key, "hex");
|
||||
scrypt(plainText, salt, KEY_LENGTH, (err, derivedKey) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(timingSafeEqual(origKey, derivedKey));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface AuthToken {
|
||||
createdAt: number;
|
||||
randomStr: string;
|
||||
}
|
||||
|
||||
export async function createAuthToken(
|
||||
saltAndPassword: string
|
||||
): Promise<string> {
|
||||
const [randomStr, secret] = saltAndPassword.split(":");
|
||||
const token = jwt.sign(
|
||||
{
|
||||
createdAt: new Date().getTime(),
|
||||
randomStr,
|
||||
},
|
||||
secret
|
||||
);
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function verifyAuthAuthToken(
|
||||
token: string,
|
||||
saltAndPassword: string
|
||||
): Promise<boolean> {
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
const [randomStr, secret] = saltAndPassword.split(":");
|
||||
try {
|
||||
const payload = jwt.verify(token, secret) as AuthToken;
|
||||
return payload.randomStr === randomStr;
|
||||
} catch (_ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -16,12 +16,41 @@
|
||||
|
||||
import { ipcRenderer } from "electron";
|
||||
import { exposeInMainWorld } from "./exposeInMainWorld";
|
||||
import type { UserInfoInputType } from "./types";
|
||||
import type {
|
||||
AccountInfoDto,
|
||||
ChangePasswordInput,
|
||||
IPreferences,
|
||||
SetPasswordInput,
|
||||
} from "./types";
|
||||
import { IpcChannels } from "./types";
|
||||
|
||||
// Export for types in contracts.d.ts
|
||||
export const store = {
|
||||
storeUserinfo: async (data?: UserInfoInputType) =>
|
||||
ipcRenderer.invoke("store:userinfo", data),
|
||||
setPassword: async (data: SetPasswordInput): Promise<void> =>
|
||||
ipcRenderer.invoke(IpcChannels.SetPassword, data),
|
||||
|
||||
// returns jwt on success
|
||||
changePassword: async (data: ChangePasswordInput): Promise<string> =>
|
||||
ipcRenderer.invoke(IpcChannels.ChangePassword, data),
|
||||
|
||||
// returns jwt on success; null on failure
|
||||
verifyPassword: async (plainText: string): Promise<string | null> =>
|
||||
ipcRenderer.invoke(IpcChannels.VerifyPassword, plainText),
|
||||
|
||||
verifyAuthToken: async (token: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke(IpcChannels.VerifyAuthToken, token),
|
||||
|
||||
setPrimaryFiat: async (value: string): Promise<void> =>
|
||||
ipcRenderer.invoke(IpcChannels.SetPrimaryFiat, value),
|
||||
|
||||
getAccountInfo: async (): Promise<AccountInfoDto> =>
|
||||
ipcRenderer.invoke(IpcChannels.GetAccountInfo),
|
||||
|
||||
setMoneroNode: async (value: string): Promise<void> =>
|
||||
ipcRenderer.invoke(IpcChannels.SetMoneroNode, value),
|
||||
|
||||
getPreferences: async (): Promise<IPreferences> =>
|
||||
ipcRenderer.invoke(IpcChannels.GetPreferences),
|
||||
};
|
||||
|
||||
exposeInMainWorld("electronStore", store);
|
||||
|
@ -14,4 +14,4 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export * from "./store";
|
||||
export * from "../../../main/src/types";
|
||||
|
@ -1,64 +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 { Schema } from "electron-store";
|
||||
|
||||
export enum StoreKeys {
|
||||
UserInfo = "UserInfo",
|
||||
Permissions = "Permissions",
|
||||
}
|
||||
|
||||
// TS types for StoreSchema
|
||||
export interface IStoreSchema {
|
||||
[StoreKeys.UserInfo]: IUserInfo;
|
||||
[StoreKeys.Permissions]: Array<IUserPermission>;
|
||||
}
|
||||
|
||||
export interface IUserInfo {
|
||||
username: string;
|
||||
password: Buffer;
|
||||
}
|
||||
|
||||
export type UserInfoInputType = Omit<IUserInfo, "password"> & {
|
||||
password: string;
|
||||
};
|
||||
|
||||
export interface IUserPermission {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// this schema is used by electron-store
|
||||
// must mirror IStoreSchema
|
||||
export const StoreSchema: Schema<IStoreSchema> = {
|
||||
[StoreKeys.UserInfo]: {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {
|
||||
username: { type: "string" },
|
||||
},
|
||||
},
|
||||
[StoreKeys.Permissions]: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -19,7 +19,8 @@
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/no-var-requires": "off",
|
||||
"@typescript-eslint/consistent-type-imports": "error",
|
||||
"prettier/prettier": "error"
|
||||
"prettier/prettier": "error",
|
||||
"react/jsx-curly-brace-presence": "error"
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
|
3
packages/renderer/assets/unknown.svg
Normal file
3
packages/renderer/assets/unknown.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M27.5805 17.3866C25.7106 24.8866 18.1143 29.4511 10.6133 27.5808C3.11545 25.7109 -1.44898 18.1141 0.421768 10.6145C2.29077 3.11358 9.88708 -1.45129 17.3858 0.418582C24.8863 2.28846 29.4503 9.88608 27.5805 17.3866Z" fill="#DBDBDB"/>
|
||||
</svg>
|
After Width: | Height: | Size: 344 B |
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="script-src 'self' blob:"
|
||||
content="script-src 'self' 'unsafe-eval' blob:"
|
||||
/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>Haveno</title>
|
||||
|
@ -15,44 +15,84 @@
|
||||
// =============================================================================
|
||||
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import { Home, Welcome } from "@pages/Onboarding";
|
||||
import { Wallet } from "@pages/Wallet";
|
||||
import { AccountPaymentAccounts } from "@pages/Account/AccountPaymentAccounts";
|
||||
import { AccountNodeSettings } from "@pages/Account/NodeSettings";
|
||||
import { AccountBackup } from "@pages/Account/AccountBackup";
|
||||
import { AccountWallet } from "@pages/Account/AccountWallet";
|
||||
import { AccountSecurity } from "@pages/Account/Security";
|
||||
import { ROUTES } from "@constants/routes";
|
||||
import { PaymentMethods } from "@pages/Account";
|
||||
import { AddPaymentMethod } from "@organisms/AddPaymentMethod";
|
||||
import { ProtectedRoute } from "@atoms/ProtectedRoute";
|
||||
import { Home } from "@pages/Home";
|
||||
import { Login } from "@pages/Login";
|
||||
import { CreateAccount, Welcome } from "@pages/Onboarding";
|
||||
import {
|
||||
AccountBackup,
|
||||
AccountNodeSettings,
|
||||
AccountPaymentAccounts,
|
||||
AccountSecurity,
|
||||
AccountWallet,
|
||||
AddPaymentAccount,
|
||||
PaymentMethods,
|
||||
} from "@pages/Account";
|
||||
|
||||
export function AppRoutes() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={ROUTES.Home} element={<Home />} />
|
||||
<Route path={ROUTES.Login} element={<Login />} />
|
||||
<Route path={ROUTES.Welcome} element={<Welcome />} />
|
||||
<Route path={ROUTES.Wallet} element={<Wallet />} />
|
||||
<Route path={ROUTES.Account}>
|
||||
<Route
|
||||
path={ROUTES.AccountPaymentAccounts}
|
||||
element={<AccountPaymentAccounts />}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.AccountNodeSettings}
|
||||
element={<AccountNodeSettings />}
|
||||
/>
|
||||
<Route path={ROUTES.AccountBackup} element={<AccountBackup />} />
|
||||
<Route path={ROUTES.AccountWallet} element={<AccountWallet />} />
|
||||
<Route path={ROUTES.AccountSecurity} element={<AccountSecurity />} />
|
||||
<Route
|
||||
path={ROUTES.AccountPaymentMethods}
|
||||
element={<PaymentMethods />}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.AccountAddPaymentMethod}
|
||||
element={<AddPaymentMethod />}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={ROUTES.CreateAccount} element={<CreateAccount />} />
|
||||
<Route
|
||||
path={ROUTES.AccountPaymentAccounts}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AccountPaymentAccounts />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.AccountNodeSettings}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AccountNodeSettings />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.AccountBackup}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AccountBackup />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.AccountWallet}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AccountWallet />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.AccountSecurity}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AccountSecurity />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.AccountPaymentAccounts}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<PaymentMethods />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path={ROUTES.AccountAddPaymentAccount}
|
||||
element={
|
||||
<ProtectedRoute>
|
||||
<AddPaymentAccount />
|
||||
</ProtectedRoute>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
import type { FC } from "react";
|
||||
import { RecoilRoot } from "recoil";
|
||||
import { HashRouter } from "react-router-dom";
|
||||
import { NotificationsProvider } from "@mantine/notifications";
|
||||
import { QueryClientProvider } from "./QueryClientProvider";
|
||||
import { IntlProvider } from "./IntlProvider";
|
||||
import { ThemeProvider } from "./ThemeProvider";
|
||||
@ -26,7 +27,9 @@ export const AppProviders: FC = ({ children }) => (
|
||||
<RecoilRoot>
|
||||
<IntlProvider>
|
||||
<QueryClientProvider>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
<ThemeProvider>
|
||||
<NotificationsProvider>{children}</NotificationsProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</IntlProvider>
|
||||
</RecoilRoot>
|
||||
|
@ -43,7 +43,6 @@ export function Button<TComponent = "button">(props: ButtonProps<TComponent>) {
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
common: {
|
||||
borderRadius: 10,
|
||||
fontSize: "0.875rem",
|
||||
fontWeight: 600,
|
||||
height: theme.other.buttonHeight,
|
@ -0,0 +1,35 @@
|
||||
// =============================================================================
|
||||
// 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 { ReactText } from "react";
|
||||
import type { UnstyledButtonProps } from "@mantine/core";
|
||||
import { UnstyledButton } from "@mantine/core";
|
||||
import { BodyText } from "@atoms/Typography";
|
||||
|
||||
interface TextButtonProps extends UnstyledButtonProps<"button"> {
|
||||
children: ReactText;
|
||||
}
|
||||
|
||||
export function TextButton(props: TextButtonProps) {
|
||||
const { children, ...rest } = props;
|
||||
return (
|
||||
<UnstyledButton {...rest}>
|
||||
<BodyText component="span" heavy sx={{ textDecoration: "underline" }}>
|
||||
{children}
|
||||
</BodyText>
|
||||
</UnstyledButton>
|
||||
);
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
// Vitest Snapshot v1
|
||||
|
||||
exports[`atoms::Buttons > renders error button 1`] = `
|
||||
<DocumentFragment>
|
||||
<button
|
||||
class="mantine-Button-filled mantine-Button-root mantine-17e9v6f"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="mantine-3xbgk5 mantine-Button-inner"
|
||||
>
|
||||
<span
|
||||
class="mantine-qo1k2 mantine-Button-label"
|
||||
>
|
||||
Error
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`atoms::Buttons > renders neutral button 1`] = `
|
||||
<DocumentFragment>
|
||||
<button
|
||||
class="mantine-Button-filled mantine-Button-root mantine-13v2nwn"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="mantine-3xbgk5 mantine-Button-inner"
|
||||
>
|
||||
<span
|
||||
class="mantine-qo1k2 mantine-Button-label"
|
||||
>
|
||||
Neutral
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`atoms::Buttons > renders primary button by default 1`] = `
|
||||
<DocumentFragment>
|
||||
<button
|
||||
class="mantine-Button-filled mantine-Button-root mantine-pfssi"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="mantine-3xbgk5 mantine-Button-inner"
|
||||
>
|
||||
<span
|
||||
class="mantine-qo1k2 mantine-Button-label"
|
||||
>
|
||||
Primary
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`atoms::Buttons > renders success button 1`] = `
|
||||
<DocumentFragment>
|
||||
<button
|
||||
class="mantine-Button-filled mantine-Button-root mantine-1phtj0c"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="mantine-3xbgk5 mantine-Button-inner"
|
||||
>
|
||||
<span
|
||||
class="mantine-qo1k2 mantine-Button-label"
|
||||
>
|
||||
Success
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</DocumentFragment>
|
||||
`;
|
@ -14,4 +14,5 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export * from "./Buttons";
|
||||
export * from "./Button";
|
||||
export * from "./TextButton";
|
||||
|
35
packages/renderer/src/components/atoms/Link/Link.tsx
Normal file
35
packages/renderer/src/components/atoms/Link/Link.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
// =============================================================================
|
||||
// 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 { LinkProps as RouterLinkProps } from "react-router-dom";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { BodyText } from "@atoms/Typography";
|
||||
import type { ReactText } from "react";
|
||||
|
||||
interface LinkProps extends RouterLinkProps {
|
||||
children: ReactText;
|
||||
}
|
||||
|
||||
export function Link(props: LinkProps) {
|
||||
const { children, ...rest } = props;
|
||||
return (
|
||||
<RouterLink {...rest}>
|
||||
<BodyText component="span" heavy>
|
||||
{children}
|
||||
</BodyText>
|
||||
</RouterLink>
|
||||
);
|
||||
}
|
@ -14,4 +14,4 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export const WIDTH = 470;
|
||||
export * from "./Link";
|
@ -30,7 +30,7 @@ const Template: ComponentStory<typeof NodeConnectSwitch> = () => {
|
||||
<NodeConnectSwitch.Method
|
||||
active={true}
|
||||
current={true}
|
||||
tabKey={"local-node"}
|
||||
tabKey="local-node"
|
||||
label="Local Node"
|
||||
icon={<ServerIcon width="32px" height="62px" />}
|
||||
>
|
||||
@ -38,7 +38,7 @@ const Template: ComponentStory<typeof NodeConnectSwitch> = () => {
|
||||
</NodeConnectSwitch.Method>
|
||||
|
||||
<NodeConnectSwitch.Method
|
||||
tabKey={"remote-node"}
|
||||
tabKey="remote-node"
|
||||
label="Remote Node"
|
||||
icon={<CloudIcon width="58px" height="54px" />}
|
||||
>
|
||||
|
@ -62,7 +62,7 @@ export function NodeConnectSwitchMethod({
|
||||
<Box className={cx(classes.tabCurrent)}>
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountSettingsCurrent}
|
||||
defaultMessage={"Current"}
|
||||
defaultMessage="Current"
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
@ -27,15 +27,12 @@ const Template: ComponentStory<typeof NodeStatus> = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<NodeStatus
|
||||
title={"node.moneroworldcom:18089"}
|
||||
title="node.moneroworldcom:18089"
|
||||
status={NodeStatusType.Active}
|
||||
/>
|
||||
<NodeStatus title="node.xmr.pt:18081" status={NodeStatusType.Inactive} />
|
||||
<NodeStatus
|
||||
title={"node.xmr.pt:18081"}
|
||||
status={NodeStatusType.Inactive}
|
||||
/>
|
||||
<NodeStatus
|
||||
title={"node.monero.net:18081"}
|
||||
title="node.monero.net:18081"
|
||||
status={NodeStatusType.Active}
|
||||
/>
|
||||
</Stack>
|
||||
|
@ -24,11 +24,11 @@ describe("atoms::NodeStatus", () => {
|
||||
const { asFragment } = render(
|
||||
<AppProviders>
|
||||
<NodeStatus
|
||||
title={"node.moneroworldcom:18089:active"}
|
||||
title="node.moneroworldcom:18089:active"
|
||||
status={NodeStatusType.Active}
|
||||
/>
|
||||
<NodeStatus
|
||||
title={"node.moneroworldcom:18089:inactive"}
|
||||
title="node.moneroworldcom:18089:inactive"
|
||||
status={NodeStatusType.Inactive}
|
||||
/>
|
||||
</AppProviders>
|
||||
|
@ -0,0 +1,39 @@
|
||||
// =============================================================================
|
||||
// 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 { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAuth } from "@hooks/session/useAuth";
|
||||
import { deleteSession } from "@src/utils/session";
|
||||
import { ROUTES } from "@constants/routes";
|
||||
|
||||
export function ProtectedRoute({ children }: { children: ReactNode }) {
|
||||
const { data: isAuthed, isLoading, isSuccess } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
if (!isAuthed) {
|
||||
deleteSession();
|
||||
navigate(ROUTES.Login);
|
||||
}
|
||||
}, [isLoading, isAuthed]);
|
||||
|
||||
return isSuccess ? <>{children}</> : null;
|
||||
}
|
@ -14,4 +14,4 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export * from "./AccountSecurity";
|
||||
export * from "./ProtectedRoute";
|
@ -25,7 +25,7 @@ describe("molecules::AccountSidebar", () => {
|
||||
const { asFragment } = render(
|
||||
<AppProviders>
|
||||
<Routes>
|
||||
<Route path={"/"} element={<AccountSidebar />} />
|
||||
<Route path="/" element={<AccountSidebar />} />
|
||||
</Routes>
|
||||
</AppProviders>
|
||||
);
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { SecondarySidebarItem } from "@molecules/SecondarySidebar";
|
||||
import { useNavLinkActive } from "@src/hooks/useNavLinkActive";
|
||||
import { useNavLinkActive } from "@src/hooks/misc/useNavLinkActive";
|
||||
|
||||
interface AccountSidebarItemProps {
|
||||
label: string;
|
||||
|
@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
Box,
|
||||
createStyles,
|
||||
@ -22,37 +23,40 @@ import {
|
||||
Text,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import type { PaymentAccount } from "haveno-ts";
|
||||
import { ReactComponent as MenuIcon } from "@assets/ellipsis.svg";
|
||||
import { HEIGHT, WIDTH, CurrencyLogos } from "./_constants";
|
||||
import type { SupportedCurrencies } from "./_types";
|
||||
import { HEIGHT, WIDTH } from "./_constants";
|
||||
import { BodyText } from "@atoms/Typography";
|
||||
import { useMemo } from "react";
|
||||
import {
|
||||
getPaymentAccountLogo,
|
||||
getPaymentAccountName,
|
||||
getPaymentAccountNumber,
|
||||
} from "@src/utils/payment-account";
|
||||
|
||||
interface PaymentMethodCardProps {
|
||||
currency: SupportedCurrencies;
|
||||
accountId: string;
|
||||
data: PaymentAccount;
|
||||
}
|
||||
|
||||
export function PaymentMethodCard(props: PaymentMethodCardProps) {
|
||||
const { accountId, currency } = props;
|
||||
const { data } = props;
|
||||
const { classes } = useStyles();
|
||||
|
||||
const Logo = useMemo(() => CurrencyLogos[currency].Logo, [currency]);
|
||||
const Logo = useMemo(() => getPaymentAccountLogo(data), [data]);
|
||||
|
||||
return (
|
||||
<Box className={classes.card}>
|
||||
<Stack>
|
||||
<Group position="apart">
|
||||
<Group>
|
||||
<Logo className={classes.logo} />
|
||||
<Text className={classes.name}>{CurrencyLogos[currency].name}</Text>
|
||||
<Logo />
|
||||
<Text className={classes.name}>{getPaymentAccountName(data)}</Text>
|
||||
</Group>
|
||||
<UnstyledButton>
|
||||
<MenuIcon className={classes.menuIcon} />
|
||||
</UnstyledButton>
|
||||
</Group>
|
||||
<BodyText heavy sx={{ wordBreak: "break-word" }}>
|
||||
{accountId}
|
||||
{getPaymentAccountNumber(data)}
|
||||
</BodyText>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
@ -0,0 +1,44 @@
|
||||
// =============================================================================
|
||||
// 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, Space, Group } from "@mantine/core";
|
||||
import { Button } from "@atoms/Buttons";
|
||||
import { Heading, BodyText } from "@atoms/Typography";
|
||||
|
||||
interface ReadyToUseProps {
|
||||
onSubmit: () => void;
|
||||
}
|
||||
|
||||
export function ReadyToUse(props: ReadyToUseProps) {
|
||||
const { onSubmit } = props;
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Heading order={1}>Haveno is ready for use.</Heading>
|
||||
<BodyText size="lg">
|
||||
You’ve succesfully set up Haveno. Please note that to be able to trade,
|
||||
you need to deposit Monero in your Haveno wallet and set up a payment
|
||||
account.
|
||||
</BodyText>
|
||||
<Space h="lg" />
|
||||
<Group position="apart">
|
||||
<Button type="submit" onClick={onSubmit}>
|
||||
Start using Haveno
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
);
|
||||
}
|
@ -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 "./ReadyToUse";
|
@ -24,9 +24,9 @@ describe("molecules::SecondarySidebar", () => {
|
||||
const { asFragment } = render(
|
||||
<ThemeProvider>
|
||||
<SecondarySidebar>
|
||||
<SecondarySidebarItem label={"Active item"} isActive={true} />
|
||||
<SecondarySidebarItem label={"Inactive item"} isActive={false} />
|
||||
<SecondarySidebarItem label={"Active item"} isActive={true} />
|
||||
<SecondarySidebarItem label="Active item" isActive={true} />
|
||||
<SecondarySidebarItem label="Inactive item" isActive={false} />
|
||||
<SecondarySidebarItem label="Active item" isActive={true} />
|
||||
</SecondarySidebar>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -114,13 +114,13 @@ export function AddPaymentMethod() {
|
||||
);
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
const schema = Joi.object<FormValues>({
|
||||
currency: Joi.string().required(),
|
||||
paymentMethod: Joi.string().required(),
|
||||
accountNumber: Joi.string().required(),
|
||||
});
|
||||
|
||||
const Currencies = SupportedCurrencies.map((curr) => ({
|
||||
value: curr.id,
|
||||
label: curr.name,
|
||||
value: curr.id,
|
||||
}));
|
||||
|
@ -172,7 +172,7 @@ exports[`organisms::AddPaymentMethod > renders without exploding 1`] = `
|
||||
class="mantine-Group-root mantine-mk7hdv"
|
||||
>
|
||||
<button
|
||||
class="mantine-Button-filled mantine-Button-root mantine-Group-child mantine-1i7r9hr"
|
||||
class="mantine-Button-filled mantine-Button-root mantine-Group-child mantine-1bqp2m7"
|
||||
type="submit"
|
||||
>
|
||||
<div
|
||||
|
@ -17,66 +17,96 @@
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Stack, Box, Group } from "@mantine/core";
|
||||
import { useForm, joiResolver } from "@mantine/form";
|
||||
import { showNotification } from "@mantine/notifications";
|
||||
import { TextInput } from "@atoms/TextInput";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { useAccountSecurityFormSchema } from "./_hooks";
|
||||
import { Button } from "@atoms/Buttons";
|
||||
import { useChangePassword } from "@hooks/storage/useChangePassword";
|
||||
import { useAccountSecurityFormSchema } from "./_hooks";
|
||||
import type { ChangePasswordFormValues } from "./_types";
|
||||
|
||||
export function AccountSecurityForm() {
|
||||
export function ChangePassword() {
|
||||
const accountSecurityFormSchema = useAccountSecurityFormSchema();
|
||||
const { mutate: changePassword } = useChangePassword();
|
||||
|
||||
const form = useForm({
|
||||
const form = useForm<ChangePasswordFormValues>({
|
||||
initialValues: {
|
||||
currentPassword: "",
|
||||
password: "",
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
},
|
||||
schema: joiResolver(accountSecurityFormSchema),
|
||||
});
|
||||
|
||||
const handleSubmit = (values: ChangePasswordFormValues) => {
|
||||
changePassword(
|
||||
{
|
||||
currentPassword: values.currentPassword,
|
||||
newPassword: values.newPassword,
|
||||
},
|
||||
{
|
||||
onError: (err) => {
|
||||
console.dir(err);
|
||||
showNotification({
|
||||
color: "red",
|
||||
message: err.message,
|
||||
title: "Something went wrong",
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
showNotification({
|
||||
color: "green",
|
||||
message: "Password updated successfully",
|
||||
});
|
||||
form.reset();
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<form onSubmit={form.onSubmit((values) => console.log(values))}>
|
||||
<form onSubmit={form.onSubmit(handleSubmit)}>
|
||||
<Stack spacing="lg">
|
||||
<TextInput
|
||||
id={"password"}
|
||||
type={"password"}
|
||||
id="password"
|
||||
type="password"
|
||||
required
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountSecurityFieldPassword}
|
||||
defaultMessage={"Password"}
|
||||
defaultMessage="Password"
|
||||
/>
|
||||
}
|
||||
{...form.getInputProps("password")}
|
||||
{...form.getInputProps("newPassword")}
|
||||
/>
|
||||
<TextInput
|
||||
id={"confirmPassword"}
|
||||
id="confirmPassword"
|
||||
required
|
||||
type={"password"}
|
||||
type="password"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountSecurityFieldRepeatPassword}
|
||||
defaultMessage={"Repeat new password"}
|
||||
defaultMessage="Repeat new password"
|
||||
/>
|
||||
}
|
||||
{...form.getInputProps("confirmPassword")}
|
||||
/>
|
||||
<TextInput
|
||||
id={"currentPassword"}
|
||||
type={"password"}
|
||||
id="currentPassword"
|
||||
type="password"
|
||||
required
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountSecurityFieldCurrentPassword}
|
||||
defaultMessage={"Current password"}
|
||||
defaultMessage="Current password"
|
||||
/>
|
||||
}
|
||||
{...form.getInputProps("currentPassword")}
|
||||
/>
|
||||
<Group position="right" mt="md">
|
||||
<Button size="md" type={"submit"}>
|
||||
<FormattedMessage id={LangKeys.Save} defaultMessage={"Save"} />
|
||||
<Button size="md" type="submit">
|
||||
<FormattedMessage id={LangKeys.Save} defaultMessage="Save" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
@ -0,0 +1,64 @@
|
||||
// =============================================================================
|
||||
// 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 { LangKeys } from "@constants/lang";
|
||||
import Joi from "joi";
|
||||
import { useIntl } from "react-intl";
|
||||
import type { ChangePasswordFormValues } from "./_types";
|
||||
|
||||
const MIN_PASSWORD_CHARS = 8;
|
||||
|
||||
const getPasswordRegex = () => {
|
||||
return RegExp(
|
||||
`^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{${MIN_PASSWORD_CHARS},})`,
|
||||
"i"
|
||||
);
|
||||
};
|
||||
|
||||
export const useAccountSecurityFormSchema = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return Joi.object<ChangePasswordFormValues>({
|
||||
newPassword: Joi.string()
|
||||
.required()
|
||||
.regex(getPasswordRegex())
|
||||
.options({
|
||||
messages: {
|
||||
"string.pattern.base": formatMessage({
|
||||
id: LangKeys.AccountSecurityFieldPasswordFormatMsg,
|
||||
defaultMessage: "This password is too weak",
|
||||
}),
|
||||
},
|
||||
}),
|
||||
confirmPassword: Joi.string()
|
||||
.required()
|
||||
.valid(Joi.ref("newPassword"))
|
||||
.messages({
|
||||
"any.only": formatMessage({
|
||||
id: LangKeys.AccountSecurityFieldRepeatPasswordMatchMsg,
|
||||
defaultMessage: "Passwords don't match",
|
||||
}),
|
||||
}),
|
||||
currentPassword: Joi.string()
|
||||
.required()
|
||||
.label(
|
||||
formatMessage({
|
||||
id: LangKeys.AccountSecurityFieldCurrentPassword,
|
||||
defaultMessage: "Current password",
|
||||
})
|
||||
),
|
||||
});
|
||||
};
|
@ -14,12 +14,8 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
import { AccountLayout } from "@templates/AccountLayout";
|
||||
|
||||
export function Account() {
|
||||
return (
|
||||
<AccountLayout>
|
||||
<h1>Payment accounts</h1>
|
||||
</AccountLayout>
|
||||
);
|
||||
export interface ChangePasswordFormValues {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
confirmPassword: string;
|
||||
}
|
@ -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 "./ChangePassword";
|
@ -20,12 +20,15 @@ import {
|
||||
AddPaymentMethodButton,
|
||||
PaymentMethodCard,
|
||||
} from "@molecules/PaymentMethodCard";
|
||||
import { usePaymentAccounts } from "@hooks/haveno/usePaymentAccounts";
|
||||
|
||||
interface PaymentMethodsProps {
|
||||
onAdd: () => void;
|
||||
}
|
||||
|
||||
export function PaymentMethodList({ onAdd }: PaymentMethodsProps) {
|
||||
const { data: paymentAccounts, isLoading } = usePaymentAccounts();
|
||||
|
||||
return (
|
||||
<Stack spacing="lg">
|
||||
<Stack sx={{ maxWidth: "32rem" }}>
|
||||
@ -37,15 +40,15 @@ export function PaymentMethodList({ onAdd }: PaymentMethodsProps) {
|
||||
</BodyText>
|
||||
</Stack>
|
||||
<Space h="xl" />
|
||||
<Group spacing="xl">
|
||||
<PaymentMethodCard
|
||||
accountId="1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"
|
||||
currency="BTC"
|
||||
/>
|
||||
<PaymentMethodCard accountId="tqTFn5Au4m4GFg7x" currency="ETH" />
|
||||
<PaymentMethodCard accountId="112233" currency="EUR" />
|
||||
<AddPaymentMethodButton onClick={onAdd} />
|
||||
</Group>
|
||||
{isLoading && <BodyText>Loading accounts ...</BodyText>}
|
||||
{!isLoading && paymentAccounts?.length && (
|
||||
<Group spacing="xl">
|
||||
{paymentAccounts.map((account) => (
|
||||
<PaymentMethodCard key={account.getId()} data={account} />
|
||||
))}
|
||||
<AddPaymentMethodButton onClick={onAdd} />
|
||||
</Group>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
// =============================================================================
|
||||
// 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, Space, Group } from "@mantine/core";
|
||||
import { BodyText, Heading } from "@atoms/Typography";
|
||||
import { Button, TextButton } from "@atoms/Buttons";
|
||||
import { Select } from "@atoms/Select";
|
||||
import type { FormEvent } from "react";
|
||||
|
||||
interface SelectMoneroNodeProps {
|
||||
onGoBack: () => void;
|
||||
onNext: ({ url, password }: { url: string; password: string }) => void;
|
||||
}
|
||||
|
||||
export function SelectMoneroNode(props: SelectMoneroNodeProps) {
|
||||
const { onGoBack, onNext } = props;
|
||||
|
||||
const handleSubmit = (ev: FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault();
|
||||
// TODO: fix
|
||||
onNext({
|
||||
url: "http://192.168.29.59:8080",
|
||||
password: "apitest",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Stack>
|
||||
<Heading order={1}>Select a node</Heading>
|
||||
<BodyText size="lg">
|
||||
We found a local node running on your machine, it’s recommended to use
|
||||
this one. Alternatively you can select one of the curated nodes below
|
||||
add another node.
|
||||
</BodyText>
|
||||
<Select id="fiat" data={[]} placeholder="Pick one" />
|
||||
<Space h="lg" />
|
||||
<Group position="apart">
|
||||
<TextButton onClick={onGoBack}>Go Back</TextButton>
|
||||
<Button type="submit">Next</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}
|
@ -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 "./SelectMoneroNode";
|
@ -0,0 +1,93 @@
|
||||
// =============================================================================
|
||||
// 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, Space, Group, Container } from "@mantine/core";
|
||||
import { joiResolver, useForm } from "@mantine/form";
|
||||
import Joi from "joi";
|
||||
import { BodyText, Heading } from "@atoms/Typography";
|
||||
import { TextInput } from "@atoms/TextInput";
|
||||
import { Button, TextButton } from "@atoms/Buttons";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
|
||||
interface SetPasswordProps {
|
||||
value: string;
|
||||
onGoBack: () => void;
|
||||
onNext: (password: string) => void;
|
||||
}
|
||||
|
||||
export function SetPassword(props: SetPasswordProps) {
|
||||
const { value, onGoBack, onNext } = props;
|
||||
const { getInputProps, onSubmit } = useForm<FormValues>({
|
||||
schema: joiResolver(schema),
|
||||
initialValues: {
|
||||
password: value ?? "",
|
||||
repeatPassword: "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (values: FormValues) => {
|
||||
onNext(values.password);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<Container>
|
||||
<Heading order={1} stringId={LangKeys.CreatePassword}>
|
||||
Create a password
|
||||
</Heading>
|
||||
</Container>
|
||||
<BodyText size="lg">
|
||||
All your data is stored locally on your machine. Haveno uses solely a
|
||||
password.
|
||||
</BodyText>
|
||||
<TextInput
|
||||
id="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
{...getInputProps("password")}
|
||||
/>
|
||||
<TextInput
|
||||
id="repeatPassword"
|
||||
label="Repeat password"
|
||||
type="password"
|
||||
{...getInputProps("repeatPassword")}
|
||||
/>
|
||||
<Space h="lg" />
|
||||
<Group position="apart">
|
||||
<TextButton onClick={onGoBack}>Go Back</TextButton>
|
||||
<Button type="submit">Next</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
interface FormValues {
|
||||
password: string;
|
||||
repeatPassword: string;
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
password: Joi.string().min(6).required().messages({
|
||||
"string.min": "Password too short",
|
||||
"string.empty": "Password can't be empty",
|
||||
}),
|
||||
repeatPassword: Joi.string().required().valid(Joi.ref("password")).messages({
|
||||
"any.only": "Passwords don't match",
|
||||
"string.empty": "Please type the password again",
|
||||
}),
|
||||
});
|
@ -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 "./SetPassword";
|
@ -0,0 +1,83 @@
|
||||
// =============================================================================
|
||||
// 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, Space, Group } from "@mantine/core";
|
||||
import { joiResolver, useForm } from "@mantine/form";
|
||||
import Joi from "joi";
|
||||
import { BodyText, Heading } from "@atoms/Typography";
|
||||
import { Button, TextButton } from "@atoms/Buttons";
|
||||
import { Select } from "@atoms/Select";
|
||||
import { SupportedCurrencies } from "@constants/currencies";
|
||||
|
||||
interface SetSetPrimaryFiatProps {
|
||||
onGoBack: () => void;
|
||||
onNext: (fiat: string) => void;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export function SetPrimaryFiat(props: SetSetPrimaryFiatProps) {
|
||||
const { onGoBack, onNext, value } = props;
|
||||
const { getInputProps, onSubmit } = useForm<FormValues>({
|
||||
schema: joiResolver(schema),
|
||||
initialValues: {
|
||||
fiat: value ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (values: FormValues) => {
|
||||
onNext(values.fiat);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<Heading order={1}>
|
||||
Choose the fiat currency you want to primarily use.
|
||||
</Heading>
|
||||
<BodyText size="lg">
|
||||
Haveno uses this to show you conversion rates of your funds. You can
|
||||
still trade every pair of Monero/Fiat.
|
||||
</BodyText>
|
||||
<Select
|
||||
id="fiat"
|
||||
data={CURRENCIES}
|
||||
placeholder="Pick one"
|
||||
{...getInputProps("fiat")}
|
||||
/>
|
||||
<Space h="lg" />
|
||||
<Group position="apart">
|
||||
<TextButton onClick={onGoBack}>Go Back</TextButton>
|
||||
<Button type="submit">Next</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
interface FormValues {
|
||||
fiat: string;
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
fiat: Joi.string().required().messages({
|
||||
"string.empty": "Please select a currency",
|
||||
}),
|
||||
});
|
||||
|
||||
const CURRENCIES = SupportedCurrencies.filter((cur) => cur.fiat).map((cur) => ({
|
||||
label: cur.name,
|
||||
value: cur.id,
|
||||
}));
|
@ -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 "./SetPrimaryFiat";
|
@ -20,14 +20,15 @@ import { HeaderWithLogo } from "@atoms/Header";
|
||||
|
||||
interface CenteredLayoutProps {
|
||||
showHeader?: boolean;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export const CenteredLayout: FC<CenteredLayoutProps> = (props) => {
|
||||
const { children, showHeader = false } = props;
|
||||
const { children, showHeader = false, size } = props;
|
||||
return (
|
||||
<Stack sx={{ width: "100%" }}>
|
||||
{showHeader && <HeaderWithLogo />}
|
||||
<Container p="sm" sx={{ display: "flex", flex: 1 }}>
|
||||
<Container p="sm" size={size} sx={{ display: "flex", flex: 1 }}>
|
||||
{children}
|
||||
</Container>
|
||||
</Stack>
|
||||
|
@ -23,18 +23,21 @@ export const SupportedCurrencies = [
|
||||
{
|
||||
id: "BTC",
|
||||
name: "Bitcoin",
|
||||
fiat: false,
|
||||
logo: BtcLogo,
|
||||
paymentMethods: [PaymentMethodIds.BLOCK_CHAINS_ID],
|
||||
},
|
||||
{
|
||||
id: "ETH",
|
||||
name: "Ethereum",
|
||||
fiat: false,
|
||||
logo: EthLogo,
|
||||
paymentMethods: [PaymentMethodIds.BLOCK_CHAINS_ID],
|
||||
},
|
||||
{
|
||||
id: "EUR",
|
||||
name: "Euro",
|
||||
fiat: true,
|
||||
logo: EurLogo,
|
||||
paymentMethods: [
|
||||
// EUR
|
||||
@ -84,6 +87,7 @@ export const SupportedCurrencies = [
|
||||
{
|
||||
id: "USD",
|
||||
name: "US Dollar",
|
||||
fiat: true,
|
||||
logo: EurLogo,
|
||||
paymentMethods: [
|
||||
// US
|
||||
|
20
packages/renderer/src/constants/haveno-daemon.ts
Normal file
20
packages/renderer/src/constants/haveno-daemon.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// =============================================================================
|
||||
// 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 const HAVENO_DAEMON_URL =
|
||||
import.meta.env.VITE_HAVENO_URL ?? "http://localhost:8080";
|
||||
export const HAVENO_DAEMON_PASSWORD =
|
||||
import.meta.env.VITE_HAVENO_PASSWORD ?? "apitest";
|
@ -21,6 +21,7 @@ export enum LangKeys {
|
||||
ConnectingToNetwork = "app.connectingToNetwork",
|
||||
WelcomeToHaveno = "app.welcomeToHaveno",
|
||||
Save = "app.save",
|
||||
CreatePassword = "onboarding.createPassword",
|
||||
AccountTitle = "account.title",
|
||||
AccountSidebarPaymentAccounts = "account.sidebar.paymentAccounts",
|
||||
AccountSidebarSecurity = "account.sidebar.security",
|
||||
|
@ -45,13 +45,14 @@ const LangPackEN: { [key in LangKeys]: string } = {
|
||||
[LangKeys.AccountNodeStopDeamon]: "Stop deamon",
|
||||
[LangKeys.AccountSettingsAddNode]: "Add a new node",
|
||||
[LangKeys.AccountSettingsCurrent]: "Current",
|
||||
[LangKeys.AccountSecurityFieldPassword]: "Password",
|
||||
[LangKeys.AccountSecurityFieldPassword]: "Update account password",
|
||||
[LangKeys.AccountSecurityFieldRepeatPassword]: "Repeat new password",
|
||||
[LangKeys.AccountSecurityFieldCurrentPassword]: "Current password",
|
||||
[LangKeys.AccountSecurityFieldPasswordFormatMsg]:
|
||||
"contain atleast {minChars} characters, one uppercase, one lowercase and one number.",
|
||||
"Password must contain atleast {minChars} characters, one uppercase, one lowercase and one number.",
|
||||
[LangKeys.AccountSecurityFieldRepeatPasswordMatchMsg]:
|
||||
"Password confirmation doesn't match Password.",
|
||||
"Passwords don't match",
|
||||
[LangKeys.CreatePassword]: "Create password",
|
||||
};
|
||||
|
||||
export default LangPackEN;
|
||||
|
@ -53,6 +53,7 @@ const LangPackES: { [key in LangKeys]: string } = {
|
||||
"contener al menos {minChars} caracteres, una mayúscula, una minúscula y un número.",
|
||||
[LangKeys.AccountSecurityFieldRepeatPasswordMatchMsg]:
|
||||
"La confirmación de la contraseña no coincide con la contraseña.",
|
||||
[LangKeys.CreatePassword]: "Crear contraseña",
|
||||
};
|
||||
|
||||
export default LangPackES;
|
||||
|
26
packages/renderer/src/constants/query-keys.ts
Normal file
26
packages/renderer/src/constants/query-keys.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.
|
||||
// =============================================================================
|
||||
|
||||
export enum QueryKeys {
|
||||
HavenoVersion = "Haveno.Version",
|
||||
Balances = "Haveno.Balances",
|
||||
PaymentAccounts = "Haveno.PaymentAccounts",
|
||||
|
||||
StorageAccountInfo = "Storage.AccountInfo",
|
||||
StoragePreferences = "Storage.Preferences",
|
||||
|
||||
AuthSession = "AuthSession",
|
||||
}
|
@ -15,19 +15,18 @@
|
||||
// =============================================================================
|
||||
|
||||
export const ROUTES = {
|
||||
Home: "/",
|
||||
Home: "",
|
||||
HomeAlias: "/",
|
||||
Login: "/login",
|
||||
Welcome: "/onboarding/welcome",
|
||||
CreateAccount: "/onboarding/create-account",
|
||||
RestoreBackup: "/onboarding/restore-backup",
|
||||
SetupAccount: "/onboarding/setup",
|
||||
Wallet: "/wallet",
|
||||
|
||||
// Account routes.
|
||||
Account: "/account",
|
||||
// Account routes
|
||||
AccountPaymentAccounts: "/account/payment-accounts",
|
||||
AccountAddPaymentAccount: "/account/payment-accounts/add",
|
||||
AccountNodeSettings: "/account/node-settings",
|
||||
AccountBackup: "/account/backup",
|
||||
AccountWallet: "/account/wallet",
|
||||
AccountSecurity: "/account/security",
|
||||
AccountPaymentMethods: "/account/payment-methods",
|
||||
AccountAddPaymentMethod: "/account/payment-methods/add",
|
||||
};
|
||||
|
26
packages/renderer/src/hooks/haveno/useBalances.ts
Normal file
26
packages/renderer/src/hooks/haveno/useBalances.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 useBalances() {
|
||||
const client = useHavenoClient();
|
||||
return useQuery(QueryKeys.Balances, async () => {
|
||||
return client.getBalances();
|
||||
});
|
||||
}
|
29
packages/renderer/src/hooks/haveno/useCreateAccount.ts
Normal file
29
packages/renderer/src/hooks/haveno/useCreateAccount.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// =============================================================================
|
||||
// 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 } from "react-query";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
interface Variables {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export function useCreateAccount() {
|
||||
const client = useHavenoClient();
|
||||
return useMutation(async (variables: Variables) =>
|
||||
client.createAccount(variables.password)
|
||||
);
|
||||
}
|
35
packages/renderer/src/hooks/haveno/useHavenoClient.ts
Normal file
35
packages/renderer/src/hooks/haveno/useHavenoClient.ts
Normal file
@ -0,0 +1,35 @@
|
||||
// =============================================================================
|
||||
// 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 { HavenoClient } from "haveno-ts";
|
||||
import { useRef } from "react";
|
||||
import {
|
||||
HAVENO_DAEMON_PASSWORD,
|
||||
HAVENO_DAEMON_URL,
|
||||
} from "@constants/haveno-daemon";
|
||||
|
||||
let havenoClient: HavenoClient;
|
||||
|
||||
export function useHavenoClient() {
|
||||
const client = useRef<HavenoClient>(havenoClient);
|
||||
if (!client.current) {
|
||||
client.current = havenoClient = new HavenoClient(
|
||||
HAVENO_DAEMON_URL,
|
||||
HAVENO_DAEMON_PASSWORD
|
||||
);
|
||||
}
|
||||
return client.current;
|
||||
}
|
26
packages/renderer/src/hooks/haveno/useHavenoVersion.ts
Normal file
26
packages/renderer/src/hooks/haveno/useHavenoVersion.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 { QueryKeys } from "@constants/query-keys";
|
||||
import { useQuery } from "react-query";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
export function useHavenoVersion() {
|
||||
const client = useHavenoClient();
|
||||
return useQuery(QueryKeys.HavenoVersion, async () => {
|
||||
return client.getVersion();
|
||||
});
|
||||
}
|
36
packages/renderer/src/hooks/haveno/usePaymentAccounts.ts
Normal file
36
packages/renderer/src/hooks/haveno/usePaymentAccounts.ts
Normal file
@ -0,0 +1,36 @@
|
||||
// =============================================================================
|
||||
// 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 type { PaymentAccount } from "haveno-ts";
|
||||
import { useQuery } from "react-query";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
export function usePaymentAccounts() {
|
||||
const client = useHavenoClient();
|
||||
return useQuery<Array<PaymentAccount>, Error>(
|
||||
QueryKeys.PaymentAccounts,
|
||||
async () => {
|
||||
try {
|
||||
const accounts = await client.getPaymentAccounts();
|
||||
return accounts.map((acc) => acc);
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
29
packages/renderer/src/hooks/haveno/useSetMoneroConnection.ts
Normal file
29
packages/renderer/src/hooks/haveno/useSetMoneroConnection.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// =============================================================================
|
||||
// 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 } from "react-query";
|
||||
import { useHavenoClient } from "./useHavenoClient";
|
||||
|
||||
interface Variables {
|
||||
connection: string;
|
||||
}
|
||||
|
||||
export function useSetMoneroConnection() {
|
||||
const client = useHavenoClient();
|
||||
return useMutation(async (variables: Variables) =>
|
||||
client.setMoneroConnection(variables.connection)
|
||||
);
|
||||
}
|
35
packages/renderer/src/hooks/session/useAuth.ts
Normal file
35
packages/renderer/src/hooks/session/useAuth.ts
Normal file
@ -0,0 +1,35 @@
|
||||
// =============================================================================
|
||||
// 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 { validateSession } from "@src/utils/session";
|
||||
import { useQuery } from "react-query";
|
||||
|
||||
export function useAuth() {
|
||||
return useQuery(
|
||||
QueryKeys.AuthSession,
|
||||
async () => {
|
||||
if (await validateSession()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{
|
||||
staleTime: 60000,
|
||||
retry: false,
|
||||
}
|
||||
);
|
||||
}
|
34
packages/renderer/src/hooks/session/useLogin.ts
Normal file
34
packages/renderer/src/hooks/session/useLogin.ts
Normal file
@ -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 { createSession } from "@src/utils/session";
|
||||
import { useMutation } from "react-query";
|
||||
|
||||
interface Variables {
|
||||
password: string;
|
||||
}
|
||||
|
||||
export function useLogin() {
|
||||
return useMutation<void, Error, Variables>(async (variables: Variables) => {
|
||||
const authToken = await window.electronStore.verifyPassword(
|
||||
variables.password
|
||||
);
|
||||
if (!authToken) {
|
||||
throw new Error("Invalid password");
|
||||
}
|
||||
createSession(authToken);
|
||||
});
|
||||
}
|
48
packages/renderer/src/hooks/storage/useChangePassword.ts
Normal file
48
packages/renderer/src/hooks/storage/useChangePassword.ts
Normal file
@ -0,0 +1,48 @@
|
||||
// =============================================================================
|
||||
// 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 { getIpcError } from "@src/utils/get-ipc-error";
|
||||
import { createSession } from "@src/utils/session";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
|
||||
interface Variables {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export function useChangePassword() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<string, Error, Variables>(
|
||||
async (variables: Variables) => {
|
||||
try {
|
||||
const authToken = await window.electronStore.changePassword(variables);
|
||||
return authToken;
|
||||
} catch (ex) {
|
||||
throw new Error(getIpcError(ex as Error));
|
||||
}
|
||||
},
|
||||
{
|
||||
onSuccess: (authToken) => {
|
||||
// update the session jwt
|
||||
createSession(authToken).then(() => {
|
||||
queryClient.invalidateQueries(QueryKeys.StorageAccountInfo);
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
44
packages/renderer/src/hooks/storage/useCreateAccount.ts
Normal file
44
packages/renderer/src/hooks/storage/useCreateAccount.ts
Normal file
@ -0,0 +1,44 @@
|
||||
// =============================================================================
|
||||
// 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 {
|
||||
password: string;
|
||||
primaryFiat: string;
|
||||
moneroNode: string;
|
||||
}
|
||||
|
||||
export function useCreateAccount() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<void, Error, Variables>(
|
||||
async (variables: Variables) => {
|
||||
await Promise.all([
|
||||
window.electronStore.setPassword({ newPassword: variables.password }),
|
||||
window.electronStore.setPrimaryFiat(variables.primaryFiat),
|
||||
window.electronStore.setMoneroNode(variables.moneroNode),
|
||||
]);
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(QueryKeys.StorageAccountInfo);
|
||||
queryClient.invalidateQueries(QueryKeys.StoragePreferences);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
26
packages/renderer/src/hooks/storage/useGetAccountInfo.ts
Normal file
26
packages/renderer/src/hooks/storage/useGetAccountInfo.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 type { AccountInfoDto } from "@src/types";
|
||||
|
||||
export function useAccountInfo() {
|
||||
return useQuery<AccountInfoDto, Error>(
|
||||
QueryKeys.StorageAccountInfo,
|
||||
async () => window.electronStore.getAccountInfo()
|
||||
);
|
||||
}
|
25
packages/renderer/src/hooks/storage/useGetPreferences.ts
Normal file
25
packages/renderer/src/hooks/storage/useGetPreferences.ts
Normal file
@ -0,0 +1,25 @@
|
||||
// =============================================================================
|
||||
// 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 type { IPreferences } from "@src/types";
|
||||
|
||||
export function usePreferences() {
|
||||
return useQuery<IPreferences, Error>(QueryKeys.StoragePreferences, async () =>
|
||||
window.electronStore.getPreferences()
|
||||
);
|
||||
}
|
36
packages/renderer/src/hooks/storage/useSetPrimaryFiat.ts
Normal file
36
packages/renderer/src/hooks/storage/useSetPrimaryFiat.ts
Normal file
@ -0,0 +1,36 @@
|
||||
// =============================================================================
|
||||
// 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 {
|
||||
fiat: string;
|
||||
}
|
||||
|
||||
export function useSetPrimaryFiat() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<void, Error, Variables>(
|
||||
async (variables: Variables) =>
|
||||
window.electronStore.setPrimaryFiat(variables.fiat),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(QueryKeys.StorageAccountInfo);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
@ -14,12 +14,19 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ROUTES } from "@constants/routes";
|
||||
import { PaymentMethodList } from "@organisms/PaymentMethodList";
|
||||
import { AccountLayout } from "@templates/AccountLayout";
|
||||
|
||||
export function AccountPaymentAccounts() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<AccountLayout>
|
||||
<h1>Payment accounts</h1>
|
||||
<PaymentMethodList
|
||||
onAdd={() => navigate(ROUTES.AccountAddPaymentAccount)}
|
||||
/>
|
||||
</AccountLayout>
|
||||
);
|
||||
}
|
||||
|
@ -17,24 +17,8 @@
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { Stack, Box, createStyles, Group } from "@mantine/core";
|
||||
import { AccountLayout } from "@templates/AccountLayout";
|
||||
import { AccountSecurityForm } from "./AccountSecurityForm";
|
||||
import { Heading, BodyText } from "@atoms/Typography";
|
||||
import { WIDTH } from "./_constants";
|
||||
|
||||
function AccountSecurityHeader() {
|
||||
return (
|
||||
<Group spacing="sm">
|
||||
<Heading stringId={LangKeys.AccountSecurityTitle} order={3}>
|
||||
Account Security
|
||||
</Heading>
|
||||
<BodyText stringId={LangKeys.AccountSecurityDesc} size="md">
|
||||
Haveno does not store any of your data, this happens solely locally on
|
||||
your device. It’s not possible to restore your password when lost.
|
||||
Please make sure you store a copy of it on a safe place.
|
||||
</BodyText>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
import { ChangePassword } from "@organisms/ChangePassword";
|
||||
|
||||
export function AccountSecurity() {
|
||||
const { classes } = useStyles();
|
||||
@ -43,16 +27,26 @@ export function AccountSecurity() {
|
||||
<AccountLayout>
|
||||
<Box className={classes.content}>
|
||||
<Stack spacing="lg">
|
||||
<AccountSecurityHeader />
|
||||
<AccountSecurityForm />
|
||||
<Group spacing="sm">
|
||||
<Heading stringId={LangKeys.AccountSecurityTitle} order={3}>
|
||||
Account Security
|
||||
</Heading>
|
||||
<BodyText heavy stringId={LangKeys.AccountSecurityDesc} size="md">
|
||||
Haveno does not store any of your data, this happens solely
|
||||
locally on your device. It’s not possible to restore your password
|
||||
when lost. Please make sure you store a copy of it on a safe
|
||||
place.
|
||||
</BodyText>
|
||||
</Group>
|
||||
<ChangePassword />
|
||||
</Stack>
|
||||
</Box>
|
||||
</AccountLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const useStyles = createStyles(() => ({
|
||||
const useStyles = createStyles((theme) => ({
|
||||
content: {
|
||||
maxWidth: WIDTH,
|
||||
maxWidth: theme.other.contentWidthMd,
|
||||
},
|
||||
}));
|
@ -15,12 +15,12 @@
|
||||
// =============================================================================
|
||||
|
||||
import { AddPaymentMethod as AddPaymentMethodOrganism } from "@organisms/AddPaymentMethod";
|
||||
import { NavbarLayout } from "@templates/NavbarLayout";
|
||||
import { AccountLayout } from "@templates/AccountLayout";
|
||||
|
||||
export function AddPaymentMethod() {
|
||||
export function AddPaymentAccount() {
|
||||
return (
|
||||
<NavbarLayout>
|
||||
<AccountLayout>
|
||||
<AddPaymentMethodOrganism />
|
||||
</NavbarLayout>
|
||||
</AccountLayout>
|
||||
);
|
||||
}
|
@ -36,23 +36,23 @@ export function NodeLocalForm() {
|
||||
<NodeLocalStopDeamon />
|
||||
|
||||
<form onSubmit={form.onSubmit((values) => console.log(values))}>
|
||||
<Stack spacing={"lg"}>
|
||||
<Stack spacing="lg">
|
||||
<TextInput
|
||||
id={"blockchainLocation"}
|
||||
id="blockchainLocation"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountNodeFieldBlockchainLocation}
|
||||
defaultMessage={"Blockchain location"}
|
||||
defaultMessage="Blockchain location"
|
||||
/>
|
||||
}
|
||||
{...form.getInputProps("blockchainLocation")}
|
||||
/>
|
||||
<TextInput
|
||||
id={"deamonFlags"}
|
||||
id="deamonFlags"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountNodeFieldDeamonFlags}
|
||||
defaultMessage={"Deamon startup flags"}
|
||||
defaultMessage="Deamon startup flags"
|
||||
/>
|
||||
}
|
||||
{...form.getInputProps("startupFlags")}
|
||||
@ -60,11 +60,11 @@ export function NodeLocalForm() {
|
||||
<Grid>
|
||||
<Grid.Col span={9}>
|
||||
<TextInput
|
||||
id={"deamonAddress"}
|
||||
id="deamonAddress"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountNodeFieldDeamonAddress}
|
||||
defaultMessage={"Deamon Address"}
|
||||
defaultMessage="Deamon Address"
|
||||
/>
|
||||
}
|
||||
{...form.getInputProps("deamonAddress")}
|
||||
@ -72,11 +72,11 @@ export function NodeLocalForm() {
|
||||
</Grid.Col>
|
||||
<Grid.Col span={3}>
|
||||
<TextInput
|
||||
id={"port"}
|
||||
id="port"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountNodeFieldPort}
|
||||
defaultMessage={"Port"}
|
||||
defaultMessage="Port"
|
||||
/>
|
||||
}
|
||||
{...form.getInputProps("port")}
|
||||
@ -94,10 +94,10 @@ function NodeLocalStopDeamon() {
|
||||
|
||||
return (
|
||||
<div className={classes.actions}>
|
||||
<Button flavor={"neutral"}>
|
||||
<Button flavor="neutral">
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountNodeStopDeamon}
|
||||
defaultMessage={"Stop deamon"}
|
||||
defaultMessage="Stop deamon"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -24,22 +24,19 @@ export function NodeRemoteStatus() {
|
||||
return (
|
||||
<Stack>
|
||||
<NodeStatus
|
||||
title={"node.moneroworldcom:18089"}
|
||||
title="node.moneroworldcom:18089"
|
||||
status={NodeStatusType.Active}
|
||||
/>
|
||||
<NodeStatus title="node.xmr.pt:18081" status={NodeStatusType.Inactive} />
|
||||
<NodeStatus
|
||||
title={"node.xmr.pt:18081"}
|
||||
status={NodeStatusType.Inactive}
|
||||
/>
|
||||
<NodeStatus
|
||||
title={"node.monero.net:18081"}
|
||||
title="node.monero.net:18081"
|
||||
status={NodeStatusType.Active}
|
||||
/>
|
||||
<AddNewNodeButton />
|
||||
|
||||
<Group position={"right"} mt={"sm"}>
|
||||
<Button size={"md"}>
|
||||
<FormattedMessage id={LangKeys.Save} defaultMessage={"Save"} />
|
||||
<Group position="right" mt="sm">
|
||||
<Button size="md">
|
||||
<FormattedMessage id={LangKeys.Save} defaultMessage="Save" />
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
@ -51,8 +48,8 @@ function AddNewNodeButton({ ...rest }) {
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant={"subtle"}
|
||||
color={"dark"}
|
||||
variant="subtle"
|
||||
color="dark"
|
||||
classNames={{
|
||||
root: classes.root,
|
||||
inner: classes.inner,
|
||||
@ -62,7 +59,7 @@ function AddNewNodeButton({ ...rest }) {
|
||||
+{" "}
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountSettingsAddNode}
|
||||
defaultMessage={"Add a new node"}
|
||||
defaultMessage="Add a new node"
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
|
@ -18,7 +18,6 @@ import { Stack, Box, createStyles } from "@mantine/core";
|
||||
import { AccountLayout } from "@templates/AccountLayout";
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import { NodeSettingsSwitch } from "./NodeSettingsSwitch";
|
||||
import { WIDTH } from "./_constants";
|
||||
import { BodyText, Heading } from "@atoms/Typography";
|
||||
|
||||
export function AccountNodeSettings() {
|
||||
@ -27,13 +26,13 @@ export function AccountNodeSettings() {
|
||||
return (
|
||||
<AccountLayout>
|
||||
<Box className={classes.content}>
|
||||
<Stack spacing={"sm"}>
|
||||
<Stack spacing="sm">
|
||||
<Heading stringId={LangKeys.AccountNodeSettingsTitle} order={3}>
|
||||
Your node settings
|
||||
</Heading>
|
||||
<BodyText
|
||||
stringId={LangKeys.AccountNodeSettingsDesc}
|
||||
size={"md"}
|
||||
size="md"
|
||||
className={classes.paragraph}
|
||||
>
|
||||
Using a local node is recommended, but does require loading the
|
||||
@ -49,7 +48,7 @@ export function AccountNodeSettings() {
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
content: {
|
||||
maxWidth: WIDTH,
|
||||
maxWidth: theme.other.contentWidthMd,
|
||||
},
|
||||
paragraph: {
|
||||
marginBottom: theme.spacing.xl,
|
||||
|
@ -28,17 +28,17 @@ export function NodeSettingsSwitch() {
|
||||
|
||||
return (
|
||||
<NodeConnectSwitch
|
||||
initialTab={"local-node"}
|
||||
initialTab="local-node"
|
||||
className={classes.connectSwitch}
|
||||
>
|
||||
<NodeConnectSwitch.Method
|
||||
active={true}
|
||||
current={true}
|
||||
tabKey={"local-node"}
|
||||
tabKey="local-node"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountNodeSettingsLocal}
|
||||
defaultMessage={"Local Node"}
|
||||
defaultMessage="Local Node"
|
||||
/>
|
||||
}
|
||||
icon={<ServerIcon width={32} height={62} />}
|
||||
@ -47,11 +47,11 @@ export function NodeSettingsSwitch() {
|
||||
</NodeConnectSwitch.Method>
|
||||
|
||||
<NodeConnectSwitch.Method
|
||||
tabKey={"remote-node"}
|
||||
tabKey="remote-node"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id={LangKeys.AccountNodeSettingsRemote}
|
||||
defaultMessage={"Remote Node"}
|
||||
defaultMessage="Remote Node"
|
||||
/>
|
||||
}
|
||||
icon={<CloudIcon width={58} height={54} />}
|
||||
|
@ -25,7 +25,7 @@ export function PaymentMethods() {
|
||||
return (
|
||||
<NavbarLayout>
|
||||
<PaymentMethodList
|
||||
onAdd={() => navigate(ROUTES.AccountAddPaymentMethod)}
|
||||
onAdd={() => navigate(ROUTES.AccountAddPaymentAccount)}
|
||||
/>
|
||||
</NavbarLayout>
|
||||
);
|
||||
|
@ -1,57 +0,0 @@
|
||||
import { LangKeys } from "@constants/lang";
|
||||
import Joi from "joi";
|
||||
import { useIntl } from "react-intl";
|
||||
import { MIN_PASSWORD_CHARS } from "./_constants";
|
||||
|
||||
const getPasswordRegex = () => {
|
||||
return RegExp(
|
||||
`^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{${MIN_PASSWORD_CHARS},})`,
|
||||
"i"
|
||||
);
|
||||
};
|
||||
|
||||
export const useAccountSecurityFormSchema = () => {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return Joi.object({
|
||||
password: Joi.string()
|
||||
.required()
|
||||
.regex(
|
||||
getPasswordRegex(),
|
||||
formatMessage(
|
||||
{
|
||||
id: LangKeys.AccountSecurityFieldPasswordFormatMsg,
|
||||
defaultMessage: `contain atleast ${MIN_PASSWORD_CHARS} characters, one uppercase, one lowercase and one number`,
|
||||
},
|
||||
{
|
||||
minChars: MIN_PASSWORD_CHARS,
|
||||
}
|
||||
)
|
||||
)
|
||||
.label(
|
||||
formatMessage({
|
||||
id: LangKeys.AccountSecurityFieldPassword,
|
||||
defaultMessage: "Password",
|
||||
})
|
||||
),
|
||||
confirmPassword: Joi.string()
|
||||
.valid(Joi.ref("password"))
|
||||
.required()
|
||||
.options({
|
||||
messages: {
|
||||
"any.only": formatMessage({
|
||||
id: LangKeys.AccountSecurityFieldRepeatPasswordMatchMsg,
|
||||
defaultMessage: "Password confirmation doesn't match Password.",
|
||||
}),
|
||||
},
|
||||
}),
|
||||
currentPassword: Joi.string()
|
||||
.required()
|
||||
.label(
|
||||
formatMessage({
|
||||
id: LangKeys.AccountSecurityFieldCurrentPassword,
|
||||
defaultMessage: "Current password",
|
||||
})
|
||||
),
|
||||
});
|
||||
};
|
@ -14,5 +14,10 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export * from "./AddPaymentMethod";
|
||||
export * from "./AddPaymentAccount";
|
||||
export * from "./PaymentMethods";
|
||||
export * from "./AccountBackup";
|
||||
export * from "./NodeSettings";
|
||||
export * from "./AccountPaymentAccounts";
|
||||
export * from "./AccountSecurity";
|
||||
export * from "./AccountWallet";
|
||||
|
65
packages/renderer/src/pages/Home/Home.tsx
Normal file
65
packages/renderer/src/pages/Home/Home.tsx
Normal file
@ -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 { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Box, Space, Stack } from "@mantine/core";
|
||||
import { LangKeys } from "@constants/lang/LangKeys";
|
||||
import { CenteredLayout } from "@templates/CenteredLayout";
|
||||
import { Heading } from "@atoms/Typography";
|
||||
import Logo from "@assets/logo.svg";
|
||||
import { useAccountInfo } from "@src/hooks/storage/useGetAccountInfo";
|
||||
import { ROUTES } from "@constants/routes";
|
||||
import { showNotification } from "@mantine/notifications";
|
||||
|
||||
export function Home() {
|
||||
const { data: accountInfo, isSuccess, isError } = useAccountInfo();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (isSuccess) {
|
||||
if (!accountInfo) {
|
||||
setTimeout(() => navigate(ROUTES.Welcome, { replace: true }), 1000);
|
||||
} else {
|
||||
setTimeout(() => navigate(ROUTES.Login, { replace: true }), 1000);
|
||||
}
|
||||
} else if (isError) {
|
||||
showNotification({
|
||||
color: "red",
|
||||
title: "Unable to load account",
|
||||
message: "Failed to load account details",
|
||||
});
|
||||
}
|
||||
}, [isSuccess, isError]);
|
||||
|
||||
return (
|
||||
<CenteredLayout>
|
||||
<Stack align="center" justify="center" sx={{ flex: 1 }}>
|
||||
<Stack>
|
||||
<Box component="img" src={Logo} alt="Haveno" />
|
||||
<Heading
|
||||
order={2}
|
||||
stringId={LangKeys.AppHeading2}
|
||||
sx={{ fontWeight: 500 }}
|
||||
>
|
||||
Monero based decentralized exchange
|
||||
</Heading>
|
||||
</Stack>
|
||||
<Space h="lg" />
|
||||
</Stack>
|
||||
</CenteredLayout>
|
||||
);
|
||||
}
|
17
packages/renderer/src/pages/Home/index.ts
Normal file
17
packages/renderer/src/pages/Home/index.ts
Normal file
@ -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 "./Home";
|
95
packages/renderer/src/pages/Login/Login.tsx
Normal file
95
packages/renderer/src/pages/Login/Login.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
// =============================================================================
|
||||
// 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 Joi from "joi";
|
||||
import { joiResolver, useForm } from "@mantine/form";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Container, Group, Space, Stack } from "@mantine/core";
|
||||
import { showNotification } from "@mantine/notifications";
|
||||
import { CenteredLayout } from "@templates/CenteredLayout";
|
||||
import { BodyText, Heading } from "@atoms/Typography";
|
||||
import { ROUTES } from "@constants/routes";
|
||||
import { useLogin } from "@hooks/session/useLogin";
|
||||
import { Button } from "@atoms/Buttons";
|
||||
import { TextInput } from "@atoms/TextInput";
|
||||
import { CONTENT_MAX_WIDTH } from "./_constants";
|
||||
|
||||
export function Login() {
|
||||
const { mutate: login } = useLogin();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { getInputProps, onSubmit } = useForm<FormValues>({
|
||||
schema: joiResolver(schema),
|
||||
initialValues: {
|
||||
password: "",
|
||||
},
|
||||
});
|
||||
|
||||
const handleSubmit = (values: FormValues) => {
|
||||
login(values, {
|
||||
onSuccess: () => {
|
||||
navigate(ROUTES.AccountPaymentAccounts, { replace: true });
|
||||
},
|
||||
onError: (err) => {
|
||||
showNotification({
|
||||
title: "Login failed",
|
||||
message: err.message,
|
||||
color: "red",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<CenteredLayout showHeader size={CONTENT_MAX_WIDTH}>
|
||||
<Stack align="center" justify="center" sx={{ flex: 1 }}>
|
||||
<form onSubmit={onSubmit(handleSubmit)}>
|
||||
<Stack>
|
||||
<Container>
|
||||
<Heading order={1}>Login to Haveno</Heading>
|
||||
</Container>
|
||||
<BodyText size="lg">
|
||||
All your data is stored locally on your machine. Haveno uses
|
||||
solely a password.
|
||||
</BodyText>
|
||||
<Space h="lg" />
|
||||
<TextInput
|
||||
id="password"
|
||||
label="Password"
|
||||
type="password"
|
||||
{...getInputProps("password")}
|
||||
/>
|
||||
<Space h="lg" />
|
||||
<Group position="apart">
|
||||
<Button type="submit">Login</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</Stack>
|
||||
</CenteredLayout>
|
||||
);
|
||||
}
|
||||
|
||||
interface FormValues {
|
||||
password: string;
|
||||
}
|
||||
|
||||
const schema = Joi.object({
|
||||
password: Joi.string().min(6).required().messages({
|
||||
"string.min": "Password too short",
|
||||
"string.empty": "Password can't be empty",
|
||||
}),
|
||||
});
|
@ -14,7 +14,4 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export const WIDTH = 475;
|
||||
|
||||
// The minimum characters that should password field contain.
|
||||
export const MIN_PASSWORD_CHARS = 8;
|
||||
export const CONTENT_MAX_WIDTH = 470;
|
17
packages/renderer/src/pages/Login/index.ts
Normal file
17
packages/renderer/src/pages/Login/index.ts
Normal file
@ -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 "./Login";
|
@ -21,7 +21,7 @@ import { ConnectionProgress } from "@atoms/ConnectionProgress";
|
||||
import { Heading } from "@atoms/Typography";
|
||||
import Logo from "@assets/logo.svg";
|
||||
|
||||
export function Home() {
|
||||
export function ConnectingMonero() {
|
||||
return (
|
||||
<CenteredLayout>
|
||||
<Stack align="center" justify="center" sx={{ flex: 1 }}>
|
105
packages/renderer/src/pages/Onboarding/CreateAccount.tsx
Normal file
105
packages/renderer/src/pages/Onboarding/CreateAccount.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
// =============================================================================
|
||||
// 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, 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 { SetPassword } from "@organisms/SetPassword";
|
||||
import { SetPrimaryFiat } from "@organisms/SetPrimaryFiat";
|
||||
import { SelectMoneroNode } from "@organisms/SelectMoneroNode";
|
||||
import { ReadyToUse } from "@molecules/ReadyToUse";
|
||||
|
||||
enum Steps {
|
||||
CreatePassword = "CreatePassword",
|
||||
SetFiat = "SetFiat",
|
||||
SelectNode = "SelectNode",
|
||||
Completed = "Completed",
|
||||
}
|
||||
|
||||
export function CreateAccount() {
|
||||
const [step, setStep] = useState<Steps>(Steps.CreatePassword);
|
||||
const [password, setPassword] = useState("");
|
||||
const [fiat, setFiat] = useState("");
|
||||
const navigate = useNavigate();
|
||||
const { mutate: createAccount } = useCreateAccount();
|
||||
|
||||
const handleSetPassword = (value: string) => {
|
||||
setPassword(value);
|
||||
setStep(Steps.SetFiat);
|
||||
};
|
||||
|
||||
const handleSetFiat = (value: string) => {
|
||||
setFiat(value);
|
||||
setStep(Steps.SelectNode);
|
||||
};
|
||||
|
||||
const handleCreateAccount = (moneroNode: {
|
||||
url: string;
|
||||
password: string;
|
||||
}) => {
|
||||
createAccount(
|
||||
{
|
||||
moneroNode: moneroNode.url,
|
||||
password,
|
||||
primaryFiat: fiat,
|
||||
},
|
||||
{
|
||||
onSuccess: () => setStep(Steps.Completed),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<CenteredLayout showHeader>
|
||||
<Stack align="center" justify="center" sx={{ flex: 1 }}>
|
||||
<Container size={CONTENT_MAX_WIDTH}>
|
||||
{step === Steps.CreatePassword && (
|
||||
<SetPassword
|
||||
value={password}
|
||||
onGoBack={() => navigate(ROUTES.Welcome)}
|
||||
onNext={handleSetPassword}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === Steps.SetFiat && (
|
||||
<SetPrimaryFiat
|
||||
value={fiat}
|
||||
onGoBack={() => setStep(Steps.CreatePassword)}
|
||||
onNext={handleSetFiat}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === Steps.SelectNode && (
|
||||
<SelectMoneroNode
|
||||
onGoBack={() => setStep(Steps.SetFiat)}
|
||||
onNext={handleCreateAccount}
|
||||
/>
|
||||
)}
|
||||
|
||||
{step === Steps.Completed && (
|
||||
<ReadyToUse
|
||||
onSubmit={() => navigate(ROUTES.AccountPaymentAccounts)}
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
</Stack>
|
||||
</CenteredLayout>
|
||||
);
|
||||
}
|
@ -20,6 +20,8 @@ import { CenteredLayout } from "@templates/CenteredLayout";
|
||||
import { Button } from "@atoms/Buttons";
|
||||
import { BodyText, Heading } from "@atoms/Typography";
|
||||
import { CONTENT_MAX_WIDTH } from "./_constants";
|
||||
import { Link } from "react-router-dom";
|
||||
import { ROUTES } from "@constants/routes";
|
||||
|
||||
export function Welcome() {
|
||||
return (
|
||||
@ -40,8 +42,12 @@ export function Welcome() {
|
||||
</Stack>
|
||||
<Space h="lg" />
|
||||
<Group position="left" sx={{ width: CONTENT_MAX_WIDTH }}>
|
||||
<Button>Setup Account</Button>
|
||||
<Button flavor="neutral">Upload Backup</Button>
|
||||
<Button component={Link} to={ROUTES.CreateAccount}>
|
||||
Setup Account
|
||||
</Button>
|
||||
<Button flavor="neutral" component={Link} to={ROUTES.RestoreBackup}>
|
||||
Upload Backup
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</CenteredLayout>
|
||||
|
@ -14,5 +14,5 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export * from "./Home";
|
||||
export * from "./Welcome";
|
||||
export * from "./CreateAccount";
|
||||
|
@ -1,64 +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 { FormEvent } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { ROUTES } from "@constants/routes";
|
||||
|
||||
export function Page2() {
|
||||
const txtUserRef = useRef<HTMLInputElement>(null);
|
||||
const txtPasswdRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleSubmit = (ev: FormEvent<HTMLFormElement>) => {
|
||||
ev.preventDefault();
|
||||
if (!txtUserRef.current || !txtPasswdRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.electronStore
|
||||
.storeUserinfo({
|
||||
username: txtUserRef.current.value,
|
||||
password: txtPasswdRef.current.value,
|
||||
})
|
||||
.then((value) => {
|
||||
console.log({ value });
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (txtUserRef.current) {
|
||||
window.electronStore.storeUserinfo().then((value) => {
|
||||
if (txtUserRef.current) {
|
||||
txtUserRef.current.value = value.username;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [txtUserRef.current]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Page 2</h1>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input ref={txtUserRef} type="text" placeholder="username" />
|
||||
<br />
|
||||
<input ref={txtPasswdRef} type="password" placeholder="password" />
|
||||
<button type="submit">Let me in</button>
|
||||
</form>
|
||||
<Link to={ROUTES.Home}>Go Home</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -17,43 +17,6 @@
|
||||
import type { MantineThemeOverride } from "@mantine/core";
|
||||
|
||||
export const themeOverride: MantineThemeOverride = {
|
||||
fontFamily: "Inter, sans-serif",
|
||||
fontSizes: {
|
||||
xl: 18,
|
||||
lg: 16,
|
||||
md: 14,
|
||||
sm: 12,
|
||||
xs: 10,
|
||||
},
|
||||
headings: {
|
||||
fontFamily: "Inter, sans-serif",
|
||||
fontWeight: 600,
|
||||
sizes: {
|
||||
h1: {
|
||||
fontSize: "2.25rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
h2: {
|
||||
fontSize: "1.25rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
h3: {
|
||||
fontSize: "1.125rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
h4: {
|
||||
fontSize: "0.875rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
h5: {
|
||||
fontSize: "0.75rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
},
|
||||
},
|
||||
other: {
|
||||
buttonHeight: 48,
|
||||
},
|
||||
colors: {
|
||||
blue: [
|
||||
"#E7F1FE",
|
||||
@ -116,4 +79,43 @@ export const themeOverride: MantineThemeOverride = {
|
||||
"#fff",
|
||||
],
|
||||
},
|
||||
defaultRadius: 10,
|
||||
fontFamily: "Inter, sans-serif",
|
||||
fontSizes: {
|
||||
xl: 18,
|
||||
lg: 16,
|
||||
md: 14,
|
||||
sm: 12,
|
||||
xs: 10,
|
||||
},
|
||||
headings: {
|
||||
fontFamily: "Inter, sans-serif",
|
||||
fontWeight: 600,
|
||||
sizes: {
|
||||
h1: {
|
||||
fontSize: "2.25rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
h2: {
|
||||
fontSize: "1.25rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
h3: {
|
||||
fontSize: "1.125rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
h4: {
|
||||
fontSize: "0.875rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
h5: {
|
||||
fontSize: "0.75rem",
|
||||
lineHeight: 1.25,
|
||||
},
|
||||
},
|
||||
},
|
||||
other: {
|
||||
buttonHeight: 48,
|
||||
contentWidthMd: "30rem",
|
||||
},
|
||||
};
|
||||
|
@ -14,4 +14,4 @@
|
||||
// limitations under the License.
|
||||
// =============================================================================
|
||||
|
||||
export * from "./store";
|
||||
export * from "../../../main/src/types/store";
|
||||
|
@ -1,64 +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 { Schema } from "electron-store";
|
||||
|
||||
export enum StoreKeys {
|
||||
UserInfo = "UserInfo",
|
||||
Permissions = "Permissions",
|
||||
}
|
||||
|
||||
// TS types for StoreSchema
|
||||
export interface IStoreSchema {
|
||||
[StoreKeys.UserInfo]: IUserInfo;
|
||||
[StoreKeys.Permissions]: Array<IUserPermission>;
|
||||
}
|
||||
|
||||
export interface IUserInfo {
|
||||
username: string;
|
||||
password: Buffer;
|
||||
}
|
||||
|
||||
export type UserInfoInputType = Omit<IUserInfo, "password"> & {
|
||||
password: string;
|
||||
};
|
||||
|
||||
export interface IUserPermission {
|
||||
name: string;
|
||||
}
|
||||
|
||||
// this schema is used by electron-store
|
||||
// must mirror IStoreSchema
|
||||
export const StoreSchema: Schema<IStoreSchema> = {
|
||||
[StoreKeys.UserInfo]: {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {
|
||||
username: { type: "string" },
|
||||
},
|
||||
},
|
||||
[StoreKeys.Permissions]: {
|
||||
type: "array",
|
||||
default: [],
|
||||
items: {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user