Ai Search finished, some makeup changes, guards, brokerSearch

This commit is contained in:
Andreas Knuth 2024-08-28 15:41:59 +02:00
parent 8721be4a90
commit ede8b66d83
20 changed files with 139 additions and 53 deletions

View File

@ -14,6 +14,5 @@ export class AppController {
@Get() @Get()
getHello(@Request() req): string { getHello(@Request() req): string {
return req.user; return req.user;
//return 'dfgdf';
} }
} }

View File

@ -1,24 +1,30 @@
import { Controller, Get, Param } from '@nestjs/common'; import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { AdminAuthGuard } from '../jwt-auth/admin-auth.guard.js';
import { AuthService } from './auth.service.js'; import { AuthService } from './auth.service.js';
@Controller('auth') @Controller('auth')
export class AuthController { export class AuthController {
constructor(private readonly authService: AuthService) {} constructor(private readonly authService: AuthService) {}
@UseGuards(AdminAuthGuard)
@Get() @Get()
getAccessToken(): any { getAccessToken(): any {
return this.authService.getAccessToken(); return this.authService.getAccessToken();
} }
@UseGuards(AdminAuthGuard)
@Get('users') @Get('users')
getUsers(): any { getUsers(): any {
return this.authService.getUsers(); return this.authService.getUsers();
} }
@UseGuards(AdminAuthGuard)
@Get('user/:userid') @Get('user/:userid')
getUser(@Param('userid') userId: string): any { getUser(@Param('userid') userId: string): any {
return this.authService.getUser(userId); return this.authService.getUser(userId);
} }
@UseGuards(AdminAuthGuard)
@Get('user/:userid/lastlogin') //e0811669-c7eb-4e5e-a699-e8334d5c5b01 -> aknuth @Get('user/:userid/lastlogin') //e0811669-c7eb-4e5e-a699-e8334d5c5b01 -> aknuth
getLastLogin(@Param('userid') userId: string): any { getLastLogin(@Param('userid') userId: string): any {
return this.authService.getLastLogin(userId); return this.authService.getLastLogin(userId);

View File

@ -0,0 +1,18 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class AdminAuthGuard extends AuthGuard('jwt') implements CanActivate {
canActivate(context: ExecutionContext) {
// Add your custom authentication logic here
// for example, call super.logIn(request) to establish a session.
return super.canActivate(context);
}
handleRequest(err, user, info) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user || !user.roles.includes('ADMIN')) {
throw err || new UnauthorizedException(info);
}
return user;
}
}

View File

@ -10,7 +10,7 @@ import { FileService } from '../file/file.service.js';
import { GeoService } from '../geo/geo.service.js'; import { GeoService } from '../geo/geo.service.js';
import { BusinessListing, BusinessListingSchema } from '../models/db.model.js'; import { BusinessListing, BusinessListingSchema } from '../models/db.model.js';
import { BusinessListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js'; import { BusinessListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js';
import { convertBusinessToDrizzleBusiness, convertDrizzleBusinessToBusiness, getDistanceQuery } from '../utils.js'; import { convertBusinessToDrizzleBusiness, convertDrizzleBusinessToBusiness, getDistanceQuery, splitName } from '../utils.js';
@Injectable() @Injectable()
export class BusinessListingService { export class BusinessListingService {
@ -94,10 +94,17 @@ export class BusinessListingService {
if (criteria.title) { if (criteria.title) {
whereConditions.push(or(ilike(businesses.title, `%${criteria.title}%`), ilike(businesses.description, `%${criteria.title}%`))); whereConditions.push(or(ilike(businesses.title, `%${criteria.title}%`), ilike(businesses.description, `%${criteria.title}%`)));
} }
if (criteria.brokerName) { if (criteria.brokerName) {
whereConditions.push(or(ilike(schema.users.firstname, `%${criteria.brokerName}%`), ilike(schema.users.lastname, `%${criteria.brokerName}%`))); const { firstname, lastname } = splitName(criteria.brokerName);
if (firstname === lastname) {
whereConditions.push(or(ilike(schema.users.firstname, `%${firstname}%`), ilike(schema.users.lastname, `%${lastname}%`)));
} else {
whereConditions.push(and(ilike(schema.users.firstname, `%${firstname}%`), ilike(schema.users.lastname, `%${lastname}%`)));
}
} }
// if (criteria.brokerName) {
// whereConditions.push(or(ilike(schema.users.firstname, `%${criteria.brokerName}%`), ilike(schema.users.lastname, `%${criteria.brokerName}%`)));
// }
if (!user?.roles?.includes('ADMIN') ?? false) { if (!user?.roles?.includes('ADMIN') ?? false) {
whereConditions.push(or(eq(businesses.email, user?.username), ne(businesses.draft, true))); whereConditions.push(or(eq(businesses.email, user?.username), ne(businesses.draft, true)));
} }

View File

@ -3,6 +3,7 @@ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston'; import { Logger } from 'winston';
import { ZodError } from 'zod'; import { ZodError } from 'zod';
import { FileService } from '../file/file.service.js'; import { FileService } from '../file/file.service.js';
import { JwtAuthGuard } from '../jwt-auth/jwt-auth.guard.js';
import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js'; import { OptionalJwtAuthGuard } from '../jwt-auth/optional-jwt-auth.guard.js';
import { User } from '../models/db.model'; import { User } from '../models/db.model';
import { JwtUser, Subscription, UserListingCriteria } from '../models/main.model.js'; import { JwtUser, Subscription, UserListingCriteria } from '../models/main.model.js';
@ -77,6 +78,7 @@ export class UserController {
return result; return result;
} }
@UseGuards(JwtAuthGuard)
@Get('subscriptions/:id') @Get('subscriptions/:id')
async findSubscriptionsById(@Param('id') id: string): Promise<Subscription[]> { async findSubscriptionsById(@Param('id') id: string): Promise<Subscription[]> {
const subscriptions = this.fileService.getSubscriptions(); const subscriptions = this.fileService.getSubscriptions();

View File

@ -10,6 +10,7 @@ import { environment } from '../environments/environment';
import { customKeycloakAdapter } from '../keycloak'; import { customKeycloakAdapter } from '../keycloak';
import { routes } from './app.routes'; import { routes } from './app.routes';
import { LoadingInterceptor } from './interceptors/loading.interceptor'; import { LoadingInterceptor } from './interceptors/loading.interceptor';
import { TimeoutInterceptor } from './interceptors/timeout.interceptor';
import { KeycloakInitializerService } from './services/keycloak-initializer.service'; import { KeycloakInitializerService } from './services/keycloak-initializer.service';
import { SelectOptionsService } from './services/select-options.service'; import { SelectOptionsService } from './services/select-options.service';
import { createLogger } from './utils/utils'; import { createLogger } from './utils/utils';
@ -44,6 +45,15 @@ export const appConfig: ApplicationConfig = {
useClass: KeycloakBearerInterceptor, useClass: KeycloakBearerInterceptor,
multi: true, multi: true,
}, },
{
provide: HTTP_INTERCEPTORS,
useClass: TimeoutInterceptor,
multi: true,
},
{
provide: 'TIMEOUT_DURATION',
useValue: 5000, // Standard-Timeout von 5 Sekunden
},
provideRouter( provideRouter(
routes, routes,
withEnabledBlockingInitialNavigation(), withEnabledBlockingInitialNavigation(),

View File

@ -124,7 +124,7 @@ export const routes: Routes = [
{ {
path: 'emailUs', path: 'emailUs',
component: EmailUsComponent, component: EmailUsComponent,
canActivate: [AuthGuard], // canActivate: [AuthGuard],
}, },
// ######### // #########
// Logout // Logout

View File

@ -73,7 +73,7 @@
<a (click)="login()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Log In</a> <a (click)="login()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Log In</a>
</li> </li>
<li> <li>
<a (click)="register()" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Register</a> <a routerLink="/pricing" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Register</a>
</li> </li>
</ul> </ul>
</div> </div>

View File

@ -109,9 +109,7 @@ export class HeaderComponent {
redirectUri: `${window.location.origin}/login${this.router.routerState.snapshot.url}`, redirectUri: `${window.location.origin}/login${this.router.routerState.snapshot.url}`,
}); });
} }
register() {
this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
}
isActive(route: string): boolean { isActive(route: string): boolean {
return this.router.url === route; return this.router.url === route;
} }

View File

@ -330,21 +330,23 @@
<div> <div>
<label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label> <label for="price" class="block mb-2 text-sm font-medium text-gray-900">Price</label>
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<input <!-- <input
type="number" type="number"
id="price-from" id="price-from"
[(ngModel)]="criteria.minPrice" [(ngModel)]="criteria.minPrice"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
placeholder="From" placeholder="From"
/> /> -->
<app-validated-price name="price-from" [(ngModel)]="criteria.minPrice" placeholder="From" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
<span>-</span> <span>-</span>
<input <app-validated-price name="price-to" [(ngModel)]="criteria.maxPrice" placeholder="To" inputClasses="bg-gray-50 text-sm !mt-0 p-2.5"></app-validated-price>
<!-- <input
type="number" type="number"
id="price-to" id="price-to"
[(ngModel)]="criteria.maxPrice" [(ngModel)]="criteria.maxPrice"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-1/2 p-2.5"
placeholder="To" placeholder="To"
/> /> -->
</div> </div>
</div> </div>
<div> <div>

View File

@ -0,0 +1,28 @@
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { Observable, throwError, TimeoutError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements HttpInterceptor {
constructor(@Optional() @Inject('TIMEOUT_DURATION') private timeoutDuration: number = 5000) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
timeout(this.timeoutDuration),
catchError((error: any) => {
if (error instanceof TimeoutError) {
// Timeout error handling
return throwError(
() =>
new HttpErrorResponse({
error: 'Request timed out',
status: 408, // HTTP status code for Request Timeout
}),
);
}
return throwError(() => error);
}),
);
}
}

View File

@ -110,7 +110,7 @@ export class DetailsBusinessListingComponent {
} catch (error) { } catch (error) {
this.messageService.addMessage({ this.messageService.addMessage({
severity: 'danger', severity: 'danger',
text: 'An error occurred while sending the request', text: 'An error occurred while sending the request - Please check your inputs',
duration: 5000, duration: 5000,
}); });
if (error.error && Array.isArray(error.error?.message)) { if (error.error && Array.isArray(error.error?.message)) {

View File

@ -138,7 +138,7 @@ export class DetailsCommercialPropertyListingComponent {
} catch (error) { } catch (error) {
this.messageService.addMessage({ this.messageService.addMessage({
severity: 'danger', severity: 'danger',
text: 'An error occurred while sending the request', text: 'An error occurred while sending the request - Please check your inputs',
duration: 5000, duration: 5000,
}); });
if (error.error && Array.isArray(error.error?.message)) { if (error.error && Array.isArray(error.error?.message)) {

View File

@ -1,7 +1,7 @@
:host ::ng-deep p { :host ::ng-deep p {
display: block; display: block;
margin-top: 1em; // margin-top: 1em;
margin-bottom: 1em; // margin-bottom: 1em;
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
font-size: 1rem; /* oder 1rem, abhängig vom Browser und den Standardeinstellungen */ font-size: 1rem; /* oder 1rem, abhängig vom Browser und den Standardeinstellungen */

View File

@ -110,7 +110,9 @@
</button> </button>
</div> </div>
</div> </div>
} @if(criteria && !aiSearch){ @if(aiSearchFailed){
<div id="error-message" class="w-full max-w-3xl mx-auto mt-2 text-red-600 text-center">Search timed out. Please try again or use classic Search</div>
} } @if(criteria && !aiSearch){
<div class="w-full max-w-3xl mx-auto bg-white rounded-lg flex flex-col md:flex-row md:border md:border-gray-300"> <div class="w-full max-w-3xl mx-auto bg-white rounded-lg flex flex-col md:flex-row md:border md:border-gray-300">
<div class="md:flex-none md:w-48 flex-1 md:border-r border-gray-300 overflow-hidden mb-2 md:mb-0"> <div class="md:flex-none md:w-48 flex-1 md:border-r border-gray-300 overflow-hidden mb-2 md:mb-0">
<div class="relative max-sm:border border-gray-300 rounded-md"> <div class="relative max-sm:border border-gray-300 rounded-md">
@ -184,7 +186,8 @@
} }
<div class="mt-4 flex items-center justify-center text-gray-700"> <div class="mt-4 flex items-center justify-center text-gray-700">
<span class="mr-2">AI-Search</span> <span class="mr-2">AI-Search</span>
<span class="bg-sky-300 text-teal-800 text-xs font-semibold px-2 py-1 rounded">BETA</span> <span [attr.data-tooltip-target]="tooltipTargetBeta" class="bg-sky-300 text-teal-800 text-xs font-semibold px-2 py-1 rounded">BETA</span>
<app-tooltip [id]="tooltipTargetBeta" text="The AI will convert your input into filter criteria. Please check them in the filter menu after the search"></app-tooltip>
<span class="ml-2">- Try now</span> <span class="ml-2">- Try now</span>
<div class="ml-4 relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in"> <div class="ml-4 relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
<input (click)="toggleAiSearch()" type="checkbox" name="toggle" id="toggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 border-gray-300 appearance-none cursor-pointer" /> <input (click)="toggleAiSearch()" type="checkbox" name="toggle" id="toggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 border-gray-300 appearance-none cursor-pointer" />

View File

@ -4,10 +4,12 @@ import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { NgSelectModule } from '@ng-select/ng-select'; import { NgSelectModule } from '@ng-select/ng-select';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { initFlowbite } from 'flowbite';
import { KeycloakService } from 'keycloak-angular'; import { KeycloakService } from 'keycloak-angular';
import { catchError, concat, debounceTime, distinctUntilChanged, lastValueFrom, Observable, of, Subject, Subscription, switchMap, tap } from 'rxjs'; import { catchError, concat, debounceTime, distinctUntilChanged, lastValueFrom, Observable, of, Subject, Subscription, switchMap, tap } from 'rxjs';
import { BusinessListingCriteria, CityAndStateResult, CommercialPropertyListingCriteria, GeoResult, KeycloakUser, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model'; import { BusinessListingCriteria, CityAndStateResult, CommercialPropertyListingCriteria, GeoResult, KeycloakUser, UserListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
import { ModalService } from '../../components/search-modal/modal.service'; import { ModalService } from '../../components/search-modal/modal.service';
import { TooltipComponent } from '../../components/tooltip/tooltip.component';
import { AiService } from '../../services/ai.service'; import { AiService } from '../../services/ai.service';
import { CriteriaChangeService } from '../../services/criteria-change.service'; import { CriteriaChangeService } from '../../services/criteria-change.service';
import { GeoService } from '../../services/geo.service'; import { GeoService } from '../../services/geo.service';
@ -29,12 +31,12 @@ import {
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
standalone: true, standalone: true,
imports: [CommonModule, FormsModule, RouterModule, NgSelectModule], imports: [CommonModule, FormsModule, RouterModule, NgSelectModule, TooltipComponent],
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrl: './home.component.scss', styleUrl: './home.component.scss',
}) })
export class HomeComponent { export class HomeComponent {
placeholders: string[] = ['waterfront property close to Houston less than 1M', 'construction area with beach access close to San Diego']; placeholders: string[] = ['Property close to Houston less than 10M', 'Franchise business in Austin price less than 500K'];
activeTabAction: 'business' | 'commercialProperty' | 'broker' = 'business'; activeTabAction: 'business' | 'commercialProperty' | 'broker' = 'business';
type: string; type: string;
maxPrice: string; maxPrice: string;
@ -53,6 +55,7 @@ export class HomeComponent {
aiSearch = false; aiSearch = false;
aiSearchText = ''; aiSearchText = '';
aiSearchFailed = false;
loadingAi = false; loadingAi = false;
@ViewChild('aiSearchInput', { static: false }) searchInput!: ElementRef; @ViewChild('aiSearchInput', { static: false }) searchInput!: ElementRef;
typingSpeed: number = 100; // Geschwindigkeit des Tippens (ms) typingSpeed: number = 100; // Geschwindigkeit des Tippens (ms)
@ -61,7 +64,7 @@ export class HomeComponent {
charIndex: number = 0; charIndex: number = 0;
typingInterval: any; typingInterval: any;
showInput: boolean = true; // Steuerung der Anzeige des Eingabefelds showInput: boolean = true; // Steuerung der Anzeige des Eingabefelds
tooltipTargetBeta = 'tooltipTargetBeta';
public constructor( public constructor(
private router: Router, private router: Router,
private modalService: ModalService, private modalService: ModalService,
@ -77,6 +80,9 @@ export class HomeComponent {
private aiService: AiService, private aiService: AiService,
) {} ) {}
async ngOnInit() { async ngOnInit() {
setTimeout(() => {
initFlowbite();
}, 0);
const token = await this.keycloakService.getToken(); const token = await this.keycloakService.getToken();
sessionStorage.removeItem('businessListings'); sessionStorage.removeItem('businessListings');
sessionStorage.removeItem('commercialPropertyListings'); sessionStorage.removeItem('commercialPropertyListings');
@ -219,6 +225,7 @@ export class HomeComponent {
} }
toggleAiSearch() { toggleAiSearch() {
this.aiSearch = !this.aiSearch; this.aiSearch = !this.aiSearch;
this.aiSearchFailed = false;
if (!this.aiSearch) { if (!this.aiSearch) {
this.aiSearchText = ''; this.aiSearchText = '';
this.stopTypingEffect(); this.stopTypingEffect();
@ -271,33 +278,39 @@ export class HomeComponent {
} }
async generateAiResponse() { async generateAiResponse() {
this.loadingAi = true; this.loadingAi = true;
const result = await this.aiService.generateAiReponse(this.aiSearchText); this.aiSearchFailed = false;
console.log(result); try {
let criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria | any; const result = await this.aiService.generateAiReponse(this.aiSearchText);
if (result.criteriaType === 'businessListings') { let criteria: BusinessListingCriteria | CommercialPropertyListingCriteria | UserListingCriteria | any;
this.changeTab('business'); if (result.criteriaType === 'businessListings') {
criteria = result as BusinessListingCriteria; this.changeTab('business');
} else if (result.criteriaType === 'commercialPropertyListings') { criteria = result as BusinessListingCriteria;
this.changeTab('commercialProperty'); } else if (result.criteriaType === 'commercialPropertyListings') {
criteria = result as CommercialPropertyListingCriteria; this.changeTab('commercialProperty');
} else { criteria = result as CommercialPropertyListingCriteria;
this.changeTab('broker');
criteria = result as UserListingCriteria;
}
const city = criteria.city as string;
if (city && city.length > 0) {
let results = await lastValueFrom(this.geoService.findCitiesStartingWith(city, criteria.state));
if (results.length > 0) {
criteria.city = results[0];
} else { } else {
criteria.city = null; this.changeTab('broker');
criteria = result as UserListingCriteria;
} }
const city = criteria.city as string;
if (city && city.length > 0) {
let results = await lastValueFrom(this.geoService.findCitiesStartingWith(city, criteria.state));
if (results.length > 0) {
criteria.city = results[0];
} else {
criteria.city = null;
}
}
if (criteria.radius && criteria.radius.length > 0) {
criteria.radius = parseInt(criteria.radius);
}
this.loadingAi = false;
this.criteria = assignProperties(this.criteria, criteria);
this.search();
} catch (error) {
console.log(error);
this.aiSearchFailed = true;
this.loadingAi = false;
} }
if (criteria.radius && criteria.radius.length > 0) {
criteria.radius = parseInt(criteria.radius);
}
this.loadingAi = false;
this.criteria = assignProperties(this.criteria, criteria);
this.search();
} }
} }

View File

@ -137,6 +137,6 @@
<div class="mt-16 text-center"> <div class="mt-16 text-center">
<h2 class="text-2xl font-semibold mb-4">Not sure which plan is right for you?</h2> <h2 class="text-2xl font-semibold mb-4">Not sure which plan is right for you?</h2>
<p class="text-gray-600 mb-8">Contact our sales team for a personalized recommendation.</p> <p class="text-gray-600 mb-8">Contact our sales team for a personalized recommendation.</p>
<a href="#" class="bg-blue-500 text-white rounded-full px-6 py-3 font-semibold hover:bg-blue-600 transition duration-300">Contact Sales</a> <a routerLink="/emailUs" class="bg-blue-500 text-white rounded-full px-6 py-3 font-semibold hover:bg-blue-600 transition duration-300">Contact Sales</a>
</div> </div>
</div> </div>

View File

@ -173,7 +173,7 @@ export class AccountComponent {
} catch (error) { } catch (error) {
this.messageService.addMessage({ this.messageService.addMessage({
severity: 'danger', severity: 'danger',
text: 'An error occurred while saving the profile', text: 'An error occurred while saving the profile - Please check your inputs',
duration: 5000, duration: 5000,
}); });
if (error.error && Array.isArray(error.error?.message)) { if (error.error && Array.isArray(error.error?.message)) {

View File

@ -128,7 +128,7 @@ export class EditBusinessListingComponent {
} catch (error) { } catch (error) {
this.messageService.addMessage({ this.messageService.addMessage({
severity: 'danger', severity: 'danger',
text: 'An error occurred while saving the profile', text: 'An error occurred while saving the profile - Please check your inputs',
duration: 5000, duration: 5000,
}); });
if (error.error && Array.isArray(error.error?.message)) { if (error.error && Array.isArray(error.error?.message)) {

View File

@ -57,8 +57,8 @@ export class EmailUsComponent {
} catch (error) { } catch (error) {
this.messageService.addMessage({ this.messageService.addMessage({
severity: 'danger', severity: 'danger',
text: 'An error occurred', text: 'Please check your inputs',
duration: 50000, duration: 5000,
}); });
if (error.error && Array.isArray(error.error?.message)) { if (error.error && Array.isArray(error.error?.message)) {
this.validationMessagesService.updateMessages(error.error.message); this.validationMessagesService.updateMessages(error.error.message);