Skip to content

Commit

Permalink
Merge pull request #73 from redpwn/feature/delete-frontend
Browse files Browse the repository at this point in the history
Update profile frontend card
  • Loading branch information
chen-robert committed Feb 29, 2020
2 parents 5fa4563 + 835808e commit 89c7be9
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 29 deletions.
20 changes: 19 additions & 1 deletion client/src/api/profile.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { request } from './util'
import { request, relog } from './util'

export const privateProfile = () => {
return request('GET', '/users/me')
Expand All @@ -9,3 +9,21 @@ export const publicProfile = uuid => {
return request('GET', '/users/' + encodeURIComponent(uuid))
.then(resp => resp.data)
}

export const deleteAccount = () => {
return request('DELETE', '/users/me')
.then(resp => {
switch (resp.kind) {
case 'goodUserDelete':
return relog()
}
})
}

export const updateAccount = (name, division) => {
return request('PATCH', '/users/me', {
name,
division: Number.parseInt(division)
})
.then(resp => resp.data)
}
2 changes: 1 addition & 1 deletion client/src/api/util.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { route } from 'preact-router'
import config from '../../../config/client'

const relog = () => {
export const relog = () => {
localStorage.removeItem('token')
route('/register')
}
Expand Down
116 changes: 93 additions & 23 deletions client/src/pages/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@ import config from '../../../config/client'
import 'linkstate/polyfill'
import withStyles from '../components/jss'

import { privateProfile, publicProfile } from '../api/profile'
import { privateProfile, publicProfile, deleteAccount, updateAccount } from '../api/profile'
import Form from '../components/form'
import util from '../util'
import Trophy from '../../static/icons/trophy.svg'
import AddressBook from '../../static/icons/address-book.svg'
import UserCircle from '../../static/icons/user-circle.svg'

const divisionMap = new Map()

for (const division of Object.entries(config.divisions)) {
divisionMap.set(division[1], division[0])
}

export default withStyles({
quote: {
Expand All @@ -20,6 +28,15 @@ export default withStyles({
fill: '#333'
},
marginRight: '25px'
},
form: {
'& button': {
margin: 0,
lineHeight: '20px',
padding: '10px',
float: 'right'
},
padding: '0 !important'
}
}, class Profile extends Component {
state = {
Expand All @@ -31,13 +48,18 @@ export default withStyles({
teamToken: '',
solves: [],
uuid: '',
error: undefined
error: undefined,
updateName: '',
updateDivision: 0,
disabledButton: false
}

processGeneric ({ name, division, score, divisionPlace, solves }) {
this.setState({
name: name,
updateName: name,
division: division,
updateDivision: config.divisions[division],
placement: util.strings.placementString(divisionPlace),
score,
solves: solves,
Expand Down Expand Up @@ -87,7 +109,30 @@ export default withStyles({
}
}

render ({ classes }, { name, division, placement, score, teamToken, solves, error, loaded }) {
handleUpdate = e => {
e.preventDefault()

this.setState({
disabledButton: true
})

updateAccount(this.state.updateName, this.state.updateDivision)
.then(resp => this.setState({
name: resp.user.name,
division: divisionMap.get(Number.parseInt(resp.user.division)),
disabledButton: false
}))
}

handleDelete = e => {
const resp = prompt('Please type your team name to confirm: ' + this.state.name)

if (resp === this.state.name) {
deleteAccount()
}
}

render ({ classes }, { name, division, placement, score, teamToken, solves, error, loaded, updateName, updateDivision, disabledButton }) {
const priv = this.isPrivate()
const hasError = error !== undefined

Expand All @@ -99,7 +144,7 @@ export default withStyles({
<div class='col-4'>
<div class='card u-flex u-flex-column'>
<div class='content'>
<p>Error</p>
<p class='title'>There was an error</p>
<p class='font-thin'>{error}</p>
</div>
</div>
Expand All @@ -122,20 +167,41 @@ export default withStyles({
<p class='font-thin'>Share this with your teammates to use at <a href='/login'>/login</a>!</p>
</div>
</div>
<div class='card u-flex u-flex-column'>
<div class='content'>
<p style='margin-bottom: 0'>Update Information</p>
<div class='row u-center'>
<Form class={`col-12 ${classes.form}`} onSubmit={this.handleUpdate} disabled={disabledButton} buttonText='Update'>
<input autofocus required icon={<UserCircle />} name='name' placeholder='Team Name' type='text' value={updateName} onChange={this.linkState('updateName')} />
<select required class='select' name='division' value={updateDivision} onChange={this.linkState('updateDivision')}>
<option value='' disabled selected>Division</option>
{
Object.entries(config.divisions).map(([name, code]) => {
return <option key={code} value={code}>{name}</option>
})
}
</select>
</Form>
</div>
<div class='u-center action-bar' style='margin: 0 0.5rem'>
<button class='btn-small btn-danger outline' style='border-color: var(--btn-color)' onClick={this.handleDelete}>Delete Account</button>
</div>
</div>
</div>
</div>
}
<div class='col-6'>
<div class='card u-flex u-flex-column'>
<div class='content'>
<h5 class='title'>{name}</h5>
<h5 class='title' style='text-overflow: ellipsis; overflow-x: hidden;'>{name}</h5>
<div class='action-bar'>
<p>
<span class={`icon ${classes.icon}`}>
<Trophy />
</span>
{
score === 0
? ('No solves yet')
? ('No points earned')
: (placement + ' with ' + score + ' points')
}
</p>
Expand All @@ -149,23 +215,27 @@ export default withStyles({
</div>
</div>

<div class='card u-flex u-flex-column'>
<div class='content'>
<h5 class='title u-text-center'>Solves</h5>
<table class='table borderless'>
<thead>
<tr>
<th>Category</th>
<th>Name</th>
<th>Points</th>
</tr>
</thead>
<tbody>
{solves.map(solve => <tr key={solve.name}><td>{solve.category}</td><td>{solve.name}</td><td>{solve.points}</td></tr>)}
</tbody>
</table>
</div>
</div>
{
solves.length !== 0 &&
<div class='card u-flex u-flex-column'>
<div class='content'>
<h5 class='title u-text-center'>Solves</h5>
<table class='table borderless'>
<thead>
<tr>
<th>Category</th>
<th>Name</th>
<th>Points</th>
</tr>
</thead>
<tbody>
{solves.map(solve => <tr key={solve.name}><td>{solve.category}</td><td>{solve.name}</td><td>{solve.points}</td></tr>)}
</tbody>
</table>
</div>
</div>
}

</div>
</div>
)
Expand Down
9 changes: 8 additions & 1 deletion config/client.js.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export default {
const ret = {
apiEndpoint: '/api/v1',
staticEndpoint: '/static',
ctfTitle: ' | yourCTF',
Expand All @@ -17,3 +17,10 @@ export default {
}
]
}

const shared = require('./shared')
Object.entries(shared).forEach(([key, val]) => {
if (ret[key] === undefined) ret[key] = val
})

module.exports = ret
8 changes: 5 additions & 3 deletions server/util/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ module.exports = {
return require(m)
},
enableCORS: (req, res, next) => {
res.header('Access-Control-Allow-Origin', config.origin)
res.header('Access-Control-Allow-Headers', 'Authorization, Content-Type')
res.header('Access-Control-Allow-Methods', 'GET, POST')
if (config.origin !== undefined) {
res.header('Access-Control-Allow-Origin', config.origin)
res.header('Access-Control-Allow-Headers', 'Authorization, Content-Type')
res.header('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE')
}

if (req.method === 'OPTIONS') {
res.sendStatus(200)
Expand Down

0 comments on commit 89c7be9

Please sign in to comment.