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: update create wallet flow #62

Merged
15 commits merged into from
Feb 8, 2022
1 change: 1 addition & 0 deletions public/sprite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
224 changes: 144 additions & 80 deletions src/components/CreateWallet.jsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,12 @@
import React, { useState } from 'react'
import * as rb from 'react-bootstrap'
import { Link, useNavigate } from 'react-router-dom'
import PageTitle from './PageTitle'
import { serialize, walletDisplayName } from '../utils'
import { useCurrentWallet } from '../context/WalletContext'

export default function CreateWallet({ currentWallet, startWallet }) {
const WalletCreationForm = ({ createWallet, isCreating }) => {
const [validated, setValidated] = useState(false)
const [alert, setAlert] = useState(null)
const [isCreating, setIsCreating] = useState(false)
const [createdWallet, setCreatedWallet] = useState(null)

const createWallet = async (name, password) => {
const walletname = name.endsWith('.jmdat') ? name : `${name}.jmdat`
setAlert(null)
setIsCreating(true)
try {
const wallettype = 'sw-fb'
const res = await fetch(`/api/v1/wallet/create`, {
method: 'POST',
body: JSON.stringify({
password,
walletname,
wallettype,
}),
})

if (res.ok) {
const { seedphrase, token, walletname: createdWallet } = await res.json()
setAlert({ variant: 'success', seedphrase, password })
setCreatedWallet(createdWallet)
startWallet(createdWallet, token)
} else {
const { message } = await res.json()
setAlert({ variant: 'danger', message })
}
} catch (e) {
setAlert({ variant: 'danger', message: e.message })
} finally {
setIsCreating(false)
}
}

const onSubmit = (e) => {
e.preventDefault()
Expand All @@ -52,57 +21,26 @@ export default function CreateWallet({ currentWallet, startWallet }) {
}
}

const isCreated = currentWallet && currentWallet.name === createdWallet
const canCreate = !currentWallet && !isCreated

if (isCreated && alert?.seedphrase) {
return (
<rb.Alert variant="success">
<rb.Alert.Heading>Wallet created succesfully!</rb.Alert.Heading>
<p className="d-flex align-items-center my-3">
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
fill="currentColor"
className="me-3"
viewBox="0 0 16 16"
>
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z" />
</svg>
<span>
Please write down your seed phrase and password!
<br />
Without this information you will not be able to access and recover your wallet!
</span>
</p>
<p>
Seed phrase: <strong>{alert.seedphrase}</strong>
</p>
<p className="mb-0">
Password: <strong>{alert.password}</strong>
</p>
</rb.Alert>
)
} else {
return canCreate ? (
return (
<>
<rb.Form onSubmit={onSubmit} validated={validated} noValidate>
<h1>Create Wallet</h1>
{alert && <rb.Alert variant={alert.variant}>{alert.message}</rb.Alert>}
<rb.Form.Group className="mb-3" controlId="walletName">
<rb.Form.Group className="mb-4" controlId="walletName">
<rb.Form.Label>Wallet Name</rb.Form.Label>
<rb.Form.Control name="wallet" style={{ maxWidth: '20em' }} required />
<rb.Form.Control name="wallet" placeholder="Your wallet..." disabled={isCreating} required />
<rb.Form.Control.Feedback>Looks good!</rb.Form.Control.Feedback>
<rb.Form.Control.Feedback type="invalid">Please set a wallet name.</rb.Form.Control.Feedback>
</rb.Form.Group>
<rb.Form.Group className="mb-3" controlId="password">
<rb.Form.Group className="mb-4" controlId="password">
<rb.Form.Label>Password</rb.Form.Label>
<rb.Form.Control
name="password"
type="password"
placeholder="Choose a secure password..."
This conversation was marked as resolved.
Show resolved Hide resolved
disabled={isCreating}
autoComplete="new-password"
style={{ maxWidth: '20em' }}
required
/>
<rb.Form.Control.Feedback>Looks good!</rb.Form.Control.Feedback>
<rb.Form.Control.Feedback type="invalid">Please set a password.</rb.Form.Control.Feedback>
</rb.Form.Group>
<rb.Button variant="dark" type="submit" disabled={isCreating}>
Expand All @@ -116,10 +54,136 @@ export default function CreateWallet({ currentWallet, startWallet }) {
)}
</rb.Button>
</rb.Form>
) : (
<rb.Alert variant="warning">
Currently <strong>{walletDisplayName(currentWallet.name)}</strong> is active. You need to lock it first.
</rb.Alert>
)
</>
)
}

const WalletCreationConfirmation = ({ createdWallet, walletConfirmed }) => {
const [userConfirmed, setUserConfirmed] = useState(false)

const onSwitch = (e) => {
setUserConfirmed(e.target.checked)
}

return (
<div>
<p className="mb-4">
<div>Wallet Name</div>
<div className="fs-4">{walletDisplayName(createdWallet.name)}</div>
</p>
<p className="mb-4">
<div className="mb-2">Seedphrase</div>
<Seedphrase seedphrase={createdWallet.seedphrase} />
</p>
<p className="mb-4">
<div>Password</div>
<div className="fs-4">{createdWallet.password}</div>
</p>
<p className="mb-4">
<rb.Form.Switch onChange={onSwitch} label="I've written down the information above." />
</p>
<rb.Button
variant="dark"
type="submit"
disabled={!userConfirmed}
onClick={() => userConfirmed && walletConfirmed()}
>
Fund wallet
</rb.Button>
</div>
)
}

const Seedphrase = ({ seedphrase }) => {
return (
<div className="seedphrase d-flex flex-wrap">
{seedphrase.split(' ').map((seedWord, index) => (
<div key={index} className="d-flex py-2 ps-2 pe-3">
<span className="seedword-index text-secondary text-end">{index + 1}</span>
<span className="text-secondary">.&nbsp;</span>
<span>{seedWord}</span>
</div>
))}
</div>
)
}

export default function CreateWallet({ startWallet }) {
const currentWallet = useCurrentWallet()
const navigate = useNavigate()

const [alert, setAlert] = useState(null)
const [isCreating, setIsCreating] = useState(false)
const [createdWallet, setCreatedWallet] = useState(null)

const createWallet = async (name, password) => {
const walletname = name.endsWith('.jmdat') ? name : `${name}.jmdat`

setAlert(null)
setIsCreating(true)

try {
const wallettype = 'sw-fb'
const res = await fetch(`/api/v1/wallet/create`, {
method: 'POST',
body: JSON.stringify({
password,
walletname,
wallettype,
}),
})

if (res.ok) {
const { seedphrase, token, walletname: name } = await res.json()
setCreatedWallet({ name, seedphrase, password, token })
} else {
const { message } = await res.json()
setAlert({ variant: 'danger', message })
}
} catch (e) {
setAlert({ variant: 'danger', message: e.message })
} finally {
setIsCreating(false)
}
}

const walletConfirmed = () => {
if (createWallet.name && createdWallet.token) {
startWallet(createdWallet.name, createdWallet.token)
navigate('/wallet')
} else {
setAlert({ variant: 'danger', message: 'Wallet confirmation failed.' })
}
}

const isCreated = createdWallet?.name && createdWallet?.seedphrase && createdWallet?.password
const canCreate = !currentWallet && !isCreated

return (
<rb.Row className="create-wallet justify-content-center">
<rb.Col md={10} lg={8} xl={6}>
{isCreated ? (
<PageTitle
title="Wallet created successfully!"
subtitle="Please write down your seed phrase and password! Without this information you will not be able to access and recover your wallet!"
success
/>
) : (
<PageTitle title="Create Wallet" />
)}
{alert && <rb.Alert variant={alert.variant}>{alert.message}</rb.Alert>}
{canCreate && <WalletCreationForm createWallet={createWallet} isCreating={isCreating} />}
{isCreated && <WalletCreationConfirmation createdWallet={createdWallet} walletConfirmed={walletConfirmed} />}
{!canCreate && !isCreated && (
<rb.Alert variant="warning">
Currently <strong>{walletDisplayName(currentWallet.name)}</strong> is active. You need to lock it first.{' '}
<Link to="/" className="alert-link">
Go back
</Link>
.
</rb.Alert>
)}
</rb.Col>
</rb.Row>
)
}
1 change: 1 addition & 0 deletions src/components/CurrentWalletMagic.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const WalletHeader = ({ name, balance, unit, showBalance }) => {
const PrivacyLevels = ({ accounts }) => {
const sortedAccounts = accounts.sort((lhs, rhs) => lhs.account - rhs.account).reverse()
const numAccounts = sortedAccounts.length

return (
<div className="d-flex justify-content-center">
<div className="d-flex flex-column align-items-start" style={{ gap: '1rem' }}>
Expand Down
25 changes: 25 additions & 0 deletions src/components/PageTitle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react'
import Sprite from './Sprite'

export default function PageTitle({ title, subtitle, success = false }) {
return (
<div className="mb-4">
{success && (
<div
className="d-flex align-items-center justify-content-center mb-2"
style={{
width: '3rem',
height: '3rem',
backgroundColor: 'rgba(39, 174, 96, 1)',
color: 'white',
borderRadius: '50%',
}}
>
<Sprite symbol="checkmark" width="24" height="30" />
</div>
)}
<h2>{title}</h2>
{subtitle && <p className="text-secondary">{subtitle}</p>}
</div>
)
}
59 changes: 50 additions & 9 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ main {
display: none;
}

/* Start Navbar Styles */
/* Navbar Styles */

#mainNav .nav-link {
font-weight: 600 !important;
Expand Down Expand Up @@ -355,7 +355,55 @@ main {
}
}

/* End Navbar Styles */
/* Wallet Creation Styles */

.seedphrase {
gap: 0.3rem;

font-family: Inter;
font-weight: 400;
font-feature-settings: 'tnum' on, 'lnum' on, 'zero' on, 'case' on;
-moz-font-feature-settings: 'tnum' on, 'lnum' on, 'zero' on, 'case' on;
-ms-font-feature-settings: 'tnum' on, 'lnum' on, 'zero' on, 'case' on;
-webkit-font-feature-settings: 'tnum' on, 'lnum' on, 'zero' on, 'case' on;
-o-font-feature-settings: 'tnum' on, 'lnum' on, 'zero' on, 'case' on;
}

.seedphrase > div {
background-color: rgba(244, 244, 244, 1);
width: 8rem;
}

.seedword-index {
width: 2ch;
}

:root[data-theme='dark'] .seedphrase > div {
background-color: var(--bs-gray-800);
}

.create-wallet form input {
height: 3.5rem;
width: 100%;
}

.create-wallet button {
height: 3rem;
width: 100%;
}

/* Boostrap overrides */

h2 {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 5px;
}

.btn:focus:not(:focus-visible) {
outline: 0;
box-shadow: none;
}

/* Start Privacy Level Styles */

Expand Down Expand Up @@ -390,13 +438,6 @@ main {
border-color: rgba(222, 222, 222, 1);
}

/* End Privacy Level Styles */

.btn:focus:not(:focus-visible) {
outline: 0;
box-shadow: none;
}

/* Theme overrides */
:root[data-theme='dark'] {
--bs-body-color-rgb: var(--bs-white);
Expand Down