diff --git a/db.json b/db.json
index 0967ef4..e36e314 100644
--- a/db.json
+++ b/db.json
@@ -1 +1,50 @@
-{}
+{
+ "donuts": [
+ {
+ "id": "xy12yr",
+ "name": "Just Chocolate",
+ "icon": "just-chocolate",
+ "price": 120,
+ "promo": "new",
+ "description": "For the pure chocoholic."
+ },
+ {
+ "id": "zy19yr",
+ "name": "Glazed Fudge",
+ "icon": "glazed-fudge",
+ "price": 129,
+ "promo": "new",
+ "description": "Sticky perfection."
+ },
+ {
+ "id": "qy19ya",
+ "name": "Caramel Swirl",
+ "icon": "caramel-swirl",
+ "price": 129,
+ "promo": "new",
+ "description": "Chocolate drizzled with caramel"
+ },
+ {
+ "id": "2dfe5",
+ "name": "Sour Supreme",
+ "icon": "sour-supreme",
+ "price": 139,
+ "description": "For the sour advocate."
+ },
+ {
+ "id": "a6rfg",
+ "name": "Zesty Lemon",
+ "icon": "zesty-lemon",
+ "price": 129,
+ "description": "Delicious luscious lemon."
+ },
+ {
+ "name": "teestfff",
+ "icon": "glazed-fudge",
+ "price": 3,
+ "promo": "limited",
+ "description": "qdsqsdf",
+ "id": "RF-n7_z"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts
new file mode 100644
index 0000000..d9867a0
--- /dev/null
+++ b/src/app/admin/admin.module.ts
@@ -0,0 +1,37 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {FormsModule} from "@angular/forms";
+import {RouterModule, Routes} from "@angular/router";
+
+// containers
+import {DonutListComponent} from './containers/donut-list/donut-list.component';
+import {DonutSingleComponent} from './containers/donut-single/donut-single.component';
+
+// components
+import {DonutCardComponent} from './components/donut-card/donut-card.component';
+import {DonutFormComponent} from './components/donut-form/donut-form.component';
+
+export const routes: Routes = [
+ {path: 'donuts', component: DonutListComponent},
+ {path: 'donuts/new', component: DonutSingleComponent, data: {isEdit: false}},
+ {path: 'donuts/:id', component: DonutSingleComponent, data: {isEdit: true}},
+ // redirects http://localhost:4200/admin to http://localhost:4200/admin/donuts
+ // put always at the end
+ {path: '', pathMatch: 'full', redirectTo: 'donuts'},
+];
+
+@NgModule({
+ declarations: [
+ DonutListComponent,
+ DonutSingleComponent,
+ DonutCardComponent,
+ DonutFormComponent,
+ ],
+ imports: [
+ CommonModule, FormsModule, RouterModule.forChild(routes)
+ ]
+})
+export class AdminModule {
+}
+
+
diff --git a/src/app/admin/components/donut-card/donut-card.component.ts b/src/app/admin/components/donut-card/donut-card.component.ts
new file mode 100644
index 0000000..79dd393
--- /dev/null
+++ b/src/app/admin/components/donut-card/donut-card.component.ts
@@ -0,0 +1,78 @@
+import {Component, Input, ViewEncapsulation} from '@angular/core';
+import {Donut} from "../../models/donut.model";
+
+@Component({
+ selector: 'donut-card',
+ encapsulation: ViewEncapsulation.Emulated, // Is de default dat wil zeggen dat nu de css van styles.scss genomen wordt
+ template: `
+
+
+
+
+ {{ donut.name }}
+
+
+ NEW
+ LIMITED
+ Nothing special...
+
+
+
+
+ {{ donut.price / 100 | currency }}
+
+
+
+ `,
+ styles: [
+ `
+ .donut-card {
+ display: flex;
+ align-items: center;
+ background: #f7f7f7;
+ border-radius: 5px;
+ margin-bottom: 5px;
+ padding: 5px 15px;
+ transition: transform 0.2s ease-in-out;
+ &:hover {
+ transform: translateY(-3px);
+ }
+ &-name {
+ font-size: 16px;
+ }
+ &-label {
+ border: 1px solid #c14583;
+ border-radius: 4px;
+ padding: 0 4px;
+ margin-left: 5px;
+ font-size: 12px;
+ color: #c14583;
+ }
+ &-price {
+ font-size: 14px;
+ color: #c14583;
+ }
+ &-promo {
+ border: 2px solid #eee;
+ }
+ &-icon {
+ width: 50px;
+ margin-right: 10px;
+ }
+ }
+ `,
+ ]
+})
+export class DonutCardComponent {
+ @Input() donut!: Donut;
+}
diff --git a/src/app/admin/components/donut-form/donut-form.component.ts b/src/app/admin/components/donut-form/donut-form.component.ts
new file mode 100644
index 0000000..392f3a4
--- /dev/null
+++ b/src/app/admin/components/donut-form/donut-form.component.ts
@@ -0,0 +1,197 @@
+import {Component, EventEmitter, Input, Output} from '@angular/core';
+import {NgForm} from "@angular/forms";
+import {Donut} from "../../models/donut.model";
+
+@Component({
+ selector: 'donut-form',
+ template: `
+
+
+ Loading...
+ `,
+ styles: [
+ `
+ .donut-form {
+ &-radios {
+ display: flex;
+ align-content: center;
+
+ &-label {
+ margin-right: 10px;
+ }
+
+ label {
+ display: flex;
+ align-items: center;
+
+ span {
+ color: #444;
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ &-working {
+ font-size: 12px;
+ font-style: italic;
+ margin: 10px 0;
+ }
+
+ &-error {
+ font-size: 12px;
+ color: #e66262;
+ }
+ }
+ `
+ ]
+})
+export class DonutFormComponent {
+
+ @Input() donut!: Donut;
+ @Input() isEdit!: boolean;
+
+ @Output() create = new EventEmitter()
+ @Output() update = new EventEmitter()
+ @Output() delete = new EventEmitter()
+
+ icons: string[] = [
+ 'caramel-swirl',
+ 'glazed-fudge',
+ 'just-chocolate',
+ 'sour-supreme',
+ 'strawberry-glaze',
+ 'vanilla-sundae',
+ 'zesty-lemon',
+ ]
+
+ constructor() {
+ }
+
+ handleCreate(form: NgForm) {
+ if (form.valid) {
+ this.create.emit(form.value)
+ } else {
+ form.form.markAllAsTouched()
+ }
+ }
+
+ handleUpdate(form: NgForm) {
+ if (form.valid) {
+ this.update.emit({id: this.donut.id, ...form.value})
+ } else {
+ form.form.markAllAsTouched()
+ }
+ }
+
+ handleDelete() {
+ if (confirm(`Really delete ${this.donut.name}?`))
+ this.delete.emit({...this.donut})
+ }
+}
diff --git a/src/app/admin/containers/donut-list/donut-list.component.ts b/src/app/admin/containers/donut-list/donut-list.component.ts
new file mode 100644
index 0000000..6c02a44
--- /dev/null
+++ b/src/app/admin/containers/donut-list/donut-list.component.ts
@@ -0,0 +1,52 @@
+import {Component, OnInit} from '@angular/core';
+
+import { Donut } from "../../models/donut.model";
+import {DonutService} from "../../services/donut.service";
+
+@Component({
+ selector: 'donut-list',
+ template: `
+
+
+
+
+
+
+
+
+ No Donust here..
+
+
+ `,
+ styles: [`
+ .donut-list {
+ &-actions {
+ margin-bottom: 10px;
+ }
+ }
+ `,
+ ],
+})
+export class DonutListComponent implements OnInit {
+ // Declaratie hier maar vullen in ngOnInit()
+ donuts!: Donut[];
+
+ constructor(private donutService: DonutService) {}
+ ngOnInit(): void {
+ this.donutService
+ .read()
+ .subscribe((donuts: Donut[]) => (this.donuts = donuts));
+ }
+
+ trackById(index: number, value: Donut) {
+ return value.id;
+
+ }
+}
diff --git a/src/app/admin/containers/donut-single/donut-single.component.ts b/src/app/admin/containers/donut-single/donut-single.component.ts
new file mode 100644
index 0000000..7135265
--- /dev/null
+++ b/src/app/admin/containers/donut-single/donut-single.component.ts
@@ -0,0 +1,59 @@
+import {Component, OnInit} from '@angular/core';
+import {Donut} from "../../models/donut.model";
+import {DonutService} from "../../services/donut.service";
+import {ActivatedRoute, Router} from "@angular/router";
+
+@Component({
+ selector: 'donut-single',
+ template: `
+
+
+
+ `,
+ styles: []
+})
+export class DonutSingleComponent implements OnInit {
+
+ donut!: Donut;
+ isEdit!: boolean;
+
+ constructor(
+ private route: ActivatedRoute,
+ private router: Router,
+ private donutService: DonutService,
+ ) {}
+
+ ngOnInit(): void {
+ const id = this.route.snapshot.paramMap.get('id'); // id refers t: { path: 'donuts/:id', component: DonutSingleComponent }, in AdminModule
+ console.log(id);
+ this.donutService
+ .readOne(id)
+ .subscribe((donut: Donut) => (this.donut = donut));
+ this.isEdit = this.route.snapshot.data['isEdit'];
+ }
+
+ onCreate(donut: Donut) {
+ this.donutService
+ .create(donut)
+ .subscribe((donut) => this.router.navigate(['admin', 'donuts', donut.id]));
+ }
+
+ onUpdate(donut: Donut) {
+ this.donutService.update(donut).subscribe({
+ next: () => this.router.navigate(['admin']),
+ error: (err) => console.log('onUpdate error:', err),
+ });
+ }
+
+ onDelete(donut: Donut) {
+ this.donutService
+ .delete(donut)
+ .subscribe(()=> this.router.navigate(['admin']));
+ }
+}
diff --git a/src/app/admin/models/donut.model.ts b/src/app/admin/models/donut.model.ts
new file mode 100644
index 0000000..bce3e22
--- /dev/null
+++ b/src/app/admin/models/donut.model.ts
@@ -0,0 +1,8 @@
+export interface Donut {
+ id?: string;
+ name: string;
+ icon: string;
+ price: number;
+ promo?: 'new' | 'limited';
+ description: string;
+}
diff --git a/src/app/admin/services/donut.service.spec.ts b/src/app/admin/services/donut.service.spec.ts
new file mode 100644
index 0000000..3e1e36f
--- /dev/null
+++ b/src/app/admin/services/donut.service.spec.ts
@@ -0,0 +1,16 @@
+import { TestBed } from '@angular/core/testing';
+
+import { DonutService } from './donut.service';
+
+describe('DonutService', () => {
+ let service: DonutService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(DonutService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/src/app/admin/services/donut.service.ts b/src/app/admin/services/donut.service.ts
new file mode 100644
index 0000000..6b6cd4f
--- /dev/null
+++ b/src/app/admin/services/donut.service.ts
@@ -0,0 +1,99 @@
+import {Injectable} from '@angular/core';
+import {HttpClient, HttpErrorResponse, HttpHeaders} from "@angular/common/http";
+
+import {tap, of, map, catchError, throwError, retryWhen, delay, take} from "rxjs";
+
+import {Donut} from "../models/donut.model";
+
+@Injectable({
+ providedIn: 'root'
+})
+export class DonutService {
+ private donuts: Donut[] = []
+
+ constructor(private http: HttpClient) {
+ }
+
+ read() {
+ if (this.donuts.length) {
+ return of(this.donuts);
+ }
+
+ let headers = new HttpHeaders({
+ 'Content-Type': 'application/json',
+ });
+
+ headers = headers.append('Api-Token', '123abc');
+
+ const options = {
+ headers,
+ }
+
+ return this.http.get(`/api/donuts`, options).pipe(
+ tap((donuts) => {
+ this.donuts = donuts;
+ }),
+ retryWhen((errors) => errors.pipe(delay(5000), take(4))),
+ catchError(this.handleError)
+ );
+ }
+
+ readOne(id: string | null) {
+ return this.read().pipe(
+ map((donuts) => {
+ const donut = donuts.find((donut: Donut) => donut.id === id);
+ if (donut) {
+ return donut;
+ }
+ return {name: '', icon: '', price: 0, description: ''};
+ }),
+ catchError(this.handleError)
+ );
+ }
+
+ create(payload: Donut) {
+ return this.http.post(`/api/donuts`, payload)
+ .pipe(
+ tap((donut) => {
+ this.donuts = [...this.donuts, donut]
+ }),
+ catchError(this.handleError)
+ )
+ }
+
+ update(payload: Donut) {
+ return this.http.put(`/api/donuts/${payload.id}`, payload)
+ .pipe(
+ tap((donut) => {
+ this.donuts = this.donuts.map((item: Donut) => {
+ if (item.id === donut.id) {
+ return donut;
+ }
+ return item;
+ });
+ }),
+ catchError(this.handleError)
+ );
+ }
+
+ delete(payload: Donut) {
+ return this.http.delete(`/api/donuts/${payload.id}`)
+ .pipe(
+ tap(() => {
+ this.donuts = this.donuts.filter((donut: Donut) => donut.id !== payload.id);
+ }),
+ catchError(this.handleError)
+ )
+ }
+
+ private handleError(err: HttpErrorResponse) {
+ if (err.error instanceof ErrorEvent) {
+ // client-side
+ console.warn('Client', err.message);
+ } else {
+ // server-side
+ console.warn('Server', err.status);
+ }
+ return throwError(() => new Error(err.message));
+ }
+}
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index c11bb60..91af124 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,21 +1,54 @@
-import { Component, OnInit } from '@angular/core';
+import {Component, OnInit} from '@angular/core';
@Component({
selector: 'app-root',
- template: ` Hello Angular!
`,
+ template: `
+
+
+
+
`,
styles: [
`
.app {
- margin-top: 50px;
- font-size: 22px;
- color: #fff;
- text-align: center;
+ background: #fff;
+ border-radius: 8px;
+ max-width: 400px;
+ width: 94%;
+ margin: 25px auto;
+ padding: 25px;
+ border: 4px solid #ef9fc7;
+ }
+
+ .header {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 25px;
+ }
+
+ .logo {
+ width: 100px;
+ height: 88px;
}
`,
],
})
export class AppComponent implements OnInit {
+ message!: string;
+ newMessage!: string;
+
ngOnInit() {
- console.log('Hello World!');
+ this.message = 'Value on init';
+ }
+
+ handleClick(event: MouseEvent) {
+ console.log(event);
+ }
+
+ handleInput(event: Event) {
+ const {value} = event.target as HTMLInputElement;
+ console.log(value);
+ this.newMessage = value;
}
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index bde0672..fb2d41c 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,11 +1,36 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
+import {RouterModule, Routes} from "@angular/router";
+import {HttpClientModule} from "@angular/common/http";
import { AppComponent } from './app.component';
+import { AdminModule } from './admin/admin.module';
+import {DonutListComponent} from "./admin/containers/donut-list/donut-list.component";
+import {DonutSingleComponent} from "./admin/containers/donut-single/donut-single.component";
+// path: '' is http://localhost:4200/
+export const routes: Routes = [
+ {
+ path: 'admin',
+ loadChildren: () => import('./admin/admin.module').then(x => x.AdminModule),
+ },
+ {
+ // redirects http://localhost:4200/ to http://localhost:4200/admin
+ // put always at the end
+ path: '',
+ pathMatch: 'full',
+ redirectTo: 'admin',
+ },
+ {
+ // Wild card selector redirects misspelled paths
+ // eg. http://localhost:4200/addddminnnn to http://localhost:4200/admin
+ path: '**',
+ redirectTo: 'admin',
+ },
+];
@NgModule({
declarations: [AppComponent],
- imports: [BrowserModule],
+ imports: [BrowserModule, HttpClientModule, RouterModule.forRoot(routes)],
bootstrap: [AppComponent],
})
export class AppModule {}