Skip to content

Commit

Permalink
Merge branch 'master' into fix/client-state-desync
Browse files Browse the repository at this point in the history
  • Loading branch information
chen-robert committed Mar 26, 2020
2 parents 9da82b6 + 5992572 commit f4e2151
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 178 deletions.
10 changes: 6 additions & 4 deletions client/src/api/profile.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { request, relog } from './util'
import { toasts } from '../util'

export const privateProfile = () => {
return request('GET', '/users/me')
Expand Down Expand Up @@ -29,10 +28,13 @@ export const updateAccount = (name, division) => {
.then(resp => {
switch (resp.kind) {
case 'goodUserUpdate':
return resp.data
return {
data: resp.data
}
default:
toasts.useToast().add(resp.message)
return null
return {
error: resp.message
}
}
})
}
86 changes: 86 additions & 0 deletions client/src/components/toast.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { createContext } from 'preact'
import { useState, useContext, useCallback, useEffect } from 'preact/hooks'
import withStyles from './jss'

const ToastCtx = createContext()

// Use sequential IDs
let toastIdCounter = 0

function genToastId () {
return toastIdCounter++
}

function Toast ({ children, remove, type, id }) {
const wrappedRemove = useCallback(() => remove(id), [remove, id])
useEffect(() => {
const duration = 1000 * 5
const timeout = setTimeout(wrappedRemove, duration)

return () => clearTimeout(timeout)
}, [wrappedRemove])

return (
<div className={`toast toast--${type}`}>
{children}
<button onClick={wrappedRemove} className='btn-close' />
</div>
)
}

const ToastContainer = withStyles({
container: {
position: 'fixed',
top: '1em',
right: '1em',
zIndex: 9999,
width: '320px'
}
}, ({ classes, ...props }) => <div class={classes.container} {...props} />)

export function ToastProvider ({ children }) {
const [toasts, setToasts] = useState([])

const remove = useCallback((id) => {
setToasts(toasts => toasts.filter(t => id !== t.id))
}, [])
const add = useCallback((data) => {
const id = genToastId()
const toast = {
...data,
id
}
setToasts(toasts => [...toasts, toast])
return () => remove(id)
}, [remove])

return (
<ToastCtx.Provider value={add}>
{children}
{ toasts.length > 0 &&
<ToastContainer>
{ toasts.map(({ id, type, body }) =>
<Toast type={type} key={id} id={id} remove={remove}>
{body}
</Toast>
)}
</ToastContainer>
}
</ToastCtx.Provider>
)
}

export function useToast () {
const toast = useContext(ToastCtx)

return { toast }
}

export function withToast (Component) {
return function Toasted (props) {
const { toast } = useToast()
return (
<Component {...props} toast={toast} />
)
}
}
14 changes: 8 additions & 6 deletions client/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Header from './components/header'
import { Home, Registration, Login, Profile, Challenges, Scoreboard, Error, Sponsors, Verify, Logout } from './pages'
import 'cirrus-ui'

import util from './util'
import { ToastProvider } from './components/toast'

class App extends Component {
state = {
Expand Down Expand Up @@ -48,10 +48,12 @@ class App extends Component {

return (
<div id='app'>
<Header paths={currentPaths} currentPath={currentPath} />
<Router onChange={this.handleRoute}>
{allPaths}
</Router>
<ToastProvider>
<Header paths={currentPaths} currentPath={currentPath} />
<Router onChange={this.handleRoute}>
{allPaths}
</Router>
</ToastProvider>
</div>
)
}
Expand All @@ -63,4 +65,4 @@ class App extends Component {
}
}

export default util.toasts.withToastProvider(App)
export default App
8 changes: 4 additions & 4 deletions client/src/pages/challenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'linkstate/polyfill'
import withStyles from '../components/jss'

import { getChallenges, submitFlag, getPrivateSolves } from '../api/challenges'
import util from '../util'
import { withToast } from '../components/toast'

export default withStyles({
frame: {
Expand All @@ -19,7 +19,7 @@ export default withStyles({
showSolved: {
marginBottom: '10px'
}
}, class Challenges extends Component {
}, withToast(class Challenges extends Component {
state = {
problems: [],
categories: {}, // Dict with {string name, bool filter}
Expand Down Expand Up @@ -63,7 +63,7 @@ export default withStyles({
.then(({ error }) => {
if (error === undefined) {
// Flag was submitted successfully
util.toasts.useToast().add('Flag successfully submitted!')
this.props.toast({ body: 'Flag successfully submitted!', type: 'success' })
this.setState(prevState => ({
solveIDs: [...prevState.solveIDs, id]
}))
Expand Down Expand Up @@ -214,4 +214,4 @@ export default withStyles({
</div>
)
}
})
}))
68 changes: 38 additions & 30 deletions client/src/pages/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'linkstate/polyfill'
import withStyles from '../components/jss'

import { privateProfile, publicProfile, deleteAccount, updateAccount } from '../api/profile'
import { withToast } from '../components/toast'
import Form from '../components/form'
import Modal from '../components/modal'
import util from '../util'
Expand Down Expand Up @@ -79,29 +80,7 @@ const DeleteModal = withStyles({
)
})

export default withStyles({
quote: {
fontSize: 'small',
overflowWrap: 'break-word'
},
icon: {
'& svg': {
verticalAlign: 'middle',
height: '20px',
fill: '#333'
},
marginRight: '25px'
},
form: {
'& button': {
margin: 0,
lineHeight: '20px',
padding: '10px',
float: 'right'
},
padding: '0 !important'
}
}, class Profile extends Component {
class Profile extends Component {
state = {
loaded: false,
name: '',
Expand Down Expand Up @@ -190,17 +169,22 @@ export default withStyles({
})

updateAccount(this.state.updateName, this.state.updateDivision)
.then(resp => {
.then(({ error, data }) => {
this.setState({
disabledButton: false
})

if (resp) {
this.setState({
name: resp.user.name,
division: divisionMap.get(Number.parseInt(resp.user.division))
})
if (error !== undefined) {
this.props.toast({ body: error, type: 'error' })
return
}

this.props.toast({ body: 'Profile updated' })

this.setState({
name: data.user.name,
division: divisionMap.get(Number.parseInt(data.user.division))
})
})
}

Expand Down Expand Up @@ -342,4 +326,28 @@ export default withStyles({
</div>
)
}
})
}

export default withStyles({
quote: {
fontSize: 'small',
overflowWrap: 'break-word'
},
icon: {
'& svg': {
verticalAlign: 'middle',
height: '20px',
fill: '#333'
},
marginRight: '25px'
},
form: {
'& button': {
margin: 0,
lineHeight: '20px',
padding: '10px',
float: 'right'
},
padding: '0 !important'
}
}, withToast(Profile))
3 changes: 1 addition & 2 deletions client/src/util/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
module.exports = {
strings: require('./strings'),
toasts: require('./toasts')
strings: require('./strings')
}
22 changes: 0 additions & 22 deletions client/src/util/toasts/Toast.js

This file was deleted.

5 changes: 0 additions & 5 deletions client/src/util/toasts/context.js

This file was deleted.

4 changes: 0 additions & 4 deletions client/src/util/toasts/index.js

This file was deleted.

10 changes: 0 additions & 10 deletions client/src/util/toasts/useToast.js

This file was deleted.

58 changes: 0 additions & 58 deletions client/src/util/toasts/withToastProvider.js

This file was deleted.

0 comments on commit f4e2151

Please sign in to comment.