JimmyMow
7 years ago
committed by
GitHub
23 changed files with 1192 additions and 217 deletions
@ -0,0 +1,160 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
import Isvg from 'react-inlinesvg' |
||||
|
|
||||
|
import x from 'icons/x.svg' |
||||
|
|
||||
|
import styles from './AddChannel.scss' |
||||
|
|
||||
|
const AddChannel = ({ |
||||
|
contactsform, |
||||
|
closeContactsForm, |
||||
|
openSubmitChannelForm, |
||||
|
updateContactFormSearchQuery, |
||||
|
updateManualFormSearchQuery, |
||||
|
setNode, |
||||
|
activeChannelPubkeys, |
||||
|
nonActiveChannelPubkeys, |
||||
|
pendingOpenChannelPubkeys, |
||||
|
filteredNetworkNodes, |
||||
|
loadingChannelPubkeys, |
||||
|
showManualForm, |
||||
|
openManualForm |
||||
|
}) => { |
||||
|
const renderRightSide = (node) => { |
||||
|
if (loadingChannelPubkeys.includes(node.pub_key)) { |
||||
|
return ( |
||||
|
<span className={styles.inactive}> |
||||
|
<div className={styles.loading}> |
||||
|
<div className={styles.spinner} /> |
||||
|
</div> |
||||
|
</span> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
if (activeChannelPubkeys.includes(node.pub_key)) { |
||||
|
return ( |
||||
|
<span className={`${styles.online} ${styles.inactive}`}> |
||||
|
<span>Online</span> |
||||
|
</span> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
if (nonActiveChannelPubkeys.includes(node.pub_key)) { |
||||
|
return ( |
||||
|
<span className={`${styles.offline} ${styles.inactive}`}> |
||||
|
<span>Offline</span> |
||||
|
</span> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
if (pendingOpenChannelPubkeys.includes(node.pub_key)) { |
||||
|
return ( |
||||
|
<span className={`${styles.pending} ${styles.inactive}`}> |
||||
|
<span>Pending</span> |
||||
|
</span> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
if (!node.addresses.length) { |
||||
|
return ( |
||||
|
<span className={`${styles.private} ${styles.inactive}`}> |
||||
|
Private |
||||
|
</span> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<span |
||||
|
className={styles.connect} |
||||
|
onClick={() => { |
||||
|
// set the node public key for the submit form
|
||||
|
setNode(node) |
||||
|
// open the submit form
|
||||
|
openSubmitChannelForm() |
||||
|
}} |
||||
|
> |
||||
|
Connect |
||||
|
</span> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
const searchUpdated = (search) => { |
||||
|
updateContactFormSearchQuery(search) |
||||
|
|
||||
|
if (search.includes('@') && search.split('@')[0].length === 66) { |
||||
|
updateManualFormSearchQuery(search) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.container}> |
||||
|
<header className={styles.header}> |
||||
|
<input |
||||
|
type='text' |
||||
|
placeholder='Search the network...' |
||||
|
className={styles.searchInput} |
||||
|
value={contactsform.searchQuery} |
||||
|
onChange={event => searchUpdated(event.target.value)} |
||||
|
// ref={input => input && input.focus()}
|
||||
|
/> |
||||
|
<span onClick={closeContactsForm} className={styles.closeIcon}> |
||||
|
<Isvg src={x} /> |
||||
|
</span> |
||||
|
</header> |
||||
|
|
||||
|
<section className={styles.nodes}> |
||||
|
<ul className={styles.networkResults}> |
||||
|
{ |
||||
|
filteredNetworkNodes.map(node => ( |
||||
|
<li key={node.pub_key}> |
||||
|
<section> |
||||
|
{ |
||||
|
node.alias.length > 0 ? |
||||
|
<h2> |
||||
|
<span>{node.alias.trim()}</span> |
||||
|
<span>({node.pub_key.substr(0, 10)}...{node.pub_key.substr(node.pub_key.length - 10)})</span> |
||||
|
</h2> |
||||
|
: |
||||
|
<h2> |
||||
|
<span>{node.pub_key}</span> |
||||
|
</h2> |
||||
|
} |
||||
|
</section> |
||||
|
<section> |
||||
|
{renderRightSide(node)} |
||||
|
</section> |
||||
|
</li> |
||||
|
)) |
||||
|
} |
||||
|
</ul> |
||||
|
</section> |
||||
|
|
||||
|
{ |
||||
|
showManualForm && |
||||
|
<section className={styles.manualForm}> |
||||
|
<p>Hm, looks like we can't see that node from here, wanna try to manually connect?</p> |
||||
|
<div className={styles.manualConnectButton} onClick={openManualForm}>Connect Manually</div> |
||||
|
</section> |
||||
|
} |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
AddChannel.propTypes = { |
||||
|
contactsform: PropTypes.object.isRequired, |
||||
|
closeContactsForm: PropTypes.func.isRequired, |
||||
|
openSubmitChannelForm: PropTypes.func.isRequired, |
||||
|
updateContactFormSearchQuery: PropTypes.func.isRequired, |
||||
|
updateManualFormSearchQuery: PropTypes.func.isRequired, |
||||
|
setNode: PropTypes.func.isRequired, |
||||
|
activeChannelPubkeys: PropTypes.array.isRequired, |
||||
|
nonActiveChannelPubkeys: PropTypes.array.isRequired, |
||||
|
pendingOpenChannelPubkeys: PropTypes.array.isRequired, |
||||
|
filteredNetworkNodes: PropTypes.array.isRequired, |
||||
|
loadingChannelPubkeys: PropTypes.array.isRequired, |
||||
|
showManualForm: PropTypes.bool.isRequired, |
||||
|
openManualForm: PropTypes.func.isRequired |
||||
|
} |
||||
|
|
||||
|
export default AddChannel |
@ -0,0 +1,141 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
.container { |
||||
|
position: relative; |
||||
|
width: 30%; |
||||
|
display: inline-block; |
||||
|
vertical-align: top; |
||||
|
height: 100vh; |
||||
|
background: #31343f; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
background: linear-gradient(270deg, #868B9F 0%, #333C5E 100%); |
||||
|
padding: 15px 10px; |
||||
|
color: $white; |
||||
|
|
||||
|
input { |
||||
|
background: transparent; |
||||
|
outline: 0; |
||||
|
border: 0; |
||||
|
color: $white; |
||||
|
font-size: 14px; |
||||
|
width: 90%; |
||||
|
} |
||||
|
|
||||
|
.closeIcon { |
||||
|
cursor: pointer; |
||||
|
transition: all 0.25s; |
||||
|
|
||||
|
&:hover { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
svg { |
||||
|
height: 14px; |
||||
|
width: 14px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.nodes { |
||||
|
background: #31343F; |
||||
|
|
||||
|
.networkResults { |
||||
|
overflow-y: auto; |
||||
|
margin-top: 30px; |
||||
|
padding: 0 10px; |
||||
|
color: $white; |
||||
|
|
||||
|
li { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
padding: 10px 0; |
||||
|
|
||||
|
h2 { |
||||
|
font-size: 10px; |
||||
|
font-weight: bold; |
||||
|
letter-spacing: 1.3px; |
||||
|
|
||||
|
span { |
||||
|
display: inline-block; |
||||
|
vertical-align: middle; |
||||
|
|
||||
|
&:nth-child(1) { |
||||
|
font-size: 10px; |
||||
|
font-weight: bold; |
||||
|
letter-spacing: 1.3px; |
||||
|
} |
||||
|
|
||||
|
&:nth-child(2) { |
||||
|
display: block; |
||||
|
color: $darkestgrey; |
||||
|
font-size: 8px; |
||||
|
margin-top: 5px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.connect { |
||||
|
cursor: pointer; |
||||
|
color: $darkestgrey; |
||||
|
transition: all 0.25s; |
||||
|
font-size: 10px; |
||||
|
|
||||
|
&:hover { |
||||
|
color: darken($darkestgrey, 10%); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.inactive { |
||||
|
font-size: 10px; |
||||
|
|
||||
|
display: inline-block; |
||||
|
vertical-align: top; |
||||
|
|
||||
|
&.online { |
||||
|
color: $green; |
||||
|
} |
||||
|
|
||||
|
&.offline { |
||||
|
color: $darkestgrey; |
||||
|
} |
||||
|
|
||||
|
&.pending { |
||||
|
color: $orange; |
||||
|
} |
||||
|
|
||||
|
&.private { |
||||
|
color: darken($darkestgrey, 50%); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.manualForm { |
||||
|
color: $white; |
||||
|
text-align: center; |
||||
|
margin: 0 25px; |
||||
|
|
||||
|
p { |
||||
|
font-size: 14px; |
||||
|
margin: 20px 0; |
||||
|
} |
||||
|
|
||||
|
div { |
||||
|
background: #383B47; |
||||
|
font-size: 16px; |
||||
|
padding: 10px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.25s; |
||||
|
|
||||
|
&:hover { |
||||
|
background: lighten(#383B47, 10%); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
|
||||
|
import Isvg from 'react-inlinesvg' |
||||
|
import x from 'icons/x.svg' |
||||
|
|
||||
|
import ConnectManually from './ConnectManually' |
||||
|
import SubmitChannelForm from './SubmitChannelForm' |
||||
|
|
||||
|
import styles from './ChannelForm.scss' |
||||
|
|
||||
|
const FORM_TYPES = { |
||||
|
MANUAL_FORM: ConnectManually, |
||||
|
SUBMIT_CHANNEL_FORM: SubmitChannelForm |
||||
|
} |
||||
|
|
||||
|
const ChannelForm = ({ formType, formProps, closeForm }) => { |
||||
|
if (!formType) { return null } |
||||
|
|
||||
|
const FormComponent = FORM_TYPES[formType] |
||||
|
return ( |
||||
|
<div className={styles.container}> |
||||
|
<div className={styles.closeContainer}> |
||||
|
<span onClick={closeForm}> |
||||
|
<Isvg src={x} /> |
||||
|
</span> |
||||
|
</div> |
||||
|
<FormComponent {...formProps} /> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
|
||||
|
ChannelForm.propTypes = { |
||||
|
formType: PropTypes.string, |
||||
|
formProps: PropTypes.object.isRequired, |
||||
|
closeForm: PropTypes.func.isRequired |
||||
|
} |
||||
|
|
||||
|
export default ChannelForm |
@ -0,0 +1,29 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
.container { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
z-index: 10; |
||||
|
height: 100vh; |
||||
|
width: 100%; |
||||
|
background: #31343F; |
||||
|
} |
||||
|
|
||||
|
.closeContainer { |
||||
|
text-align: right; |
||||
|
padding: 20px 40px 0px; |
||||
|
|
||||
|
span { |
||||
|
cursor: pointer; |
||||
|
opacity: 1.0; |
||||
|
transition: 0.25s all; |
||||
|
|
||||
|
&:hover { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
svg { |
||||
|
color: $white; |
||||
|
} |
||||
|
} |
@ -0,0 +1,99 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
import styles from './ConnectManually.scss' |
||||
|
|
||||
|
class ConnectManually extends React.Component { |
||||
|
render() { |
||||
|
const { |
||||
|
manualSearchQuery, |
||||
|
|
||||
|
manualFormIsValid, |
||||
|
updateManualFormErrors, |
||||
|
|
||||
|
openSubmitChannelForm, |
||||
|
updateManualFormSearchQuery, |
||||
|
|
||||
|
setNode, |
||||
|
|
||||
|
showErrors |
||||
|
} = this.props |
||||
|
|
||||
|
const formSubmitted = () => { |
||||
|
if (!manualFormIsValid.isValid) { |
||||
|
updateManualFormErrors(manualFormIsValid.errors) |
||||
|
|
||||
|
return |
||||
|
} |
||||
|
// clear any existing errors
|
||||
|
updateManualFormErrors({ manualInput: null }) |
||||
|
|
||||
|
const [pub_key, addr] = manualSearchQuery && manualSearchQuery.split('@') |
||||
|
|
||||
|
// the SubmitChannel component is expecting a node object that looks like the following
|
||||
|
// {
|
||||
|
// pub_key: 'some_string',
|
||||
|
// addresses: [
|
||||
|
// {
|
||||
|
// addr: 'some_host_address'
|
||||
|
// }
|
||||
|
// ]
|
||||
|
// }
|
||||
|
// knowing this we will set the node object with the known format and plug in the pubkey + host accordingly
|
||||
|
setNode({ pub_key, addresses: [{ addr }] }) |
||||
|
|
||||
|
// now we close the ConnectManually form and open the SubmitChannel form by chaning the channelFormType
|
||||
|
openSubmitChannelForm() |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.content}> |
||||
|
<header className={styles.header}> |
||||
|
<h1>Connect Manually</h1> |
||||
|
<p>Please enter the peer's pubkey@host</p> |
||||
|
</header> |
||||
|
|
||||
|
<section className={styles.peer}> |
||||
|
<div className={styles.input}> |
||||
|
<input |
||||
|
type='text' |
||||
|
placeholder='pubkey@host' |
||||
|
value={manualSearchQuery} |
||||
|
onChange={event => updateManualFormSearchQuery(event.target.value)} |
||||
|
/> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section className={`${styles.errorMessage} ${showErrors.manualInput && styles.active}`}> |
||||
|
{showErrors.manualInput && |
||||
|
<span>{manualFormIsValid && manualFormIsValid.errors.manualInput}</span> |
||||
|
} |
||||
|
</section> |
||||
|
|
||||
|
<section className={styles.submit}> |
||||
|
<div |
||||
|
className={`${styles.button} ${manualFormIsValid.isValid && styles.active}`} |
||||
|
onClick={formSubmitted} |
||||
|
> |
||||
|
Submit |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ConnectManually.propTypes = { |
||||
|
manualSearchQuery: PropTypes.string.isRequired, |
||||
|
|
||||
|
manualFormIsValid: PropTypes.object.isRequired, |
||||
|
updateManualFormErrors: PropTypes.func.isRequired, |
||||
|
|
||||
|
openSubmitChannelForm: PropTypes.func.isRequired, |
||||
|
updateManualFormSearchQuery: PropTypes.func.isRequired, |
||||
|
|
||||
|
setNode: PropTypes.func.isRequired, |
||||
|
|
||||
|
showErrors: PropTypes.object.isRequired |
||||
|
} |
||||
|
|
||||
|
export default ConnectManually |
@ -0,0 +1,139 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
.container { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
z-index: 10; |
||||
|
height: 100vh; |
||||
|
width: 100%; |
||||
|
background: #31343F; |
||||
|
} |
||||
|
|
||||
|
.closeContainer { |
||||
|
text-align: right; |
||||
|
padding: 20px 40px 0px; |
||||
|
|
||||
|
span { |
||||
|
cursor: pointer; |
||||
|
opacity: 1.0; |
||||
|
transition: 0.25s all; |
||||
|
|
||||
|
&:hover { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
svg { |
||||
|
color: $white; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.content { |
||||
|
padding: 0 40px; |
||||
|
font-family: Roboto; |
||||
|
color: $white; |
||||
|
|
||||
|
.header { |
||||
|
padding: 20px 100px; |
||||
|
|
||||
|
h1 { |
||||
|
margin-bottom: 15px; |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
|
||||
|
p { |
||||
|
text-align: center; |
||||
|
line-height: 1.3; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
text-align: center; |
||||
|
padding-bottom: 20px; |
||||
|
border-bottom: 1px solid $spaceborder; |
||||
|
|
||||
|
h1 { |
||||
|
font-size: 22px; |
||||
|
font-weight: 100; |
||||
|
margin-top: 10px; |
||||
|
letter-spacing: 1.5px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
margin: 50px 0; |
||||
|
|
||||
|
h2 { |
||||
|
font-size: 14px; |
||||
|
background: $spaceblue; |
||||
|
padding: 10px; |
||||
|
border-radius: 17.5px; |
||||
|
display: inline; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.input { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
margin-top: 50px; |
||||
|
|
||||
|
input { |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.input input { |
||||
|
background: transparent; |
||||
|
outline: none; |
||||
|
border: 0; |
||||
|
color: $gold; |
||||
|
-webkit-text-fill-color: $white; |
||||
|
width: 100%; |
||||
|
font-weight: 200; |
||||
|
} |
||||
|
|
||||
|
.input input::-webkit-input-placeholder, ::-webkit-input-placeholder { |
||||
|
text-shadow: none; |
||||
|
-webkit-text-fill-color: initial; |
||||
|
} |
||||
|
|
||||
|
.errorMessage { |
||||
|
margin-top: 20px; |
||||
|
font-size: 12px; |
||||
|
color: $red; |
||||
|
min-height: 12px; |
||||
|
visibility: hidden; |
||||
|
|
||||
|
&.active { |
||||
|
visibility: visible; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.submit { |
||||
|
margin-top: 50px; |
||||
|
text-align: center; |
||||
|
|
||||
|
.button { |
||||
|
width: 235px; |
||||
|
margin: 0 auto; |
||||
|
padding: 20px 10px; |
||||
|
background: #31343f; |
||||
|
opacity: 0.5; |
||||
|
cursor: pointer; |
||||
|
transition: 0.25s all; |
||||
|
|
||||
|
&.active { |
||||
|
background: $gold; |
||||
|
opacity: 1.0; |
||||
|
|
||||
|
&:hover { |
||||
|
background: darken($gold, 5%); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
@ -0,0 +1,121 @@ |
|||||
|
import React from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
|
||||
|
import { FaAngleDown } from 'react-icons/lib/fa' |
||||
|
|
||||
|
import styles from './SubmitChannelForm.scss' |
||||
|
|
||||
|
class SubmitChannelForm extends React.Component { |
||||
|
render() { |
||||
|
const { |
||||
|
closeChannelForm, |
||||
|
closeContactsForm, |
||||
|
|
||||
|
node, |
||||
|
contactCapacity, |
||||
|
updateContactCapacity, |
||||
|
openChannel, |
||||
|
|
||||
|
toggleCurrencyProps: { |
||||
|
setContactsCurrencyFilters, |
||||
|
showCurrencyFilters, |
||||
|
currencyName, |
||||
|
currentCurrencyFilters, |
||||
|
onCurrencyFilterClick, |
||||
|
contactFormUsdAmount |
||||
|
} |
||||
|
} = this.props |
||||
|
|
||||
|
const renderTitle = () => { |
||||
|
// if the node has an alias set we will show that with the pubkey in parens
|
||||
|
// if not, just show the pubkey (would look ugly with rando parens)
|
||||
|
if (node.alias && node.alias.length) { |
||||
|
return `${node.alias} (${node.pub_key})` |
||||
|
} |
||||
|
|
||||
|
return node.pub_key |
||||
|
} |
||||
|
|
||||
|
const formSubmitted = () => { |
||||
|
// submit the channel to LND
|
||||
|
openChannel({ pubkey: node.pub_key, host: node.addresses[0].addr, local_amt: contactCapacity }) |
||||
|
|
||||
|
// close the ChannelForm component
|
||||
|
closeChannelForm() |
||||
|
|
||||
|
// close the AddChannel component
|
||||
|
closeContactsForm() |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.content}> |
||||
|
<header className={styles.header}> |
||||
|
<h1>Add Funds to Network</h1> |
||||
|
<p> |
||||
|
Adding a connection will help you send and receive money on the Lightning Network. |
||||
|
You aren't spening any money, rather moving the money you plan to use onto the network. |
||||
|
</p> |
||||
|
</header> |
||||
|
|
||||
|
<section className={styles.title}> |
||||
|
<h2>{renderTitle()}</h2> |
||||
|
</section> |
||||
|
|
||||
|
<section className={styles.amount}> |
||||
|
<div className={styles.input}> |
||||
|
<input |
||||
|
type='number' |
||||
|
min='0' |
||||
|
size='' |
||||
|
placeholder='0.00000000' |
||||
|
value={contactCapacity || ''} |
||||
|
onChange={event => updateContactCapacity(event.target.value)} |
||||
|
id='amount' |
||||
|
/> |
||||
|
<div className={styles.currency}> |
||||
|
<section className={styles.currentCurrency} onClick={() => setContactsCurrencyFilters(!showCurrencyFilters)}> |
||||
|
<span>{currencyName}</span><span><FaAngleDown /></span> |
||||
|
</section> |
||||
|
<ul className={showCurrencyFilters && styles.active}> |
||||
|
{ |
||||
|
currentCurrencyFilters.map(filter => |
||||
|
<li key={filter.key} onClick={() => onCurrencyFilterClick(filter.key)}>{filter.name}</li>) |
||||
|
} |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div className={styles.usdAmount}> |
||||
|
{`≈ ${contactFormUsdAmount || 0} USD`} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section className={styles.submit}> |
||||
|
<div |
||||
|
className={`${styles.button} ${contactCapacity > 0 && styles.active}`} |
||||
|
onClick={formSubmitted} |
||||
|
> |
||||
|
Submit |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
SubmitChannelForm.propTypes = { |
||||
|
closeChannelForm: PropTypes.func.isRequired, |
||||
|
closeContactsForm: PropTypes.func.isRequired, |
||||
|
|
||||
|
node: PropTypes.object.isRequired, |
||||
|
contactCapacity: PropTypes.PropTypes.oneOfType([ |
||||
|
PropTypes.number, |
||||
|
PropTypes.string |
||||
|
]), |
||||
|
updateContactCapacity: PropTypes.func.isRequired, |
||||
|
openChannel: PropTypes.func.isRequired, |
||||
|
|
||||
|
toggleCurrencyProps: PropTypes.object.isRequired |
||||
|
} |
||||
|
|
||||
|
export default SubmitChannelForm |
@ -0,0 +1,150 @@ |
|||||
|
@import '../../variables.scss'; |
||||
|
|
||||
|
|
||||
|
.content { |
||||
|
padding: 0 40px; |
||||
|
font-family: Roboto; |
||||
|
color: $white; |
||||
|
|
||||
|
.header { |
||||
|
padding: 20px 100px; |
||||
|
|
||||
|
h1 { |
||||
|
margin-bottom: 15px; |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
|
||||
|
p { |
||||
|
text-align: center; |
||||
|
line-height: 1.3; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
text-align: center; |
||||
|
padding-bottom: 20px; |
||||
|
border-bottom: 1px solid $spaceborder; |
||||
|
|
||||
|
h1 { |
||||
|
font-size: 22px; |
||||
|
font-weight: 100; |
||||
|
margin-top: 10px; |
||||
|
letter-spacing: 1.5px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
margin: 50px 0; |
||||
|
|
||||
|
h2 { |
||||
|
font-size: 14px; |
||||
|
background: $spaceblue; |
||||
|
padding: 10px; |
||||
|
border-radius: 17.5px; |
||||
|
display: inline; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.input { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
|
||||
|
input { |
||||
|
font-size: 40px; |
||||
|
max-width: 230px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.input input { |
||||
|
background: transparent; |
||||
|
outline: none; |
||||
|
border: 0; |
||||
|
color: $gold; |
||||
|
-webkit-text-fill-color: $white; |
||||
|
width: 100%; |
||||
|
font-weight: 200; |
||||
|
} |
||||
|
|
||||
|
.input input::-webkit-input-placeholder, ::-webkit-input-placeholder { |
||||
|
text-shadow: none; |
||||
|
-webkit-text-fill-color: initial; |
||||
|
} |
||||
|
|
||||
|
.currency { |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
|
||||
|
.currentCurrency { |
||||
|
cursor: pointer; |
||||
|
transition: 0.25s all; |
||||
|
|
||||
|
&:hover { |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
font-size: 14px; |
||||
|
|
||||
|
&:nth-child(1) { |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
ul { |
||||
|
visibility: hidden; |
||||
|
position: absolute; |
||||
|
top: 30px; |
||||
|
|
||||
|
&.active { |
||||
|
visibility: visible; |
||||
|
} |
||||
|
|
||||
|
li { |
||||
|
padding: 8px 15px; |
||||
|
background: #191919; |
||||
|
cursor: pointer; |
||||
|
transition: 0.25s hover; |
||||
|
border-bottom: 1px solid #0f0f0f; |
||||
|
|
||||
|
&:hover { |
||||
|
background: #0f0f0f; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.usdAmount { |
||||
|
margin-top: 20px; |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
.submit { |
||||
|
margin-top: 50px; |
||||
|
text-align: center; |
||||
|
|
||||
|
.button { |
||||
|
width: 235px; |
||||
|
margin: 0 auto; |
||||
|
padding: 20px 10px; |
||||
|
background: #31343f; |
||||
|
opacity: 0.5; |
||||
|
cursor: pointer; |
||||
|
transition: 0.25s all; |
||||
|
|
||||
|
&.active { |
||||
|
background: $gold; |
||||
|
opacity: 1.0; |
||||
|
|
||||
|
&:hover { |
||||
|
background: darken($gold, 5%); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -1,130 +0,0 @@ |
|||||
import { createSelector } from 'reselect' |
|
||||
|
|
||||
// Initial State
|
|
||||
const initialState = { |
|
||||
isOpen: false, |
|
||||
node_key: '', |
|
||||
local_amt: 0, |
|
||||
push_amt: 0, |
|
||||
|
|
||||
step: 1 |
|
||||
} |
|
||||
|
|
||||
// Constants
|
|
||||
// ------------------------------------
|
|
||||
export const OPEN_CHANNEL_FORM = 'OPEN_CHANNEL_FORM' |
|
||||
export const CLOSE_CHANNEL_FORM = 'CLOSE_CHANNEL_FORM' |
|
||||
|
|
||||
export const SET_NODE_KEY = 'SET_NODE_KEY' |
|
||||
export const SET_LOCAL_AMOUNT = 'SET_LOCAL_AMOUNT' |
|
||||
export const SET_PUSH_AMOUNT = 'SET_PUSH_AMOUNT' |
|
||||
|
|
||||
export const CHANGE_STEP = 'CHANGE_STEP' |
|
||||
|
|
||||
export const RESET_CHANNEL_FORM = 'RESET_CHANNEL_FORM' |
|
||||
|
|
||||
// ------------------------------------
|
|
||||
// Actions
|
|
||||
// ------------------------------------
|
|
||||
export function openChannelForm() { |
|
||||
return { |
|
||||
type: OPEN_CHANNEL_FORM |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function closeChannelForm() { |
|
||||
return { |
|
||||
type: CLOSE_CHANNEL_FORM |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function setNodeKey(node_key) { |
|
||||
return { |
|
||||
type: SET_NODE_KEY, |
|
||||
node_key |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function setLocalAmount(local_amt) { |
|
||||
return { |
|
||||
type: SET_LOCAL_AMOUNT, |
|
||||
local_amt |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function setPushAmount(push_amt) { |
|
||||
return { |
|
||||
type: SET_PUSH_AMOUNT, |
|
||||
push_amt |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function changeStep(step) { |
|
||||
return { |
|
||||
type: CHANGE_STEP, |
|
||||
step |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export function resetChannelForm() { |
|
||||
return { |
|
||||
type: RESET_CHANNEL_FORM |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// ------------------------------------
|
|
||||
// Action Handlers
|
|
||||
// ------------------------------------
|
|
||||
const ACTION_HANDLERS = { |
|
||||
[OPEN_CHANNEL_FORM]: state => ({ ...state, isOpen: true }), |
|
||||
[CLOSE_CHANNEL_FORM]: state => ({ ...state, isOpen: false }), |
|
||||
|
|
||||
[SET_NODE_KEY]: (state, { node_key }) => ({ ...state, node_key }), |
|
||||
[SET_LOCAL_AMOUNT]: (state, { local_amt }) => ({ ...state, local_amt }), |
|
||||
[SET_PUSH_AMOUNT]: (state, { push_amt }) => ({ ...state, push_amt }), |
|
||||
|
|
||||
[CHANGE_STEP]: (state, { step }) => ({ ...state, step }), |
|
||||
|
|
||||
[RESET_CHANNEL_FORM]: () => (initialState) |
|
||||
} |
|
||||
|
|
||||
const channelFormSelectors = {} |
|
||||
const channelFormStepSelector = state => state.channelform.step |
|
||||
const channelFormLocalAmountSelector = state => state.channelform.local_amt |
|
||||
|
|
||||
channelFormSelectors.channelFormHeader = createSelector( |
|
||||
channelFormStepSelector, |
|
||||
(step) => { |
|
||||
switch (step) { |
|
||||
case 1: |
|
||||
return 'Step 1: Select a peer' |
|
||||
case 2: |
|
||||
return 'Step 2: Set your local amount' |
|
||||
case 3: |
|
||||
return 'Step 3: Set your push amount' |
|
||||
default: |
|
||||
return 'Step 4: Create your channel' |
|
||||
} |
|
||||
} |
|
||||
) |
|
||||
|
|
||||
channelFormSelectors.channelFormProgress = createSelector( |
|
||||
channelFormStepSelector, |
|
||||
step => ((step - 1) / 3) * 100 |
|
||||
) |
|
||||
|
|
||||
channelFormSelectors.stepTwoIsValid = createSelector( |
|
||||
channelFormLocalAmountSelector, |
|
||||
local_amt => local_amt > 0 |
|
||||
) |
|
||||
|
|
||||
export { channelFormSelectors } |
|
||||
|
|
||||
// ------------------------------------
|
|
||||
// Reducer
|
|
||||
// ------------------------------------
|
|
||||
export default function channelFormReducer(state = initialState, action) { |
|
||||
const handler = ACTION_HANDLERS[action.type] |
|
||||
|
|
||||
return handler ? handler(state, action) : state |
|
||||
} |
|
Loading…
Reference in new issue