Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: set analytics-sharing to false #2

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
}
},
"cli": {
"schematicCollections": ["@angular-eslint/schematics"]
"schematicCollections": ["@angular-eslint/schematics"],
"analytics": false
}
}
4 changes: 4 additions & 0 deletions src/app/about/about.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<section>
<h1>This app is all about your books.</h1>
<a routerLink="/" class="link-blue">This link demonstrates the redirect of '/' to '/about'</a>
</section>
12 changes: 12 additions & 0 deletions src/app/about/about.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
:host {
height: 50vh;
display: grid;
align-items: center;
justify-content: center;

color: #064d9e;
}

.link-blue {
color: #064d9e;
}
11 changes: 11 additions & 0 deletions src/app/about/about.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';

@Component({
selector: 'app-about',
standalone: true,
imports: [RouterLink],
templateUrl: './about.component.html',
styleUrl: './about.component.scss'
})
export class AboutComponent {}
329 changes: 2 additions & 327 deletions src/app/app.component.html

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';
import { NavigationComponent } from './navigation/navigation.component';

@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule],
imports: [NavigationComponent, RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'bookmonkey-client';
}
export class AppComponent {}
8 changes: 7 additions & 1 deletion src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
providers: []
providers: [
provideHttpClient(),
provideRouter(routes, withComponentInputBinding())
]
};
21 changes: 21 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Routes } from '@angular/router';
import { AboutComponent } from './about/about.component';
import { isUserAuthenticatedGuardFn } from './is-user-authenticated.guard';

export const routes: Routes = [
{
path: '',
redirectTo: '/about',
pathMatch: 'full'
},
{
path: 'about',
component: AboutComponent
},
{
path: 'books',
loadChildren: () =>
import('./book/book.routes').then(mod => mod.bookRoutes),
canMatch: [isUserAuthenticatedGuardFn]
}
];
26 changes: 26 additions & 0 deletions src/app/book/book-api.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Injectable } from '@angular/core';
import { Book } from './book';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';

@Injectable({
providedIn: 'root'
})
export class BookApiService {
readonly #baseUrl = 'http://localhost:4730';

constructor(private readonly http: HttpClient) {
}

getAll(): Observable<Book[]> {
return this.http.get<Book[]>(`${this.#baseUrl}/books`);
}

getByIsbn(isbn: string): Observable<Book> {
return this.http.get<Book>(`${this.#baseUrl}/books/${isbn}`);
}

create(book: Partial<Book>): Observable<Book> {
return this.http.post<Book>('http://localhost:4730/books', book);
}
}
4 changes: 4 additions & 0 deletions src/app/book/book-card/book-card.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<h3>{{ content.title }}</h3>
<h4 [style]="customStyle">{{ content.author }}</h4>
<a href="" (click)="handleDetailClick($event)">Details</a>
<p>{{ content.abstract }}</p>
14 changes: 14 additions & 0 deletions src/app/book/book-card/book-card.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
:host {
display: inline-block;
border-radius: 2px;
margin: 1rem;
padding: 1rem;
width: 300px;

box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);

&:hover {
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
}
}
24 changes: 24 additions & 0 deletions src/app/book/book-card/book-card.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Book } from '../book';

@Component({
selector: 'app-book-card',
standalone: true,
imports: [],
templateUrl: './book-card.component.html',
styleUrl: './book-card.component.scss'
})
export class BookCardComponent {
customStyle = { color: '#064D9E', fontWeight: 600 };

@Input({ required: true }) content!: Book;
@Output() detailClick = new EventEmitter<Book>();

handleDetailClick(click: MouseEvent) {
click.preventDefault();

console.log('Click Details-Link:', click);

this.detailClick.emit(this.content);
}
}
13 changes: 13 additions & 0 deletions src/app/book/book-detail/book-detail.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<ng-container *ngIf="book$ | async as book">
<h3>
{{ book.title }}
</h3>
<h4>ISBN - {{ book.isbn }}</h4>
<section class="book-insights">
<p>
{{ book.abstract }}
<i>{{ book.author }}</i>
</p>
<img [src]="book.cover" [alt]="book.title" class="book-cover" />
</section>
</ng-container>
14 changes: 14 additions & 0 deletions src/app/book/book-detail/book-detail.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
:host {
display: block;
margin: 1rem;
}

.book-insights {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
}

.book-cover {
max-height: 200px;
}
23 changes: 23 additions & 0 deletions src/app/book/book-detail/book-detail.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Component, Input } from '@angular/core';
import { Observable } from 'rxjs';
import { Book } from '../book';
import { BookApiService } from '../book-api.service';
import { AsyncPipe, NgIf } from '@angular/common';

@Component({
selector: 'app-book-detail',
standalone: true,
imports: [AsyncPipe, NgIf],
templateUrl: './book-detail.component.html',
styleUrl: './book-detail.component.scss'
})
export class BookDetailComponent {
book$!: Observable<Book>;

constructor(private readonly bookApi: BookApiService) {}

@Input({ required: true })
set isbn(isbn: string) {
this.book$ = this.bookApi.getByIsbn(isbn);
}
}
58 changes: 58 additions & 0 deletions src/app/book/book-filter/book-filter.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Pipe, PipeTransform } from '@angular/core';
import { Book } from '../book';

@Pipe({
name: 'bookFilter',
standalone: true
})
export class BookFilterPipe implements PipeTransform {
/**
* A note on defensive design
* --------------------------
*
* We type books as Book[] or null here.
* This is done because we do not know in which context "| bookFilter" is used.
* For example "bookFilter" could sit in a chain of pipes that produce invalid values.
*
* That's why we want to make sure to handle possible null values explicitly.
*
* @param books A collection of books
* @param searchTerm The search term to filter books
*/
transform(
books: Book[] | null | undefined,
searchTerm: string | null
): Book[] {
if (!searchTerm) {
return books || [];
}

if (!books) {
return [];
}

return books.filter(book =>
book.title.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
);

/*
* Bonus
* -----
* If you want to search in all fields the book provides you can iterate
* through its properties. The code would look like this.
*
* return books.filter(
* book => this.matchBook(book, searchTerm)
* );
*
* ...
*
* private matchBook(book: { [key: string]: any }, searchTerm: string): boolean {
* return Object.keys(book)
* .filter(key => typeof book[key] === 'string')
* .some(key => book[key].toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
* );
* }
*/
}
}
41 changes: 41 additions & 0 deletions src/app/book/book-new/book-new.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<h2>New</h2>

<form [formGroup]="form" (ngSubmit)="submit()">
<label class="form-field">
<span>ISBN</span>
<input formControlName="isbn" />
</label>

<label class="form-field">
<span>Title</span>
<input formControlName="title" />
<small *ngIf="form.get('title')?.dirty && form.get('title')?.hasError('required')">
Please insert a title.
</small>
</label>

<label class="form-field">
<span>Subtitle</span>
<input formControlName="subtitle" />
</label>

<label class="form-field">
<span>Author</span>
<input formControlName="author" />
<small *ngIf="form.get('author')?.dirty && form.get('author')?.hasError('required')">
Please insert an Author.
</small>
<small *ngIf="form.get('author')?.dirty && form.get('author')?.hasError('invalidAuthor')">
Der Name eines Autors darf keine Zahlen beinhalten!
</small>
</label>

<label class="form-field">
<span>Abstract</span>
<input formControlName="abstract" />
</label>

<div class="form-actions">
<button type="submit" [disabled]="form.invalid">Save</button>
</div>
</form>
31 changes: 31 additions & 0 deletions src/app/book/book-new/book-new.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
:host {
display: block;
margin: 1rem;
}

.form-field {
display: block;
margin-top: 1rem;

> span {
display: block;
margin-bottom: 0.5rem;
}

> input {
padding: 12px 20px 12px 6px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
min-width: 250px;
}
}

.form-field-hint {
display: block;
color: #3c3c3c;
}

.form-actions {
margin-top: 1rem;
}
30 changes: 30 additions & 0 deletions src/app/book/book-new/book-new.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Component } from '@angular/core';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import { NgIf } from '@angular/common';
import { BookApiService } from '../book-api.service';
import { take } from 'rxjs';
import { validAuthorName } from '../validators/author.validator';

@Component({
selector: 'app-book-new',
standalone: true,
imports: [ReactiveFormsModule, NgIf],
templateUrl: './book-new.component.html',
styleUrls: ['./book-new.component.scss']
})
export class BookNewComponent {
form = this.formBuilder.nonNullable.group({
title: ['', [Validators.required]],
subtitle: [''],
author: ['', [Validators.required, validAuthorName()]],
abstract: [''],
isbn: ['']
});

constructor(private readonly formBuilder: FormBuilder, private readonly bookApiService: BookApiService) {
}

submit() {
this.bookApiService.create(this.form.getRawValue()).pipe(take(1)).subscribe()
}
}
17 changes: 17 additions & 0 deletions src/app/book/book.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<input
class="book-filter"
type="search"
placeholder="Filter your books..."
(input)="updateBookSearchTerm($event)"
/>

<div>
<a routerLink="new">Create Book</a>
</div>


<app-book-card
[content]="book"
(detailClick)="goToBookDetails($event)"
*ngFor="let book of books() | bookFilter: bookSearchTerm"
></app-book-card>
Loading