Skip to content

Commit

Permalink
Merge pull request #24 from ciatph/dev
Browse files Browse the repository at this point in the history
v1.0.7
  • Loading branch information
ciatph authored Apr 8, 2022
2 parents 0ad7288 + 428e4b9 commit 14ccf84
Show file tree
Hide file tree
Showing 22 changed files with 311 additions and 38 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ A basic web app client in the **/client** directory will show basic API usage an
- > **NOTE:** Take note to make sure that the value starts and ends with a double-quote on WINDOWS OS localhost. Some systems may or may not require the double-quotes (i.e., Ubuntu running on heroku).
- `ALLOWED_ORIGINS`
- IP/domain origins in comma-separated values that are allowed to access the API
- `EMAIL_WHITELIST`
- Comma-separated email addresses linked to Firebase Auth UserRecords that are not allowed to be deleted or updated (write-protected)

### client

Expand Down
2 changes: 1 addition & 1 deletion client/src/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ module.exports = {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 2018,
ecmaVersion: 2020,
sourceType: 'module'
},
plugins: [
Expand Down
2 changes: 1 addition & 1 deletion client/src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types'
import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom'
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'
import Container from '@mui/material/Container'
import Navigation from './components/common/navigation'
import WithAuth from './containers/login/withauth'
Expand Down
101 changes: 101 additions & 0 deletions client/src/components/common/userform/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import PropTypes from 'prop-types'
import Button from '@mui/material/Button'
import Box from '@mui/material/Box'
import FormControlLabel from '@mui/material/FormControlLabel'
import InputLabel from '@mui/material/InputLabel'
import MenuItem from '@mui/material/MenuItem'
import Select from '@mui/material/Select'
import Switch from '@mui/material/Switch'
import TextField from '@mui/material/TextField'
import AlertMessage from '../alert_message'
import styles from './styles'

function UserForm (props) {
const { state, loadstatus, onTextChange, onBtnClick, type = 'create' } = props

return (
<Box
component='form'
noValidate
autoComplete='off'
sx={styles.container}
>

{type !== 'create' &&
<TextField
id='uid'
label='Enter UID'
variant='outlined'
size='small'
disabled
value={state.uid}
/>}

<TextField
id='email'
label='Enter email'
variant='outlined'
size='small'
disabled={loadstatus.isLoading}
value={state.email}
onChange={onTextChange}
/>

<TextField
id='displayname'
label='Enter display name'
variant='outlined'
size='small'
disabled={loadstatus.isLoading}
value={state.displayname}
onChange={onTextChange}
/>

<InputLabel sx={styles.formlabel} id='accountlevel-label'>Account Type</InputLabel>
<Select
labelId='accountlevel-label'
id='account_level'
size='small'
disabled={loadstatus.isLoading}
value={state.account_level}
onChange={onTextChange}
>
<MenuItem value={1} size='small'>Superadmin</MenuItem>
<MenuItem value={2} size='small'>Admin</MenuItem>
</Select>

<FormControlLabel control={
<Switch checked={state.disabled} id='disabled' name='disabled' onChange={onTextChange} />}
label="Account Disabled" />
<FormControlLabel control={
<Switch checked={state.emailverified} id='emailverified' name='emailverified' onChange={onTextChange} />}
label="Email Verified" />

<Button
variant='contained'
sx={styles.button}
disabled={loadstatus.isLoading}
onClick={onBtnClick}
>Submit
</Button>

{(loadstatus.message !== '' || loadstatus.error !== '') &&
<AlertMessage
severity={(loadstatus.error !== '') ? 'error' : 'success'}
title={(loadstatus.error !== '') ? 'Error' : 'Success'}
message={(loadstatus.error !== '') ? loadstatus.error : loadstatus.message}
/>
}
</Box>
)
}

UserForm.propTypes = {
state: PropTypes.object,
loadstatus: PropTypes.object,
onTextChange: PropTypes.func,
onBtnClick: PropTypes.func,
type: PropTypes.string
}

export default UserForm
17 changes: 17 additions & 0 deletions client/src/components/common/userform/styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const styles = {
container: {
width: '400px',
display: 'flex',
flexDirection: 'column',
'& .MuiTextField-root, button': {
marginTop: (theme) => theme.spacing(2)
}
},
formlabel: {
fontSize: '12px',
marginTop: (theme) => theme.spacing(1),
marginBottom: '4px'
}
}

export default styles
18 changes: 14 additions & 4 deletions client/src/components/dashboard/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import PropTypes from 'prop-types'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
Expand All @@ -7,7 +8,7 @@ import Stack from '@mui/material/Stack'
import AlertMessage from '../../components/common/alert_message'
import styles from './styles'

function Dashboard ({ currentUser, users, loadstatus, onBtnClick }) {
function Dashboard ({ currentUser, users, loadstatus, onBtnClick, onBtnEditClick }) {
return (
<div>
<h1>Dashboard</h1>
Expand Down Expand Up @@ -58,14 +59,22 @@ function Dashboard ({ currentUser, users, loadstatus, onBtnClick }) {
}
</span>
</div>
<div>
<Box>
<Button
variant='contained'
size='small'
disabled={loadstatus.isLoading}
onClick={() => onBtnClick(item.uid)}
>Delete</Button>
</div>

<Button
sx={styles.buttons}
variant='contained'
size='small'
disabled={loadstatus.isLoading}
onClick={() => onBtnEditClick(item)}
>Edit</Button>
</Box>
</CardContent>
</Card>
})}
Expand All @@ -77,7 +86,8 @@ Dashboard.propTypes = {
currentUser: PropTypes.object,
users: PropTypes.array,
loadstatus: PropTypes.object,
onBtnClick: PropTypes.func
onBtnClick: PropTypes.func,
onBtnEditClick: PropTypes.func
}

export default Dashboard
6 changes: 6 additions & 0 deletions client/src/components/dashboard/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ const styles = {
'& span': {
fontSize: '14px'
}
},
buttons: {
display: 'flex',
flexDirection: 'column',
marginTop: (theme) => theme.spacing(1),
width: '100%'
}
}

