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

Update profile frontend card #73

Merged
merged 4 commits into from
Feb 29, 2020
Merged
Show file tree
Hide file tree
Changes from 3 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
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)
Copy link
Member

Choose a reason for hiding this comment

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

prompt blocks all execution the main thread, we should not be using it.


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