Skip to content

Commit

Permalink
feat: wallet password wizard (#146)
Browse files Browse the repository at this point in the history
* Add Icons and statuses to MiningBox, initialize WalletPasswordWizard

* Password Wizard
  • Loading branch information
tomaszantas authored May 10, 2022
1 parent d7f3d91 commit 9c9d264
Show file tree
Hide file tree
Showing 23 changed files with 444 additions and 26 deletions.
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

0 comments on commit 9c9d264

Please sign in to comment.