Validation first Part
This commit is contained in:
parent
55e800009e
commit
2955c034a0
|
|
@ -277,3 +277,7 @@ export function emailToDirName(email: string): string {
|
||||||
return normalizedEmail;
|
return normalizedEmail;
|
||||||
}
|
}
|
||||||
export const LISTINGS_PER_PAGE = 12;
|
export const LISTINGS_PER_PAGE = 12;
|
||||||
|
export interface ValidationMessage {
|
||||||
|
field: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<p>validated-input works!</p>
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core';
|
||||||
|
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-validated-input',
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<label [for]="id" class="block text-sm font-medium text-gray-700">
|
||||||
|
{{ label }}
|
||||||
|
<span class="text-red-500 ml-1">{{ validationMessage }}</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
[ngModel]="value"
|
||||||
|
(input)="onInputChange($event)"
|
||||||
|
(blur)="onTouched()"
|
||||||
|
[attr.name]="name"
|
||||||
|
[required]="required"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, FormsModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => ValidatedInputComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ValidatedInputComponent implements ControlValueAccessor {
|
||||||
|
@Input() label: string = '';
|
||||||
|
@Input() id: string = '';
|
||||||
|
@Input() name: string = '';
|
||||||
|
@Input() type: string = 'text';
|
||||||
|
@Input() required: boolean = false;
|
||||||
|
@Input() validationMessage: string = '';
|
||||||
|
|
||||||
|
@Input() value: any = '';
|
||||||
|
@Output() valueChange = new EventEmitter<any>();
|
||||||
|
|
||||||
|
onChange: any = () => {};
|
||||||
|
onTouched: any = () => {};
|
||||||
|
|
||||||
|
writeValue(value: any): void {
|
||||||
|
if (value !== undefined) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: any): void {
|
||||||
|
this.onChange = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: any): void {
|
||||||
|
this.onTouched = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
onInputChange(event: Event): void {
|
||||||
|
const value = (event.target as HTMLInputElement).value;
|
||||||
|
this.value = value;
|
||||||
|
this.onChange(value);
|
||||||
|
this.valueChange.emit(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState?(isDisabled: boolean): void {
|
||||||
|
// Implementieren Sie dies, wenn Sie die Deaktivierung des Inputs unterstützen möchten
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<p>validated-select works!</p>
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core';
|
||||||
|
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-validated-select',
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<label [for]="id" class="block text-sm font-medium text-gray-700">
|
||||||
|
{{ label }}
|
||||||
|
<span class="text-red-500 ml-1">{{ validationMessage }}</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
[id]="id"
|
||||||
|
[name]="name"
|
||||||
|
[ngModel]="value"
|
||||||
|
(change)="onSelectChange($event)"
|
||||||
|
(blur)="onTouched()"
|
||||||
|
[required]="required"
|
||||||
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||||
|
>
|
||||||
|
<option value="" disabled selected>Select an option</option>
|
||||||
|
<option *ngFor="let option of options" [value]="option.value">
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, FormsModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => ValidatedSelectComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class ValidatedSelectComponent implements ControlValueAccessor {
|
||||||
|
@Input() label: string = '';
|
||||||
|
@Input() id: string = '';
|
||||||
|
@Input() name: string = '';
|
||||||
|
@Input() required: boolean = false;
|
||||||
|
@Input() validationMessage: string = '';
|
||||||
|
@Input() options: Array<{ value: any; label: string }> = [];
|
||||||
|
|
||||||
|
@Input() value: any = '';
|
||||||
|
@Output() valueChange = new EventEmitter<any>();
|
||||||
|
|
||||||
|
onChange: any = () => {};
|
||||||
|
onTouched: any = () => {};
|
||||||
|
|
||||||
|
writeValue(value: any): void {
|
||||||
|
if (value !== undefined) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: any): void {
|
||||||
|
this.onChange = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(fn: any): void {
|
||||||
|
this.onTouched = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectChange(event: Event): void {
|
||||||
|
const value = (event.target as HTMLSelectElement).value;
|
||||||
|
this.value = value;
|
||||||
|
this.onChange(value);
|
||||||
|
this.valueChange.emit(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState?(isDisabled: boolean): void {
|
||||||
|
// Implementieren Sie dies, wenn Sie die Deaktivierung des Selects unterstützen möchten
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<label for="email" class="block text-sm font-medium text-gray-700">E-mail (required)</label>
|
<label for="email" class="block text-sm font-medium text-gray-700">E-mail (required)</label>
|
||||||
<input type="email" id="email" name="email" [(ngModel)]="user.email" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
|
<input type="email" id="email" name="email" [(ngModel)]="user.email" disabled class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
|
||||||
<p class="text-xs text-gray-500 mt-1">You can only modify your email by contacting us at support@bizwatch.net</p>
|
<p class="text-xs text-gray-500 mt-1">You can only modify your email by contacting us at support@bizwatch.net</p>
|
||||||
</div>
|
</div>
|
||||||
@if (isProfessional){
|
@if (isProfessional){
|
||||||
|
|
@ -60,23 +60,26 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<app-validated-input label="First Name" name="firstname" [(ngModel)]="user.firstname" [required]="true" [validationMessage]="getValidationMessage('firstname')"></app-validated-input>
|
||||||
<label for="firstname" class="block text-sm font-medium text-gray-700">First Name</label>
|
<app-validated-input label="Last Name" name="lastname" [(ngModel)]="user.lastname" [required]="true" [validationMessage]="getValidationMessage('lastname')"></app-validated-input>
|
||||||
<input type="text" id="firstname" name="firstname" [(ngModel)]="user.firstname" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="lastname" class="block text-sm font-medium text-gray-700">Last Name</label>
|
|
||||||
<input type="text" id="lastname" name="lastname" [(ngModel)]="user.lastname" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<!-- <div>
|
||||||
<label for="customerType" class="block text-sm font-medium text-gray-700">Customer Type</label>
|
<label for="customerType" class="block text-sm font-medium text-gray-700">Customer Type</label>
|
||||||
<select id="customerType" name="customerType" [(ngModel)]="user.customerType" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
<select id="customerType" name="customerType" [(ngModel)]="user.customerType" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||||
<option *ngFor="let type of customerTypes" [value]="type">{{ type | titlecase }}</option>
|
<option *ngFor="let type of customerTypes" [value]="type">{{ type | titlecase }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div> -->
|
||||||
|
<app-validated-select
|
||||||
|
label="Customer Type"
|
||||||
|
id="customerType"
|
||||||
|
name="customerType"
|
||||||
|
[(ngModel)]="user.customerType"
|
||||||
|
[required]="true"
|
||||||
|
[validationMessage]="getValidationMessage('customerType')"
|
||||||
|
[options]="customerTypeOptions"
|
||||||
|
></app-validated-select>
|
||||||
@if (isProfessional){
|
@if (isProfessional){
|
||||||
<div>
|
<div>
|
||||||
<label for="customerSubType" class="block text-sm font-medium text-gray-700">Professional Type</label>
|
<label for="customerSubType" class="block text-sm font-medium text-gray-700">Professional Type</label>
|
||||||
|
|
@ -223,7 +226,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Responsive version for small screens -->
|
|
||||||
<div class="mt-8 sm:hidden">
|
<div class="mt-8 sm:hidden">
|
||||||
<h3 class="text-lg font-medium text-gray-700 mb-1">Membership Level</h3>
|
<h3 class="text-lg font-medium text-gray-700 mb-1">Membership Level</h3>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
|
|
@ -264,270 +266,5 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<!-- @if(showModal){ -->
|
|
||||||
<app-image-crop-and-upload [uploadParams]="uploadParams" (uploadFinished)="uploadFinished($event)"></app-image-crop-and-upload>
|
<app-image-crop-and-upload [uploadParams]="uploadParams" (uploadFinished)="uploadFinished($event)"></app-image-crop-and-upload>
|
||||||
<app-confirmation></app-confirmation>
|
<app-confirmation></app-confirmation>
|
||||||
<!-- } -->
|
|
||||||
<!-- <div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
|
|
||||||
<div class="p-fluid flex flex-column lg:flex-row">
|
|
||||||
<menu-account></menu-account>
|
|
||||||
<p-toast></p-toast>
|
|
||||||
<div class="surface-card p-5 shadow-2 border-round flex-auto">
|
|
||||||
<div class="text-900 font-semibold text-lg mt-3">Account Details</div>
|
|
||||||
<p-divider></p-divider>
|
|
||||||
<div class="flex gap-5 flex-column-reverse md:flex-row">
|
|
||||||
<div class="flex-auto p-fluid">
|
|
||||||
@if (user){
|
|
||||||
<div class="grid">
|
|
||||||
<div class="mb-4 col-12 md:col-4">
|
|
||||||
<label for="state" class="block font-medium text-900 mb-2">E-mail (required)</label>
|
|
||||||
<input id="state" type="text" [disabled]="true" pInputText [(ngModel)]="user.email" />
|
|
||||||
<p class="font-italic text-xs line-height-1">You can only modify your email by contacting us at support@bizmatch.net</p>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 col-12 md:col-4">
|
|
||||||
<label for="customerType" class="block font-medium text-900 mb-2">Customer Type</label>
|
|
||||||
<p-dropdown id="customerType" [options]="selectOptions?.customerTypes" [(ngModel)]="user.customerType" optionLabel="name" optionValue="value" placeholder="State" [style]="{ width: '100%' }"></p-dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 col-12 md:col-4">
|
|
||||||
<label for="customerType" class="block font-medium text-900 mb-2">Professional Role</label>
|
|
||||||
<p-dropdown
|
|
||||||
id="customerSubType"
|
|
||||||
[options]="selectOptions?.customerSubTypes"
|
|
||||||
[(ngModel)]="user.customerSubType"
|
|
||||||
optionLabel="name"
|
|
||||||
optionValue="value"
|
|
||||||
placeholder="State"
|
|
||||||
[style]="{ width: '100%' }"
|
|
||||||
></p-dropdown>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="firstname" class="block font-medium text-900 mb-2">First Name</label>
|
|
||||||
<input id="firstname" type="text" pInputText [(ngModel)]="user.firstname" />
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="lastname" class="block font-medium text-900 mb-2">Last Name</label>
|
|
||||||
<input id="lastname" type="text" pInputText [(ngModel)]="user.lastname" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (isProfessional){
|
|
||||||
<div class="grid">
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="firstname" class="block font-medium text-900 mb-2">Company Name</label>
|
|
||||||
<input id="firstname" type="text" pInputText [(ngModel)]="user.companyName" />
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="lastname" class="block font-medium text-900 mb-2">Describe yourself</label>
|
|
||||||
<input id="lastname" type="text" pInputText [(ngModel)]="user.description" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
} @if (isProfessional){
|
|
||||||
<div class="grid">
|
|
||||||
<div class="mb-4 col-12 md:col-4">
|
|
||||||
<label for="phoneNumber" class="block font-medium text-900 mb-2">Your Phone Number</label>
|
|
||||||
<p-inputMask mask="(999) 999-9999" placeholder="(123) 456-7890" [(ngModel)]="user.phoneNumber"></p-inputMask>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 col-12 md:col-4">
|
|
||||||
<label for="companyWebsite" class="block font-medium text-900 mb-2">Company Website</label>
|
|
||||||
<input id="companyWebsite" type="text" pInputText [(ngModel)]="user.companyWebsite" />
|
|
||||||
</div>
|
|
||||||
<div class="mb-4 col-12 md:col-4">
|
|
||||||
<label for="companyLocation" class="block font-medium text-900 mb-2">Company Location</label>
|
|
||||||
<p-autoComplete [(ngModel)]="user.companyLocation" [suggestions]="suggestions" (completeMethod)="search($event)"></p-autoComplete>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<div class="grid">
|
|
||||||
<div class="mb-4 col-12 md:col-6">
|
|
||||||
<label for="phoneNumber" class="block font-medium text-900 mb-2">Your Phone Number</label>
|
|
||||||
<p-inputMask mask="(999) 999-9999" placeholder="(123) 456-7890" [(ngModel)]="user.phoneNumber"></p-inputMask>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
} @if (isProfessional){
|
|
||||||
<div class="mb-4">
|
|
||||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Company Overview</label>
|
|
||||||
<p-editor [(ngModel)]="user.companyOverview" [style]="{ height: '320px' }" [modules]="editorModules">
|
|
||||||
<ng-template pTemplate="header"></ng-template>
|
|
||||||
</p-editor>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Services We offer</label>
|
|
||||||
<p-editor [(ngModel)]="user.offeredServices" [style]="{ height: '320px' }" [modules]="editorModules">
|
|
||||||
<ng-template pTemplate="header"></ng-template>
|
|
||||||
</p-editor>
|
|
||||||
</div>
|
|
||||||
<div class="mb-4">
|
|
||||||
<label for="areasServed" class="block font-medium text-900 mb-2">Areas We Serve</label>
|
|
||||||
@for (areasServed of user.areasServed; track areasServed){
|
|
||||||
<div class="grid">
|
|
||||||
<div class="flex col-12 md:col-6">
|
|
||||||
<p-dropdown
|
|
||||||
[filter]="true"
|
|
||||||
filterBy="name"
|
|
||||||
id="states"
|
|
||||||
[options]="selectOptions?.states"
|
|
||||||
[(ngModel)]="areasServed.state"
|
|
||||||
optionLabel="name"
|
|
||||||
optionValue="value"
|
|
||||||
placeholder="State"
|
|
||||||
[ngStyle]="{ width: '100%' }"
|
|
||||||
></p-dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="flex col-12 md:col-6">
|
|
||||||
<input id="county" type="text" pInputText [(ngModel)]="areasServed.county" placeholder="Area/County Served" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="field mb-5 col-12 md:col-6 flex align-items-center">
|
|
||||||
<p-button class="mr-1" icon="pi pi-plus" severity="success" (click)="addArea()"></p-button>
|
|
||||||
<p-button icon="pi pi-minus" severity="danger" (click)="removeArea()" [disabled]="user.areasServed?.length < 2"></p-button>
|
|
||||||
<span class="text-xs"> (Add more Areas or remove existing ones.)</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Licensed In</label>
|
|
||||||
@for (licensedIn of user.licensedIn; track licensedIn){
|
|
||||||
<div class="grid">
|
|
||||||
<div class="flex col-12 md:col-6">
|
|
||||||
<p-dropdown
|
|
||||||
[filter]="true"
|
|
||||||
filterBy="name"
|
|
||||||
id="states"
|
|
||||||
[options]="selectOptions?.states"
|
|
||||||
[(ngModel)]="licensedIn.state"
|
|
||||||
optionLabel="name"
|
|
||||||
optionValue="value"
|
|
||||||
placeholder="State"
|
|
||||||
[ngStyle]="{ width: '100%' }"
|
|
||||||
></p-dropdown>
|
|
||||||
</div>
|
|
||||||
<div class="flex col-12 md:col-6">
|
|
||||||
<input id="registerNo" type="text" pInputText [(ngModel)]="licensedIn.registerNo" placeholder="Licence Number" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="field mb-5 col-12 md:col-6 flex align-items-center">
|
|
||||||
<p-button class="mr-1" icon="pi pi-plus" severity="success" (click)="addLicence()"></p-button>
|
|
||||||
<p-button icon="pi pi-minus" severity="danger" (click)="removeLicence()" [disabled]="user.licensedIn?.length < 2"></p-button>
|
|
||||||
<span class="text-xs"> (Add more licenses or remove existing ones.)</span>
|
|
||||||
</div>
|
|
||||||
} }
|
|
||||||
<div>
|
|
||||||
<button pButton pRipple label="Update Profile" class="w-auto" (click)="updateProfile(user)"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if (isProfessional){
|
|
||||||
<div>
|
|
||||||
<div class="flex flex-column align-items-center flex-or mb-8">
|
|
||||||
<span class="font-medium text-900 mb-2">Company Logo</span>
|
|
||||||
<span class="font-medium text-xs mb-2">(is shown in every offer)</span>
|
|
||||||
@if(user?.hasCompanyLogo){
|
|
||||||
<div class="image-wrap">
|
|
||||||
<img src="{{ companyLogoUrl }}" class="rounded-profile" />
|
|
||||||
<fa-icon [icon]="faTrash" (click)="deleteConfirm('logo')"></fa-icon>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<img src="assets/images/placeholder.png" class="rounded-profile" />
|
|
||||||
}
|
|
||||||
<p-fileUpload
|
|
||||||
#companyUpload
|
|
||||||
mode="basic"
|
|
||||||
chooseLabel="Upload"
|
|
||||||
name="file"
|
|
||||||
[customUpload]="true"
|
|
||||||
accept="image/*"
|
|
||||||
[maxFileSize]="maxFileSize"
|
|
||||||
(onSelect)="select($event, 'company')"
|
|
||||||
styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"
|
|
||||||
></p-fileUpload>
|
|
||||||
</div>
|
|
||||||
<p-divider></p-divider>
|
|
||||||
<div class="flex flex-column align-items-center flex-or">
|
|
||||||
<span class="font-medium text-900 mb-2">Your Profile Picture</span>
|
|
||||||
@if(user?.hasProfile){
|
|
||||||
<div class="image-wrap">
|
|
||||||
<img src="{{ profileUrl }}" class="rounded-profile" />
|
|
||||||
<fa-icon [icon]="faTrash" (click)="deleteConfirm('profile')"></fa-icon>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<img src="assets/images/person_placeholder.jpg" class="rounded-profile" />
|
|
||||||
}
|
|
||||||
<p-fileUpload
|
|
||||||
#profileUpload
|
|
||||||
mode="basic"
|
|
||||||
chooseLabel="Upload"
|
|
||||||
name="file"
|
|
||||||
[customUpload]="true"
|
|
||||||
accept="image/*"
|
|
||||||
[maxFileSize]="maxFileSize"
|
|
||||||
(onSelect)="select($event, 'profile')"
|
|
||||||
styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"
|
|
||||||
></p-fileUpload>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="text-900 font-semibold text-lg mt-3">Membership Level</div>
|
|
||||||
<p-divider></p-divider>
|
|
||||||
<p-table [value]="userSubscriptions" [tableStyle]="{ 'min-width': '50rem' }" dataKey="id">
|
|
||||||
<ng-template pTemplate="header">
|
|
||||||
<tr>
|
|
||||||
<th style="width: 5rem"></th>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Level</th>
|
|
||||||
<th>Start Date</th>
|
|
||||||
<th>Date Modified</th>
|
|
||||||
<th>End Date</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template pTemplate="body" let-subscription let-expanded="expanded">
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<button type="button" pButton pRipple [pRowToggler]="subscription" class="p-button-text p-button-rounded p-button-plain" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
|
|
||||||
</td>
|
|
||||||
<td>{{ subscription.id }}</td>
|
|
||||||
<td>{{ subscription.level }}</td>
|
|
||||||
<td>{{ subscription.start | date }}</td>
|
|
||||||
<td>{{ subscription.modified | date }}</td>
|
|
||||||
<td>{{ subscription.end | date }}</td>
|
|
||||||
<td>{{ subscription.status }}</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template pTemplate="rowexpansion" let-subscription>
|
|
||||||
<tr>
|
|
||||||
<td colspan="7">
|
|
||||||
<div class="p-3">
|
|
||||||
<p-table [value]="subscription.invoices" dataKey="id">
|
|
||||||
<ng-template pTemplate="header">
|
|
||||||
<tr>
|
|
||||||
<th style="width: 5rem"></th>
|
|
||||||
<th>ID</th>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Price</th>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template pTemplate="body" let-invoice>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<button pButton pRipple icon="pi pi-print" class="p-button-rounded p-button-success mr-2" (click)="printInvoice(invoice)"></button>
|
|
||||||
</td>
|
|
||||||
<td>{{ invoice.id }}</td>
|
|
||||||
<td>{{ invoice.date | date }}</td>
|
|
||||||
<td>{{ invoice.price | currency }}</td>
|
|
||||||
<td></td>
|
|
||||||
<td></td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</ng-template>
|
|
||||||
</p-table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p-confirmDialog></p-confirmDialog> -->
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { TitleCasePipe } from '@angular/common';
|
||||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
@ -8,13 +9,15 @@ import { ImageCropperComponent } from 'ngx-image-cropper';
|
||||||
import { QuillModule } from 'ngx-quill';
|
import { QuillModule } from 'ngx-quill';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { AutoCompleteCompleteEvent, Invoice, Subscription, UploadParams, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { AutoCompleteCompleteEvent, Invoice, Subscription, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
import { ConfirmationComponent } from '../../../components/confirmation/confirmation.component';
|
import { ConfirmationComponent } from '../../../components/confirmation/confirmation.component';
|
||||||
import { ConfirmationService } from '../../../components/confirmation/confirmation.service';
|
import { ConfirmationService } from '../../../components/confirmation/confirmation.service';
|
||||||
import { ImageCropAndUploadComponent, UploadReponse } from '../../../components/image-crop-and-upload/image-crop-and-upload.component';
|
import { ImageCropAndUploadComponent, UploadReponse } from '../../../components/image-crop-and-upload/image-crop-and-upload.component';
|
||||||
import { MessageComponent } from '../../../components/message/message.component';
|
import { MessageComponent } from '../../../components/message/message.component';
|
||||||
import { MessageService } from '../../../components/message/message.service';
|
import { MessageService } from '../../../components/message/message.service';
|
||||||
|
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
|
||||||
|
import { ValidatedSelectComponent } from '../../../components/validated-select/validated-select.component';
|
||||||
import { GeoService } from '../../../services/geo.service';
|
import { GeoService } from '../../../services/geo.service';
|
||||||
import { ImageService } from '../../../services/image.service';
|
import { ImageService } from '../../../services/image.service';
|
||||||
import { LoadingService } from '../../../services/loading.service';
|
import { LoadingService } from '../../../services/loading.service';
|
||||||
|
|
@ -28,8 +31,19 @@ import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account',
|
selector: 'app-account',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule, QuillModule, NgxCurrencyDirective, NgSelectModule, ImageCropperComponent, ConfirmationComponent, ImageCropAndUploadComponent, MessageComponent],
|
imports: [
|
||||||
providers: [],
|
SharedModule,
|
||||||
|
QuillModule,
|
||||||
|
NgxCurrencyDirective,
|
||||||
|
NgSelectModule,
|
||||||
|
ImageCropperComponent,
|
||||||
|
ConfirmationComponent,
|
||||||
|
ImageCropAndUploadComponent,
|
||||||
|
MessageComponent,
|
||||||
|
ValidatedInputComponent,
|
||||||
|
ValidatedSelectComponent,
|
||||||
|
],
|
||||||
|
providers: [TitleCasePipe],
|
||||||
templateUrl: './account.component.html',
|
templateUrl: './account.component.html',
|
||||||
styleUrl: './account.component.scss',
|
styleUrl: './account.component.scss',
|
||||||
})
|
})
|
||||||
|
|
@ -51,6 +65,9 @@ export class AccountComponent {
|
||||||
toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']],
|
toolbar: [['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], [{ header: [1, 2, 3, 4, 5, 6, false] }], [{ color: [] }, { background: [] }], ['clean']],
|
||||||
};
|
};
|
||||||
uploadParams: UploadParams;
|
uploadParams: UploadParams;
|
||||||
|
validationMessages: ValidationMessage[] = [];
|
||||||
|
customerTypeOptions: Array<{ value: string; label: string }> = [];
|
||||||
|
customerSubTypeOptions: Array<{ value: string; label: string }> = [];
|
||||||
constructor(
|
constructor(
|
||||||
public userService: UserService,
|
public userService: UserService,
|
||||||
private subscriptionService: SubscriptionsService,
|
private subscriptionService: SubscriptionsService,
|
||||||
|
|
@ -65,6 +82,7 @@ export class AccountComponent {
|
||||||
private confirmationService: ConfirmationService,
|
private confirmationService: ConfirmationService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private sharedService: SharedService,
|
private sharedService: SharedService,
|
||||||
|
private titleCasePipe: TitleCasePipe,
|
||||||
) {}
|
) {}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
if (this.id) {
|
if (this.id) {
|
||||||
|
|
@ -95,6 +113,16 @@ export class AccountComponent {
|
||||||
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.id));
|
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.id));
|
||||||
this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
||||||
this.companyLogoUrl = this.user.hasCompanyLogo ? `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
this.companyLogoUrl = this.user.hasCompanyLogo ? `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
||||||
|
|
||||||
|
this.customerTypeOptions = this.customerTypes.map(type => ({
|
||||||
|
value: type,
|
||||||
|
label: this.titleCasePipe.transform(type),
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.customerSubTypeOptions = this.customerSubTypes.map(type => ({
|
||||||
|
value: type,
|
||||||
|
label: this.titleCasePipe.transform(type),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
printInvoice(invoice: Invoice) {}
|
printInvoice(invoice: Invoice) {}
|
||||||
|
|
||||||
|
|
@ -182,76 +210,8 @@ export class AccountComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// select(event: any, type: 'company' | 'profile') {
|
getValidationMessage(fieldName: string): string {
|
||||||
// const imageUrl = URL.createObjectURL(event.files[0]);
|
const message = this.validationMessages.find(msg => msg.field === fieldName);
|
||||||
// this.type = type;
|
return message ? message.message : '';
|
||||||
// const config = { aspectRatio: type === 'company' ? stateOptions[0].value : stateOptions[2].value };
|
}
|
||||||
// getImageDimensions(imageUrl).then(dimensions => {
|
|
||||||
// const dialogWidth = getDialogWidth(dimensions);
|
|
||||||
|
|
||||||
// this.dialogRef = this.dialogService.open(ImageCropperComponent, {
|
|
||||||
// data: {
|
|
||||||
// imageUrl: imageUrl,
|
|
||||||
// fileUpload: type === 'company' ? this.companyUpload : this.profileUpload,
|
|
||||||
// config: config,
|
|
||||||
// ratioVariable: type === 'company' ? true : false,
|
|
||||||
// },
|
|
||||||
// header: 'Edit Image',
|
|
||||||
// width: dialogWidth,
|
|
||||||
// modal: true,
|
|
||||||
// closeOnEscape: true,
|
|
||||||
// keepInViewport: true,
|
|
||||||
// closable: false,
|
|
||||||
// });
|
|
||||||
// this.dialogRef.onClose.subscribe(cropper => {
|
|
||||||
// if (cropper) {
|
|
||||||
// this.loadingService.startLoading('uploadImage');
|
|
||||||
// cropper.getCroppedCanvas().toBlob(async blob => {
|
|
||||||
// this.imageUploadService.uploadImage(blob, type === 'company' ? 'uploadCompanyLogo' : 'uploadProfile', emailToDirName(this.user.email)).subscribe(
|
|
||||||
// async event => {
|
|
||||||
// if (event.type === HttpEventType.Response) {
|
|
||||||
// this.loadingService.stopLoading('uploadImage');
|
|
||||||
// if (this.type === 'company') {
|
|
||||||
// this.user.hasCompanyLogo = true; //
|
|
||||||
// this.companyLogoUrl = `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}`;
|
|
||||||
// } else {
|
|
||||||
// this.user.hasProfile = true;
|
|
||||||
// this.profileUrl = `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}`;
|
|
||||||
// }
|
|
||||||
// await this.userService.save(this.user);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// error => console.error('Fehler beim Upload:', error),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// this.confirmationService.showConfirmation({
|
|
||||||
// target: event.target as EventTarget,
|
|
||||||
// message: `Do you want to delete your ${type === 'logo' ? 'Logo' : 'Profile'} image`,
|
|
||||||
// header: 'Delete Confirmation',
|
|
||||||
// icon: 'pi pi-info-circle',
|
|
||||||
// acceptButtonStyleClass: 'p-button-danger p-button-text',
|
|
||||||
// rejectButtonStyleClass: 'p-button-text p-button-text',
|
|
||||||
// acceptIcon: 'none',
|
|
||||||
// rejectIcon: 'none',
|
|
||||||
|
|
||||||
// accept: async () => {
|
|
||||||
// if (type === 'profile') {
|
|
||||||
// this.user.hasProfile = false;
|
|
||||||
// await Promise.all([this.imageService.deleteProfileImagesById(this.user.email), this.userService.save(this.user)]);
|
|
||||||
// } else {
|
|
||||||
// this.user.hasCompanyLogo = false;
|
|
||||||
// await Promise.all([this.imageService.deleteLogoImagesById(this.user.email), this.userService.save(this.user)]);
|
|
||||||
// }
|
|
||||||
// this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
|
|
||||||
// this.user = await this.userService.getById(this.user.id);
|
|
||||||
// },
|
|
||||||
// reject: () => {
|
|
||||||
// console.log('deny');
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export class EditBusinessListingComponent {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.typesOfBusiness = selectOptions.typesOfBusiness.map(e => {
|
this.typesOfBusiness = selectOptions.typesOfBusiness.map(e => {
|
||||||
return { name: e.name, value: parseInt(e.value) };
|
return { name: e.name, value: e.value };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ export class EditCommercialPropertyListingComponent {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.typesOfCommercialProperty = selectOptions.typesOfCommercialProperty.map(e => {
|
this.typesOfCommercialProperty = selectOptions.typesOfCommercialProperty.map(e => {
|
||||||
return { name: e.name, value: parseInt(e.value) };
|
return { name: e.name, value: e.value };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue