Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: wallet password wizard #146

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions applications/launchpad/gui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"deepmerge": "^4.2.2",
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
"react-hook-form": "^7.30.0",
"react-redux": "^8.0.0",
"react-scripts": "5.0.0",
"react-spring": "^9.4.5-beta.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { render, screen } from '@testing-library/react'
import { ThemeProvider } from 'styled-components'

import Callout from '.'
import themes from '../../styles/themes'

describe('Callout', () => {
it('should render without crashing when children is a string', () => {
const testText = 'The callout test text'
render(
<ThemeProvider theme={themes.light}>
<Callout>{testText}</Callout>
</ThemeProvider>,
)

const el = screen.getByText(testText)
expect(el).toBeInTheDocument()
})

it('should render without crashing when children is a React Node', () => {
const testId = 'the-callout-test-id'
const testText = 'The callout test text'
const testCmp = <span data-testid={testId}>{testText}</span>
render(
<ThemeProvider theme={themes.light}>
<Callout>{testCmp}</Callout>
</ThemeProvider>,
)

const elText = screen.getByText(testText)
expect(elText).toBeInTheDocument()

const elCmp = screen.getByTestId(testId)
expect(elCmp).toBeInTheDocument()
})

it('should render without crashing when inverted prop is used', () => {
const testText = 'The callout test text'
render(
<ThemeProvider theme={themes.light}>
<Callout inverted={true}>{testText}</Callout>
</ThemeProvider>,
)

const el = screen.getByText(testText)
expect(el).toBeInTheDocument()
})
})
38 changes: 38 additions & 0 deletions applications/launchpad/gui-react/src/components/Callout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Text from '../Text'
import { CalloutIcon, StyledCallout } from './styles'

import { CalloutProps } from './types'

/**
* Callout component that renders styled box with proper icon.
* NOTE: It supports only the `warning` type for now.
*
* @param {CalloutType} [type='warning'] - the callout type/style.
* @param {ReactNode} [icon] - override the icon.
* @param {string | ReactNode} children - the callout content (text or ReactNode).
*/
const Callout = ({
type = 'warning',
icon = '⚠️',
inverted,
children,
}: CalloutProps) => {
let content = children

if (typeof children === 'string') {
content = (
<Text style={{ display: 'inline' }} type='microMedium'>
{children}
</Text>
)
}

return (
<StyledCallout $type={type} $inverted={inverted}>
<CalloutIcon>{icon}</CalloutIcon>
{content}
</StyledCallout>
)
}

export default Callout
23 changes: 23 additions & 0 deletions applications/launchpad/gui-react/src/components/Callout/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styled from 'styled-components'
import { CalloutType } from './types'

export const StyledCallout = styled.div<{
$type: CalloutType
$inverted?: boolean
}>`
padding: ${({ theme }) =>
`${theme.spacingVertical(0.62)} ${theme.spacingHorizontal(0.5)}`};
background: ${({ theme, $inverted }) => {
return $inverted ? theme.inverted.backgroundSecondary : theme.warning
}}};
color: ${({ theme, $inverted }) => {
return $inverted ? theme.inverted.warningText : theme.warningText
}};
border-radius: ${({ theme }) => theme.borderRadius(0.5)};
`

export const CalloutIcon = styled.span`
display: inline-block;
font-size: 12px;
margin-right: ${({ theme }) => theme.spacingHorizontal(0.15)};
`
10 changes: 10 additions & 0 deletions applications/launchpad/gui-react/src/components/Callout/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ReactNode } from 'react'

export type CalloutType = 'warning'

export interface CalloutProps {
type?: CalloutType
icon?: string | ReactNode
inverted?: boolean
children: string | ReactNode
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
UnitsText,
IconWrapper,
} from './styles'
import { ChangeEvent } from 'react'
import { ChangeEvent, forwardRef } from 'react'

/**
* @name Input component
Expand All @@ -26,19 +26,22 @@ import { ChangeEvent } from 'react'
* @prop {CSSProperties} [containerStyle] - styles for input container
*/