Expand Down
32 changes: 20 additions & 12 deletions client/src/containers/createuser/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useState } from 'react'
import { createUser } from '../../utils/service'
import Home from '../../components/createuser'
import UserForm from '../../components/common/userform'

const defaultState = {
email: '', displayname: '', account_level: '1'
email: '', displayname: '', account_level: '1', disabled: false, emailverified: false
}

const defaultLoadingState = {
Expand All @@ -15,10 +15,14 @@ function CreateUserContainer () {
const [loading, setLoading] = useState(defaultLoadingState)

const onInputChange = (e) => {
const { id, value } = e.target
let { id, value, checked } = e.target
const key = (id !== undefined) ? id : 'account_level'
setState({ ...state, [key]: value })

if (['emailverified', 'disabled'].includes(key)) {
value = checked
}

setState({ ...state, [key]: value })
if (loading.error !== '' || loading.message !== '') {
setLoading(defaultLoadingState)
}
Expand All @@ -28,19 +32,23 @@ function CreateUserContainer () {
try {
setLoading({ ...loading, isLoading: true })
await createUser(state)
setLoading({ ...loading, isLoading: false, message: 'User created!' })
setLoading(prev => ({ ...defaultLoadingState, message: 'User created!' }))
} catch (err) {
setLoading({ ...loading, isLoading: false, error: err.response ? err.response.data : err.message })
setLoading(prev => ({ ...defaultLoadingState, error: err.response ? err.response.data : err.message }))
}
}

return (
<Home
state={state}
loadstatus={loading}
onTextChange={onInputChange}
onBtnClick={createNewUser}
/>
<div>
<h1>Create User</h1>

<UserForm
state={state}
loadstatus={loading}
onTextChange={onInputChange}
onBtnClick={createNewUser}
/>
</div>
)
}

Expand Down
23 changes: 20 additions & 3 deletions client/src/containers/dashboard/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import PropTypes from 'prop-types'
import Dashboard from '../../components/dashboard'
import { getUsers, deleteUser } from '../../utils/service'
Expand All @@ -10,6 +11,7 @@ const defaultLoadingState = {
function DashboardContainer (props) {
const [state, setState] = useState([])
const [loading, setLoading] = useState(defaultLoadingState)
const navigate = useNavigate()

useEffect(() => {
let loaded = true
Expand All @@ -32,22 +34,37 @@ function DashboardContainer (props) {

const onDeleteUser = async (uid) => {
try {
setLoading({ ...loading, isLoading: true })
setLoading({ ...defaultLoadingState, isLoading: true })
await deleteUser(uid)
const result = await getUsers()
setState(prev => result.data.users)
setLoading({ ...loading, isLoading: false, message: 'User deleted!' })
setLoading(prev => ({ ...defaultLoadingState, message: 'User deleted!' }))
} catch (err) {
setLoading({ ...loading, isLoading: false, error: err.response ? err.response.data : err.message })
setLoading(prev => ({ ...defaultLoadingState, error: err.response ? err.response.data : err.message }))
}
}

const onEditUser = (info) => {
navigate('/edit', {
replace: true,
state: {
uid: info.uid,
email: info.email,
displayname: info.displayName,
disabled: info.disabled,
emailverified: info.emailVerified,
account_level: (info.customClaims) ? info.customClaims.account_level : -1
}
})
}

return (
<Dashboard
currentUser={props.currentUser}
users={state}
loadstatus={loading}
onBtnClick={onDeleteUser}
onBtnEditClick={onEditUser}
/>
)
}
Expand Down
58 changes: 58 additions & 0 deletions client/src/containers/updateuser/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useState } from 'react'
import { useLocation } from 'react-router-dom'
import { updateUser } from '../../utils/service'
import UserForm from '../../components/common/userform'

const defaultState = {
email: '', displayname: '', account_level: '1', disabled: false, emailverified: false
}

const defaultLoadingState = {
isLoading: false, error: '', message: ''
}

function UpdateUserContainer () {
const location = useLocation()
const [state, setState] = useState(location?.state || defaultState)
const [loading, setLoading] = useState(defaultLoadingState)

const onInputChange = (e) => {
let { id, value, checked } = e.target
const key = (id !== undefined) ? id : 'account_level'

if (['emailverified', 'disabled'].includes(key)) {
value = checked
}

setState({ ...state, [key]: value })
if (loading.error !== '' || loading.message !== '') {
setLoading(defaultLoadingState)
}
}

const onBtnUpdateClick = async () => {
try {
setLoading({ ...loading, isLoading: true })
await updateUser(state)
setLoading(prev => ({ ...defaultLoadingState, message: 'User info updated.' }))
} catch (err) {
setLoading(prev => ({ ...defaultLoadingState, error: err.response ? err.response.data : err.message }))
}
}

return (
<div>
<h1>Update User</h1>

<UserForm
state={state}
loadstatus={loading}
onTextChange={onInputChange}
onBtnClick={onBtnUpdateClick}
type='update'
/>
</div>
)
}

export default UpdateUserContainer
Loading

0 comments on commit 14ccf84

Please sign in to comment.