Fixes and adjustments to demo
This commit is contained in:
@@ -4,7 +4,8 @@ import {
|
||||
} from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { appRoutes } from './app.routes';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideBrowserGlobalErrorListeners(), provideRouter(appRoutes)],
|
||||
providers: [provideBrowserGlobalErrorListeners(), provideRouter(appRoutes), provideHttpClient()],
|
||||
};
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
<app-nx-welcome></app-nx-welcome>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import { Route } from '@angular/router';
|
||||
import { Home } from './features/home/home';
|
||||
|
||||
export const appRoutes: Route[] = [];
|
||||
export const appRoutes: Route[] = [
|
||||
{
|
||||
path: '',
|
||||
component: Home,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideRouter, Router } from '@angular/router';
|
||||
import { App } from './app';
|
||||
import { NxWelcome } from './nx-welcome';
|
||||
import { appRoutes } from './app.routes';
|
||||
|
||||
describe('App', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [App, NxWelcome],
|
||||
imports: [App],
|
||||
providers: [provideRouter(appRoutes)],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should render title', async () => {
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render Home page on default route', async () => {
|
||||
const fixture = TestBed.createComponent(App);
|
||||
const router = TestBed.inject(Router);
|
||||
|
||||
await router.navigateByUrl('/');
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain(
|
||||
'Welcome web-app',
|
||||
);
|
||||
expect(compiled.textContent).toContain('Posts');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NxWelcome } from './nx-welcome';
|
||||
import { Post } from '@shared/prisma-generated/src/types'
|
||||
|
||||
@Component({
|
||||
imports: [NxWelcome, RouterModule],
|
||||
imports: [RouterModule],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss',
|
||||
})
|
||||
export class App {
|
||||
protected title = 'web-app';
|
||||
protected posts: Post[] = [];
|
||||
}
|
||||
|
||||
32
apps/web-app/src/app/features/home/home.html
Normal file
32
apps/web-app/src/app/features/home/home.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<section class="home">
|
||||
<h1>Posts</h1>
|
||||
|
||||
@if (isLoading) {
|
||||
<p>Loading posts...</p>
|
||||
} @else if (errorMessage) {
|
||||
<p class="error-message">{{ errorMessage }}</p>
|
||||
} @else if (posts.length === 0) {
|
||||
<p class="no-posts-message">No posts found.</p>
|
||||
} @else {
|
||||
<div class="cards-container">
|
||||
@for (post of posts; track post) {
|
||||
<mat-card class="post-card" appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{ post.title }}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>{{ post.content || 'No content' }}</p>
|
||||
<p>UserId: {{ post.userId }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="button-container" data-testid="fetch-button">
|
||||
<button mat-raised-button color="primary" (click)="fetchPosts()" [disabled]="isLoading">
|
||||
{{ isLoading ? 'Loading...' : 'Fetch Posts' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
56
apps/web-app/src/app/features/home/home.scss
Normal file
56
apps/web-app/src/app/features/home/home.scss
Normal file
@@ -0,0 +1,56 @@
|
||||
.button-container {
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.cards-container {
|
||||
display: flex;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
justify-items: center;
|
||||
width: 100%;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.post-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
mat-card-header {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
mat-card-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
max-height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #d32f2f;
|
||||
font-weight: 500;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.no-posts-message {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.home {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
34
apps/web-app/src/app/features/home/home.spec.ts
Normal file
34
apps/web-app/src/app/features/home/home.spec.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { Home } from './home';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DebugElement } from '@angular/core';
|
||||
|
||||
describe('Home', () => {
|
||||
let component: Home;
|
||||
let fixture: ComponentFixture<Home>;
|
||||
let debugElement: DebugElement;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Home],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Home);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
|
||||
debugElement = fixture.debugElement;
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should have button', () => {
|
||||
const fetchButton = debugElement.query(
|
||||
By.css('[data-testid="fetch-button"]')
|
||||
);
|
||||
|
||||
expect(fetchButton).toBeDefined();
|
||||
})
|
||||
});
|
||||
39
apps/web-app/src/app/features/home/home.ts
Normal file
39
apps/web-app/src/app/features/home/home.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { ChangeDetectorRef, Component, inject } from '@angular/core';
|
||||
import { PostDto } from '@shared/shared-dto'
|
||||
import { PostService } from '../../services/PostService/PostService/post-service';
|
||||
import { timeout } from 'rxjs';
|
||||
import { MatButtonModule } from '@angular/material/button'
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home',
|
||||
imports: [MatButtonModule, MatCardModule],
|
||||
templateUrl: './home.html',
|
||||
styleUrl: './home.scss',
|
||||
})
|
||||
export class Home {
|
||||
posts: PostDto[] = [];
|
||||
isLoading = false;
|
||||
errorMessage = '';
|
||||
|
||||
postService = inject(PostService);
|
||||
cdr = inject(ChangeDetectorRef);
|
||||
|
||||
fetchPosts(): void {
|
||||
this.isLoading = true;
|
||||
this.errorMessage = '';
|
||||
this.postService.getPosts().pipe(timeout(8000)).subscribe({
|
||||
next: (posts) => {
|
||||
this.posts = posts;
|
||||
this.isLoading = false;
|
||||
this.cdr.detectChanges();
|
||||
},
|
||||
error: () => {
|
||||
this.errorMessage = 'Failed to load posts.';
|
||||
this.isLoading = false;
|
||||
this.cdr.detectChanges();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PostService } from './post-service';
|
||||
|
||||
describe('PostService', () => {
|
||||
let service: PostService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(PostService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Observable } from 'rxjs';
|
||||
import { PostDto } from '@shared/shared-dto';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PostService {
|
||||
private httpClient = inject(HttpClient);
|
||||
private baseUrl = 'http://localhost:3000/api';
|
||||
|
||||
getPosts(): Observable<PostDto[]> {
|
||||
return this.httpClient.get<PostDto[]>(`${this.baseUrl}/posts`);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,11 @@
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
|
||||
@@ -1 +1,41 @@
|
||||
|
||||
// Include theming for Angular Material with `mat.theme()`.
|
||||
// This Sass mixin will define CSS variables that are used for styling Angular Material
|
||||
// components according to the Material 3 design spec.
|
||||
// Learn more about theming and how to use it for your application's
|
||||
// custom components at https://material.angular.dev/guide/theming
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
@include mat.theme((
|
||||
color: (
|
||||
primary: mat.$azure-palette,
|
||||
tertiary: mat.$blue-palette,
|
||||
),
|
||||
typography: Roboto,
|
||||
density: 0,
|
||||
));
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
// Default the application to a light color theme. This can be changed to
|
||||
// `dark` to enable the dark color theme, or to `light dark` to defer to the
|
||||
// user's system settings.
|
||||
color-scheme: light;
|
||||
|
||||
// Set a default background, font and text colors for the application using
|
||||
// Angular Material's system-level CSS variables. Learn more about these
|
||||
// variables at https://material.angular.dev/guide/system-variables
|
||||
background-color: var(--mat-sys-surface);
|
||||
color: var(--mat-sys-on-surface);
|
||||
font: var(--mat-sys-body-medium);
|
||||
|
||||
// Reset the user agent margin.
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
}
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
|
||||
Reference in New Issue
Block a user