Adedd front-end support for validation and some other minor UI improvements.
This commit is contained in:
parent
cbc49f1037
commit
75b0d09974
@ -1 +1 @@
|
||||
{}
|
||||
{"i6bxw12wk2k5lzpk":{"number":32,"timestamp":1572855599882,"message":"Henl"}}
|
@ -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]
|
||||
|
@ -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);
|
||||
|
@ -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'});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
8
server/views/validate-error.pug
Normal file
8
server/views/validate-error.pug
Normal 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.
|
4
server/views/validate-success.pug
Normal file
4
server/views/validate-success.pug
Normal 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.
|
@ -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>
|
||||
|
@ -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%;
|
||||
}
|
||||
|
@ -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) { }
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
27
src/app/components/form/form.component.html
Normal file
27
src/app/components/form/form.component.html
Normal 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>
|
16
src/app/components/form/form.component.scss
Normal file
16
src/app/components/form/form.component.scss
Normal 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;
|
||||
}
|
||||
}
|
25
src/app/components/form/form.component.spec.ts
Normal file
25
src/app/components/form/form.component.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
45
src/app/components/form/form.component.ts
Normal file
45
src/app/components/form/form.component.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
12
src/app/services/form.service.spec.ts
Normal file
12
src/app/services/form.service.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
22
src/app/services/form.service.ts
Normal file
22
src/app/services/form.service.ts
Normal 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() { }
|
||||
}
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user