diff --git a/client/src/api/profile.js b/client/src/api/profile.js index 48246ac61..f1cbcf250 100644 --- a/client/src/api/profile.js +++ b/client/src/api/profile.js @@ -1,4 +1,4 @@ -import { request } from './util' +import { request, relog } from './util' export const privateProfile = () => { return request('GET', '/users/me') @@ -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) +} diff --git a/client/src/api/util.js b/client/src/api/util.js index 7d87c2f04..8fc12ce38 100644 --- a/client/src/api/util.js +++ b/client/src/api/util.js @@ -1,7 +1,7 @@ import { route } from 'preact-router' import config from '../../../config/client' -const relog = () => { +export const relog = () => { localStorage.removeItem('token') route('/register') } diff --git a/client/src/pages/profile.js b/client/src/pages/profile.js index 8707e4818..dc4a30fe7 100644 --- a/client/src/pages/profile.js +++ b/client/src/pages/profile.js @@ -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: { @@ -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 = { @@ -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, @@ -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 @@ -99,7 +144,7 @@ export default withStyles({
-

Error

+

There was an error

{error}

@@ -122,12 +167,33 @@ export default withStyles({

Share this with your teammates to use at /login!

+
+
+

Update Information

+
+
+ } name='name' placeholder='Team Name' type='text' value={updateName} onChange={this.linkState('updateName')} /> + +
+
+
+ +
+
+
}
-
{name}
+
{name}

@@ -135,7 +201,7 @@ export default withStyles({ { score === 0 - ? ('No solves yet') + ? ('No points earned') : (placement + ' with ' + score + ' points') }

@@ -149,23 +215,27 @@ export default withStyles({
-
-
-
Solves
- - - - - - - - - - {solves.map(solve => )} - -
CategoryNamePoints
{solve.category}{solve.name}{solve.points}
-
-
+ { + solves.length !== 0 && +
+
+
Solves
+ + + + + + + + + + {solves.map(solve => )} + +
CategoryNamePoints
{solve.category}{solve.name}{solve.points}
+
+
+ } +
) diff --git a/config/client.js.example b/config/client.js.example index 9c3ef8ce1..d3e739b44 100644 --- a/config/client.js.example +++ b/config/client.js.example @@ -1,4 +1,4 @@ -export default { +const ret = { apiEndpoint: '/api/v1', staticEndpoint: '/static', ctfTitle: ' | yourCTF', @@ -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 diff --git a/server/util/index.js b/server/util/index.js index 0086ae1b4..a4575248e 100644 --- a/server/util/index.js +++ b/server/util/index.js @@ -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)