Adedd front-end support for validation and some other minor UI improvements.

This commit is contained in:
corner 2019-11-04 10:14:16 +01:00
parent cbc49f1037
commit 75b0d09974
21 changed files with 243 additions and 20 deletions

View File

@ -1 +1 @@
{}
{"i6bxw12wk2k5lzpk":{"number":32,"timestamp":1572855599882,"message":"Henl"}}

View File

@ -1 +1 @@
{"data":[false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,false,false,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false]}
[false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,true,false,false,false,false,true,false,false,false,false,false,false,true,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false]

View File

@ -7,7 +7,7 @@ const path = __dirname + '/../database/data.json'
/* GET home page. */
router.get('/', function(req, res, next) {
res.json(JSON.parse(fs.readFileSync(path).toString()));
res.json({ result: { success: true, data: JSON.parse(fs.readFileSync(path).toString())}});
});
router.post('/login', (req, res, _next) => {
@ -21,7 +21,7 @@ router.post('/login', (req, res, _next) => {
router.post('/update/:index', (req, res, _next) => {
if (req.body.username === 'Mediatheek' && req.body.password === '@MediatheekHetHeerenlanden!') {
const data = JSON.parse(fs.readFileSync(path).toString());
data.data[Number(req.params.index)] = data.data[Number(req.params.index)] ? false : true;
data[Number(req.params.index)] = data[Number(req.params.index)] ? false : true;
fs.writeFileSync(path, JSON.stringify(data));
}
res.send(null);

View File

@ -15,8 +15,19 @@ const readJsonFile = (file) => {
return JSON.parse(fs.readFileSync(path.join(__dirname, file)).toString());
};
/** This function is for the validation of the email address.
* Required arguments (in POST) are:
* email: string
* message: string
* number: number
* name: string
*/
router.post('/', (req, res, _next) => {
// Generate an id.
const id = uniqid();
// Send an email with a link to validate the id.
sendmail({
from: 'surpise-box@jobbel.nl',
to: req.body.email,
@ -24,6 +35,7 @@ router.post('/', (req, res, _next) => {
html: `
<html>
<body>
<p>Beste ${req.body.name},</p>
<p>
Deze email is verzonden omdat u surprise box ${req.body.number} voor de kerstmarkt van Het Heerenlanden heeft aangevraagd.<br>
Om uw aanvraag te bevestigen, moet u om de volgende link klikken:<br>
@ -34,22 +46,38 @@ router.post('/', (req, res, _next) => {
</body>
</html>
`,
// Once it is sent
}, function(err, reply) {
// If there's an error
if (err) {
// Notify the front-end
res.json({result: {success: false, data: err}});
} else {
// Add the user's data to the database identified by the id.
writeJsonFile(confirmationsPath, {
// Make sure that the other records are preserved.
...readJsonFile(confirmationsPath),
// Then add the new one
[id]: {
'name': req.body.name,
'number': Number(req.body.number),
'timestamp': Date.now()
'timestamp': Date.now(),
'message': req.body.message
}
})
// Notify the front-end
res.json({result: {success: true, data: reply}});
}
});
});
/** This function is used when a user clicks on the link in their email.
*
* This should be extremely user-friendly.
*/
router.get('/validate/:id', (req, res, _next) => {
const data = readJsonFile(confirmationsPath);
const id = req.params.id;
@ -58,7 +86,8 @@ router.get('/validate/:id', (req, res, _next) => {
if (data[id] && Date.now() <= (data[id].timestamp + 3600000 /* one hour in milliseconds */)) {
// send success result
res.json({result: {success: true, data: data[id].number}});
// res.json({result: {success: true, data: data[id].number}});
res.render('validate-success', {number: data[id].number})
// Delete the record
delete data[id];
@ -69,7 +98,8 @@ router.get('/validate/:id', (req, res, _next) => {
if (data[id] && Date.now() > (data[id].timestamp + 3600000)) {
// send result
res.json({result: {success: false, data: 'expired'}});
//res.json({result: {success: false, data: 'expired'}});
res.render('validate-error', {data: 'expired', number: data[id].number });
// delete the record
delete data[id];
@ -77,7 +107,8 @@ router.get('/validate/:id', (req, res, _next) => {
// If the id is not found
} else {
res.json({result: {success: false, data: 'ID not found'}});
//res.json({result: {success: false, data: 'ID not found'}});
res.render('validate-error', {data: 'not found'});
}
}
});

View File

@ -0,0 +1,8 @@
extends layout.pug
block content
if data == 'expired'
h1 Uw aanvraag is verlopen
p Probeer uw aanvraag voor surprise box #{number} opnieuw in te dienen.
else if data == 'not found'
h1 Een aanvraag met de gegeven ID is niet gevonden
p Als u gelooft dat dit een fout is, meld het dan bij het surprise box team van Het Heerenlanden.

View File

@ -0,0 +1,4 @@
extends layout.pug
block content
h1 Uw aanvraag voor box #{number} is sucessvol verstuurd!
p U krijgt binnenkort antwoord van het surprise box team van Het Heerenlanden.

View File

@ -1,8 +1,10 @@
<nav>
<!-- nav here -->
<span>Surprise box reserveren</span>
<a routerLink="login"><i class="fas fa-sign-in-alt"></i></a>
<a routerLink="login"><fa-icon [icon]="faSignInAlt"></fa-icon></a>
</nav>
<main>
<div class="overlay" *ngIf="form.visible"></div>
<router-outlet></router-outlet>
</main>
<app-form *ngIf="form.visible" [number]="form.number"></app-form>
</main>

View File

@ -16,7 +16,7 @@ body {
@if type-of($number) == 'number' and not unitless($number) {
@return $number / ($number * 0 + 1);
}
@return $number;
}
@ -44,7 +44,7 @@ nav {
top: 50%;
right: 20px;
transform: translate(0, -50%);
i {
fa-icon {
padding: 5px;
}
}
@ -62,4 +62,13 @@ main {
border-radius: 1em;
background-color: #ffffff;
text-align: center;
}
}
.overlay {
background-color: rgba(0,0,0,0.5);
position:fixed;
left:0;
top: 0;
width:100%;
height:100%;
}

View File

@ -1,4 +1,6 @@
import { Component } from '@angular/core';
import { FormService } from './services/form.service';
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
@Component({
selector: 'app-root',
@ -6,5 +8,9 @@ import { Component } from '@angular/core';
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'HHFSBRS';
faSignInAlt = faSignInAlt;
constructor(public form: FormService) { }
}

View File

@ -8,12 +8,14 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './components/home/home.component';
import { LoginComponent } from './components/login/login.component';
import { FormComponent } from './components/form/form.component';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
LoginComponent
LoginComponent,
FormComponent
],
imports: [
BrowserModule,

View File

@ -0,0 +1,27 @@
<div class="container">
<fa-icon (click)="formService.hideForm()" [icon]="faTimes"></fa-icon>
<form *ngIf="!submitted" [formGroup]="form" (ngSubmit)="onSubmit()">
<h4>Formulier voor surpise box {{ number }}</h4>
<label for="name">Naam: </label>
<input type="text" formControlName="name"><br>
<label for="email">Email: </label>
<input type="email" formControlName="email"><br>
<label for="message">Bericht: </label>
<textarea formControlName="message"></textarea><br>
<input type="submit" value="Verzenden" [disabled]="!form.valid">
</form>
<div *ngIf="submitted">
<h4>Uw aanvraag is ingediend</h4>
<p>Check uw mailbox om uw aanvrag te valideren. Als u de mail niet kan vinden, check dan ook uw spam.</p>
</div>
</div>

View File

@ -0,0 +1,16 @@
.container {
background: white;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
padding-bottom: 20px;
fa-icon {
position: relative;
float: right;
padding-right: 10px;
padding-top: 10px;
cursor: pointer;
}
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormComponent } from './form.component';
describe('FormComponent', () => {
let component: FormComponent;
let fixture: ComponentFixture<FormComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FormComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,45 @@
import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
import { DataService } from 'src/app/services/data.service';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { FormService } from 'src/app/services/form.service';
@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class FormComponent implements OnInit {
// tslint:disable-next-line: no-input-rename
@Input('number') number: number;
faTimes = faTimes;
form = this.fb.group({
email: ['', [Validators.email, Validators.required]],
name: ['', Validators.required],
message: ['', Validators.required]
});
submitted = false;
constructor(
private fb: FormBuilder,
private data: DataService,
public formService: FormService
) { }
ngOnInit() {
}
onSubmit() {
const value = this.form.value;
value.number = 32;
this.data.mail(value.email, value.number, value.message, value.name).subscribe(res => {
this.submitted = true;
});
}
}

View File

@ -12,8 +12,9 @@
<div>
<span *ngIf="loggedIn" (click)="logout()">Log uit</span>
<div class="container" *ngIf="!loggedIn && (data | async) as data">
<div *ngFor="let item of data.data; let i = index">
<a [href]="item ? '' : ('mailto:doemiddag.hetheerenlanden@cvo-av.nl?subject=Reservatie Surprise box ' + (i + 1))">
<div *ngFor="let item of data.result.data; let i = index">
<!-- [href]="item ? '' : ('mailto:doemiddag.hetheerenlanden@cvo-av.nl?subject=Reservatie Surprise box ' + (i + 1))" -->
<a [class]="item && 'disabled'" (click)="!item && form.showForm(i + 1)">
<span class="{{ item ? 'unavailable' : 'available' }}">{{ i + 1 }}<br>
<div class="tooltip">{{ item ? 'Gereserveerd' : 'Klik om te reserveren' }}</div>
<fa-icon [icon]="faGift"></fa-icon>

View File

@ -1,5 +1,9 @@
// HLC blue (0,175,241) red (247,0,0) yellow (255,255,0)
a.disabled {
cursor: not-allowed;
}
p:nth-child(2) {
border-bottom: 1px solid rgba(0,0,0,0.5);
font-weight: bold;

View File

@ -3,6 +3,7 @@ import { Observable } from 'rxjs';
import { ApiData, DataService } from 'src/app/services/data.service';
import { Router } from '@angular/router';
import { faGift } from '@fortawesome/free-solid-svg-icons';
import { FormService } from 'src/app/services/form.service';
@Component({
selector: 'app-home',
@ -18,7 +19,8 @@ export class HomeComponent implements OnInit {
constructor(
private dataService: DataService,
private router: Router
private router: Router,
public form: FormService
) { }
ngOnInit() {

View File

@ -3,7 +3,10 @@ import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
export interface ApiData {
data: Array<boolean>;
result: {
success: boolean,
data: any
};
}
@Injectable({
@ -26,4 +29,9 @@ export class DataService {
update(index: number, username: string, password: string): Observable<void> {
return this.http.post<void>(this.apiUrl + '/update/' + index, {username, password});
}
// tslint:disable-next-line: variable-name
mail(email: string, number: number, message: string, name: string): Observable<ApiData> {
return this.http.post<ApiData>(this.apiUrl + '/mail/', {email, number, message, name});
}
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { FormService } from './form.service';
describe('FormService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: FormService = TestBed.get(FormService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class FormService {
visible = false;
number: number;
showForm(number: number) {
this.number = number;
this.visible = true;
}
hideForm() {
this.visible = false;
this.number = undefined;
}
constructor() { }
}

View File

@ -7,7 +7,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Staatliches&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="//use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
</head>
<body>
<app-root></app-root>