Compare commits
11 commits
59ae90e88b
...
093180f5b2
| Author | SHA1 | Date | |
|---|---|---|---|
| 093180f5b2 | |||
| 4b10d3d056 | |||
| 1b19988279 | |||
| bd10e76379 | |||
| e32f36d10f | |||
| 37604c40a2 | |||
| f4c344633a | |||
| 353fec9fe0 | |||
| 4c91d8a87d | |||
| c1c49e91e5 | |||
| e0f76fc5de |
25 changed files with 215 additions and 37 deletions
|
|
@ -29,6 +29,7 @@
|
|||
],
|
||||
"styles": [
|
||||
"node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"node_modules/bootstrap-icons/font/bootstrap-icons.min.css",
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
|
|
|
|||
17
package-lock.json
generated
17
package-lock.json
generated
|
|
@ -19,6 +19,7 @@
|
|||
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.0"
|
||||
|
|
@ -5840,6 +5841,22 @@
|
|||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap-icons": {
|
||||
"version": "1.11.3",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
|
||||
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"@ng-bootstrap/ng-bootstrap": "^18.0.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"bootstrap": "^5.3.3",
|
||||
"bootstrap-icons": "^1.11.3",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.0"
|
||||
|
|
|
|||
|
|
@ -1,2 +1,19 @@
|
|||
<app-toast-container aria-live="polite" aria-atomic="true"/>
|
||||
<nav class="navbar navbar-expand-sm bg-body-secondary">
|
||||
<div class="container-fluid">
|
||||
<button class="navbar-toggler" type="button" (click)="isMenuCollapsed = !isMenuCollapsed">☰</button>
|
||||
<div [ngbCollapse]="isMenuCollapsed" class="collapse navbar-collapse">
|
||||
<ul class="navbar-nav">
|
||||
@for (route of routes; track $index; ) {
|
||||
@if (route.component !== undefined) {
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-capitalize" [routerLink]="route.path" routerLinkActive="active"
|
||||
(click)="isMenuCollapsed = true">{{ route.path }}</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<router-outlet/>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {AppComponent} from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import {Component} from '@angular/core';
|
||||
import {RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
|
||||
import {ToastContainerComponent} from '../components/toast-container/toast-container/toast-container.component';
|
||||
import {NgbCollapse} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {routes} from './app.routes';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet, ToastContainerComponent],
|
||||
imports: [RouterOutlet, ToastContainerComponent, NgbCollapse, RouterLink, RouterLinkActive],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css'
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'untitled';
|
||||
isMenuCollapsed: boolean = true;
|
||||
protected readonly routes = routes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Routes } from '@angular/router';
|
||||
import {Routes} from '@angular/router';
|
||||
import {ShowsComponent} from '../pages/shows/shows.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@
|
|||
<h4 class="modal-title"> Add new show</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="dismiss()"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form [formGroup]="newShowForm" (ngSubmit)="formSubmitted(newShowForm)">
|
||||
<form [formGroup]="newShowForm" (submit)="formSubmitted(newShowForm)">
|
||||
<div class="modal-body">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Title</label>
|
||||
<input formControlName="title" type="text" class="form-control"/>
|
||||
|
|
@ -24,8 +25,8 @@
|
|||
<label class="form-label">Description</label>
|
||||
<input formControlName="description" type="text" class="form-control"/>
|
||||
</div>
|
||||
<div class="d-grid justify-content-md-end">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-success" [disabled]="newShowForm.invalid">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import { CreateModalComponent } from './create-modal.component';
|
||||
import {CreateModalComponent} from './create-modal.component';
|
||||
|
||||
describe('CreateModalComponent', () => {
|
||||
let component: CreateModalComponent;
|
||||
|
|
|
|||
0
src/components/delete-modal/delete-modal.component.css
Normal file
0
src/components/delete-modal/delete-modal.component.css
Normal file
25
src/components/delete-modal/delete-modal.component.html
Normal file
25
src/components/delete-modal/delete-modal.component.html
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<div class="modal-header">
|
||||
<h4 class="modal-title text-truncate">Delete {{ showName }}?</h4>
|
||||
<button type="button" class="btn-close" aria-label="Close" (click)="this.activeModal.dismiss()"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete this show?</p>
|
||||
<b class="text-danger-emphasis">This cannot be reversed</b>
|
||||
<div class="card" #collapse="ngbCollapse" [(ngbCollapse)]="formHidden">
|
||||
<div class="card-body">
|
||||
<form [formGroup]="confirmationForm" (ngSubmit)="formSubmitted()">
|
||||
<label class="form-label">To avoid mistakes, please type the name of the show to continue:</label>
|
||||
<input formControlName="name" type="text" class="form-control">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-danger"
|
||||
(click)="deletePressed()"
|
||||
[disabled]="confirmationForm.invalid && buttonDisabled"
|
||||
>Delete
|
||||
</button>
|
||||
</div>
|
||||
23
src/components/delete-modal/delete-modal.component.spec.ts
Normal file
23
src/components/delete-modal/delete-modal.component.spec.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {DeleteModalComponent} from './delete-modal.component';
|
||||
|
||||
describe('DeleteModalComponent', () => {
|
||||
let component: DeleteModalComponent;
|
||||
let fixture: ComponentFixture<DeleteModalComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DeleteModalComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(DeleteModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
46
src/components/delete-modal/delete-modal.component.ts
Normal file
46
src/components/delete-modal/delete-modal.component.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import {Component, inject} from '@angular/core';
|
||||
import {NgbActiveModal, NgbCollapse} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-delete-modal',
|
||||
imports: [
|
||||
NgbCollapse,
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
templateUrl: './delete-modal.component.html',
|
||||
styleUrl: './delete-modal.component.css'
|
||||
})
|
||||
export class DeleteModalComponent {
|
||||
protected activeModal: NgbActiveModal = inject(NgbActiveModal)
|
||||
showName: string = ""
|
||||
formHidden: boolean = true;
|
||||
buttonDisabled: boolean = false;
|
||||
confirmationForm: FormGroup
|
||||
|
||||
constructor() {
|
||||
this.confirmationForm = new FormGroup({
|
||||
name: new FormControl("", Validators.required)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
deletePressed() {
|
||||
if (this.formHidden) {
|
||||
const regex = new RegExp(this.showName)
|
||||
const control = new FormControl("", [Validators.required, Validators.pattern(regex)])
|
||||
this.confirmationForm.setControl("name", control)
|
||||
this.formHidden = false;
|
||||
this.buttonDisabled = true;
|
||||
} else {
|
||||
this.formSubmitted()
|
||||
}
|
||||
}
|
||||
|
||||
formSubmitted() {
|
||||
if (this.confirmationForm.valid) {
|
||||
this.activeModal.close(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,5 +3,6 @@
|
|||
[header]="toast.header || ''" [autohide]="true" [delay]="toast.delay || 5000"
|
||||
(hidden)="toastService.remove(toast)"
|
||||
[animation]="true" [class]="toast.htmlClass"
|
||||
>{{ toast.body }}</ngb-toast>
|
||||
>{{ toast.body }}
|
||||
</ngb-toast>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import { ToastContainerComponent } from './toast-container.component';
|
||||
import {ToastContainerComponent} from './toast-container.component';
|
||||
|
||||
describe('ToastContainerComponent', () => {
|
||||
let component: ToastContainerComponent;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@
|
|||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
4
src/interfaces/shows-api-deletion.ts
Normal file
4
src/interfaces/shows-api-deletion.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export interface ShowsApiDeletion {
|
||||
status: string
|
||||
message: string
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
/// <reference types="@angular/localize" />
|
||||
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import {bootstrapApplication} from '@angular/platform-browser';
|
||||
import {appConfig} from './app/app.config';
|
||||
import {AppComponent} from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig)
|
||||
.catch((err) => console.error(err));
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@
|
|||
<div class="card-body">
|
||||
<h5 class="card-title">{{ show.title }}</h5>
|
||||
<p class="card-text">{{ show.date | date }}</p>
|
||||
<button type="button" class="btn btn-outline-danger" (click)="deleteShow(show)">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-warning">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import { ShowsComponent } from './shows.component';
|
||||
import {ShowsComponent} from './shows.component';
|
||||
|
||||
describe('ShowsComponent', () => {
|
||||
let component: ShowsComponent;
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ import {Show} from '../../interfaces/show';
|
|||
import {ShowsApiResponse} from '../../interfaces/shows-api-response';
|
||||
import {Toast} from '../../interfaces/toast';
|
||||
import {ToastService} from '../../services/toast/toast.service';
|
||||
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {NgbModal, NgbModalRef} from '@ng-bootstrap/ng-bootstrap';
|
||||
import {CreateModalComponent} from '../../components/create-modal/create-modal/create-modal.component';
|
||||
import {DatePipe} from '@angular/common';
|
||||
import {ShowsApiIdResponse} from '../../interfaces/shows-api-id-response';
|
||||
import {DeleteModalComponent} from '../../components/delete-modal/delete-modal.component';
|
||||
import {ShowsApiDeletion} from '../../interfaces/shows-api-deletion';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shows',
|
||||
|
|
@ -51,13 +53,38 @@ export class ShowsComponent {
|
|||
|
||||
this.api.getShow(result).subscribe({
|
||||
next: (response: ShowsApiIdResponse) => {
|
||||
console.log(response.show)
|
||||
this.shows.push(response.show)
|
||||
}, error: (err: any) => {
|
||||
console.error(`Error: ${err}`)
|
||||
}, complete: () => {}
|
||||
}, complete: () => {
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
deleteShow(show: Show) {
|
||||
let modal: NgbModalRef = this.modalService.open(DeleteModalComponent)
|
||||
modal.componentInstance.showName = show.title;
|
||||
modal.result.then(
|
||||
(result) => {
|
||||
if (result) {
|
||||
this.api.deleteShw(show._id).subscribe({
|
||||
next: (response: ShowsApiDeletion) => {
|
||||
// Do nothing
|
||||
}, error: (err: any) => {
|
||||
console.log(err)
|
||||
}, complete: () => {
|
||||
this.toastService.show({body: "Show deleted!"})
|
||||
this.shows = this.shows.filter(((s: Show) => s != show))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
(result) => {
|
||||
// Dismissed, do nothing
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import { ShowsApiService } from './shows-api.service';
|
||||
import {ShowsApiService} from './shows-api.service';
|
||||
|
||||
describe('ShowsApiService', () => {
|
||||
let service: ShowsApiService;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {Observable} from 'rxjs';
|
|||
import {ShowsApiResponse} from '../../interfaces/shows-api-response';
|
||||
import {ShowsApiCreation} from '../../interfaces/shows-api-creation';
|
||||
import {ShowsApiIdResponse} from '../../interfaces/shows-api-id-response';
|
||||
import {ShowsApiDeletion} from '../../interfaces/shows-api-deletion';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
|
@ -23,10 +24,14 @@ export class ShowsApiService {
|
|||
}
|
||||
|
||||
getShow(id: string): Observable<ShowsApiIdResponse> {
|
||||
return this.http.get<ShowsApiIdResponse>(this.showsEndpoint + "id/" + id )
|
||||
return this.http.get<ShowsApiIdResponse>(this.showsEndpoint + "id/" + id)
|
||||
}
|
||||
|
||||
sendShow(newShow: {}) {
|
||||
return this.http.post<ShowsApiCreation>(this.showsEndpoint, newShow)
|
||||
}
|
||||
|
||||
deleteShw(id: string): Observable<ShowsApiDeletion> {
|
||||
return this.http.delete<ShowsApiDeletion>(this.showsEndpoint + "id/" + id)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
|
||||
import { ToastService } from './toast.service';
|
||||
import {ToastService} from './toast.service';
|
||||
|
||||
describe('ToastService', () => {
|
||||
let service: ToastService;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Toast} from '../../interfaces/toast';
|
||||
|
||||
@Injectable({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue