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

Notifications frontend #904

Merged
merged 116 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
603384d
Add subsection (2nd level) routing to global preferences
2color Jul 11, 2019
3bd241d
Notifications: authentication flow
2color Jul 11, 2019
56249a7
Pass apps to notifications
2color Jul 11, 2019
a555477
Move api functions to separate file
2color Jul 11, 2019
53c0a6d
Move components and constants into separate file and add subscriptions
2color Jul 11, 2019
ce50333
Fix subsection parsing
2color Aug 7, 2019
83725be
Export extend error
2color Aug 7, 2019
d1a0424
Notifications auth flow and layout
2color Aug 7, 2019
1094ddd
Upgrade @aragon/ui
2color Aug 7, 2019
4a6cf4d
Fix linting errors
2color Aug 7, 2019
997132b
Remove unneeded comment
2color Aug 8, 2019
3ec3dcb
Pass apps to subscriptions component
2color Aug 8, 2019
46e759b
Improve state handling of verify flow
2color Aug 8, 2019
e3eb482
Update url of notification service to live one
2color Aug 8, 2019
0df8fb7
Add create subscriptions function
2color Aug 8, 2019
33ac665
Add subscription creation form
2color Aug 8, 2019
f472d3b
Vertically align notification login form
2color Aug 9, 2019
e94e361
Add logout function
2color Aug 9, 2019
3cc6d02
Style preverify and verify steps
2color Aug 9, 2019
62d1867
Handle and style email login/verification steps
2color Aug 12, 2019
8dcb3de
Use css for circle around checkmark
2color Aug 12, 2019
7fee586
Add delete account function
2color Aug 12, 2019
c03b565
Add parent component to manage notifications and delete account
2color Aug 12, 2019
cfa1ed1
Filter getSubscriptions for network
2color Aug 12, 2019
b83a16f
Create subscription journey
2color Aug 12, 2019
47aef63
Bump @aragon/ui for disabled dropdown state
2color Aug 15, 2019
790dd89
Remove dependency on email in hook
2color Aug 18, 2019
7ccf751
Typo correction
2color Aug 18, 2019
3dbb2bb
Use index when infering subsection in global preferences
2color Aug 18, 2019
08aa412
Verify token and add failed and authenticating states
2color Aug 18, 2019
1b51483
Make network type compatible with backend
2color Aug 18, 2019
1140652
Take single argument for consistency
2color Aug 18, 2019
9b01a77
Rename contractAddress to appContractAddress
2color Aug 18, 2019
15f73f2
Wrap in form to allow submission with enter key
2color Aug 18, 2019
a054ba2
Add email validation and style login error
2color Aug 18, 2019
9e76df6
Fix typo 🙈
2color Aug 18, 2019
76ee465
Use strong instead of span
2color Aug 19, 2019
ebf001d
Disable button while awaiting server response
2color Aug 19, 2019
ab682ea
Fix typo
2color Aug 19, 2019
c2afb65
Ensure correct order to avoid rendering problems
2color Aug 19, 2019
2585472
Use ButtonText for text link
2color Aug 21, 2019
b108200
Add comment about network type
2color Aug 21, 2019
6f99616
Use symbols for constants
2color Aug 21, 2019
e8d7085
Rename token to authToken for clarity
2color Aug 21, 2019
3df25fe
Bump @aragon/ui
2color Aug 21, 2019
f7201a2
Improve email validation
2color Aug 21, 2019
836df88
Add email validation icon
2color Aug 21, 2019
ed6c63b
Add email regex source
2color Aug 21, 2019
6577afa
Remove unused theme
2color Aug 21, 2019
607747c
Merge remote-tracking branch 'origin/newstyle' into newstyle-notifica…
2color Aug 21, 2019
b2e2a46
Submit an ABI subset with only the relevant event
2color Aug 21, 2019
d932092
Add deleteSubscriptions method
2color Aug 21, 2019
bdb2ff6
Use DataView for subscriptions table
2color Aug 21, 2019
6375f39
Fix prop type
2color Aug 21, 2019
474c41a
Render subscription filters
2color Aug 22, 2019
fd24a56
Do not reset app after creating a subscription
2color Aug 22, 2019
26afff7
Add subscription filters
2color Aug 22, 2019
2a4bc57
Render appName
2color Aug 26, 2019
e8b869f
Refine the subscription form
2color Aug 26, 2019
ebe2512
Style the table correctly
2color Aug 26, 2019
5c8808b
style and render clear filters conditionally
2color Aug 26, 2019
9eec3e0
Enable sign in button when email is valid
2color Aug 26, 2019
842a9b4
Disable the log in button initially
2color Aug 26, 2019
3ef0938
Use constants instead of magic numbers
2color Aug 26, 2019
f4a2077
Set unauthenticated state as the default too
2color Aug 26, 2019
2bd21d8
Update src/components/GlobalPreferences/Notifications/ManageNotificat…
2color Aug 26, 2019
2a1c1cc
Handle network error in verification
2color Aug 26, 2019
442467c
Handle verification errors more generally
2color Aug 26, 2019
ee7572e
Update login error
2color Aug 26, 2019
b0fdc3a
Show logout and reset email button in verification error states
2color Aug 26, 2019
e51e90b
Let there be consts
2color Aug 26, 2019
34ac76e
Remove completed todo
2color Aug 26, 2019
b459901
Use useCallback
2color Aug 26, 2019
55b77cb
Render loading ring when subscriptions are fetching
2color Aug 27, 2019
ba0a363
Add loading DAO state
2color Aug 27, 2019
c640eeb
Fix bugs in form when unsubscribing and subscribing to apps
2color Aug 27, 2019
23898b9
Improve subscription filters styling
2color Aug 27, 2019
2d25aa0
Use named function expression for subscriptions table
2color Aug 27, 2019
fa0e691
Style filter buttons vertically in small layouts
2color Aug 28, 2019
5ba5b31
Make function statement
2color Aug 28, 2019
a1bfb22
Render nothing while authenticating
2color Aug 28, 2019
352a7af
Set isFetching correctly and render table once fetched
2color Aug 28, 2019
1b0fbf6
Add cleanup function to useEffect hook
2color Aug 28, 2019
433ea60
Merge remote-tracking branch 'origin/newstyle' into newstyle-notifica…
2color Aug 28, 2019
122cab0
Update login copy
2color Aug 29, 2019
6554b8f
Update different verification states to align with designs
2color Aug 29, 2019
1ea0ccc
Navigate back when resetting account
2color Aug 29, 2019
e7e6c8f
Filter out non-relevant eventa
2color Aug 29, 2019
4034014
Prettify
2color Aug 29, 2019
4975906
Move errors to manage notifications and use InfoBox more generally
2color Aug 29, 2019
6dce2ee
Move subscriptions table to separate component
2color Aug 29, 2019
f2a2fb8
Merge remote-tracking branch 'origin/newstyle' into newstyle-notifica…
2color Aug 29, 2019
0f88a81
Global network error page with retry action
2color Aug 30, 2019
88f6300
Remove usused import
2color Aug 30, 2019
ff53022
Add confirmation modal and update images
2color Aug 30, 2019
295dbe8
Show image in form when subscribed to all events
2color Aug 30, 2019
b975615
Update delete account confirmation copy
2color Aug 30, 2019
93eed0c
notifications info-box: reduce margin between header and content
2color Sep 2, 2019
87ecabc
manage-notifications: make buttons consistently wide
2color Sep 2, 2019
484da85
Update expired magic link token message
2color Sep 2, 2019
a5f52ed
Refactor and simplify verification errors
2color Sep 2, 2019
6564e7c
Center the text to be aligned with the loading ring
2color Sep 2, 2019
cb159c1
subscriptions from: make submit button wide
2color Sep 2, 2019
0911d01
Revert "Render nothing while authenticating"
2color Sep 2, 2019
a4e109b
Style loading states
2color Sep 2, 2019
2bda588
Merge remote-tracking branch 'origin/newstyle' into newstyle-notifica…
2color Sep 2, 2019
a3205d1
Set consistent padding of 2/3 GU
2color Sep 2, 2019
dfe4899
Remove unused var
2color Sep 2, 2019
1992b1c
Style verify fetching state
2color Sep 2, 2019
0f788d0
Add automatic redirection upon successful verification
2color Sep 2, 2019
8eebf90
Unsubscribe from table modal
2color Sep 2, 2019
b8444bc
Render form loading only while apps are loading
2color Sep 2, 2019
987d05b
Fix bug with email input losing focus
2color Sep 2, 2019
1f1dec3
Merge branch 'master' into newstyle-notifications
2color Sep 3, 2019
6bda0ca
Improve mobile styles for feedback box
2color Sep 3, 2019
aa813ed
Make subscribe wide only in small viewports
2color Sep 3, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ class Wrapper extends React.PureComponent {
<GlobalPreferences
locator={locator}
wrapper={wrapper}
apps={apps}
onScreenChange={this.openPreferences}
onClose={this.closePreferences}
/>
Expand Down
47 changes: 36 additions & 11 deletions src/components/GlobalPreferences/GlobalPreferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
useViewport,
} from '@aragon/ui'
import { Transition, animated } from 'react-spring'
import { AragonType } from '../../prop-types'
import { AragonType, AppType } from '../../prop-types'
import { useEsc } from '../../hooks'
import Network from './Network/Network'
import Notifications from './Notifications/Notifications'
Expand All @@ -33,13 +33,20 @@ const SECTIONS = new Map([
const PATHS = Array.from(SECTIONS.keys())
const VALUES = Array.from(SECTIONS.values())

const CUSTOM_LABELS_INDEX = 0
const NETWORK_INDEX = 1
const NOTIFICATIONS_INDEX = 2
const HELP_AND_FEEDBACK_INDEX = 3

function GlobalPreferences({
apps,
compact,
locator,
onClose,
onNavigation,
onScreenChange,
sectionIndex,
subsection,
wrapper,
}) {
const toast = useToast()
Expand Down Expand Up @@ -97,12 +104,20 @@ function GlobalPreferences({
selected={sectionIndex}
/>
<main>
{sectionIndex === 0 && (
{sectionIndex === CUSTOM_LABELS_INDEX && (
<CustomLabels dao={dao} wrapper={wrapper} locator={locator} />
)}
{sectionIndex === 1 && <Network wrapper={wrapper} />}
{sectionIndex === 2 && <Notifications />}
{sectionIndex === 3 && <HelpAndFeedback />}
{sectionIndex === NETWORK_INDEX && <Network wrapper={wrapper} />}
{sectionIndex === NOTIFICATIONS_INDEX && (
<Notifications
apps={apps}
dao={dao}
subsection={subsection}
handleNavigation={onNavigation}
navigationIndex={2}
/>
)}
{sectionIndex === HELP_AND_FEEDBACK_INDEX && <HelpAndFeedback />}
</main>
</React.Fragment>
)}
Expand All @@ -111,18 +126,20 @@ function GlobalPreferences({
}

GlobalPreferences.propTypes = {
apps: PropTypes.arrayOf(AppType).isRequired,
compact: PropTypes.bool,
locator: PropTypes.object,
onClose: PropTypes.func.isRequired,
onNavigation: PropTypes.func.isRequired,
onScreenChange: PropTypes.func.isRequired,
sectionIndex: PropTypes.number,
subsection: PropTypes.string,
wrapper: AragonType,
}

function useGlobalPreferences({ locator = {}, onScreenChange }) {
const [sectionIndex, setSectionIndex] = useState(null)

const [subsection, setSubsection] = useState(null)
const handleNavigation = useCallback(
index => {
onScreenChange(PATHS[index])
Expand All @@ -132,14 +149,21 @@ function useGlobalPreferences({ locator = {}, onScreenChange }) {

useEffect(() => {
const { preferences: { path = '' } = {} } = locator
if (!path || !SECTIONS.has(path)) {
if (!path) {
setSectionIndex(null)
return
}
setSectionIndex(PATHS.findIndex(item => item === path))
}, [locator])
const index = PATHS.findIndex(item => path.startsWith(item))
setSectionIndex(index === -1 ? null : index)

// subsection is the part after the PATH, e.g. for `?p=/notifications/verify` - `/verify`
const subsection = index !== -1 ? path.substring(PATHS[index].length) : null
Copy link
Contributor

@AquiGorka AquiGorka Aug 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const subsection = index !== -1 ? path.substring(PATHS[index].length) : null
const [, subsection = null] = index !== -1 ? PATHS[index].split('/') : ''

or

Suggested change
const subsection = index !== -1 ? path.substring(PATHS[index].length) : null
const [, subsection = null] = (PATHS[index] || '').split('/')

Copy link
Contributor Author

@2color 2color Aug 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The subsection is derived from the current path. Splitting PATHS[index] doesn't take the current path into account. I don't think this would work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, true sorry about suggesting something erroneous, but you get the gist, just an idea to use split + array destructuring, nothing else


setSubsection(subsection)
// Does the current path start with any of the declared route paths
}, [locator, sectionIndex])

return { sectionIndex, handleNavigation }
return { sectionIndex, subsection, handleNavigation }
}

function Close({ compact, onClick }) {
Expand Down Expand Up @@ -176,7 +200,7 @@ Close.propTypes = {
}

function AnimatedGlobalPreferences(props) {
const { sectionIndex, handleNavigation } = useGlobalPreferences({
const { sectionIndex, subsection, handleNavigation } = useGlobalPreferences({
locator: props.locator,
onScreenChange: props.onScreenChange,
})
Expand Down Expand Up @@ -218,6 +242,7 @@ function AnimatedGlobalPreferences(props) {
{...props}
compact={compact}
sectionIndex={sectionIndex}
subsection={subsection}
onNavigation={handleNavigation}
/>
</AnimatedWrap>
Expand Down
195 changes: 195 additions & 0 deletions src/components/GlobalPreferences/Notifications/ManageNotifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import React, { useState, useEffect, useCallback } from 'react'
import PropTypes from 'prop-types'
import { AppType } from '../../../prop-types'
import {
Box,
Button,
GU,
IconTrash,
IconCheck,
LoadingRing,
useTheme,
Split,
} from '@aragon/ui'
import { getSubscriptions, deleteAccount } from './notification-service-api'
import {
NOTIFICATION_SERVICE_TOKEN_KEY,
NOTIFICATION_SERVICE_EMAIL_KEY,
} from './constants'
import SubscriptionsForm from './SubscriptionsForm'
import SubscriptionsTable from './SubscriptionsTable'
import { DeleteAccountConfirmationModal } from './NotificationModals'

export default function ManageNotifications({
apps,
dao,
email,
onLogout,
token,
onServiceUnavailable,
}) {
const [apiError, setApiError] = useState(null)
const [isFetching, setIsFetching] = useState(true)
const [subscriptions, setSubscriptions] = useState([])

const fetchSubscriptions = useCallback(() => {
setIsFetching(true)
return getSubscriptions(token)
.then(subscriptions => {
setApiError(null) // reset the error after successfully fetching
setSubscriptions(subscriptions)
setIsFetching(false)
return subscriptions
})
.catch(error => {
setIsFetching(false)
setApiError(error)
})
}, [token, setSubscriptions, setIsFetching, setApiError])

useEffect(() => {
if (!token) {
return
}
fetchSubscriptions()
}, [fetchSubscriptions, token])

useEffect(() => {
// Effect for handling api errors
if (!apiError) {
return
}
if (apiError instanceof TypeError) {
onServiceUnavailable()
} else {
console.error('Unhandled API error:', apiError)
}
}, [apiError, onServiceUnavailable])

return (
<React.Fragment>
<Split
primary={
<SubscriptionsForm
onApiError={setApiError}
fetchSubscriptions={fetchSubscriptions}
dao={dao}
apps={apps}
token={token}
isFetchingSubscriptions={isFetching}
subscriptions={subscriptions}
/>
}
secondary={
<React.Fragment>
<Box heading="Signed In With Email">
{email}
<Button
css={`
margin-top: ${2 * GU}px;
`}
wide
onClick={onLogout}
>
Sign Out
</Button>
</Box>
<DeleteAccount
onApiError={setApiError}
token={token}
onLogout={onLogout}
/>
</React.Fragment>
}
/>
{(apiError || subscriptions.length > 0) && (
<SubscriptionsTable
apps={apps}
apiError={apiError}
onApiError={setApiError}
authToken={token}
subscriptions={subscriptions}
fetchSubscriptions={fetchSubscriptions}
isFetchingSubscriptions={isFetching}
/>
)}
</React.Fragment>
)
}

ManageNotifications.propTypes = {
apps: PropTypes.arrayOf(AppType).isRequired,
dao: PropTypes.string,
email: PropTypes.string,
onLogout: PropTypes.func,
onServiceUnavailable: PropTypes.func,
token: PropTypes.string,
}

function DeleteAccount({ token, onLogout, onApiError }) {
const [isFetching, setIsFetching] = useState(false)
const [isAccountDeleted, setIsAccountDeleted] = useState(false)
const theme = useTheme()

const handleDeleteAccount = useCallback(async () => {
try {
setIsFetching(true)
await deleteAccount(token)
localStorage.removeItem(NOTIFICATION_SERVICE_TOKEN_KEY)
localStorage.removeItem(NOTIFICATION_SERVICE_EMAIL_KEY)
setIsAccountDeleted(true)
onLogout()
} catch (e) {
onApiError(e)
}
setIsFetching(false)
}, [token, onLogout, onApiError])

const [isModalOpen, setIsModalOpen] = useState(false)

const onClick = useCallback(() => {
setIsModalOpen(true)
}, [setIsModalOpen])

const onCloseModal = useCallback(() => {
setIsModalOpen(false)
}, [setIsModalOpen])

const onModalConfirm = useCallback(() => {
setIsModalOpen(false)
handleDeleteAccount()
}, [handleDeleteAccount, setIsModalOpen])

return (
<React.Fragment>
<DeleteAccountConfirmationModal
visible={isModalOpen}
onConfirm={onModalConfirm}
onClose={onCloseModal}
/>
<Box heading="Email Notification Data">
<Button wide onClick={onClick}>
{isFetching ? (
<LoadingRing />
) : isAccountDeleted ? (
<IconCheck />
) : (
<IconTrash
css={`
color: ${theme.negative};
margin-right: ${GU}px;
`}
/>
)}
Delete your email
</Button>
</Box>
</React.Fragment>
)
}

DeleteAccount.propTypes = {
onApiError: PropTypes.func,
onLogout: PropTypes.func,
token: PropTypes.string,
}
Loading