account, myListings and emailUs pages
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
<div class="flex items-center md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse">
|
||||
<button
|
||||
type="button"
|
||||
class="flex text-sm bg-gray-800 rounded-full md:me-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600"
|
||||
class="flex text-sm bg-gray-200 rounded-full md:me-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600"
|
||||
id="user-menu-button"
|
||||
aria-expanded="false"
|
||||
[attr.data-dropdown-toggle]="user ? 'user-login' : 'user-unknown'"
|
||||
@@ -27,7 +27,7 @@
|
||||
>
|
||||
<span class="sr-only">Open user menu</span>
|
||||
@if(user){
|
||||
<img class="w-8 h-8 rounded-full" src="/docs/images/people/profile-picture-3.jpg" alt="user photo" />
|
||||
<img class="w-8 h-8 rounded-full object-cover" src="{{ profileUrl }}" alt="user photo" />
|
||||
} @else {
|
||||
<i class="flex justify-center items-center text-stone-50 w-8 h-8 rounded-full fa-solid fa-bars"></i>
|
||||
}
|
||||
@@ -36,7 +36,7 @@
|
||||
@if(user){
|
||||
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600" id="user-login">
|
||||
<div class="px-4 py-3">
|
||||
<span class="block text-sm text-gray-900 dark:text-white">Welcome, {{ user.firstName }} </span>
|
||||
<span class="block text-sm text-gray-900 dark:text-white">Welcome, {{ user.firstname }} </span>
|
||||
<span class="block text-sm text-gray-500 truncate dark:text-gray-400">{{ user.email }}</span>
|
||||
</div>
|
||||
<ul class="py-2" aria-labelledby="user-menu-button">
|
||||
|
||||
@@ -5,8 +5,11 @@ import { faUserGear } from '@fortawesome/free-solid-svg-icons';
|
||||
import { initFlowbite } from 'flowbite';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { Observable } from 'rxjs';
|
||||
import { KeycloakUser } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
||||
import { emailToDirName, KeycloakUser } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { SharedService } from '../../services/shared.service';
|
||||
import { UserService } from '../../services/user.service';
|
||||
import { map2User } from '../../utils/utils';
|
||||
@Component({
|
||||
selector: 'header',
|
||||
@@ -18,17 +21,27 @@ import { map2User } from '../../utils/utils';
|
||||
export class HeaderComponent {
|
||||
public buildVersion = environment.buildVersion;
|
||||
user$: Observable<KeycloakUser>;
|
||||
user: KeycloakUser;
|
||||
keycloakUser: KeycloakUser;
|
||||
user: User;
|
||||
activeItem;
|
||||
faUserGear = faUserGear;
|
||||
constructor(public keycloakService: KeycloakService, private router: Router) {}
|
||||
profileUrl: string;
|
||||
env = environment;
|
||||
constructor(public keycloakService: KeycloakService, private router: Router, private userService: UserService, private sharedService: SharedService) {}
|
||||
|
||||
async ngOnInit() {
|
||||
const token = await this.keycloakService.getToken();
|
||||
this.user = map2User(token);
|
||||
this.keycloakUser = map2User(token);
|
||||
this.user = await this.userService.getByMail(this.keycloakUser.email);
|
||||
this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
||||
setTimeout(() => {
|
||||
initFlowbite();
|
||||
});
|
||||
this.sharedService.currentProfilePhoto.subscribe(photoUrl => {
|
||||
if (photoUrl) {
|
||||
this.profileUrl = photoUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- Modal -->
|
||||
<div *ngIf="showModal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center">
|
||||
<div class="bg-white p-5 rounded-lg shadow-xl" style="width: 90%; max-width: 600px">
|
||||
<h3 class="text-lg font-semibold mb-4">Crop Image</h3>
|
||||
<image-cropper (loadImageFailed)="loadImageFailed()" [imageChangedEvent]="imageChangedEvent" [maintainAspectRatio]="false" format="png" (imageCropped)="imageCropped($event)"></image-cropper>
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button (click)="closeModal()" class="mr-2 px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300">Cancel</button>
|
||||
<button (click)="uploadImage()" class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" #fileInput style="display: none" (change)="fileChangeEvent($event)" accept="image/*" />
|
||||
@@ -0,0 +1,6 @@
|
||||
::ng-deep image-cropper {
|
||||
justify-content: center;
|
||||
& > div {
|
||||
width: unset !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Component, ElementRef, Input, output, ViewChild } from '@angular/core';
|
||||
import { ImageCroppedEvent, ImageCropperComponent } from 'ngx-image-cropper';
|
||||
import { UploadParams } from '../../../../../bizmatch-server/src/models/main.model';
|
||||
import { ImageService } from '../../services/image.service';
|
||||
import { ListingsService } from '../../services/listings.service';
|
||||
import { SharedModule } from '../../shared/shared/shared.module';
|
||||
export interface UploadReponse {
|
||||
success: boolean;
|
||||
type: 'uploadPropertyPicture' | 'uploadCompanyLogo' | 'uploadProfile';
|
||||
}
|
||||
@Component({
|
||||
selector: 'app-image-crop-and-upload',
|
||||
standalone: true,
|
||||
imports: [SharedModule, ImageCropperComponent],
|
||||
templateUrl: './image-crop-and-upload.component.html',
|
||||
styleUrl: './image-crop-and-upload.component.scss',
|
||||
})
|
||||
export class ImageCropAndUploadComponent {
|
||||
showModal = false;
|
||||
imageChangedEvent: any = '';
|
||||
croppedImage: Blob | null = null;
|
||||
@Input() uploadParams: UploadParams;
|
||||
uploadFinished = output<UploadReponse>();
|
||||
@ViewChild('fileInput', { static: true }) fileInput!: ElementRef<HTMLInputElement>;
|
||||
|
||||
constructor(private imageService: ImageService, private listingsService: ListingsService) {}
|
||||
|
||||
ngOnInit() {}
|
||||
ngOnChanges() {
|
||||
this.openFileDialog();
|
||||
}
|
||||
openFileDialog() {
|
||||
if (this.uploadParams) {
|
||||
this.fileInput.nativeElement.click();
|
||||
}
|
||||
}
|
||||
|
||||
fileChangeEvent(event: any): void {
|
||||
this.imageChangedEvent = event;
|
||||
this.showModal = true;
|
||||
}
|
||||
|
||||
imageCropped(event: ImageCroppedEvent) {
|
||||
this.croppedImage = event.blob;
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
this.imageChangedEvent = null;
|
||||
this.croppedImage = null;
|
||||
this.showModal = false;
|
||||
this.uploadFinished.emit({ success: false, type: this.uploadParams.type });
|
||||
}
|
||||
|
||||
uploadImage() {
|
||||
if (this.croppedImage) {
|
||||
this.imageService.uploadImage(this.croppedImage, this.uploadParams.type, this.uploadParams.imagePath, this.uploadParams.serialId).subscribe(
|
||||
response => {
|
||||
console.log('Upload successful', response);
|
||||
this.closeModal();
|
||||
this.uploadFinished.emit({ success: true, type: this.uploadParams.type });
|
||||
},
|
||||
error => {
|
||||
console.error('Upload failed', error);
|
||||
this.closeModal();
|
||||
this.uploadFinished.emit({ success: false, type: this.uploadParams.type });
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
loadImageFailed() {
|
||||
console.error('Load image failed');
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,25 @@ import { MessageService } from './message.service';
|
||||
standalone: true,
|
||||
imports: [AsyncPipe, NgIf],
|
||||
template: `
|
||||
<div id="toast-success" class="flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800" role="alert">
|
||||
<div
|
||||
*ngIf="messageService.modalVisible$ | async"
|
||||
id="toast-success"
|
||||
class="fixed top-[0.5rem] right-[1rem] flex items-center w-full max-w-xs p-4 mb-4 text-gray-500 bg-slate-200 rounded-lg shadow dark:text-gray-400 dark:bg-gray-800"
|
||||
role="alert"
|
||||
>
|
||||
<div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 text-green-500 bg-green-100 rounded-lg dark:bg-green-800 dark:text-green-200">
|
||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5Zm3.707 8.207-4 4a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L9 10.586l3.293-3.293a1 1 0 0 1 1.414 1.414Z" />
|
||||
</svg>
|
||||
<span class="sr-only">Check icon</span>
|
||||
</div>
|
||||
<div class="ms-3 text-sm font-normal">Item moved successfully.</div>
|
||||
<div class="ms-3 text-sm font-normal">{{ messageService.message$ | async }}</div>
|
||||
<button
|
||||
type="button"
|
||||
class="ms-auto -mx-1.5 -my-1.5 bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex items-center justify-center h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:bg-gray-800 dark:hover:bg-gray-700"
|
||||
data-dismiss-target="#toast-success"
|
||||
aria-label="Close"
|
||||
(click)="messageService.reject()"
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||
|
||||
Reference in New Issue
Block a user