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 #16

Merged
merged 4 commits into from Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions components/lib/notifier.js
@@ -0,0 +1,24 @@
import { Flex, Box, Button } from 'ooni-components'
import React from 'react'
import toast, { Toaster } from 'react-hot-toast'

const NotifyComponent = () => <Toaster position='top-right' />

export const useNotifier = () => {
const Notification = React.memo(NotifyComponent)
const error = (message) => {
toast.error((t) => {
return (
<Flex justifyContent='space-between' alignItems='center' sx={{ width: '100%' }}>
<Box width={4 / 5}>{message}</Box>
<Button inverted fontSize={12} onClick={() => toast.dismiss(t.id)}>Dismiss</Button>
</Flex>
)
}, { duration: Infinity, position: 'top-right', style: { maxWidth: '600px' } })
}
return {
toast,
notify: { ...toast, error },
Notification
}
}
3 changes: 1 addition & 2 deletions components/submit/DeleteForm.js
@@ -1,5 +1,5 @@
import { useCallback } from 'react'
import { Box, Button, Flex, Heading, Input } from 'ooni-components'
import { Button, Flex, Heading, Input } from 'ooni-components'

const DeleteForm = ({ oldEntry, onDelete, onCancel, error }) => {
const handleSubmit = useCallback((e) => {
Expand All @@ -19,7 +19,6 @@ const DeleteForm = ({ oldEntry, onDelete, onCancel, error }) => {
<Button hollow onClick={onCancel}> Cancel </Button>
<Button type='submit'> Delete </Button>
</Flex>
<Box as='small' color='red6'> {error} </Box>
</form>
)
}
Expand Down
3 changes: 1 addition & 2 deletions components/submit/EditForm.js
@@ -1,5 +1,5 @@
import { useCallback, useState } from 'react'
import { Box, Button, Flex, Heading, Label as LLabel } from 'ooni-components'
import { Button, Flex, Heading, Label as LLabel } from 'ooni-components'
import { Input } from 'ooni-components/dist/components'

import CategoryList from './CategoryList'
Expand Down Expand Up @@ -87,7 +87,6 @@ export const EditForm = ({ oldEntry, error, onSubmit, onCancel, layout = 'column

{!isEdit && <Button type='submit' hollow disabled={submitting}>Add</Button>}
</Flex>
<Box color='red6'> {error} </Box>
</form>
)
}
21 changes: 21 additions & 0 deletions components/submit/SubmissionContext.js
@@ -0,0 +1,21 @@
import React from 'react'
import useSWR from 'swr'
import { apiEndpoints, customErrorRetry, fetcher } from '../lib/api'

export const SubmissionContext = React.createContext()

// TOOD: Handle when the API request fails
export const PageContextProvider = ({ children }) => {
const { data, mutate } = useSWR(apiEndpoints.SUBMISSION_STATE, fetcher, {
errorRetryCount: 2,
onErrorRetry: customErrorRetry
})

const { state, pr_url } = data ?? { state: 'CLEAN', pr_url: undefined }

return (
<SubmissionContext.Provider value={{ submissionState: state, linkToPR: pr_url, mutate }}>
{children}
</SubmissionContext.Provider>
)
}
37 changes: 18 additions & 19 deletions components/submit/SubmitButton.js
@@ -1,11 +1,12 @@
import React, { useCallback, useState } from 'react'
import React, { useCallback, useContext } from 'react'
import { Box, Button, Heading, Link, Flex } from 'ooni-components'
import styled from 'styled-components'
import useSWR, { mutate } from 'swr'
import Lottie from 'react-lottie-player'

import { apiEndpoints, customErrorRetry, fetcher, submitChanges } from '../lib/api'
import { submitChanges } from '../lib/api'
import reviewAnimation from './review-animation.json'
import { SubmissionContext } from './SubmissionContext'
import { useNotifier } from '../lib/notifier'

const FloatingBox = styled(Box)`
position: fixed;
Expand All @@ -22,39 +23,37 @@ const AttributionBox = styled(Box)`
`

const SubmitButton = () => {
const [error, setError] = useState(null)

const { data } = useSWR(apiEndpoints.SUBMISSION_STATE, fetcher, {
errorRetryCount: 2,
onErrorRetry: customErrorRetry
})
const { notify } = useNotifier()
const { submissionState, linkToPR, mutate } = useContext(SubmissionContext)

const onSubmit = useCallback(() => {
submitChanges().then(() => {
mutate(apiEndpoints.SUBMISSION_STATE, true)
console.log('Submission done!')
setError(null)
const loadingNotification = notify.loading('Submitting...')
submitChanges().then(({ pr_id }) => {
mutate({ state: 'PR_OPEN', pr_url: pr_id }, true)
notify.dismiss(loadingNotification)
notify.success('Submitted!')
}).catch(e => {
notify.dismiss(loadingNotification)
notify.error(`Submission failed. Reason: ${e?.response?.data?.error ?? String(e)}`)
console.error('Submission failed')
console.error(e)
setError(`Submission failed: ${e?.response?.data?.error ?? e}`)
})
}, [])
}, [mutate, notify])

if (data && data.state === 'IN_PROGRESS') {
if (submissionState === 'IN_PROGRESS') {
return (
<FloatingBox>
<Button
fontSize={2}
ml='auto'
onClick={onSubmit}
title={error || `Current state: ${data.state}`}
title={`Current state: ${submissionState}`}
>
Submit
</Button>
</FloatingBox>
)
} else if (data && data.state === 'PR_OPEN') {
} else if (submissionState === 'PR_OPEN') {
return (
<Flex flexDirection={['column', 'row']} alignItems='center' color='gray9' bg='white' px={2} py={4} my={4} sx={{ position: 'relative' }}>
<Box width={[1, 1 / 5]} px={[5, 0]}>
Expand All @@ -68,7 +67,7 @@ const SubmitButton = () => {
<Heading h={3}>Submitted!</Heading>
<Heading h={4}>
Thank you for contributing to improve to the test lists. Your
changes are being reviewed <Link href={data.pr_url}>here.</Link> You
changes are being reviewed <Link href={linkToPR}>here.</Link> You
will be able to make further changes after this contribution has been processed by our team.
</Heading>
</Box>
Expand Down
35 changes: 22 additions & 13 deletions components/submit/UrlList.js
@@ -1,8 +1,8 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import useSWR, { mutate as globalMutate } from 'swr'
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
import useSWR from 'swr'
import { Box, Flex, Container, Heading, Link } from 'ooni-components'

import { fetcher, fetchTestList, apiEndpoints, updateURL, addURL, deleteURL, customErrorRetry } from '../lib/api'
import { fetchTestList, apiEndpoints, updateURL, addURL, deleteURL, customErrorRetry } from '../lib/api'
import Error from './Error'
import Table from './Table'
import { EditForm } from './EditForm'
Expand All @@ -11,12 +11,15 @@ import DeleteForm from './DeleteForm'
import Loading from '../Loading'
import SubmitButton from './SubmitButton'
import { getPrettyErrorMessage } from '../lib/translateErrors'
import { SubmissionContext } from './SubmissionContext'
import { useNotifier } from '../lib/notifier'

// Does these
// * Decides what data to pass down to the table
// * Controls when the table is allowed to reset its state
// e.g when editing is going on, no resetting sort order
const UrlList = ({ cc }) => {
const { notify } = useNotifier()
// holds rowIndex of row being edited
const [editIndex, setEditIndex] = useState(null)
const [deleteIndex, setDeleteIndex] = useState(null)
Expand All @@ -39,11 +42,7 @@ const UrlList = ({ cc }) => {
}
)

const { data: { state: submissionState } } = useSWR(
apiEndpoints.SUBMISSION_STATE,
fetcher,
{ initialData: { state: null } }
)
const { submissionState, mutate: mutateSubmissionState } = useContext(SubmissionContext)

const entryToEdit = useMemo(() => {
let entry = {}
Expand All @@ -66,7 +65,7 @@ const UrlList = ({ cc }) => {
}, [])

const handleSubmit = useCallback((newEntry, comment) => {
return new Promise((resolve, reject) => {
const actionPromise = new Promise((resolve, reject) => {
if (deleteIndex !== null) {
// Delete
deleteURL(cc, comment, entryToEdit).then(() => {
Expand All @@ -76,7 +75,7 @@ const UrlList = ({ cc }) => {
mutate(updatedData, true)

// Revalidate the submission state to show the submit button
globalMutate(apiEndpoints.SUBMISSION_STATE)
mutateSubmissionState()

resolve()
}).catch(e => {
Expand All @@ -92,7 +91,7 @@ const UrlList = ({ cc }) => {
mutate(updatedData, true)

// Revalidate the submission state to show the submit button
globalMutate(apiEndpoints.SUBMISSION_STATE)
mutateSubmissionState()

resolve()
}).catch(e => {
Expand All @@ -108,7 +107,7 @@ const UrlList = ({ cc }) => {
const updatedData = data.map((v, i) => editIndex === i ? updatedEntry : v)
mutate(updatedData, true)
// Revalidate the submission state to show the submit button
globalMutate(apiEndpoints.SUBMISSION_STATE)
mutateSubmissionState()

resolve()
}).catch(e => {
Expand All @@ -118,7 +117,17 @@ const UrlList = ({ cc }) => {
})
}
})
}, [editIndex, deleteIndex, entryToEdit, cc, data, mutate])
notify.promise(actionPromise, {
loading: 'Requesting...',
success: deleteIndex !== null ? 'Deleted' : editIndex === null ? 'Added' : 'Updated',
error: (err) => `Failed: ${err.toString()}`,
}, {
style: {
maxWidth: '600px'
}
})
return actionPromise
}, [notify, deleteIndex, editIndex, cc, entryToEdit, data, mutate, mutateSubmissionState])

const onCancel = () => {
setEditIndex(null)
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -20,6 +20,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-hook-form": "^7.6.4",
"react-hot-toast": "^2.1.1",
"react-icons": "^4.2.0",
"react-lottie-player": "^1.3.3",
"react-table": "^7.7.0",
Expand Down
12 changes: 11 additions & 1 deletion pages/[cc].js
@@ -1,10 +1,13 @@
import { useCallback } from 'react'
import { useRouter } from 'next/router'
import { Heading } from 'ooni-components'
import { Toaster } from 'react-hot-toast'

import Layout from '../components/Layout'
import CountryList from '../components/submit/CountryList'
import UrlList from '../components/submit/UrlList'
import { PageContextProvider } from '../components/submit/SubmissionContext'
import { useNotifier } from '../components/lib/notifier'

export default function Submit () {
const router = useRouter()
Expand All @@ -17,11 +20,18 @@ export default function Submit () {
router.push(`/${selectedCountry}`, undefined, { shallow: true })
}, [router])

const { Notification } = useNotifier()

return (
<Layout title='Url Submission'>
<Notification />
<Heading h={1}>Test List</Heading>
<CountryList defaultValue={countryCode} onChange={onCountryChange} />
{countryCode && <UrlList cc={countryCode} />}
{countryCode && (
<PageContextProvider>
<UrlList cc={countryCode} />
</PageContextProvider>
)}
</Layout>
)
}
12 changes: 12 additions & 0 deletions yarn.lock
Expand Up @@ -2069,6 +2069,11 @@ globals@^13.6.0, globals@^13.9.0:
dependencies:
type-fest "^0.20.2"

goober@^2.0.35:
version "2.0.41"
resolved "https://registry.yarnpkg.com/goober/-/goober-2.0.41.tgz#0a3d786ff9917bcf2a096eef703bf717838cbec9"
integrity sha512-kwjegMT5018zWydhOQlQneCgCtrKJaPsru7TaBWmTYV0nsMeUrM6L6O8JmNYb9UbPMgWcmtf+9p4Y3oJabIH1A==

graceful-fs@^4.1.2:
version "4.2.4"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
Expand Down Expand Up @@ -3404,6 +3409,13 @@ react-hook-form@^7.6.4:
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.6.4.tgz#f450654af23fee65f8b948397805830099346c0d"
integrity sha512-eKLRWkZcUKGYKMnKvN/VwPVc8t3pjfs69UEp/DJNS2ccGk18UKFq/XE049W+ye144aUj+1++NgUjs2/yMNuBWw==

react-hot-toast@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-hot-toast/-/react-hot-toast-2.1.1.tgz#56409ab406b534e9e58274cf98d80355ba0fdda0"
integrity sha512-Odrp4wue0fHh0pOfZt5H+9nWCMtqs3wdlFSzZPp7qsxfzmbE26QmGWIh6hG43CukiPeOjA8WQhBJU8JwtWvWbQ==
dependencies:
goober "^2.0.35"

react-icon-base@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.2.tgz#a17101dad9c1192652356096860a9ab43a0766c7"
Expand Down