Einbau Validation finished
This commit is contained in:
parent
f58448679d
commit
4c1b1fbc87
|
|
@ -1,13 +1,14 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { and, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm';
|
import { and, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm';
|
||||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
|
import { ZodError } from 'zod';
|
||||||
import * as schema from '../drizzle/schema.js';
|
import * as schema from '../drizzle/schema.js';
|
||||||
import { businesses, PG_CONNECTION } from '../drizzle/schema.js';
|
import { businesses, PG_CONNECTION } from '../drizzle/schema.js';
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
import { GeoService } from '../geo/geo.service.js';
|
import { GeoService } from '../geo/geo.service.js';
|
||||||
import { BusinessListing, CommercialPropertyListing } from '../models/db.model';
|
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 { getDistanceQuery } from '../utils.js';
|
import { getDistanceQuery } from '../utils.js';
|
||||||
|
|
||||||
|
|
@ -169,17 +170,41 @@ export class BusinessListingService {
|
||||||
}
|
}
|
||||||
// #### CREATE ########################################
|
// #### CREATE ########################################
|
||||||
async createListing(data: BusinessListing): Promise<BusinessListing> {
|
async createListing(data: BusinessListing): Promise<BusinessListing> {
|
||||||
data.created = new Date();
|
try {
|
||||||
data.updated = new Date();
|
data.created = new Date();
|
||||||
const [createdListing] = await this.conn.insert(businesses).values(data).returning();
|
data.updated = new Date();
|
||||||
return createdListing as BusinessListing;
|
const validatedBusinessListing = BusinessListingSchema.parse(data);
|
||||||
|
const [createdListing] = await this.conn.insert(businesses).values(validatedBusinessListing).returning();
|
||||||
|
return createdListing as BusinessListing;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
const formattedErrors = error.errors.map(err => ({
|
||||||
|
field: err.path.join('.'),
|
||||||
|
message: err.message,
|
||||||
|
}));
|
||||||
|
throw new BadRequestException(formattedErrors);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// #### UPDATE Business ########################################
|
// #### UPDATE Business ########################################
|
||||||
async updateBusinessListing(id: string, data: BusinessListing): Promise<BusinessListing | CommercialPropertyListing> {
|
async updateBusinessListing(id: string, data: BusinessListing): Promise<BusinessListing> {
|
||||||
data.updated = new Date();
|
try {
|
||||||
data.created = new Date(data.created);
|
data.updated = new Date();
|
||||||
const [updateListing] = await this.conn.update(businesses).set(data).where(eq(businesses.id, id)).returning();
|
data.created = new Date(data.created);
|
||||||
return updateListing as BusinessListing | CommercialPropertyListing;
|
const validatedBusinessListing = BusinessListingSchema.parse(data);
|
||||||
|
const [updateListing] = await this.conn.update(businesses).set(data).where(eq(businesses.id, id)).returning();
|
||||||
|
return updateListing as BusinessListing;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
const formattedErrors = error.errors.map(err => ({
|
||||||
|
field: err.path.join('.'),
|
||||||
|
message: err.message,
|
||||||
|
}));
|
||||||
|
throw new BadRequestException(formattedErrors);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// #### DELETE ########################################
|
// #### DELETE ########################################
|
||||||
async deleteListing(id: string): Promise<void> {
|
async deleteListing(id: string): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { and, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm';
|
import { and, count, eq, gte, ilike, inArray, lte, ne, or, SQL, sql } from 'drizzle-orm';
|
||||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
|
import { ZodError } from 'zod';
|
||||||
import * as schema from '../drizzle/schema.js';
|
import * as schema from '../drizzle/schema.js';
|
||||||
import { commercials, PG_CONNECTION } from '../drizzle/schema.js';
|
import { commercials, PG_CONNECTION } from '../drizzle/schema.js';
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
import { GeoService } from '../geo/geo.service.js';
|
import { GeoService } from '../geo/geo.service.js';
|
||||||
import { CommercialPropertyListing } from '../models/db.model';
|
import { CommercialPropertyListing, CommercialPropertyListingSchema } from '../models/db.model.js';
|
||||||
import { CommercialPropertyListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js';
|
import { CommercialPropertyListingCriteria, emailToDirName, JwtUser } from '../models/main.model.js';
|
||||||
import { getDistanceQuery } from '../utils.js';
|
import { getDistanceQuery } from '../utils.js';
|
||||||
|
|
||||||
|
|
@ -123,23 +124,47 @@ export class CommercialPropertyService {
|
||||||
}
|
}
|
||||||
// #### CREATE ########################################
|
// #### CREATE ########################################
|
||||||
async createListing(data: CommercialPropertyListing): Promise<CommercialPropertyListing> {
|
async createListing(data: CommercialPropertyListing): Promise<CommercialPropertyListing> {
|
||||||
data.created = new Date();
|
try {
|
||||||
data.updated = new Date();
|
data.created = new Date();
|
||||||
const [createdListing] = await this.conn.insert(commercials).values(data).returning();
|
data.updated = new Date();
|
||||||
return createdListing as CommercialPropertyListing;
|
const validatedCommercialPropertyListing = CommercialPropertyListingSchema.parse(data);
|
||||||
|
const [createdListing] = await this.conn.insert(commercials).values(validatedCommercialPropertyListing).returning();
|
||||||
|
return createdListing as CommercialPropertyListing;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
const formattedErrors = error.errors.map(err => ({
|
||||||
|
field: err.path.join('.'),
|
||||||
|
message: err.message,
|
||||||
|
}));
|
||||||
|
throw new BadRequestException(formattedErrors);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// #### UPDATE CommercialProps ########################################
|
// #### UPDATE CommercialProps ########################################
|
||||||
async updateCommercialPropertyListing(id: string, data: CommercialPropertyListing): Promise<CommercialPropertyListing> {
|
async updateCommercialPropertyListing(id: string, data: CommercialPropertyListing): Promise<CommercialPropertyListing> {
|
||||||
data.updated = new Date();
|
try {
|
||||||
data.created = new Date(data.created);
|
data.updated = new Date();
|
||||||
const imageOrder = await this.fileService.getPropertyImages(data.imagePath, String(data.serialId));
|
data.created = new Date(data.created);
|
||||||
let difference = imageOrder.filter(x => !data.imageOrder.includes(x)).concat(data.imageOrder.filter(x => !imageOrder.includes(x)));
|
const validatedCommercialPropertyListing = CommercialPropertyListingSchema.parse(data);
|
||||||
if (difference.length > 0) {
|
const imageOrder = await this.fileService.getPropertyImages(data.imagePath, String(data.serialId));
|
||||||
this.logger.warn(`changes between image directory and imageOrder in listing ${data.serialId}: ${difference.join(',')}`);
|
let difference = imageOrder.filter(x => !data.imageOrder.includes(x)).concat(data.imageOrder.filter(x => !imageOrder.includes(x)));
|
||||||
data.imageOrder = imageOrder;
|
if (difference.length > 0) {
|
||||||
|
this.logger.warn(`changes between image directory and imageOrder in listing ${data.serialId}: ${difference.join(',')}`);
|
||||||
|
data.imageOrder = imageOrder;
|
||||||
|
}
|
||||||
|
const [updateListing] = await this.conn.update(commercials).set(data).where(eq(commercials.id, id)).returning();
|
||||||
|
return updateListing as CommercialPropertyListing;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
const formattedErrors = error.errors.map(err => ({
|
||||||
|
field: err.path.join('.'),
|
||||||
|
message: err.message,
|
||||||
|
}));
|
||||||
|
throw new BadRequestException(formattedErrors);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
const [updateListing] = await this.conn.update(commercials).set(data).where(eq(commercials.id, id)).returning();
|
|
||||||
return updateListing as CommercialPropertyListing;
|
|
||||||
}
|
}
|
||||||
// ##############################################################
|
// ##############################################################
|
||||||
// Images for commercial Properties
|
// Images for commercial Properties
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import { MailerService } from '@nestjs-modules/mailer';
|
import { MailerService } from '@nestjs-modules/mailer';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
import { ZodError } from 'zod';
|
||||||
|
import { SenderSchema } from '../models/db.model.js';
|
||||||
import { ErrorResponse, MailInfo, isEmpty } from '../models/main.model.js';
|
import { ErrorResponse, MailInfo, isEmpty } from '../models/main.model.js';
|
||||||
import { UserService } from '../user/user.service.js';
|
import { UserService } from '../user/user.service.js';
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
|
@ -15,9 +17,19 @@ export class MailService {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async sendInquiry(mailInfo: MailInfo): Promise<void | ErrorResponse> {
|
async sendInquiry(mailInfo: MailInfo): Promise<void | ErrorResponse> {
|
||||||
//const user = await this.authService.getUser(mailInfo.userId) as KeycloakUser;
|
try {
|
||||||
|
const validatedSender = SenderSchema.parse(mailInfo.sender);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
const formattedErrors = error.errors.map(err => ({
|
||||||
|
field: err.path.join('.'),
|
||||||
|
message: err.message,
|
||||||
|
}));
|
||||||
|
throw new BadRequestException(formattedErrors);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
const user = await this.userService.getUserByMail(mailInfo.email);
|
const user = await this.userService.getUserByMail(mailInfo.email);
|
||||||
console.log(JSON.stringify(user));
|
|
||||||
if (isEmpty(mailInfo.sender.name)) {
|
if (isEmpty(mailInfo.sender.name)) {
|
||||||
return { fields: [{ fieldname: 'name', message: 'Required' }] };
|
return { fields: [{ fieldname: 'name', message: 'Required' }] };
|
||||||
}
|
}
|
||||||
|
|
@ -42,8 +54,17 @@ export class MailService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async sendRequest(mailInfo: MailInfo): Promise<void | ErrorResponse> {
|
async sendRequest(mailInfo: MailInfo): Promise<void | ErrorResponse> {
|
||||||
if (isEmpty(mailInfo.sender.name)) {
|
try {
|
||||||
return { fields: [{ fieldname: 'name', message: 'Required' }] };
|
const validatedSender = SenderSchema.parse(mailInfo.sender);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
const formattedErrors = error.errors.map(err => ({
|
||||||
|
field: err.path.join('.'),
|
||||||
|
message: err.message,
|
||||||
|
}));
|
||||||
|
throw new BadRequestException(formattedErrors);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
await this.mailerService.sendMail({
|
await this.mailerService.sendMail({
|
||||||
to: 'support@bizmatch.net',
|
to: 'support@bizmatch.net',
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,5 @@
|
||||||
// export interface User {
|
|
||||||
// id?: string;
|
|
||||||
// firstname: string;
|
|
||||||
// lastname: string;
|
|
||||||
// email: string;
|
|
||||||
// phoneNumber?: string;
|
|
||||||
// description?: string;
|
|
||||||
// companyName?: string;
|
|
||||||
// companyOverview?: string;
|
|
||||||
// companyWebsite?: string;
|
|
||||||
// companyLocation?: string;
|
|
||||||
// offeredServices?: string;
|
|
||||||
// areasServed?: AreasServed[];
|
|
||||||
// hasProfile?: boolean;
|
|
||||||
// hasCompanyLogo?: boolean;
|
|
||||||
// licensedIn?: LicensedIn[];
|
|
||||||
// gender?: 'male' | 'female';
|
|
||||||
// customerType?: 'buyer' | 'professional';
|
|
||||||
// customerSubType?: 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser';
|
|
||||||
// created?: Date;
|
|
||||||
// updated?: Date;
|
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
// }
|
|
||||||
export interface UserData {
|
export interface UserData {
|
||||||
id?: string;
|
id?: string;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
|
|
@ -49,45 +26,80 @@ export type Gender = 'male' | 'female';
|
||||||
export type CustomerType = 'buyer' | 'professional';
|
export type CustomerType = 'buyer' | 'professional';
|
||||||
export type CustomerSubType = 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser';
|
export type CustomerSubType = 'broker' | 'cpa' | 'attorney' | 'titleCompany' | 'surveyor' | 'appraiser';
|
||||||
export type ListingsCategory = 'commercialProperty' | 'business';
|
export type ListingsCategory = 'commercialProperty' | 'business';
|
||||||
// export interface User {
|
|
||||||
// id: string; // UUID as a string
|
|
||||||
// firstname: string;
|
|
||||||
// lastname: string;
|
|
||||||
// email: string;
|
|
||||||
// phoneNumber?: string;
|
|
||||||
// description?: string;
|
|
||||||
// companyName?: string;
|
|
||||||
// companyOverview?: string;
|
|
||||||
// companyWebsite?: string;
|
|
||||||
// companyLocation?: string;
|
|
||||||
// offeredServices?: string;
|
|
||||||
// areasServed?: AreasServed[];
|
|
||||||
// hasProfile?: boolean;
|
|
||||||
// hasCompanyLogo?: boolean;
|
|
||||||
// licensedIn?: LicensedIn[];
|
|
||||||
// gender?: Gender;
|
|
||||||
// customerType?: CustomerType;
|
|
||||||
// customerSubType?: CustomerSubType;
|
|
||||||
// created?: Date;
|
|
||||||
// updated?: Date;
|
|
||||||
// latitude?: number;
|
|
||||||
// longitude?: number;
|
|
||||||
// }
|
|
||||||
// export interface AreasServed {
|
|
||||||
// county: string;
|
|
||||||
// state: string;
|
|
||||||
// }
|
|
||||||
// export interface LicensedIn {
|
|
||||||
// registerNo: string;
|
|
||||||
// state: string;
|
|
||||||
// }
|
|
||||||
// --------------------------------
|
|
||||||
//
|
|
||||||
// --------------------------------
|
|
||||||
export const GenderEnum = z.enum(['male', 'female']);
|
export const GenderEnum = z.enum(['male', 'female']);
|
||||||
export const CustomerTypeEnum = z.enum(['buyer', 'professional']);
|
export const CustomerTypeEnum = z.enum(['buyer', 'professional']);
|
||||||
export const CustomerSubTypeEnum = z.enum(['broker', 'cpa', 'attorney', 'titleCompany', 'surveyor', 'appraiser']);
|
export const CustomerSubTypeEnum = z.enum(['broker', 'cpa', 'attorney', 'titleCompany', 'surveyor', 'appraiser']);
|
||||||
export const ListingsCategoryEnum = z.enum(['commercialProperty', 'business']);
|
export const ListingsCategoryEnum = z.enum(['commercialProperty', 'business']);
|
||||||
|
const PropertyTypeEnum = z.enum(['retail', 'land', 'industrial', 'office', 'mixedUse', 'multifamily', 'uncategorized']);
|
||||||
|
const TypeEnum = z.enum([
|
||||||
|
'automotive',
|
||||||
|
'industrialServices',
|
||||||
|
'foodAndRestaurant',
|
||||||
|
'realEstate',
|
||||||
|
'retail',
|
||||||
|
'oilfield',
|
||||||
|
'service',
|
||||||
|
'advertising',
|
||||||
|
'agriculture',
|
||||||
|
'franchise',
|
||||||
|
'professional',
|
||||||
|
'manufacturing',
|
||||||
|
'uncategorized',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const USStates = z.enum([
|
||||||
|
'AL',
|
||||||
|
'AK',
|
||||||
|
'AZ',
|
||||||
|
'AR',
|
||||||
|
'CA',
|
||||||
|
'CO',
|
||||||
|
'CT',
|
||||||
|
'DE',
|
||||||
|
'FL',
|
||||||
|
'GA',
|
||||||
|
'HI',
|
||||||
|
'ID',
|
||||||
|
'IL',
|
||||||
|
'IN',
|
||||||
|
'IA',
|
||||||
|
'KS',
|
||||||
|
'KY',
|
||||||
|
'LA',
|
||||||
|
'ME',
|
||||||
|
'MD',
|
||||||
|
'MA',
|
||||||
|
'MI',
|
||||||
|
'MN',
|
||||||
|
'MS',
|
||||||
|
'MO',
|
||||||
|
'MT',
|
||||||
|
'NE',
|
||||||
|
'NV',
|
||||||
|
'NH',
|
||||||
|
'NJ',
|
||||||
|
'NM',
|
||||||
|
'NY',
|
||||||
|
'NC',
|
||||||
|
'ND',
|
||||||
|
'OH',
|
||||||
|
'OK',
|
||||||
|
'OR',
|
||||||
|
'PA',
|
||||||
|
'RI',
|
||||||
|
'SC',
|
||||||
|
'SD',
|
||||||
|
'TN',
|
||||||
|
'TX',
|
||||||
|
'UT',
|
||||||
|
'VT',
|
||||||
|
'VA',
|
||||||
|
'WA',
|
||||||
|
'WV',
|
||||||
|
'WI',
|
||||||
|
'WY',
|
||||||
|
]);
|
||||||
export const AreasServedSchema = z.object({
|
export const AreasServedSchema = z.object({
|
||||||
county: z.string().nonempty('County is required'),
|
county: z.string().nonempty('County is required'),
|
||||||
state: z.string().nonempty('State is required'),
|
state: z.string().nonempty('State is required'),
|
||||||
|
|
@ -98,56 +110,6 @@ export const LicensedInSchema = z.object({
|
||||||
state: z.string().nonempty('State is required'),
|
state: z.string().nonempty('State is required'),
|
||||||
});
|
});
|
||||||
|
|
||||||
// export const UserSchema = z
|
|
||||||
// .object({
|
|
||||||
// id: z.string().uuid('Invalid ID format. Must be a valid UUID').optional(),
|
|
||||||
// firstname: z.string().min(2, 'First name must be at least 2 characters long'),
|
|
||||||
// lastname: z.string().min(2, 'Last name must be at least 2 characters long'),
|
|
||||||
// email: z.string().email('Invalid email address'),
|
|
||||||
// phoneNumber: z.string().optional().nullable(),
|
|
||||||
// description: z.string().min(10, 'Description must be at least 10 characters long').optional().nullable(),
|
|
||||||
// companyName: z.string().optional().nullable(),
|
|
||||||
// companyOverview: z.string().min(10, 'Company overview must be at least 10 characters long').optional().nullable(),
|
|
||||||
// companyWebsite: z.string().url('Invalid company website URL').optional().nullable(),
|
|
||||||
// companyLocation: z.string().optional().nullable(), // Additional validation for US locations could be implemented here
|
|
||||||
// offeredServices: z.string().min(10, 'Offered services must be at least 10 characters long').optional().nullable(),
|
|
||||||
// areasServed: z.array(AreasServedSchema).optional().nullable(),
|
|
||||||
// hasProfile: z.boolean().optional().nullable(),
|
|
||||||
// hasCompanyLogo: z.boolean().optional().nullable(),
|
|
||||||
// licensedIn: z.array(LicensedInSchema).optional().nullable(),
|
|
||||||
// gender: GenderEnum.optional().nullable(),
|
|
||||||
// customerType: CustomerTypeEnum.optional().nullable(),
|
|
||||||
// customerSubType: CustomerSubTypeEnum.optional().nullable(),
|
|
||||||
// created: z.date().optional().nullable(),
|
|
||||||
// updated: z.date().optional().nullable(),
|
|
||||||
// latitude: z.number().optional().nullable(),
|
|
||||||
// longitude: z.number().optional().nullable(),
|
|
||||||
// })
|
|
||||||
// .refine(
|
|
||||||
// data => {
|
|
||||||
// if (data.customerType === 'professional') {
|
|
||||||
// return !!data.customerSubType && !!data.phoneNumber && !!data.companyOverview && !!data.description && !!data.offeredServices && !!data.companyLocation && data.areasServed && data.areasServed.length > 0;
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// message: 'For professional customers, additional fields are required: customer subtype, phone number, company overview, description, offered services, company location, and at least one area served',
|
|
||||||
// path: ['customerType'],
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// .refine(
|
|
||||||
// data => {
|
|
||||||
// if (data.customerType === 'professional') {
|
|
||||||
// return /\(\d{3}\) \d{3}-\d{4}$/.test(data.phoneNumber || '');
|
|
||||||
// }
|
|
||||||
// return true;
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// message: 'Phone number must be in US format: +1 (XXX) XXX-XXXX',
|
|
||||||
// path: ['phoneNumber'],
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
|
|
||||||
const phoneRegex = /^\+1 \(\d{3}\) \d{3}-\d{4}$/;
|
const phoneRegex = /^\+1 \(\d{3}\) \d{3}-\d{4}$/;
|
||||||
|
|
||||||
export const UserSchema = z
|
export const UserSchema = z
|
||||||
|
|
@ -238,64 +200,90 @@ export const UserSchema = z
|
||||||
export type AreasServed = z.infer<typeof AreasServedSchema>;
|
export type AreasServed = z.infer<typeof AreasServedSchema>;
|
||||||
export type LicensedIn = z.infer<typeof LicensedInSchema>;
|
export type LicensedIn = z.infer<typeof LicensedInSchema>;
|
||||||
export type User = z.infer<typeof UserSchema>;
|
export type User = z.infer<typeof UserSchema>;
|
||||||
export interface BusinessListing {
|
|
||||||
id: string; // UUID as a string
|
|
||||||
email: string; // References users.email
|
|
||||||
type?: string;
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
city?: string;
|
|
||||||
state?: string; // 2-character state code
|
|
||||||
zipCode?: number;
|
|
||||||
county?: string;
|
|
||||||
price?: number; // double precision
|
|
||||||
favoritesForUser?: string[]; // Array of strings
|
|
||||||
draft?: boolean;
|
|
||||||
listingsCategory?: ListingsCategory;
|
|
||||||
realEstateIncluded?: boolean;
|
|
||||||
leasedLocation?: boolean;
|
|
||||||
franchiseResale?: boolean;
|
|
||||||
salesRevenue?: number; // double precision
|
|
||||||
cashFlow?: number; // double precision
|
|
||||||
supportAndTraining?: string;
|
|
||||||
employees?: number;
|
|
||||||
established?: number;
|
|
||||||
internalListingNumber?: number;
|
|
||||||
reasonForSale?: string;
|
|
||||||
brokerLicencing?: string;
|
|
||||||
internals?: string;
|
|
||||||
imageName?: string;
|
|
||||||
created?: Date;
|
|
||||||
updated?: Date;
|
|
||||||
visits?: number;
|
|
||||||
lastVisit?: Date;
|
|
||||||
latitude?: number; // double precision
|
|
||||||
longitude?: number; // double precision
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CommercialPropertyListing {
|
export const BusinessListingSchema = z.object({
|
||||||
id: string; // UUID as a string
|
id: z.string().uuid().optional(),
|
||||||
serialId: number; // Serial ID
|
email: z.string().email(),
|
||||||
email: string; // References users.email
|
type: z.string().refine(val => TypeEnum.safeParse(val).success, {
|
||||||
type?: string;
|
message: 'Invalid type. Must be one of: ' + TypeEnum.options.join(', '),
|
||||||
title?: string;
|
}),
|
||||||
description?: string;
|
title: z.string().min(10),
|
||||||
city?: string;
|
description: z.string().min(10),
|
||||||
state?: string; // 2-character state code
|
city: z.string(),
|
||||||
price?: number; // double precision
|
state: z.string().refine(val => USStates.safeParse(val).success, {
|
||||||
favoritesForUser?: string[]; // Array of strings
|
message: 'Invalid state. Must be a valid 2-letter US state code.',
|
||||||
listingsCategory?: ListingsCategory;
|
}),
|
||||||
hideImage?: boolean;
|
zipCode: z.number().int().positive().optional().nullable(),
|
||||||
draft?: boolean;
|
county: z.string().optional().nullable(),
|
||||||
zipCode?: number;
|
price: z.number().positive().max(100000000),
|
||||||
county?: string;
|
favoritesForUser: z.array(z.string()),
|
||||||
imageOrder?: string[]; // Array of strings
|
draft: z.boolean(),
|
||||||
imagePath?: string;
|
listingsCategory: ListingsCategoryEnum,
|
||||||
created?: Date;
|
realEstateIncluded: z.boolean().optional().nullable(),
|
||||||
updated?: Date;
|
leasedLocation: z.boolean().optional().nullable(),
|
||||||
visits?: number;
|
franchiseResale: z.boolean().optional().nullable(),
|
||||||
lastVisit?: Date;
|
salesRevenue: z.number().positive().max(100000000),
|
||||||
latitude?: number; // double precision
|
cashFlow: z.number().positive().max(100000000),
|
||||||
longitude?: number; // double precision
|
supportAndTraining: z.string().min(5),
|
||||||
// embedding?: number[]; // Uncomment if needed for vector embedding
|
employees: z.number().int().positive().max(100000).optional().nullable(),
|
||||||
}
|
established: z.number().int().min(1800).max(2030).optional().nullable(),
|
||||||
|
internalListingNumber: z.number().int().positive().optional().nullable(),
|
||||||
|
reasonForSale: z.string().min(5).optional().nullable(),
|
||||||
|
brokerLicencing: z.string().min(5).optional().nullable(),
|
||||||
|
internals: z.string().min(5).optional().nullable(),
|
||||||
|
imageName: z.string().optional().nullable(),
|
||||||
|
created: z.date(),
|
||||||
|
updated: z.date(),
|
||||||
|
visits: z.number().int().positive().optional().nullable(),
|
||||||
|
lastVisit: z.date().optional().nullable(),
|
||||||
|
latitude: z.number().optional().nullable(),
|
||||||
|
longitude: z.number().optional().nullable(),
|
||||||
|
});
|
||||||
|
export type BusinessListing = z.infer<typeof BusinessListingSchema>;
|
||||||
|
|
||||||
|
export const CommercialPropertyListingSchema = z
|
||||||
|
.object({
|
||||||
|
id: z.string().uuid().optional(),
|
||||||
|
serialId: z.number().int().positive().optional(),
|
||||||
|
email: z.string().email(),
|
||||||
|
//type: PropertyTypeEnum.optional(),
|
||||||
|
type: z.string().refine(val => PropertyTypeEnum.safeParse(val).success, {
|
||||||
|
message: 'Invalid type. Must be one of: ' + PropertyTypeEnum.options.join(', '),
|
||||||
|
}),
|
||||||
|
title: z.string().min(10),
|
||||||
|
description: z.string().min(10),
|
||||||
|
city: z.string(), // You might want to add a custom validation for valid US cities
|
||||||
|
state: z.string().refine(val => USStates.safeParse(val).success, {
|
||||||
|
message: 'Invalid state. Must be a valid 2-letter US state code.',
|
||||||
|
}), // You might want to add a custom validation for valid US states
|
||||||
|
price: z.number().positive().max(100000000),
|
||||||
|
favoritesForUser: z.array(z.string()),
|
||||||
|
listingsCategory: ListingsCategoryEnum,
|
||||||
|
draft: z.boolean(),
|
||||||
|
zipCode: z.number().int().positive().nullable().optional(), // You might want to add a custom validation for valid US zip codes
|
||||||
|
county: z.string().nullable().optional(), // You might want to add a custom validation for valid US counties
|
||||||
|
imageOrder: z.array(z.string()),
|
||||||
|
imagePath: z.string().nullable().optional(),
|
||||||
|
created: z.date(),
|
||||||
|
updated: z.date(),
|
||||||
|
visits: z.number().int().positive().nullable().optional(),
|
||||||
|
lastVisit: z.date().nullable().optional(),
|
||||||
|
latitude: z.number().nullable().optional(),
|
||||||
|
longitude: z.number().nullable().optional(),
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export type CommercialPropertyListing = z.infer<typeof CommercialPropertyListingSchema>;
|
||||||
|
|
||||||
|
export const SenderSchema = z.object({
|
||||||
|
name: z.string().min(6, { message: 'Name must be at least 6 characters long' }),
|
||||||
|
email: z.string().email({ message: 'Invalid email address' }),
|
||||||
|
phoneNumber: z.string().regex(/^(\+1|1)?[-.\s]?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/, {
|
||||||
|
message: 'Invalid US phone number format',
|
||||||
|
}),
|
||||||
|
state: z.string().refine(val => USStates.safeParse(val).success, {
|
||||||
|
message: 'Invalid state. Must be a valid 2-letter US state code.',
|
||||||
|
}),
|
||||||
|
comments: z.string().min(10, { message: 'Comments must be at least 10 characters long' }),
|
||||||
|
});
|
||||||
|
export type Sender = z.infer<typeof SenderSchema>;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BusinessListing, CommercialPropertyListing, User } from './db.model';
|
import { BusinessListing, CommercialPropertyListing, Sender, User } from './db.model.js';
|
||||||
|
|
||||||
export interface StatesResult {
|
export interface StatesResult {
|
||||||
state: string;
|
state: string;
|
||||||
|
|
@ -199,13 +199,13 @@ export interface MailInfo {
|
||||||
url: string;
|
url: string;
|
||||||
listing?: BusinessListing;
|
listing?: BusinessListing;
|
||||||
}
|
}
|
||||||
export interface Sender {
|
// export interface Sender {
|
||||||
name?: string;
|
// name?: string;
|
||||||
email?: string;
|
// email?: string;
|
||||||
phoneNumber?: string;
|
// phoneNumber?: string;
|
||||||
state?: string;
|
// state?: string;
|
||||||
comments?: string;
|
// comments?: string;
|
||||||
}
|
// }
|
||||||
export interface ImageProperty {
|
export interface ImageProperty {
|
||||||
id: string;
|
id: string;
|
||||||
code: string;
|
code: string;
|
||||||
|
|
@ -309,20 +309,19 @@ export function createDefaultCommercialPropertyListing(): CommercialPropertyList
|
||||||
return {
|
return {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
serialId: undefined,
|
serialId: undefined,
|
||||||
email: '',
|
email: null,
|
||||||
type: null,
|
type: null,
|
||||||
title: '',
|
title: null,
|
||||||
description: '',
|
description: null,
|
||||||
city: '',
|
city: null,
|
||||||
state: '',
|
state: null,
|
||||||
price: null,
|
price: null,
|
||||||
favoritesForUser: [],
|
favoritesForUser: [],
|
||||||
hideImage: false,
|
|
||||||
draft: false,
|
draft: false,
|
||||||
zipCode: null,
|
zipCode: null,
|
||||||
county: '',
|
county: null,
|
||||||
imageOrder: [],
|
imageOrder: [],
|
||||||
imagePath: '',
|
imagePath: null,
|
||||||
created: null,
|
created: null,
|
||||||
updated: null,
|
updated: null,
|
||||||
visits: null,
|
visits: null,
|
||||||
|
|
@ -335,12 +334,12 @@ export function createDefaultCommercialPropertyListing(): CommercialPropertyList
|
||||||
export function createDefaultBusinessListing(): BusinessListing {
|
export function createDefaultBusinessListing(): BusinessListing {
|
||||||
return {
|
return {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
email: '',
|
email: null,
|
||||||
type: null,
|
type: null,
|
||||||
title: '',
|
title: null,
|
||||||
description: '',
|
description: null,
|
||||||
city: '',
|
city: null,
|
||||||
state: '',
|
state: null,
|
||||||
price: null,
|
price: null,
|
||||||
favoritesForUser: [],
|
favoritesForUser: [],
|
||||||
draft: false,
|
draft: false,
|
||||||
|
|
@ -349,13 +348,13 @@ export function createDefaultBusinessListing(): BusinessListing {
|
||||||
franchiseResale: false,
|
franchiseResale: false,
|
||||||
salesRevenue: null,
|
salesRevenue: null,
|
||||||
cashFlow: null,
|
cashFlow: null,
|
||||||
supportAndTraining: '',
|
supportAndTraining: null,
|
||||||
employees: null,
|
employees: null,
|
||||||
established: null,
|
established: null,
|
||||||
internalListingNumber: null,
|
internalListingNumber: null,
|
||||||
reasonForSale: '',
|
reasonForSale: null,
|
||||||
brokerLicencing: '',
|
brokerLicencing: null,
|
||||||
internals: '',
|
internals: null,
|
||||||
created: null,
|
created: null,
|
||||||
updated: null,
|
updated: null,
|
||||||
visits: null,
|
visits: null,
|
||||||
|
|
|
||||||
|
|
@ -21,18 +21,7 @@ export class UserService {
|
||||||
private fileService: FileService,
|
private fileService: FileService,
|
||||||
private geoService: GeoService,
|
private geoService: GeoService,
|
||||||
) {}
|
) {}
|
||||||
// private getConditions(criteria: UserListingCriteria): any[] {
|
|
||||||
// const conditions = [];
|
|
||||||
// if (criteria.states?.length > 0) {
|
|
||||||
// criteria.states.forEach(state => {
|
|
||||||
// conditions.push(sql`${schema.users.areasServed} @> ${JSON.stringify([{ state: state }])}`);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// if (criteria.firstname || criteria.lastname) {
|
|
||||||
// conditions.push(or(ilike(schema.users.firstname, `%${criteria.lastname}%`), ilike(schema.users.lastname, `%${criteria.lastname}%`)));
|
|
||||||
// }
|
|
||||||
// return conditions;
|
|
||||||
// }
|
|
||||||
private getWhereConditions(criteria: UserListingCriteria): SQL[] {
|
private getWhereConditions(criteria: UserListingCriteria): SQL[] {
|
||||||
const whereConditions: SQL[] = [];
|
const whereConditions: SQL[] = [];
|
||||||
|
|
||||||
|
|
@ -161,25 +150,6 @@ export class UserService {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// async findUser(criteria: UserListingCriteria) {
|
|
||||||
// const start = criteria.start ? criteria.start : 0;
|
|
||||||
// const length = criteria.length ? criteria.length : 12;
|
|
||||||
// const conditions = this.getConditions(criteria);
|
|
||||||
// const [data, total] = await Promise.all([
|
|
||||||
// this.conn
|
|
||||||
// .select()
|
|
||||||
// .from(schema.users)
|
|
||||||
// .where(and(...conditions))
|
|
||||||
// .offset(start)
|
|
||||||
// .limit(length),
|
|
||||||
// this.conn
|
|
||||||
// .select({ count: sql`count(*)` })
|
|
||||||
// .from(schema.users)
|
|
||||||
// .where(and(...conditions))
|
|
||||||
// .then(result => Number(result[0].count)),
|
|
||||||
// ]);
|
|
||||||
// return { total, data };
|
|
||||||
// }
|
|
||||||
async getStates(): Promise<any[]> {
|
async getStates(): Promise<any[]> {
|
||||||
const query = sql`SELECT jsonb_array_elements(${schema.users.areasServed}) ->> 'state' AS state, COUNT(DISTINCT ${schema.users.id}) AS count FROM ${schema.users} GROUP BY state ORDER BY count DESC`;
|
const query = sql`SELECT jsonb_array_elements(${schema.users.areasServed}) ->> 'state' AS state, COUNT(DISTINCT ${schema.users.id}) AS count FROM ${schema.users} GROUP BY state ORDER BY count DESC`;
|
||||||
const result = await this.conn.execute(query);
|
const result = await this.conn.execute(query);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
[type]="kind"
|
[type]="kind"
|
||||||
[id]="name"
|
[id]="name"
|
||||||
[ngModel]="value"
|
[ngModel]="value"
|
||||||
(input)="onInputChange($event)"
|
(ngModelChange)="onInputChange($event)"
|
||||||
(blur)="onTouched()"
|
(blur)="onTouched()"
|
||||||
[attr.name]="name"
|
[attr.name]="name"
|
||||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core';
|
import { Component, forwardRef, Input } from '@angular/core';
|
||||||
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
import { BaseInputComponent } from '../base-input/base-input.component';
|
import { BaseInputComponent } from '../base-input/base-input.component';
|
||||||
import { TooltipComponent } from '../tooltip/tooltip.component';
|
import { TooltipComponent } from '../tooltip/tooltip.component';
|
||||||
|
|
@ -19,7 +19,6 @@ import { ValidationMessagesService } from '../validation-messages.service';
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ValidatedInputComponent extends BaseInputComponent {
|
export class ValidatedInputComponent extends BaseInputComponent {
|
||||||
@Output() valueChange = new EventEmitter<any>();
|
|
||||||
@Input() kind: 'text' | 'number' | 'email' | 'tel' = 'text';
|
@Input() kind: 'text' | 'number' | 'email' | 'tel' = 'text';
|
||||||
constructor(validationMessagesService: ValidationMessagesService) {
|
constructor(validationMessagesService: ValidationMessagesService) {
|
||||||
super(validationMessagesService);
|
super(validationMessagesService);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<div>
|
<div>
|
||||||
<label for="type" class="block text-sm font-bold text-gray-700 mb-1 relative w-fit"
|
<label for="type" class="block text-sm font-bold text-gray-700 mb-1 relative w-fit"
|
||||||
>Property Category @if(validationMessage){
|
>{{ label }} @if(validationMessage){
|
||||||
<div
|
<div
|
||||||
attr.data-tooltip-target="tooltip-{{ name }}"
|
attr.data-tooltip-target="tooltip-{{ name }}"
|
||||||
class="absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full -top-2 dark:border-gray-900 hover:cursor-pointer"
|
class="absolute inline-flex items-center justify-center w-6 h-6 text-xs font-bold text-white bg-red-500 border-2 border-white rounded-full -top-2 dark:border-gray-900 hover:cursor-pointer"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
</div>
|
</div>
|
||||||
<app-tooltip id="tooltip-{{ name }}" [text]="validationMessage"></app-tooltip>
|
<app-tooltip id="tooltip-{{ name }}" [text]="validationMessage"></app-tooltip>
|
||||||
}
|
}
|
||||||
<span class="text-red-500 ml-1">{{ validationMessage }}</span>
|
|
||||||
</label>
|
</label>
|
||||||
<textarea [id]="name" [ngModel]="value" (ngModelChange)="onInputChange($event)" [attr.name]="name" class="w-full p-2 border border-gray-300 rounded-md" rows="3"></textarea>
|
<textarea [id]="name" [ngModel]="value" (ngModelChange)="onInputChange($event)" [attr.name]="name" class="w-full p-2 border border-gray-300 rounded-md" rows="3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ import { lastValueFrom } from 'rxjs';
|
||||||
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { BusinessListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { BusinessListingCriteria, KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { BusinessListingCriteria, KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { MessageService } from '../../../components/message/message.service';
|
||||||
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
|
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
|
||||||
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
|
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
|
||||||
|
import { ValidationMessagesService } from '../../../components/validation-messages.service';
|
||||||
import { HistoryService } from '../../../services/history.service';
|
import { HistoryService } from '../../../services/history.service';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { MailService } from '../../../services/mail.service';
|
import { MailService } from '../../../services/mail.service';
|
||||||
|
|
@ -65,6 +67,8 @@ export class DetailsBusinessListingComponent {
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
public historyService: HistoryService,
|
public historyService: HistoryService,
|
||||||
public keycloakService: KeycloakService,
|
public keycloakService: KeycloakService,
|
||||||
|
private validationMessagesService: ValidationMessagesService,
|
||||||
|
private messageService: MessageService,
|
||||||
) {
|
) {
|
||||||
this.router.events.subscribe(event => {
|
this.router.events.subscribe(event => {
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
|
|
@ -86,15 +90,28 @@ export class DetailsBusinessListingComponent {
|
||||||
this.listingUser = await this.userService.getByMail(this.listing.email);
|
this.listingUser = await this.userService.getByMail(this.listing.email);
|
||||||
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
||||||
}
|
}
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
||||||
|
}
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
return this.keycloakService.getUserRoles(true).includes('ADMIN');
|
return this.keycloakService.getUserRoles(true).includes('ADMIN');
|
||||||
}
|
}
|
||||||
async mail() {
|
async mail() {
|
||||||
this.mailinfo.email = this.listingUser.email;
|
try {
|
||||||
this.mailinfo.listing = this.listing;
|
this.mailinfo.email = this.listingUser.email;
|
||||||
await this.mailService.mail(this.mailinfo);
|
this.mailinfo.listing = this.listing;
|
||||||
// this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Your message has been sent to the creator of the listing', life: 3000 });
|
await this.mailService.mail(this.mailinfo);
|
||||||
|
this.messageService.addMessage({ severity: 'success', text: 'Your message has been sent to the creator of the listing', duration: 3000 });
|
||||||
|
} catch (error) {
|
||||||
|
this.messageService.addMessage({
|
||||||
|
severity: 'danger',
|
||||||
|
text: 'An error occurred while sending the request',
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
if (error.error && Array.isArray(error.error?.message)) {
|
||||||
|
this.validationMessagesService.updateMessages(error.error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
get listingDetails() {
|
get listingDetails() {
|
||||||
let typeOfRealEstate = '';
|
let typeOfRealEstate = '';
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,10 @@ import { lastValueFrom } from 'rxjs';
|
||||||
import { CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { CommercialPropertyListingCriteria, ErrorResponse, KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { CommercialPropertyListingCriteria, ErrorResponse, KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { MessageService } from '../../../components/message/message.service';
|
||||||
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
|
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
|
||||||
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
|
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
|
||||||
|
import { ValidationMessagesService } from '../../../components/validation-messages.service';
|
||||||
import { HistoryService } from '../../../services/history.service';
|
import { HistoryService } from '../../../services/history.service';
|
||||||
import { ImageService } from '../../../services/image.service';
|
import { ImageService } from '../../../services/image.service';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
|
|
@ -71,6 +73,8 @@ export class DetailsCommercialPropertyListingComponent {
|
||||||
public keycloakService: KeycloakService,
|
public keycloakService: KeycloakService,
|
||||||
private imageService: ImageService,
|
private imageService: ImageService,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
|
private validationMessagesService: ValidationMessagesService,
|
||||||
|
private messageService: MessageService,
|
||||||
) {
|
) {
|
||||||
this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl };
|
this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl };
|
||||||
|
|
||||||
|
|
@ -100,6 +104,9 @@ export class DetailsCommercialPropertyListingComponent {
|
||||||
];
|
];
|
||||||
//this.initFlowbite();
|
//this.initFlowbite();
|
||||||
}
|
}
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
||||||
|
}
|
||||||
private initFlowbite() {
|
private initFlowbite() {
|
||||||
this.ngZone.runOutsideAngular(() => {
|
this.ngZone.runOutsideAngular(() => {
|
||||||
import('flowbite')
|
import('flowbite')
|
||||||
|
|
@ -113,14 +120,20 @@ export class DetailsCommercialPropertyListingComponent {
|
||||||
return this.keycloakService.getUserRoles(true).includes('ADMIN');
|
return this.keycloakService.getUserRoles(true).includes('ADMIN');
|
||||||
}
|
}
|
||||||
async mail() {
|
async mail() {
|
||||||
this.mailinfo.email = this.listingUser.email;
|
try {
|
||||||
this.mailinfo.listing = this.listing;
|
this.mailinfo.email = this.listingUser.email;
|
||||||
const result = await this.mailService.mail(this.mailinfo);
|
this.mailinfo.listing = this.listing;
|
||||||
if (result) {
|
await this.mailService.mail(this.mailinfo);
|
||||||
this.errorResponse = result as ErrorResponse;
|
this.messageService.addMessage({ severity: 'success', text: 'Your message has been sent to the creator of the listing', duration: 3000 });
|
||||||
} else {
|
} catch (error) {
|
||||||
this.errorResponse = null;
|
this.messageService.addMessage({
|
||||||
// this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Your message has been sent to the creator of the listing', life: 3000 });
|
severity: 'danger',
|
||||||
|
text: 'An error occurred while sending the request',
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
if (error.error && Array.isArray(error.error?.message)) {
|
||||||
|
this.validationMessagesService.updateMessages(error.error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
containsError(fieldname: string) {
|
containsError(fieldname: string) {
|
||||||
|
|
|
||||||
|
|
@ -81,9 +81,7 @@ export class BusinessListingsComponent {
|
||||||
this.criteria.page = page;
|
this.criteria.page = page;
|
||||||
this.search();
|
this.search();
|
||||||
}
|
}
|
||||||
imageErrorHandler(listing: ListingType) {
|
imageErrorHandler(listing: ListingType) {}
|
||||||
// listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann
|
|
||||||
}
|
|
||||||
reset() {
|
reset() {
|
||||||
this.criteria.title = null;
|
this.criteria.title = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,9 @@ export class AccountComponent {
|
||||||
label: this.titleCasePipe.transform(type),
|
label: this.titleCasePipe.transform(type),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
||||||
|
}
|
||||||
printInvoice(invoice: Invoice) {}
|
printInvoice(invoice: Invoice) {}
|
||||||
|
|
||||||
async updateProfile(user: User) {
|
async updateProfile(user: User) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,16 @@
|
||||||
<form #listingForm="ngForm" class="space-y-4">
|
<form #listingForm="ngForm" class="space-y-4">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="listingsCategory" class="block text-sm font-bold text-gray-700 mb-1">Listing category</label>
|
<label for="listingsCategory" class="block text-sm font-bold text-gray-700 mb-1">Listing category</label>
|
||||||
<ng-select [readonly]="mode === 'edit'" [items]="selectOptions?.listingCategories" bindLabel="name" bindValue="value" [(ngModel)]="listing.listingsCategory" name="listingsCategory"> </ng-select>
|
<ng-select
|
||||||
|
[readonly]="mode === 'edit'"
|
||||||
|
[items]="selectOptions?.listingCategories"
|
||||||
|
bindLabel="name"
|
||||||
|
bindValue="value"
|
||||||
|
[(ngModel)]="listing.listingsCategory"
|
||||||
|
(ngModelChange)="changeListingCategory($event)"
|
||||||
|
name="listingsCategory"
|
||||||
|
>
|
||||||
|
</ng-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="mb-4">
|
<!-- <div class="mb-4">
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import { ValidatedNgSelectComponent } from '../../../components/validated-ng-sel
|
||||||
import { ValidatedPriceComponent } from '../../../components/validated-price/validated-price.component';
|
import { ValidatedPriceComponent } from '../../../components/validated-price/validated-price.component';
|
||||||
import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component';
|
import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component';
|
||||||
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
|
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
|
||||||
|
import { ValidationMessagesService } from '../../../components/validation-messages.service';
|
||||||
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
||||||
import { GeoService } from '../../../services/geo.service';
|
import { GeoService } from '../../../services/geo.service';
|
||||||
import { ImageService } from '../../../services/image.service';
|
import { ImageService } from '../../../services/image.service';
|
||||||
|
|
@ -80,6 +81,7 @@ export class EditBusinessListingComponent {
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private keycloakService: KeycloakService,
|
private keycloakService: KeycloakService,
|
||||||
|
private validationMessagesService: ValidationMessagesService,
|
||||||
) {
|
) {
|
||||||
this.router.events.subscribe(event => {
|
this.router.events.subscribe(event => {
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
|
|
@ -111,11 +113,25 @@ export class EditBusinessListingComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
||||||
|
}
|
||||||
async save() {
|
async save() {
|
||||||
this.listing = await this.listingsService.save(this.listing, this.listing.listingsCategory);
|
try {
|
||||||
this.router.navigate(['editBusinessListing', this.listing.id]);
|
this.listing = await this.listingsService.save(this.listing, this.listing.listingsCategory);
|
||||||
this.messageService.addMessage({ severity: 'success', text: 'Listing changes have been persisted', duration: 3000 });
|
this.router.navigate(['editBusinessListing', this.listing.id]);
|
||||||
|
this.messageService.addMessage({ severity: 'success', text: 'Listing changes have been persisted', duration: 3000 });
|
||||||
|
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
||||||
|
} catch (error) {
|
||||||
|
this.messageService.addMessage({
|
||||||
|
severity: 'danger',
|
||||||
|
text: 'An error occurred while saving the profile',
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
if (error.error && Array.isArray(error.error?.message)) {
|
||||||
|
this.validationMessagesService.updateMessages(error.error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestions: string[] | undefined;
|
suggestions: string[] | undefined;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,16 @@
|
||||||
<form #listingForm="ngForm" class="space-y-4">
|
<form #listingForm="ngForm" class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="listingsCategory" class="block text-sm font-bold text-gray-700 mb-1">Listing category</label>
|
<label for="listingsCategory" class="block text-sm font-bold text-gray-700 mb-1">Listing category</label>
|
||||||
<ng-select [readonly]="mode === 'edit'" [items]="selectOptions?.listingCategories" bindLabel="name" bindValue="value" [(ngModel)]="listing.listingsCategory" name="listingsCategory"> </ng-select>
|
<ng-select
|
||||||
|
[readonly]="mode === 'edit'"
|
||||||
|
[items]="selectOptions?.listingCategories"
|
||||||
|
bindLabel="name"
|
||||||
|
bindValue="value"
|
||||||
|
(ngModelChange)="changeListingCategory($event)"
|
||||||
|
[(ngModel)]="listing.listingsCategory"
|
||||||
|
name="listingsCategory"
|
||||||
|
>
|
||||||
|
</ng-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div class="mb-4">
|
<!-- <div class="mb-4">
|
||||||
|
|
@ -26,7 +35,9 @@
|
||||||
<label for="type" class="block text-sm font-bold text-gray-700 mb-1">Property Category</label>
|
<label for="type" class="block text-sm font-bold text-gray-700 mb-1">Property Category</label>
|
||||||
<ng-select [items]="typesOfCommercialProperty" bindLabel="name" bindValue="value" [(ngModel)]="listing.type" name="type"> </ng-select>
|
<ng-select [items]="typesOfCommercialProperty" bindLabel="name" bindValue="value" [(ngModel)]="listing.type" name="type"> </ng-select>
|
||||||
</div> -->
|
</div> -->
|
||||||
<app-validated-ng-select label="Property Category" name="type" [(ngModel)]="listing.type" [items]="typesOfCommercialProperty"></app-validated-ng-select>
|
<div>
|
||||||
|
<app-validated-ng-select label="Property Category" name="type" [(ngModel)]="listing.type" [items]="typesOfCommercialProperty"></app-validated-ng-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- <div class="flex mb-4 space-x-4">
|
<!-- <div class="flex mb-4 space-x-4">
|
||||||
<div class="w-1/2">
|
<div class="w-1/2">
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { ValidatedInputComponent } from '../../../components/validated-input/val
|
||||||
import { ValidatedNgSelectComponent } from '../../../components/validated-ng-select/validated-ng-select.component';
|
import { ValidatedNgSelectComponent } from '../../../components/validated-ng-select/validated-ng-select.component';
|
||||||
import { ValidatedPriceComponent } from '../../../components/validated-price/validated-price.component';
|
import { ValidatedPriceComponent } from '../../../components/validated-price/validated-price.component';
|
||||||
import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component';
|
import { ValidatedQuillComponent } from '../../../components/validated-quill/validated-quill.component';
|
||||||
|
import { ValidationMessagesService } from '../../../components/validation-messages.service';
|
||||||
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
|
||||||
import { GeoService } from '../../../services/geo.service';
|
import { GeoService } from '../../../services/geo.service';
|
||||||
import { ImageService } from '../../../services/image.service';
|
import { ImageService } from '../../../services/image.service';
|
||||||
|
|
@ -119,6 +120,7 @@ export class EditCommercialPropertyListingComponent {
|
||||||
private confirmationService: ConfirmationService,
|
private confirmationService: ConfirmationService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private viewportRuler: ViewportRuler,
|
private viewportRuler: ViewportRuler,
|
||||||
|
private validationMessagesService: ValidationMessagesService,
|
||||||
) {
|
) {
|
||||||
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
// Abonniere Router-Events, um den aktiven Link zu ermitteln
|
||||||
this.router.events.subscribe(event => {
|
this.router.events.subscribe(event => {
|
||||||
|
|
@ -151,11 +153,25 @@ export class EditCommercialPropertyListingComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
||||||
|
}
|
||||||
async save() {
|
async save() {
|
||||||
this.listing = (await this.listingsService.save(this.listing, this.listing.listingsCategory)) as CommercialPropertyListing;
|
try {
|
||||||
this.router.navigate(['editCommercialPropertyListing', this.listing.id]);
|
this.listing = (await this.listingsService.save(this.listing, this.listing.listingsCategory)) as CommercialPropertyListing;
|
||||||
this.messageService.addMessage({ severity: 'success', text: 'Listing changes have been persisted', duration: 3000 });
|
this.router.navigate(['editCommercialPropertyListing', this.listing.id]);
|
||||||
|
this.messageService.addMessage({ severity: 'success', text: 'Listing changes have been persisted', duration: 3000 });
|
||||||
|
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
||||||
|
} catch (error) {
|
||||||
|
this.messageService.addMessage({
|
||||||
|
severity: 'danger',
|
||||||
|
text: 'An error occurred while saving the profile',
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
if (error.error && Array.isArray(error.error?.message)) {
|
||||||
|
this.validationMessagesService.updateMessages(error.error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(event: AutoCompleteCompleteEvent) {
|
async search(event: AutoCompleteCompleteEvent) {
|
||||||
|
|
@ -214,47 +230,4 @@ export class EditCommercialPropertyListingComponent {
|
||||||
imageOrderChanged(imageOrder: string[]) {
|
imageOrderChanged(imageOrder: string[]) {
|
||||||
this.listing.imageOrder = imageOrder;
|
this.listing.imageOrder = imageOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// select(event: any) {
|
|
||||||
// const imageUrl = URL.createObjectURL(event.files[0]);
|
|
||||||
// getImageDimensions(imageUrl).then(dimensions => {
|
|
||||||
// const dialogWidth = getDialogWidth(dimensions);
|
|
||||||
// this.dialogRef = this.dialogService.open(ImageCropperComponent, {
|
|
||||||
// data: {
|
|
||||||
// imageUrl: imageUrl,
|
|
||||||
// fileUpload: this.fileUpload,
|
|
||||||
// ratioVariable: false,
|
|
||||||
// },
|
|
||||||
// header: 'Edit Image',
|
|
||||||
// width: dialogWidth,
|
|
||||||
// modal: true,
|
|
||||||
// closeOnEscape: true,
|
|
||||||
// keepInViewport: true,
|
|
||||||
// closable: false,
|
|
||||||
// breakpoints: {
|
|
||||||
// '960px': '75vw',
|
|
||||||
// '640px': '90vw',
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// this.dialogRef.onClose.subscribe(cropper => {
|
|
||||||
// if (cropper) {
|
|
||||||
// this.loadingService.startLoading('uploadImage');
|
|
||||||
// cropper.getCroppedCanvas().toBlob(async blob => {
|
|
||||||
// this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.imagePath, this.listing.serialId).subscribe(
|
|
||||||
// async event => {
|
|
||||||
// if (event.type === HttpEventType.Response) {
|
|
||||||
// this.ts = new Date().getTime();
|
|
||||||
// console.log('Upload abgeschlossen', event.body);
|
|
||||||
// this.loadingService.stopLoading('uploadImage');
|
|
||||||
// this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// error => console.error('Fehler beim Upload:', error),
|
|
||||||
// );
|
|
||||||
// }, 'image/jpg');
|
|
||||||
// cropper.destroy();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ import { KeycloakService } from 'keycloak-angular';
|
||||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { ErrorResponse, KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { ErrorResponse, KeycloakUser, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
import { MessageService } from '../../../components/message/message.service';
|
||||||
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
|
import { ValidatedInputComponent } from '../../../components/validated-input/validated-input.component';
|
||||||
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
|
import { ValidatedTextareaComponent } from '../../../components/validated-textarea/validated-textarea.component';
|
||||||
|
import { ValidationMessagesService } from '../../../components/validation-messages.service';
|
||||||
import { MailService } from '../../../services/mail.service';
|
import { MailService } from '../../../services/mail.service';
|
||||||
import { UserService } from '../../../services/user.service';
|
import { UserService } from '../../../services/user.service';
|
||||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
|
|
@ -23,7 +25,13 @@ export class EmailUsComponent {
|
||||||
keycloakUser: KeycloakUser;
|
keycloakUser: KeycloakUser;
|
||||||
user: User;
|
user: User;
|
||||||
errorResponse: ErrorResponse;
|
errorResponse: ErrorResponse;
|
||||||
constructor(private mailService: MailService, private userService: UserService, public keycloakService: KeycloakService) {
|
constructor(
|
||||||
|
private mailService: MailService,
|
||||||
|
private userService: UserService,
|
||||||
|
public keycloakService: KeycloakService,
|
||||||
|
private validationMessagesService: ValidationMessagesService,
|
||||||
|
private messageService: MessageService,
|
||||||
|
) {
|
||||||
this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl };
|
this.mailinfo = { sender: {}, email: '', url: environment.mailinfoUrl };
|
||||||
}
|
}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
|
@ -34,14 +42,23 @@ export class EmailUsComponent {
|
||||||
this.mailinfo.sender = { name: `${this.user.firstname} ${this.user.lastname}`, email: this.user.email, phoneNumber: this.user.phoneNumber, state: this.user.companyLocation };
|
this.mailinfo.sender = { name: `${this.user.firstname} ${this.user.lastname}`, email: this.user.email, phoneNumber: this.user.phoneNumber, state: this.user.companyLocation };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
|
||||||
|
}
|
||||||
async mail() {
|
async mail() {
|
||||||
this.mailinfo.email = 'support@bizmatch.net';
|
try {
|
||||||
const result = await this.mailService.mail(this.mailinfo);
|
this.mailinfo.email = 'support@bizmatch.net';
|
||||||
if (result) {
|
await this.mailService.mail(this.mailinfo);
|
||||||
this.errorResponse = result as ErrorResponse;
|
this.messageService.addMessage({ severity: 'success', text: 'Your request has been forwarded to the support team of bizmatch.', duration: 3000 });
|
||||||
} else {
|
} catch (error) {
|
||||||
this.errorResponse = null;
|
this.messageService.addMessage({
|
||||||
// this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Your request has been forwarded to the support team of bizmatch.', life: 3000 });
|
severity: 'danger',
|
||||||
|
text: 'An error occurred',
|
||||||
|
duration: 5000,
|
||||||
|
});
|
||||||
|
if (error.error && Array.isArray(error.error?.message)) {
|
||||||
|
this.validationMessagesService.updateMessages(error.error.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
containsError(fieldname: string) {
|
containsError(fieldname: string) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue