Skip to content
This repository was archived by the owner on Jan 6, 2023. It is now read-only.

Commit 6e93e58

Browse files
committed
feat: Better UX of payment details form, update status icon
1 parent d2fac88 commit 6e93e58

File tree

7 files changed

+204
-123
lines changed

7 files changed

+204
-123
lines changed

components/maker/index.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import Status from './status'
55
import Notification from './notification'
66
import { SidebarButton } from './styled'
77

8-
98
export default {
109
SidebarButton,
1110
PaymentDetails,
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react'
2+
import { Flex, Box, Type } from 'blockstack-ui'
3+
import { MakerCardHeader, MakerCardText, MakerButton, MakerField } from '../styled'
4+
5+
export const PaymentContainer = ({ children }) => (
6+
<Flex>
7+
<Box width={1} mt={0} as="form">
8+
{children}
9+
</Box>
10+
</Flex>
11+
)
12+
13+
export const PaymentHeader = MakerCardHeader
14+
15+
export const PaymentDescription = () => (
16+
<MakerCardText mb={5} mt={0}>
17+
This is where you will receive your App Mining payments.
18+
Currently, payments are made in Bitcoin (BTC). Payments will be made
19+
in Stacks (STX) in the future.
20+
</MakerCardText>
21+
)
22+
23+
export const PaymentHelpText = () => (
24+
<Type.p fontSize={12} mt={0} display="block">
25+
{"Don't"} have a Stacks address? <a href="https://wallet.blockstack.org" target="_blank" rel="noopener noreferrer">Download the Stacks wallet to get one</a>
26+
</Type.p>
27+
)
28+
29+
export const PaymentBtcField = props => (
30+
<MakerField
31+
name="btcAddress"
32+
label="Bitcoin Address"
33+
placeholder="Enter a Bitcoin address"
34+
{...props}
35+
/>
36+
)
37+
38+
export const PaymentStxField = props => (
39+
<MakerField
40+
name="stacksAddress"
41+
label="Stacks Address"
42+
placeholder="Enter a Stacks address"
43+
{...props}
44+
/>
45+
)
46+
47+
export const PaymentButton = ({ children, ...props }) => (
48+
<MakerButton type="button" mt={4} {...props}>
49+
{children}
50+
</MakerButton>
51+
)
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { address, networks } from 'bitcoinjs-lib'
2+
import * as c32Check from 'c32check'
3+
import memoize from 'lodash/memoize'
4+
5+
export const validateBTC = memoize(addr => {
6+
try {
7+
address.toOutputScript(addr, networks.bitcoin)
8+
return true
9+
} catch (error) {
10+
return false
11+
}
12+
})
13+
14+
export const validateSTX = memoize(addr => {
15+
try {
16+
c32Check.c32addressDecode(addr)
17+
return true
18+
} catch (error) {
19+
return false
20+
}
21+
})
22+
23+
export const savePaymentDetails = async ({ apiServer, appId, jwt, btcAddress, stxAddress }) => {
24+
const response = await fetch(`${apiServer}/api/maker/apps?appId=${appId}`, {
25+
method: 'POST',
26+
headers: new Headers({
27+
'Content-Type': 'application/json',
28+
authorization: `Bearer ${jwt}`
29+
}),
30+
body: JSON.stringify({
31+
BTCAddress: btcAddress,
32+
stacksAddress: stxAddress
33+
})
34+
})
35+
await response.json()
36+
}

components/maker/payment/index.js

Lines changed: 58 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,75 @@
11
import React, { useState } from 'react'
2-
import { Flex, Box, Type } from 'blockstack-ui'
3-
import { address, networks } from 'bitcoinjs-lib'
4-
import * as c32Check from 'c32check'
5-
import Notification from '../notification'
6-
2+
import { validateBTC, validateSTX, savePaymentDetails } from './helpers'
73
import {
8-
MakerCardHeader,
9-
MakerCardText,
10-
MakerButton,
11-
MakerField
12-
} from '../styled'
13-
14-
const validateBTC = (addr) => {
15-
try {
16-
address.toOutputScript(addr, networks.bitcoin)
17-
return true
18-
} catch (error) {
19-
return false
20-
}
21-
}
22-
23-
const validateSTX = (addr) => {
24-
try {
25-
c32Check.c32addressDecode(addr)
26-
return true
27-
} catch (error) {
28-
return false
29-
}
30-
}
4+
PaymentContainer,
5+
PaymentHeader,
6+
PaymentDescription,
7+
PaymentHelpText,
8+
PaymentBtcField,
9+
PaymentStxField,
10+
PaymentButton
11+
} from './content'
3112

32-
const PaymentDetails = ({ app, apiServer, accessToken, user }) => {
13+
const PaymentDetails = ({ app, apiServer, accessToken, user, onPaymentDetailsComplete }) => {
3314
const [btcAddress, setBTCAddress] = useState(app.BTCAddress)
3415
const [stxAddress, setSTXAddress] = useState(app.stacksAddress)
35-
const [showNotification, setShowNotification] = useState(false)
36-
const [btcValid, setBtcValid] = useState(true)
37-
const [stxValid, setStxValid] = useState(true)
3816
const [saving, setSaving] = useState(false)
17+
const [hasAttemptedSaved, setHasAttemptedSaved] = useState(false)
18+
const [savedValues, setSavedValue] = useState({ btcAddress, stxAddress })
3919

40-
const notify = () => {
41-
setShowNotification(true)
42-
setTimeout(() => {
43-
setShowNotification(false)
44-
}, 10000)
45-
}
20+
const isSaved = (
21+
btcAddress === savedValues.btcAddress &&
22+
btcAddress !== '' &&
23+
stxAddress === savedValues.stxAddress &&
24+
stxAddress !== ''
25+
)
4626

4727
const save = async () => {
48-
let isValid = true
49-
if (!validateBTC(btcAddress)) {
50-
isValid = false
51-
setBtcValid(false)
52-
}
53-
if (!validateSTX(stxAddress)) {
54-
isValid = false
55-
setStxValid(false)
56-
}
57-
if (!isValid) {
58-
return
59-
} else {
60-
setBtcValid(true)
61-
setStxValid(true)
62-
}
28+
setHasAttemptedSaved(true)
29+
if (!validateBTC(btcAddress) && !validateSTX(stxAddress)) return
6330
setSaving(true)
64-
console.log(stxAddress)
65-
const response = await fetch(`${apiServer}/api/maker/apps?appId=${app.id}`, {
66-
method: 'POST',
67-
headers: new Headers({
68-
'Content-Type': 'application/json',
69-
authorization: `Bearer ${user.jwt}`
70-
}),
71-
body: JSON.stringify({
72-
BTCAddress: btcAddress,
73-
stacksAddress: stxAddress
74-
})
75-
})
76-
await response.json()
77-
notify()
31+
await savePaymentDetails({ apiServer, appId: app.id, jwt: user.jwt, btcAddress, stxAddress })
7832
setSaving(false)
33+
setSavedValue({ btcAddress, stxAddress })
34+
onPaymentDetailsComplete()
35+
}
36+
37+
const buttonText = () => {
38+
if (saving) return 'Saving…'
39+
if (isSaved) return 'Saved'
40+
return 'Save'
41+
}
42+
43+
const createInputError = ({ validateFn, currencySymbol }) => addressHash => {
44+
if (!hasAttemptedSaved) return null
45+
if (!validateFn(addressHash)) return `Please enter a valid ${currencySymbol} address`
46+
return null
7947
}
48+
const getBtcError = createInputError({ validateFn: validateBTC, currencySymbol: 'BTC' })
49+
const getStxError = createInputError({ validateFn: validateSTX, currencySymbol: 'STX' })
8050

8151
return (
82-
<Flex>
83-
<Box width={1} mt={0}>
84-
<MakerCardHeader>Payment details</MakerCardHeader>
85-
{showNotification && <Notification message="Thanks! Your payment details have been updated." />}
86-
<MakerCardText mb={5} mt={0}>
87-
This is where you will receive your App Mining payments.
88-
Currently, payments are made in Bitcoin (BTC). Payments will be made
89-
in Stacks (STX) in the future.
90-
</MakerCardText>
91-
<MakerField
92-
name="btcAddress"
93-
label="Bitcoin Address"
94-
placeholder="Enter a Bitcoin address"
95-
onChange={(e) => setBTCAddress(e.target.value)}
96-
value={btcAddress || ''}
97-
error={!btcValid ? 'Please enter a valid BTC address' : null}
98-
/>
99-
<MakerField
100-
name="stacksAddress"
101-
label="Stacks Address"
102-
placeholder="Enter a Stacks address"
103-
onChange={(e) => setSTXAddress(e.target.value)}
104-
value={stxAddress || ''}
105-
error={!stxValid ? 'Please enter a valid STX address' : null}
106-
/>
107-
<Type.p fontSize={12} mt={0} display="block">
108-
{"Don't"} have a Stacks address? <a href="https://wallet.blockstack.org" target="_blank" rel="noopener noreferrer">Download the Stacks wallet to get one</a>
109-
</Type.p>
110-
<MakerButton mt={4} disabled={saving} onClick={() => save({ btcAddress, stxAddress, apiServer, accessToken })}>
111-
{saving ? 'Saving...' : 'Save'}
112-
</MakerButton>
113-
</Box>
114-
</Flex>
52+
<PaymentContainer>
53+
<PaymentHeader>Payment details</PaymentHeader>
54+
<PaymentDescription />
55+
<PaymentBtcField
56+
onChange={e => setBTCAddress(e.target.value)}
57+
value={btcAddress || ''}
58+
error={getBtcError(btcAddress)}
59+
/>
60+
<PaymentStxField
61+
onChange={e => setSTXAddress(e.target.value)}
62+
value={stxAddress || ''}
63+
error={getStxError(stxAddress)}
64+
/>
65+
<PaymentHelpText/>
66+
<PaymentButton
67+
disabled={isSaved || saving}
68+
onClick={() => save({ btcAddress, stxAddress, apiServer, accessToken })}
69+
>
70+
{buttonText()}
71+
</PaymentButton>
72+
</PaymentContainer>
11573
)
11674
}
11775

components/maker/status/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const AppMiningIncomplete = () => (
6060
</MakerCardText>
6161
)
6262

63-
const Status = ({ app }) => {
63+
const Status = ({ app, status }) => {
6464
const isReady = isMiningReady(app)
6565

6666
return (
@@ -70,8 +70,8 @@ const Status = ({ app }) => {
7070

7171
{isReady ? <AppMiningComplete /> : <AppMiningIncomplete/>}
7272

73-
<ItemToCompleteField label="Payment details" status={hasPaymentDetails(app)} />
74-
<ItemToCompleteField label="Identity Verification" status={app.hasCollectedKYC} />
73+
<ItemToCompleteField label="Payment details" status={status.paymentDetailsComplete || hasPaymentDetails(app)} />
74+
<ItemToCompleteField label="Identity Verification" status={status.kycComplete || app.hasCollectedKYC} />
7575
<ItemToCompleteField label="Tax Documents" status={app.isKYCVerified} />
7676
<ItemToCompleteField label="SEC Participation Agreement" status={app.hasAcceptedSECTerms} />
7777
</Box>

pages/all.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@ import { AppsList } from '@components/list/apps'
44
import Modal from '@containers/modals/app'
55
import Head from '@containers/head'
66

7-
class AllAppsPage extends React.PureComponent {
8-
9-
render() {
10-
return (
11-
<Page>
12-
<Head title='Popular decentralized apps' />
13-
<Page.Section flexDirection="column" px>
14-
<AppsList image="g3" single title="Popular decentralized apps" />
15-
</Page.Section>
16-
<Modal />
17-
</Page>
18-
)
19-
}
20-
}
7+
const AllAppsPage = () => (
8+
<Page>
9+
<Head title='Popular decentralized apps' />
10+
<Page.Section flexDirection="column" px>
11+
<AppsList image="g3" single title="Popular decentralized apps" />
12+
</Page.Section>
13+
<Modal />
14+
</Page>
15+
)
2116

2217
export default AllAppsPage

0 commit comments

Comments
 (0)