mirror of
https://github.com/haveno-dex/haveno-ui.git
synced 2025-06-19 11:54:20 -04: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
109 changed files with 2276 additions and 573 deletions
|
@ -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" },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
32
packages/renderer/src/utils/get-ipc-error.ts
Normal file
32
packages/renderer/src/utils/get-ipc-error.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
// =============================================================================
|
||||
// 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.
|
||||
// =============================================================================
|
||||
|
||||
const REGEX = /\[\[(.+)\]\]/;
|
||||
|
||||
export function getIpcError(err: Error, fallback?: string) {
|
||||
const message = err?.message;
|
||||
if (message && REGEX.test(message)) {
|
||||
const matches = message.match(REGEX);
|
||||
if (matches && matches[1] && matches[1].length) {
|
||||
return matches[1];
|
||||
}
|
||||
const index = message.lastIndexOf("Error:");
|
||||
return index !== -1
|
||||
? message.slice(index + 6)
|
||||
: fallback ?? "Something went wrong";
|
||||
}
|
||||
return fallback ?? "Something went wrong";
|
||||
}
|
57
packages/renderer/src/utils/payment-account.ts
Normal file
57
packages/renderer/src/utils/payment-account.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
// =============================================================================
|
||||
// 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.
|
||||
// =============================================================================
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { FC } from "react";
|
||||
import type { PaymentAccount } from "haveno-ts";
|
||||
import { CurrencyLogos } from "@molecules/PaymentMethodCard/_constants";
|
||||
import type { SupportedCurrencies } from "@molecules/PaymentMethodCard/_types";
|
||||
import { ReactComponent as UnknownLogo } from "@assets/unknown.svg";
|
||||
|
||||
export function getPaymentAccountName(account: PaymentAccount): string {
|
||||
const name = account.getAccountName();
|
||||
try {
|
||||
return name.split(" ")[0];
|
||||
} catch (_ex) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPaymentAccountCode(account: PaymentAccount): string {
|
||||
return (
|
||||
account.getSelectedTradeCurrency()?.getCode() ??
|
||||
account.getPaymentMethod()?.getId() ??
|
||||
getPaymentAccountName(account)
|
||||
);
|
||||
}
|
||||
|
||||
export function getPaymentAccountNumber(account: PaymentAccount): string {
|
||||
// TODO: how to get account id/number for other payment accounts?
|
||||
return (
|
||||
account
|
||||
.getPaymentAccountPayload()
|
||||
?.getCryptoCurrencyAccountPayload()
|
||||
?.getAddress() ?? ""
|
||||
);
|
||||
}
|
||||
|
||||
export function getPaymentAccountLogo(account: PaymentAccount): FC<any> {
|
||||
return (
|
||||
CurrencyLogos[
|
||||
getPaymentAccountCode(account) as unknown as SupportedCurrencies
|
||||
]?.Logo ?? UnknownLogo
|
||||
);
|
||||
}
|
33
packages/renderer/src/utils/session.ts
Normal file
33
packages/renderer/src/utils/session.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
// =============================================================================
|
||||
// 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.
|
||||
// =============================================================================
|
||||
|
||||
const SESSION_KEY = "AUTH_TOKEN";
|
||||
|
||||
export async function createSession(authToken: string) {
|
||||
window.sessionStorage.setItem(SESSION_KEY, authToken);
|
||||
}
|
||||
|
||||
export async function validateSession(): Promise<boolean> {
|
||||
const token = window.sessionStorage.getItem(SESSION_KEY);
|
||||
if (!token) {
|
||||
return false;
|
||||
}
|
||||
return window.electronStore.verifyAuthToken(token);
|
||||
}
|
||||
|
||||
export async function deleteSession() {
|
||||
window.sessionStorage.removeItem(SESSION_KEY);
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
"@assets/*": ["../assets/*"],
|
||||
"@atoms/*": ["components/atoms/*"],
|
||||
"@constants/*": ["constants/*"],
|
||||
"@hooks/*": ["hooks/*"],
|
||||
"@molecules/*": ["components/molecules/*"],
|
||||
"@organisms/*": ["components/organisms/*"],
|
||||
"@pages/*": ["pages/*"],
|
||||
|
|
|
@ -36,6 +36,7 @@ const config = {
|
|||
"@assets/": join(PACKAGE_ROOT, "assets") + "/",
|
||||
"@atoms/": join(PACKAGE_ROOT, "src", "components", "atoms") + "/",
|
||||
"@constants/": join(PACKAGE_ROOT, "src", "constants") + "/",
|
||||
"@hooks/": join(PACKAGE_ROOT, "src", "hooks") + "/",
|
||||
"@molecules/": join(PACKAGE_ROOT, "src", "components", "molecules") + "/",
|
||||
"@organisms/": join(PACKAGE_ROOT, "src", "components", "organisms") + "/",
|
||||
"@pages/": join(PACKAGE_ROOT, "src", "pages") + "/",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue