From a354b1ddc8c25617eb8a74d2587e84d97216e7e6 Mon Sep 17 00:00:00 2001 From: Florian Hartwich Date: Sun, 11 Jun 2017 13:18:03 +0200 Subject: [PATCH] Optimize sign up ux; Add confirmation for promotion --- api/routes/authenticate.js | 2 +- api/routes/request.js | 82 +++++++++++++++++-- api/server.js | 2 +- api/signature-tool/signature-tool.js | 2 +- static/src/app/login/signup.component.html | 17 +++- static/src/app/login/signup.component.ts | 9 +- static/src/app/models/model-interfaces.ts | 1 + .../confirm-award.component.html | 6 ++ .../confirm-award/confirm-award.component.ts | 7 ++ .../confirm-promotion.component.css | 2 +- .../confirm-promotion.component.html | 65 ++------------- .../confirm-promotion.component.ts | 80 +++++------------- .../services/login-service/login-service.ts | 6 +- .../promotion-service/promotion.service.ts | 10 +++ .../award-user/award-user.component.html | 2 +- 15 files changed, 153 insertions(+), 140 deletions(-) diff --git a/api/routes/authenticate.js b/api/routes/authenticate.js index 1dfb8aa..11958cf 100644 --- a/api/routes/authenticate.js +++ b/api/routes/authenticate.js @@ -102,7 +102,7 @@ let create = (userParam) => { if (user) { // username already exists - deferred.reject('Username "' + userParam.username + '" is already taken'); + deferred.reject(new Error("Username already exists")); } else { createUser(); } diff --git a/api/routes/request.js b/api/routes/request.js index e11d641..07b0ad1 100644 --- a/api/routes/request.js +++ b/api/routes/request.js @@ -9,6 +9,10 @@ const codes = require('./http-codes'); const routerHandling = require('../middleware/router-handling'); +const apiAuthenticationMiddleware = require('../middleware/auth-middleware'); +const checkSql = require('../middleware/permission-check').checkSql; +const checkHl = require('../middleware/permission-check').checkHl; + // Mongoose Model using mongoDB const UserModel = require('../models/user'); const AwardingModel = require('../models/awarding'); @@ -31,7 +35,7 @@ const request = express.Router(); // routes ********************** request.route('/award') - .post((req, res, next) => { + .post(apiAuthenticationMiddleware, checkSql, (req, res, next) => { const award = new AwardingModel(req.body); award.confirmed = 0; award.proposer = req.user._id; @@ -54,18 +58,36 @@ request.route('/award') request.route('/promotion') - .get((req, res, next) => { + .get(apiAuthenticationMiddleware, checkSql, (req, res, next) => { const squadFilter = req.query.squadId; + const fractFilter = req.query.fractFilter; + const progressFilter = req.query.inProgress; + let filter; + if (squadFilter) { + filter = {squadId: squadFilter}; + } let userIds = []; - UserModel.find({squadId: squadFilter}, (err, items) => { + UserModel.find(filter).populate('squadId').exec((err, items) => { if (err) { err.status = codes.servererror; return next(err); } + for (let item of items) { - userIds.push(item._id); + if (!fractFilter || (fractFilter && item.squadId && item.squadId.fraction === fractFilter)) { + userIds.push(item._id); + } } - PromotionModel.find({userId: {"$in": userIds}}, {}, {sort: {timestamp: 'desc'}}).populate('userId').populate('proposer', resultSet).exec((err, items) => { + + let promotionFilter = { + userId: {"$in": userIds} + }; + if (progressFilter) { + promotionFilter.confirmed = 0; + } + + PromotionModel.find(promotionFilter, {}, {sort: {timestamp: 'desc'}}) + .populate('userId').populate('proposer', resultSet).exec((err, items) => { if (err) { err.status = codes.servererror; return next(err); @@ -83,7 +105,7 @@ request.route('/promotion') }) - .post((req, res, next) => { + .post(apiAuthenticationMiddleware, checkSql, (req, res, next) => { const promotion = new PromotionModel(req.body); promotion.confirmed = 0; promotion.proposer = req.user._id; @@ -104,6 +126,54 @@ request.route('/promotion') routerHandling.httpMethodNotAllowed ); + +request.route('/promotion/:id') + .patch(apiAuthenticationMiddleware, checkHl, (req, res, next) => { + if (!req.body || (req.body._id && req.body._id !== req.params.id)) { + // little bit different as in PUT. :id does not need to be in data, but if the _id and url id must match + const err = new Error('id of PATCH resource and send JSON body are not equal ' + req.params.id + " " + req.body._id); + err.status = codes.notfound; + next(err); + return; // prevent node to process this function further after next() has finished. + } + + req.body.updatedAt = new Date(); + req.body.$inc = {__v: 1}; + + // PATCH is easier with mongoose than PUT. You simply update by all data that comes from outside. no need to reset attributes that are missing. + PromotionModel.findByIdAndUpdate(req.params.id, req.body, {new: true}, (err, item) => { + if (err) { + err.status = codes.wrongrequest; + } + else if (!item) { + err = new Error("item not found"); + err.status = codes.notfound; + } + else { + if (item.confirmed === 1) { + let updateUser = { + _id: item.userId, + rankLvl: item.newRankLvl + }; + UserModel.findByIdAndUpdate(updateUser._id, updateUser, {new: true}, (err, item) => { + if (err) { + err.status = codes.wrongrequest; + } + else if (!item) { + err = new Error("user not found"); + err.status = codes.notfound; + } + }); + } + res.locals.items = item; + } + next(err); + }) + }) + .all( + routerHandling.httpMethodNotAllowed + ); + // this middleware function can be used, if you like or remove it // it looks for object(s) in res.locals.items and if they exist, they are send to the client as json request.use(routerHandling.emptyResponse); diff --git a/api/server.js b/api/server.js index c7b375f..0645d01 100644 --- a/api/server.js +++ b/api/server.js @@ -73,7 +73,7 @@ app.use(urls.users, userRouter); app.use(urls.squads, squadRouter); app.use(urls.ranks, rankRouter); app.use(urls.decorations, decorationRouter); -app.use(urls.request, apiAuthenticationMiddleware, checkSql, requestRouter); +app.use(urls.request, requestRouter); app.use(urls.awards, awardingRouter); app.use(urls.command, apiAuthenticationMiddleware, checkAdmin, commandRouter); app.use(urls.account, apiAuthenticationMiddleware, checkAdmin, accountRouter); diff --git a/api/signature-tool/signature-tool.js b/api/signature-tool/signature-tool.js index b965556..f08ae7f 100644 --- a/api/signature-tool/signature-tool.js +++ b/api/signature-tool/signature-tool.js @@ -126,7 +126,7 @@ let addDecorationsAndSave = (userId, loadedImage, res, next) => { let ribbonPx = 598; let ribbonPy = 95; - AwardingModel.find({'userId': userId}, ['decorationId', 'date'], + AwardingModel.find({'userId': userId, 'confirmed': 1}, ['decorationId', 'date'], {sort: {date: 'asc'}}).populate('decorationId', ['isMedal']) .exec((err, awardings) => { if (err) { diff --git a/static/src/app/login/signup.component.html b/static/src/app/login/signup.component.html index b3bd14d..2d78a14 100644 --- a/static/src/app/login/signup.component.html +++ b/static/src/app/login/signup.component.html @@ -3,9 +3,11 @@
-

Dieses Formular nur ausfüllen wenn du einer HL angehörst oder SQL bist. Dabei den Nutzernamen aus dem OPT Forum verwenden! - Im Forum eine Nachricht an HardiReady - senden, in welcher der 'geheime Text' drin steht, den du bei der Registrierung nutzt.
+

Dieses Formular nur ausfüllen wenn du einer HL angehörst oder SQL bist. Dabei den Nutzernamen aus + dem OPT Forum verwenden! + Im Forum eine Nachricht an HardiReady + senden, in welcher der 'geheime Text' steht, den du bei der Registrierung nutzt.
Dabei kann es sich um irgend eine willkürliche Zeichenfolge oder einen Satz handeln - dient nur dem Abgleich. Anschließend wird dein Account aktiviert und du wirst darüber per PN informiert.

@@ -25,9 +27,16 @@ Registrieren +

+ + Account erfolgreich erstellt + +

- Login fehlgeschlagen + {{error}}
diff --git a/static/src/app/login/signup.component.ts b/static/src/app/login/signup.component.ts index 7359704..1d151f5 100644 --- a/static/src/app/login/signup.component.ts +++ b/static/src/app/login/signup.component.ts @@ -13,6 +13,10 @@ export class SignupComponent implements OnInit { showErrorLabel = false; + showSuccessLabel = false; + + error: string; + loading = false; returnUrl: string; @@ -35,10 +39,11 @@ export class SignupComponent implements OnInit { this.loginService.signUp(username, password, secret) .subscribe( data => { - console.log(data) - //this.router.navigate([this.returnUrl]); + this.loading = false; + this.showSuccessLabel = true; }, error => { + this.error = error; this.showErrorLabel = true; setTimeout(() => { this.showErrorLabel = false; diff --git a/static/src/app/models/model-interfaces.ts b/static/src/app/models/model-interfaces.ts index 2371b06..837a6e5 100644 --- a/static/src/app/models/model-interfaces.ts +++ b/static/src/app/models/model-interfaces.ts @@ -41,6 +41,7 @@ export interface Award { } export interface Promotion { + _id?: string; userId?: string oldRankLvl: number, newRankLvl: number diff --git a/static/src/app/request/confirm-award/confirm-award.component.html b/static/src/app/request/confirm-award/confirm-award.component.html index 710b41d..0167564 100644 --- a/static/src/app/request/confirm-award/confirm-award.component.html +++ b/static/src/app/request/confirm-award/confirm-award.component.html @@ -1,6 +1,12 @@

Offene Anträge - Auszeichnungen

+ + Erfolgreich gespeichert + +
diff --git a/static/src/app/request/confirm-award/confirm-award.component.ts b/static/src/app/request/confirm-award/confirm-award.component.ts index ef0e55d..6c4a282 100644 --- a/static/src/app/request/confirm-award/confirm-award.component.ts +++ b/static/src/app/request/confirm-award/confirm-award.component.ts @@ -9,8 +9,11 @@ import {AwardingService} from "../../services/awarding-service/awarding.service" styleUrls: ['./confirm-award.component.css'], }) export class ConfirmAwardComponent { + awards: Award[]; + showSuccessLabel = false; + constructor(private router: Router, private route: ActivatedRoute, private awardingService: AwardingService) { @@ -34,6 +37,10 @@ export class ConfirmAwardComponent { let currentUser = JSON.parse(localStorage.getItem('currentUser')); this.awardingService.getUnconfirmedAwards(currentUser.squad.fraction).subscribe(awards => { this.awards = awards; + this.showSuccessLabel = true; + setTimeout(() => { + this.showSuccessLabel = false; + }, 2000); }); }); } diff --git a/static/src/app/request/confirm-promotion/confirm-promotion.component.css b/static/src/app/request/confirm-promotion/confirm-promotion.component.css index d9356a4..af8f5d3 100644 --- a/static/src/app/request/confirm-promotion/confirm-promotion.component.css +++ b/static/src/app/request/confirm-promotion/confirm-promotion.component.css @@ -2,7 +2,7 @@ padding: 5px; } -.trash { +.action { cursor: pointer; } diff --git a/static/src/app/request/confirm-promotion/confirm-promotion.component.html b/static/src/app/request/confirm-promotion/confirm-promotion.component.html index 4d2f3ad..78ed8df 100644 --- a/static/src/app/request/confirm-promotion/confirm-promotion.component.html +++ b/static/src/app/request/confirm-promotion/confirm-promotion.component.html @@ -1,60 +1,5 @@ -

Beförderung beantragen

- -
- - -
- -
- -
- - -
- -
- - -
- -
- - - - +

Offene Anträge - Beförderung

-
@@ -74,9 +18,10 @@ + - + +
Antragsteller Datum StatusAktion
{{promotion.userId.username}} @@ -96,6 +41,10 @@ {{promotion.confirmed === 0? 'In Bearbeitung' : (promotion.confirmed === 1? 'Genehmigt' : 'Abgelehnt')}} + Bestätigen
+ Ablehnen +
diff --git a/static/src/app/request/confirm-promotion/confirm-promotion.component.ts b/static/src/app/request/confirm-promotion/confirm-promotion.component.ts index 2a39a98..543daaa 100644 --- a/static/src/app/request/confirm-promotion/confirm-promotion.component.ts +++ b/static/src/app/request/confirm-promotion/confirm-promotion.component.ts @@ -1,8 +1,6 @@ -import {Component, ViewChild} from "@angular/core"; +import {Component} from "@angular/core"; import {ActivatedRoute, Router} from "@angular/router"; -import {Rank, User} from "../../models/model-interfaces"; -import {NgForm} from "@angular/forms"; -import {UserService} from "../../services/user-service/user.service"; +import {Promotion, Rank} from "../../models/model-interfaces"; import {RankService} from "../../services/rank-service/rank.service"; import {PromotionService} from "../../services/promotion-service/promotion.service"; @@ -13,84 +11,46 @@ import {PromotionService} from "../../services/promotion-service/promotion.servi }) export class ConfirmPromotionComponent { - @ViewChild(NgForm) form: NgForm; - - showForm = false; - showSuccessLabel = false; - user: User = {}; - - newLevel: number; - ranks: Rank[]; - users: User[]; - - uncheckedPromotions = []; + promotions: Promotion[]; constructor(private router: Router, private route: ActivatedRoute, - private userService: UserService, private rankService: RankService, private promotionService: PromotionService) { } ngOnInit() { let currentUser = JSON.parse(localStorage.getItem('currentUser')); - // show only current users squad members - this.userService.findUsers('', undefined, currentUser.squad._id).subscribe(users => { - this.users = users; - }); + // show only current users fraction promotions this.rankService.findRanks('', currentUser.squad.fraction).subscribe(ranks => { this.ranks = ranks; }); - this.promotionService.getSquadPromotions(currentUser.squad._id).subscribe(promotions => { - this.uncheckedPromotions = promotions; + this.promotionService.getUnconfirmedPromotions(currentUser.squad.fraction).subscribe(promotions => { + this.promotions = promotions; }) } - toggleUser() { - this.showForm = true; - this.newLevel = this.user.rank.level; - } - - - addPromotion() { - const promotion = { - "userId": this.user._id, - "oldRankLvl": this.user.rank.level, - "newRankLvl": this.newLevel, + confirm(promotion: Promotion, decision: boolean) { + const updateObject = { + _id: promotion._id, + confirmed: decision ? 1 : 2 }; - this.promotionService.requestPromotion(promotion).subscribe(); - this.showSuccessLabel = true; - setTimeout(() => { - this.showSuccessLabel = false; - }, 2000); - this.showForm = false; - this.user = {}; - - let currentUser = JSON.parse(localStorage.getItem('currentUser')); - this.promotionService.getSquadPromotions(currentUser.squad._id).subscribe(promotions => { - this.uncheckedPromotions = promotions; - }) - - } - - cancel() { - this.router.navigate(['..'], {relativeTo: this.route}); - return false; - } - - /** - * compare ngValue with ngModel to assign selected element - */ - equals(o1: User, o2: User) { - if (o1 && o2) { - return o1._id === o2._id; - } + this.promotionService.updatePromotion(updateObject).subscribe(res => { + let currentUser = JSON.parse(localStorage.getItem('currentUser')); + this.promotionService.getUnconfirmedPromotions(currentUser.squad.fraction).subscribe(promotions => { + this.promotions = promotions; + this.showSuccessLabel = true; + setTimeout(() => { + this.showSuccessLabel = false; + }, 2000); + }); + }); } } diff --git a/static/src/app/services/login-service/login-service.ts b/static/src/app/services/login-service/login-service.ts index c58e0b2..cc28f8a 100644 --- a/static/src/app/services/login-service/login-service.ts +++ b/static/src/app/services/login-service/login-service.ts @@ -27,11 +27,7 @@ export class LoginService { signUp(username: string, password: string, secret: string) { return this.http.post(this.config.apiUrl + this.config.apiSignupPath, {username: username, password: password, secret: secret}) .map((response: Response) => { - // login successful if there's a jwt token in the response - let user = response.json(); - if (user) { - //TODO - } + }); } diff --git a/static/src/app/services/promotion-service/promotion.service.ts b/static/src/app/services/promotion-service/promotion.service.ts index 195cf50..994f74f 100644 --- a/static/src/app/services/promotion-service/promotion.service.ts +++ b/static/src/app/services/promotion-service/promotion.service.ts @@ -10,6 +10,11 @@ export class PromotionService { private config: AppConfig) { } + getUnconfirmedPromotions(fraction?: string) { + return this.http.get(this.config.apiUrl + this.config.apiPromotionPath + '?inProgress=true&fractFilter=' + fraction) + .map(res => res.json()) + } + getSquadPromotions(squadId: string) { return this.http.get(this.config.apiUrl + this.config.apiPromotionPath + '?squadId=' + squadId) .map(res => res.json()) @@ -19,6 +24,11 @@ export class PromotionService { return this.http.post(this.config.apiUrl + this.config.apiPromotionPath, promotion) } + updatePromotion(promotion) { + return this.http.patch(this.config.apiUrl + this.config.apiPromotionPath + '/' + promotion._id, promotion) + .map(res => res.json()) + } + deletePromotion(promotionId) { return this.http.delete(this.config.apiUrl + this.config.apiPromotionPath + promotionId) } diff --git a/static/src/app/users/award-user/award-user.component.html b/static/src/app/users/award-user/award-user.component.html index 416b614..cd15e9c 100644 --- a/static/src/app/users/award-user/award-user.component.html +++ b/static/src/app/users/award-user/award-user.component.html @@ -92,7 +92,7 @@ {{award.date | date: 'dd.MM.yyyy'}} - {{award.confirmed? 'Bestätigt' : 'In Bearbeitung'}} + {{award.confirmed === 0? 'In Bearbeitung' : (award.confirmed === 1? 'Genehmigt' : 'Abgelehnt')}}