const Input = ({
type = 'text',
value,
disabled,
placeholder,
inputIcon,
inputUnits,
onIconClick,
onChange,
testId,
style,
containerStyle,
}: InputProps) => {
const Input = (
{
type = 'text',
value,
disabled,
placeholder,
inputIcon,
inputUnits,
onIconClick,
onChange,
testId,
style,
containerStyle,
}: InputProps,
ref?: React.ForwardedRef<HTMLInputElement>,
) => {
const onChangeTextLocal = (event: ChangeEvent<HTMLInputElement>) => {
if (onChange) {
onChange(event.target.value)
Expand All @@ -55,6 +58,7 @@ const Input = ({
spellCheck={false}
data-testid={testId || 'input-cmp'}
style={style}
ref={ref}
/>
<IconUnitsContainer>
{inputIcon && (
Expand All @@ -75,4 +79,4 @@ const Input = ({
)
}

export default Input
export default forwardRef(Input)
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ const MiningBox = ({
tag: {
text: t.common.phrases.startHere,
},
boxStyle: {
boxShadow: theme.shadow,
borderColor: 'transparent',
},
},
BLOCKED: {
tag: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
import { ReactNode } from 'react'
import { useSelector } from 'react-redux'

import MiningBox from '../MiningBox'
import WalletPasswordWizard from '../../WalletPasswordWizard'

import SvgTariSignet from '../../../styles/Icons/TariSignet'

import t from '../../../locales'

import { useAppDispatch } from '../../../store/hooks'
import { actions as miningActions } from '../../../store/mining'
import { selectTariMiningStatus } from '../../../store/mining/selectors'
import { MiningNodesStatus } from '../../../store/mining/types'
import SvgTariSignet from '../../../styles/Icons/TariSignet'
import WalletPasswordWizard from '../../WalletPasswordWizard'
import MiningBox from '../MiningBox'

const MiningBoxTari = () => {
const dispatch = useAppDispatch()
const tariNodeStatus = useSelector(selectTariMiningStatus)

let boxContent: ReactNode | undefined

if (tariNodeStatus === MiningNodesStatus.SETUP_REQUIRED) {
boxContent = <WalletPasswordWizard />
boxContent = (
<WalletPasswordWizard
submitBtnText={t.mining.setUpTariWalletSubmitBtn}
onSuccess={() =>
dispatch(
miningActions.setNodeStatus({
node: 'tari',
status: MiningNodesStatus.PAUSED,
}),
)
}
/>
)
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import styled from 'styled-components'
export const NodesContainer = styled.div`
display: flex;
flex-wrap: wrap;
align-items: flex-start;

& > div {
margin: ${({ theme }) => theme.spacing(0.34)};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { act, fireEvent, render, screen } from '@testing-library/react'
import { ThemeProvider } from 'styled-components'

import WalletPasswordForm from '.'

import themes from '../../../styles/themes'

describe('WalletPasswordForm', () => {
it('should render without crashing when custom submit button text is given', () => {
const testSubmitBtnText = 'The test text of submit button'
const submitMock = jest.fn()

render(
<ThemeProvider theme={themes.light}>
<WalletPasswordForm
onSubmit={submitMock}
submitBtnText={testSubmitBtnText}
/>
</ThemeProvider>,
)
const el = screen.getByText(testSubmitBtnText)
expect(el).toBeInTheDocument()
})

it('should submit form only if password is set', async () => {
const testPassword = 'pass'
const submitMock = jest.fn()

await act(async () => {
render(
<ThemeProvider theme={themes.light}>
<WalletPasswordForm onSubmit={submitMock} />
</ThemeProvider>,
)
})

const elInput = screen.getByTestId('password-input')
expect(elInput).toBeInTheDocument()

const elSubmitBtn = screen.getByTestId('wallet-password-wizard-submit-btn')
expect(elSubmitBtn).toBeInTheDocument()

// Firstly, the form cannot be submitted if password input is empty:
await act(async () => {
fireEvent.click(elSubmitBtn)
})

expect(submitMock).toHaveBeenCalledTimes(0)

// Now, set the password...
await act(async () => {
fireEvent.input(elInput, { target: { value: testPassword } })
})

// ...check the presence of the password in the input...
expect((elInput as HTMLInputElement).value).toBe(testPassword)

// ...and submit form again:
await act(async () => {
fireEvent.click(elSubmitBtn)
})

expect(submitMock).toHaveBeenCalledWith({ password: testPassword })
expect(submitMock).toHaveBeenCalledTimes(1)
})
})
Loading