Skip to content
Permalink
Browse files

feat(autopay): add autopay list view

Address #1774
  • Loading branch information...
mrfelton committed Mar 13, 2019
1 parent f387fa1 commit ee70c0a4da382e3978699e7d680e999c1f5659a3
@@ -0,0 +1,17 @@
import React from 'react'
import AutopayList from 'containers/Autopay/AutopayList'
import { Panel } from 'components/UI'
import AutopayHeader from './AutopayHeader'

const Autopay = props => (
<Panel {...props}>
<Panel.Header mx={4}>
<AutopayHeader />
</Panel.Header>
<Panel.Body css={{ 'overflow-y': 'overlay', 'overflow-x': 'hidden' }}>
<AutopayList mx={4} />
</Panel.Body>
</Panel>
)

export default Autopay
@@ -0,0 +1,11 @@
import React from 'react'
import { Box } from 'rebass'
import AutopaySearch from 'containers/Autopay/AutopaySearch'

const AutopayActions = props => (
<Box {...props}>
<AutopaySearch />
</Box>
)

export default AutopayActions
@@ -0,0 +1,67 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Flex } from 'rebass'
import { Card, Heading } from 'components/UI'

const CardWithBg = styled(Card)`
position: relative;
background-image: url(${props => props.src});
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
height: 300px;
width: 195px;
border-radius: 40px;
padding: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
cursor: pointer;
`

const GradientOverlay = styled(Card)`
position: absolute;
background-image: linear-gradient(
146deg,
${props => props.theme.colors.tertiaryColor},
${props => props.theme.colors.primaryColor}
);
opacity: 0.5;
width: 100%;
height: 100%;
border-radius: 40px;
transition: all 0.25s;
&:hover {
opacity: 0;
}
`

const TextOverlay = styled(Flex)`
position: absolute;
pointer-events: none;
width: 100%;
height: 100%;
`

const AutopayCardView = ({ merchant: { host, image, nickname, pubkey }, onClick, ...rest }) => (
<CardWithBg src={image} {...rest} onClick={() => onClick(`${pubkey}@${host}`)}>
<GradientOverlay />
<TextOverlay alignItems="center" flexDirection="column" justifyContent="center" p={3}>
<Heading.h1 fontWeight="normal" textAlign="center">
{nickname}
</Heading.h1>
</TextOverlay>
</CardWithBg>
)

AutopayCardView.propTypes = {
merchant: PropTypes.shape({
description: PropTypes.string.isRequired,
host: PropTypes.string.isRequired,
image: PropTypes.string.isRequired,
nickname: PropTypes.string.isRequired,
pubkey: PropTypes.string.isRequired,
}),
onClick: PropTypes.func.isRequired,
}

export default React.memo(AutopayCardView)
@@ -0,0 +1,13 @@
import React from 'react'
import { Box } from 'rebass'
import AutopayActions from './AutopayActions'
import AutopayHeading from './AutopayHeading'

const AutopayHeader = props => (
<Box {...props}>
<AutopayHeading mb={3} />
<AutopayActions mb={3} />
</Box>
)

export default AutopayHeader
@@ -0,0 +1,26 @@
import React from 'react'
import { FormattedMessage, intlShape, injectIntl } from 'react-intl'
import { Box, Flex } from 'rebass'
import { Heading } from 'components/UI'
import Autopay from 'components/Icon/Autopay'
import messages from './messages'

const AutopayHeading = ({ intl, ...rest }) => (
<Flex alignItems="baseline" {...rest}>
<Heading.h1 fontSize="xxxl" mr={2}>
<FormattedMessage {...messages.title} />
</Heading.h1>
<Box alignSelf="flex-start">
<Autopay height={28} width={28} />
</Box>
<Heading.h1>
<FormattedMessage {...messages.subtitle} />
</Heading.h1>
</Flex>
)

AutopayHeading.propTypes = {
intl: intlShape.isRequired,
}

export default injectIntl(AutopayHeading)
@@ -0,0 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Box } from 'rebass'
import styled from 'styled-components'
import AutopayCardView from './AutopayCardView'
import AutopaySearchNoResults from './AutopaySearchNoResults'

const Grid = styled(Box)`
display: grid;
grid-template-columns: repeat(auto-fill, 195px);
grid-gap: 1rem;
justify-content: space-between;
`

const AutopayList = ({ merchants, ...rest }) => {
if (merchants.length === 0) {
return <AutopaySearchNoResults {...rest} />
}

return (
<Box as="article" {...rest}>
<Grid>
{merchants.map(merchant => {
return (
<Box key={merchant.pubkey} py={2}>
<AutopayCardView merchant={merchant} onClick={() => alert('NOT IMPLEMENTED')} />
</Box>
)
})}
</Grid>
</Box>
)
}

AutopayList.propTypes = {
merchants: PropTypes.array,
}

AutopayList.defaultProps = {
merchants: [],
}

export default AutopayList
@@ -0,0 +1,27 @@
import React from 'react'
import PropTypes from 'prop-types'
import { intlShape, injectIntl } from 'react-intl'
import { Form, Input } from 'components/UI'
import messages from './messages'

const AutopaySearch = ({ intl, searchQuery, updateAutopaySearchQuery, ...rest }) => (
<Form width={1} {...rest}>
<Input
field="channel-search"
highlightOnValid={false}
id="channel-search"
initialValue={searchQuery}
onValueChange={updateAutopaySearchQuery}
placeholder={intl.formatMessage({ ...messages.search_placeholder })}
type="search"
/>
</Form>
)

AutopaySearch.propTypes = {
intl: intlShape.isRequired,
searchQuery: PropTypes.string,
updateAutopaySearchQuery: PropTypes.func.isRequired,
}

export default injectIntl(AutopaySearch)
@@ -0,0 +1,12 @@
import React from 'react'
import { FormattedMessage } from 'react-intl'
import { Text } from 'components/UI'
import messages from './messages'

const AutopaySearchNoResults = props => (
<Text color="gray" {...props}>
<FormattedMessage {...messages.search_no_Results} />
</Text>
)

export default AutopaySearchNoResults
@@ -0,0 +1,10 @@
import Autopay from './Autopay'

export default Autopay
export AutopayActions from './AutopayActions'
export AutopayCardView from './AutopayCardView'
export AutopayHeader from './AutopayHeader'
export AutopayHeading from './AutopayHeading'
export AutopayList from './AutopayList'
export AutopaySearch from './AutopaySearch'
export AutopaySearchNoResults from './AutopaySearchNoResults'
@@ -0,0 +1,9 @@
import { defineMessages } from 'react-intl'

/* eslint-disable max-len */
export default defineMessages({
title: 'autopay',
subtitle: 'Frictionless digital payments',
search_placeholder: 'Search Autopay',
search_no_Results: 'Your seach did not return any results.',
})
@@ -0,0 +1,11 @@
import { connect } from 'react-redux'
import Autopay from 'components/Autopay'

const mapStateToProps = () => ({})

const mapDispatchToProps = {}

export default connect(
mapStateToProps,
mapDispatchToProps
)(Autopay)
@@ -0,0 +1,12 @@
import { connect } from 'react-redux'
import AutopayList from 'components/Autopay/AutopayList'
import { autopaySelectors } from 'reducers/autopay'

const mapStateToProps = state => ({
merchants: autopaySelectors.filteredMerchants(state),
})

export default connect(
mapStateToProps,
null
)(AutopayList)
@@ -0,0 +1,16 @@
import { connect } from 'react-redux'
import AutopaySearch from 'components/Autopay/AutopaySearch'
import { updateAutopaySearchQuery, autopaySelectors } from 'reducers/autopay'

const mapStateToProps = state => ({
searchQuery: autopaySelectors.searchQuery(state),
})

const mapDispatchToProps = {
updateAutopaySearchQuery,
}

export default connect(
mapStateToProps,
mapDispatchToProps
)(AutopaySearch)
@@ -0,0 +1,59 @@
import { createSelector } from 'reselect'
import { contactFormSelectors } from './contactsform'

// Initial State
const initialState = {
searchQuery: null,
merchants: [],
}

// Constants
// ------------------------------------
export const UPDATE_AUTOPAY_SEARCH_QUERY = 'UPDATE_AUTOPAY_SEARCH_QUERY'

// ------------------------------------
// Actions
// ------------------------------------
export function updateAutopaySearchQuery(searchQuery) {
return {
type: UPDATE_AUTOPAY_SEARCH_QUERY,
searchQuery,
}
}

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
[UPDATE_AUTOPAY_SEARCH_QUERY]: (state, { searchQuery }) => ({ ...state, searchQuery }),
}

// ------------------------------------
// Selector
// ------------------------------------
const autopaySelectors = {}
autopaySelectors.searchQuery = state => state.autopay.searchQuery
autopaySelectors.merchants = state => contactFormSelectors.suggestedNodes(state)

autopaySelectors.filteredMerchants = createSelector(
autopaySelectors.merchants,
autopaySelectors.searchQuery,
(merchants, searchQuery) => {
if (!searchQuery) {
return merchants
}
const cleanedSearchQuery = searchQuery.toLowerCase()
return merchants.filter(m => m.nickname.toLowerCase().includes(cleanedSearchQuery))
}
)

export { autopaySelectors }

// ------------------------------------
// Reducer
// ------------------------------------
export default function autopayReducer(state = initialState, action) {
const handler = ACTION_HANDLERS[action.type]

return handler ? handler(state, action) : state
}
@@ -4,6 +4,7 @@ import { intlReducer as intl } from 'react-intl-redux'
import locale from './locale'
import theme from './theme'
import app from './app'
import autopay from './autopay'
import onboarding from './onboarding'
import lnd from './lnd'
import ticker from './ticker'
@@ -37,6 +38,7 @@ export default history => {
activity,
address,
app,
autopay,
balance,
channels,
contactsform,

0 comments on commit ee70c0a

Please sign in to comment.
You can’t perform that action at this time.