Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d6768b3da9 | |||
| 7fdc87fb0b | |||
| 0b7e33612a | |||
| 8fba3aa832 | |||
| 214327031c | |||
| dc9adb151d | |||
| 747435bfba | |||
| 782c254a33 | |||
| df4e2b00e2 | |||
| 0684b9534f | |||
| e0ecea5af2 | |||
| 327aef0f21 | |||
| cb73daf863 | |||
| 08c53e2eb2 | |||
| 492c03c2be | |||
| f51a298227 | |||
| 474d7c63d5 | |||
| d2e5562602 | |||
| aff55c5433 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -62,7 +62,6 @@ pids
|
|||||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
*.js
|
|
||||||
*.map
|
*.map
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
|
|||||||
25
bizmatch-server/.eslintrc.js
Normal file
25
bizmatch-server/.eslintrc.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
ignorePatterns: ['.eslintrc.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -14,7 +14,7 @@ import { ListingsModule } from './listings/listings.module.js';
|
|||||||
import { MailModule } from './mail/mail.module.js';
|
import { MailModule } from './mail/mail.module.js';
|
||||||
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware.js';
|
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware.js';
|
||||||
import { SelectOptionsModule } from './select-options/select-options.module.js';
|
import { SelectOptionsModule } from './select-options/select-options.module.js';
|
||||||
import { SubscriptionsController } from './subscriptions/subscriptions.controller.js';
|
|
||||||
import { UserModule } from './user/user.module.js';
|
import { UserModule } from './user/user.module.js';
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
@@ -47,7 +47,7 @@ const __dirname = path.dirname(__filename);
|
|||||||
SelectOptionsModule,
|
SelectOptionsModule,
|
||||||
ImageModule,
|
ImageModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController, SubscriptionsController],
|
controllers: [AppController],
|
||||||
providers: [AppService, FileService],
|
providers: [AppService, FileService],
|
||||||
})
|
})
|
||||||
export class AppModule {
|
export class AppModule {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { join } from 'path';
|
|||||||
import pkg from 'pg';
|
import pkg from 'pg';
|
||||||
import { rimraf } from 'rimraf';
|
import { rimraf } from 'rimraf';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { BusinessListing, CommercialPropertyListing, User } from 'src/models/db.model.js';
|
import { BusinessListing, CommercialPropertyListing, User, UserData } from 'src/models/db.model.js';
|
||||||
import * as schema from './schema.js';
|
import * as schema from './schema.js';
|
||||||
const { Pool } = pkg;
|
const { Pool } = pkg;
|
||||||
|
|
||||||
@@ -22,9 +22,9 @@ await db.delete(schema.users);
|
|||||||
//Broker
|
//Broker
|
||||||
let filePath = `./data/broker.json`;
|
let filePath = `./data/broker.json`;
|
||||||
let data: string = readFileSync(filePath, 'utf8');
|
let data: string = readFileSync(filePath, 'utf8');
|
||||||
const userData: User[] = JSON.parse(data); // Erwartet ein Array von Objekten
|
const usersData: UserData[] = JSON.parse(data); // Erwartet ein Array von Objekten
|
||||||
const generatedUserData = [];
|
const generatedUserData = [];
|
||||||
console.log(userData.length);
|
console.log(usersData.length);
|
||||||
let i = 0,
|
let i = 0,
|
||||||
male = 0,
|
male = 0,
|
||||||
female = 0;
|
female = 0;
|
||||||
@@ -32,11 +32,32 @@ const targetPathProfile = `./pictures/profile`;
|
|||||||
deleteFilesOfDir(targetPathProfile);
|
deleteFilesOfDir(targetPathProfile);
|
||||||
const targetPathLogo = `./pictures/logo`;
|
const targetPathLogo = `./pictures/logo`;
|
||||||
deleteFilesOfDir(targetPathLogo);
|
deleteFilesOfDir(targetPathLogo);
|
||||||
for (const user of userData) {
|
for (const userData of usersData) {
|
||||||
delete user.id;
|
const user: User = { firstname: '', lastname: '', email: '' };
|
||||||
user.licensedIn = user.licensedIn.map(l => `${l['name']}|${l['value']}`);
|
user.licensedIn = [];
|
||||||
|
userData.licensedIn.forEach(l => {
|
||||||
|
console.log(l['value'], l['name']);
|
||||||
|
user.licensedIn.push({ registerNo: l['value'], state: l['name'] });
|
||||||
|
});
|
||||||
|
user.areasServed = [];
|
||||||
|
user.areasServed = userData.areasServed.map(l => {
|
||||||
|
return { county: l.split(',')[0].trim(), state: l.split(',')[1].trim() };
|
||||||
|
});
|
||||||
user.hasCompanyLogo = true;
|
user.hasCompanyLogo = true;
|
||||||
user.hasProfile = true;
|
user.hasProfile = true;
|
||||||
|
user.firstname = userData.firstname;
|
||||||
|
user.lastname = userData.lastname;
|
||||||
|
user.email = userData.email;
|
||||||
|
user.phoneNumber = userData.phoneNumber;
|
||||||
|
user.description = userData.description;
|
||||||
|
user.companyName = userData.companyName;
|
||||||
|
user.companyOverview = userData.companyOverview;
|
||||||
|
user.companyWebsite = userData.companyWebsite;
|
||||||
|
user.companyLocation = userData.companyLocation;
|
||||||
|
user.offeredServices = userData.offeredServices;
|
||||||
|
user.gender = userData.gender;
|
||||||
|
user.created = new Date();
|
||||||
|
user.updated = new Date();
|
||||||
const u = await db.insert(schema.users).values(user).returning({ insertedId: schema.users.id, gender: schema.users.gender });
|
const u = await db.insert(schema.users).values(user).returning({ insertedId: schema.users.id, gender: schema.users.gender });
|
||||||
generatedUserData.push(u[0].insertedId);
|
generatedUserData.push(u[0].insertedId);
|
||||||
i++;
|
i++;
|
||||||
@@ -46,7 +67,7 @@ for (const user of userData) {
|
|||||||
await storeProfilePicture(data, u[0].insertedId);
|
await storeProfilePicture(data, u[0].insertedId);
|
||||||
} else {
|
} else {
|
||||||
female++;
|
female++;
|
||||||
const data = readFileSync(`./pictures/profile_base/Frau_${male}.jpg`);
|
const data = readFileSync(`./pictures/profile_base/Frau_${female}.jpg`);
|
||||||
await storeProfilePicture(data, u[0].insertedId);
|
await storeProfilePicture(data, u[0].insertedId);
|
||||||
}
|
}
|
||||||
const data = readFileSync(`./pictures/logos_base/${i}.jpg`);
|
const data = readFileSync(`./pictures/logos_base/${i}.jpg`);
|
||||||
@@ -60,6 +81,7 @@ const businessJsonData = JSON.parse(data) as BusinessListing[]; // Erwartet ein
|
|||||||
for (const business of businessJsonData) {
|
for (const business of businessJsonData) {
|
||||||
delete business.id;
|
delete business.id;
|
||||||
business.created = new Date(business.created);
|
business.created = new Date(business.created);
|
||||||
|
business.updated = new Date(business.created);
|
||||||
business.userId = getRandomItem(generatedUserData);
|
business.userId = getRandomItem(generatedUserData);
|
||||||
await db.insert(schema.businesses).values(business);
|
await db.insert(schema.businesses).values(business);
|
||||||
}
|
}
|
||||||
@@ -73,7 +95,9 @@ for (const commercial of commercialJsonData) {
|
|||||||
|
|
||||||
commercial.imageOrder = getFilenames(id);
|
commercial.imageOrder = getFilenames(id);
|
||||||
commercial.imagePath = id;
|
commercial.imagePath = id;
|
||||||
commercial.created = getRandomDateWithinLastYear();
|
const insertionDate = getRandomDateWithinLastYear();
|
||||||
|
commercial.created = insertionDate;
|
||||||
|
commercial.updated = insertionDate;
|
||||||
commercial.userId = getRandomItem(generatedUserData);
|
commercial.userId = getRandomItem(generatedUserData);
|
||||||
await db.insert(schema.commercials).values(commercial);
|
await db.insert(schema.commercials).values(commercial);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
DO $$ BEGIN
|
||||||
|
CREATE TYPE "gender" AS ENUM('male', 'female');
|
||||||
|
EXCEPTION
|
||||||
|
WHEN duplicate_object THEN null;
|
||||||
|
END $$;
|
||||||
|
--> statement-breakpoint
|
||||||
CREATE TABLE IF NOT EXISTS "businesses" (
|
CREATE TABLE IF NOT EXISTS "businesses" (
|
||||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
"userId" uuid,
|
"userId" uuid,
|
||||||
@@ -38,6 +44,7 @@ CREATE TABLE IF NOT EXISTS "commercials" (
|
|||||||
"state" char(2),
|
"state" char(2),
|
||||||
"price" double precision,
|
"price" double precision,
|
||||||
"favoritesForUser" varchar(30)[],
|
"favoritesForUser" varchar(30)[],
|
||||||
|
"listingsCategory" varchar(255),
|
||||||
"hideImage" boolean,
|
"hideImage" boolean,
|
||||||
"draft" boolean,
|
"draft" boolean,
|
||||||
"zipCode" integer,
|
"zipCode" integer,
|
||||||
@@ -45,7 +52,7 @@ CREATE TABLE IF NOT EXISTS "commercials" (
|
|||||||
"email" varchar(255),
|
"email" varchar(255),
|
||||||
"website" varchar(255),
|
"website" varchar(255),
|
||||||
"phoneNumber" varchar(255),
|
"phoneNumber" varchar(255),
|
||||||
"imageOrder" varchar(30)[],
|
"imageOrder" varchar(200)[],
|
||||||
"imagePath" varchar(50),
|
"imagePath" varchar(50),
|
||||||
"created" timestamp,
|
"created" timestamp,
|
||||||
"updated" timestamp,
|
"updated" timestamp,
|
||||||
@@ -65,10 +72,11 @@ CREATE TABLE IF NOT EXISTS "users" (
|
|||||||
"companyWebsite" varchar(255),
|
"companyWebsite" varchar(255),
|
||||||
"companyLocation" varchar(255),
|
"companyLocation" varchar(255),
|
||||||
"offeredServices" text,
|
"offeredServices" text,
|
||||||
"areasServed" varchar(100)[],
|
"areasServed" jsonb,
|
||||||
"hasProfile" boolean,
|
"hasProfile" boolean,
|
||||||
"hasCompanyLogo" boolean,
|
"hasCompanyLogo" boolean,
|
||||||
"licensedIn" varchar(50)[]
|
"licensedIn" jsonb,
|
||||||
|
"gender" "gender"
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
DO $$ BEGIN
|
DO $$ BEGIN
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "users" ADD COLUMN "created" timestamp;--> statement-breakpoint
|
||||||
|
ALTER TABLE "users" ADD COLUMN "updated" timestamp;
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE "commercials" ALTER COLUMN "imageOrder" SET DATA TYPE varchar(200)[];
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
DO $$ BEGIN
|
|
||||||
CREATE TYPE "gender" AS ENUM('male', 'female');
|
|
||||||
EXCEPTION
|
|
||||||
WHEN duplicate_object THEN null;
|
|
||||||
END $$;
|
|
||||||
--> statement-breakpoint
|
|
||||||
ALTER TABLE "users" ADD COLUMN "gender" "gender";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE "commercials" ADD COLUMN "listingsCategory" varchar(255);
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"id": "f6d421f9-2394-4a1c-9268-9e46285f0a41",
|
"id": "98e2be90-3301-49a8-b323-78d9d8f79cb5",
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"dialect": "pg",
|
"dialect": "pg",
|
||||||
@@ -250,6 +250,12 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
|
"listingsCategory": {
|
||||||
|
"name": "listingsCategory",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
"hideImage": {
|
"hideImage": {
|
||||||
"name": "hideImage",
|
"name": "hideImage",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -294,7 +300,7 @@
|
|||||||
},
|
},
|
||||||
"imageOrder": {
|
"imageOrder": {
|
||||||
"name": "imageOrder",
|
"name": "imageOrder",
|
||||||
"type": "varchar(30)[]",
|
"type": "varchar(200)[]",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
@@ -421,7 +427,7 @@
|
|||||||
},
|
},
|
||||||
"areasServed": {
|
"areasServed": {
|
||||||
"name": "areasServed",
|
"name": "areasServed",
|
||||||
"type": "varchar(100)[]",
|
"type": "jsonb",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
@@ -439,7 +445,13 @@
|
|||||||
},
|
},
|
||||||
"licensedIn": {
|
"licensedIn": {
|
||||||
"name": "licensedIn",
|
"name": "licensedIn",
|
||||||
"type": "varchar(50)[]",
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"type": "gender",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
}
|
}
|
||||||
@@ -450,7 +462,15 @@
|
|||||||
"uniqueConstraints": {}
|
"uniqueConstraints": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enums": {},
|
"enums": {
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"values": {
|
||||||
|
"male": "male",
|
||||||
|
"female": "female"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"schemas": {},
|
"schemas": {},
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"columns": {},
|
"columns": {},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"id": "3e4b8c5f-4474-4877-abec-38283408ee34",
|
"id": "41802273-1335-433f-97cb-77774ddb3362",
|
||||||
"prevId": "f6d421f9-2394-4a1c-9268-9e46285f0a41",
|
"prevId": "98e2be90-3301-49a8-b323-78d9d8f79cb5",
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"dialect": "pg",
|
"dialect": "pg",
|
||||||
"tables": {
|
"tables": {
|
||||||
@@ -250,6 +250,12 @@
|
|||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
|
"listingsCategory": {
|
||||||
|
"name": "listingsCategory",
|
||||||
|
"type": "varchar(255)",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
"hideImage": {
|
"hideImage": {
|
||||||
"name": "hideImage",
|
"name": "hideImage",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -421,7 +427,7 @@
|
|||||||
},
|
},
|
||||||
"areasServed": {
|
"areasServed": {
|
||||||
"name": "areasServed",
|
"name": "areasServed",
|
||||||
"type": "varchar(100)[]",
|
"type": "jsonb",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
},
|
},
|
||||||
@@ -439,7 +445,25 @@
|
|||||||
},
|
},
|
||||||
"licensedIn": {
|
"licensedIn": {
|
||||||
"name": "licensedIn",
|
"name": "licensedIn",
|
||||||
"type": "varchar(50)[]",
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"type": "gender",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"name": "created",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updated": {
|
||||||
|
"name": "updated",
|
||||||
|
"type": "timestamp",
|
||||||
"primaryKey": false,
|
"primaryKey": false,
|
||||||
"notNull": false
|
"notNull": false
|
||||||
}
|
}
|
||||||
@@ -450,7 +474,15 @@
|
|||||||
"uniqueConstraints": {}
|
"uniqueConstraints": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enums": {},
|
"enums": {
|
||||||
|
"gender": {
|
||||||
|
"name": "gender",
|
||||||
|
"values": {
|
||||||
|
"male": "male",
|
||||||
|
"female": "female"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"schemas": {},
|
"schemas": {},
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"columns": {},
|
"columns": {},
|
||||||
|
|||||||
@@ -1,474 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "ad48c6eb-2d04-442f-9242-b6765553c7c4",
|
|
||||||
"prevId": "3e4b8c5f-4474-4877-abec-38283408ee34",
|
|
||||||
"version": "5",
|
|
||||||
"dialect": "pg",
|
|
||||||
"tables": {
|
|
||||||
"businesses": {
|
|
||||||
"name": "businesses",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "gen_random_uuid()"
|
|
||||||
},
|
|
||||||
"userId": {
|
|
||||||
"name": "userId",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"name": "type",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"name": "title",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"name": "description",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"city": {
|
|
||||||
"name": "city",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"name": "state",
|
|
||||||
"type": "char(2)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"price": {
|
|
||||||
"name": "price",
|
|
||||||
"type": "double precision",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"favoritesForUser": {
|
|
||||||
"name": "favoritesForUser",
|
|
||||||
"type": "varchar(30)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"draft": {
|
|
||||||
"name": "draft",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"listingsCategory": {
|
|
||||||
"name": "listingsCategory",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"realEstateIncluded": {
|
|
||||||
"name": "realEstateIncluded",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"leasedLocation": {
|
|
||||||
"name": "leasedLocation",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"franchiseResale": {
|
|
||||||
"name": "franchiseResale",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"salesRevenue": {
|
|
||||||
"name": "salesRevenue",
|
|
||||||
"type": "double precision",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"cashFlow": {
|
|
||||||
"name": "cashFlow",
|
|
||||||
"type": "double precision",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"supportAndTraining": {
|
|
||||||
"name": "supportAndTraining",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"employees": {
|
|
||||||
"name": "employees",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"established": {
|
|
||||||
"name": "established",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"internalListingNumber": {
|
|
||||||
"name": "internalListingNumber",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"reasonForSale": {
|
|
||||||
"name": "reasonForSale",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"brokerLicencing": {
|
|
||||||
"name": "brokerLicencing",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"internals": {
|
|
||||||
"name": "internals",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"created": {
|
|
||||||
"name": "created",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"updated": {
|
|
||||||
"name": "updated",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"visits": {
|
|
||||||
"name": "visits",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"lastVisit": {
|
|
||||||
"name": "lastVisit",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {
|
|
||||||
"businesses_userId_users_id_fk": {
|
|
||||||
"name": "businesses_userId_users_id_fk",
|
|
||||||
"tableFrom": "businesses",
|
|
||||||
"tableTo": "users",
|
|
||||||
"columnsFrom": [
|
|
||||||
"userId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {}
|
|
||||||
},
|
|
||||||
"commercials": {
|
|
||||||
"name": "commercials",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "gen_random_uuid()"
|
|
||||||
},
|
|
||||||
"userId": {
|
|
||||||
"name": "userId",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"name": "type",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"name": "title",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"name": "description",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"city": {
|
|
||||||
"name": "city",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"name": "state",
|
|
||||||
"type": "char(2)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"price": {
|
|
||||||
"name": "price",
|
|
||||||
"type": "double precision",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"favoritesForUser": {
|
|
||||||
"name": "favoritesForUser",
|
|
||||||
"type": "varchar(30)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"hideImage": {
|
|
||||||
"name": "hideImage",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"draft": {
|
|
||||||
"name": "draft",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"zipCode": {
|
|
||||||
"name": "zipCode",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"county": {
|
|
||||||
"name": "county",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"name": "email",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"website": {
|
|
||||||
"name": "website",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"phoneNumber": {
|
|
||||||
"name": "phoneNumber",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"imageOrder": {
|
|
||||||
"name": "imageOrder",
|
|
||||||
"type": "varchar(200)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"imagePath": {
|
|
||||||
"name": "imagePath",
|
|
||||||
"type": "varchar(50)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"created": {
|
|
||||||
"name": "created",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"updated": {
|
|
||||||
"name": "updated",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"visits": {
|
|
||||||
"name": "visits",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"lastVisit": {
|
|
||||||
"name": "lastVisit",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {
|
|
||||||
"commercials_userId_users_id_fk": {
|
|
||||||
"name": "commercials_userId_users_id_fk",
|
|
||||||
"tableFrom": "commercials",
|
|
||||||
"tableTo": "users",
|
|
||||||
"columnsFrom": [
|
|
||||||
"userId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {}
|
|
||||||
},
|
|
||||||
"users": {
|
|
||||||
"name": "users",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "gen_random_uuid()"
|
|
||||||
},
|
|
||||||
"firstname": {
|
|
||||||
"name": "firstname",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"lastname": {
|
|
||||||
"name": "lastname",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"name": "email",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"phoneNumber": {
|
|
||||||
"name": "phoneNumber",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"name": "description",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"companyName": {
|
|
||||||
"name": "companyName",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"companyOverview": {
|
|
||||||
"name": "companyOverview",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"companyWebsite": {
|
|
||||||
"name": "companyWebsite",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"companyLocation": {
|
|
||||||
"name": "companyLocation",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"offeredServices": {
|
|
||||||
"name": "offeredServices",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"areasServed": {
|
|
||||||
"name": "areasServed",
|
|
||||||
"type": "varchar(100)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"hasProfile": {
|
|
||||||
"name": "hasProfile",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"hasCompanyLogo": {
|
|
||||||
"name": "hasCompanyLogo",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"licensedIn": {
|
|
||||||
"name": "licensedIn",
|
|
||||||
"type": "varchar(50)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"gender": {
|
|
||||||
"name": "gender",
|
|
||||||
"type": "gender",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"enums": {
|
|
||||||
"gender": {
|
|
||||||
"name": "gender",
|
|
||||||
"values": {
|
|
||||||
"male": "male",
|
|
||||||
"female": "female"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"schemas": {},
|
|
||||||
"_meta": {
|
|
||||||
"columns": {},
|
|
||||||
"schemas": {},
|
|
||||||
"tables": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,480 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "da786c6a-fd5f-4629-bd5e-3ecd42ab1f2c",
|
|
||||||
"prevId": "ad48c6eb-2d04-442f-9242-b6765553c7c4",
|
|
||||||
"version": "5",
|
|
||||||
"dialect": "pg",
|
|
||||||
"tables": {
|
|
||||||
"businesses": {
|
|
||||||
"name": "businesses",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "gen_random_uuid()"
|
|
||||||
},
|
|
||||||
"userId": {
|
|
||||||
"name": "userId",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"name": "type",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"name": "title",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"name": "description",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"city": {
|
|
||||||
"name": "city",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"name": "state",
|
|
||||||
"type": "char(2)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"price": {
|
|
||||||
"name": "price",
|
|
||||||
"type": "double precision",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"favoritesForUser": {
|
|
||||||
"name": "favoritesForUser",
|
|
||||||
"type": "varchar(30)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"draft": {
|
|
||||||
"name": "draft",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"listingsCategory": {
|
|
||||||
"name": "listingsCategory",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"realEstateIncluded": {
|
|
||||||
"name": "realEstateIncluded",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"leasedLocation": {
|
|
||||||
"name": "leasedLocation",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"franchiseResale": {
|
|
||||||
"name": "franchiseResale",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"salesRevenue": {
|
|
||||||
"name": "salesRevenue",
|
|
||||||
"type": "double precision",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"cashFlow": {
|
|
||||||
"name": "cashFlow",
|
|
||||||
"type": "double precision",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"supportAndTraining": {
|
|
||||||
"name": "supportAndTraining",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"employees": {
|
|
||||||
"name": "employees",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"established": {
|
|
||||||
"name": "established",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"internalListingNumber": {
|
|
||||||
"name": "internalListingNumber",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"reasonForSale": {
|
|
||||||
"name": "reasonForSale",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"brokerLicencing": {
|
|
||||||
"name": "brokerLicencing",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"internals": {
|
|
||||||
"name": "internals",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"created": {
|
|
||||||
"name": "created",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"updated": {
|
|
||||||
"name": "updated",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"visits": {
|
|
||||||
"name": "visits",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"lastVisit": {
|
|
||||||
"name": "lastVisit",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {
|
|
||||||
"businesses_userId_users_id_fk": {
|
|
||||||
"name": "businesses_userId_users_id_fk",
|
|
||||||
"tableFrom": "businesses",
|
|
||||||
"tableTo": "users",
|
|
||||||
"columnsFrom": [
|
|
||||||
"userId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {}
|
|
||||||
},
|
|
||||||
"commercials": {
|
|
||||||
"name": "commercials",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "gen_random_uuid()"
|
|
||||||
},
|
|
||||||
"userId": {
|
|
||||||
"name": "userId",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"name": "type",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"title": {
|
|
||||||
"name": "title",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"name": "description",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"city": {
|
|
||||||
"name": "city",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"state": {
|
|
||||||
"name": "state",
|
|
||||||
"type": "char(2)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"price": {
|
|
||||||
"name": "price",
|
|
||||||
"type": "double precision",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"favoritesForUser": {
|
|
||||||
"name": "favoritesForUser",
|
|
||||||
"type": "varchar(30)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"listingsCategory": {
|
|
||||||
"name": "listingsCategory",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"hideImage": {
|
|
||||||
"name": "hideImage",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"draft": {
|
|
||||||
"name": "draft",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"zipCode": {
|
|
||||||
"name": "zipCode",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"county": {
|
|
||||||
"name": "county",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"name": "email",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"website": {
|
|
||||||
"name": "website",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"phoneNumber": {
|
|
||||||
"name": "phoneNumber",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"imageOrder": {
|
|
||||||
"name": "imageOrder",
|
|
||||||
"type": "varchar(200)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"imagePath": {
|
|
||||||
"name": "imagePath",
|
|
||||||
"type": "varchar(50)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"created": {
|
|
||||||
"name": "created",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"updated": {
|
|
||||||
"name": "updated",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"visits": {
|
|
||||||
"name": "visits",
|
|
||||||
"type": "integer",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"lastVisit": {
|
|
||||||
"name": "lastVisit",
|
|
||||||
"type": "timestamp",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {
|
|
||||||
"commercials_userId_users_id_fk": {
|
|
||||||
"name": "commercials_userId_users_id_fk",
|
|
||||||
"tableFrom": "commercials",
|
|
||||||
"tableTo": "users",
|
|
||||||
"columnsFrom": [
|
|
||||||
"userId"
|
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "no action",
|
|
||||||
"onUpdate": "no action"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {}
|
|
||||||
},
|
|
||||||
"users": {
|
|
||||||
"name": "users",
|
|
||||||
"schema": "",
|
|
||||||
"columns": {
|
|
||||||
"id": {
|
|
||||||
"name": "id",
|
|
||||||
"type": "uuid",
|
|
||||||
"primaryKey": true,
|
|
||||||
"notNull": true,
|
|
||||||
"default": "gen_random_uuid()"
|
|
||||||
},
|
|
||||||
"firstname": {
|
|
||||||
"name": "firstname",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"lastname": {
|
|
||||||
"name": "lastname",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"email": {
|
|
||||||
"name": "email",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
"phoneNumber": {
|
|
||||||
"name": "phoneNumber",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"name": "description",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"companyName": {
|
|
||||||
"name": "companyName",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"companyOverview": {
|
|
||||||
"name": "companyOverview",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"companyWebsite": {
|
|
||||||
"name": "companyWebsite",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"companyLocation": {
|
|
||||||
"name": "companyLocation",
|
|
||||||
"type": "varchar(255)",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"offeredServices": {
|
|
||||||
"name": "offeredServices",
|
|
||||||
"type": "text",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"areasServed": {
|
|
||||||
"name": "areasServed",
|
|
||||||
"type": "varchar(100)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"hasProfile": {
|
|
||||||
"name": "hasProfile",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"hasCompanyLogo": {
|
|
||||||
"name": "hasCompanyLogo",
|
|
||||||
"type": "boolean",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"licensedIn": {
|
|
||||||
"name": "licensedIn",
|
|
||||||
"type": "varchar(50)[]",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
},
|
|
||||||
"gender": {
|
|
||||||
"name": "gender",
|
|
||||||
"type": "gender",
|
|
||||||
"primaryKey": false,
|
|
||||||
"notNull": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"indexes": {},
|
|
||||||
"foreignKeys": {},
|
|
||||||
"compositePrimaryKeys": {},
|
|
||||||
"uniqueConstraints": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"enums": {
|
|
||||||
"gender": {
|
|
||||||
"name": "gender",
|
|
||||||
"values": {
|
|
||||||
"male": "male",
|
|
||||||
"female": "female"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"schemas": {},
|
|
||||||
"_meta": {
|
|
||||||
"columns": {},
|
|
||||||
"schemas": {},
|
|
||||||
"tables": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,29 +5,15 @@
|
|||||||
{
|
{
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"when": 1714913766996,
|
"when": 1715627517508,
|
||||||
"tag": "0000_third_spacker_dave",
|
"tag": "0000_open_hannibal_king",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"version": "5",
|
"version": "5",
|
||||||
"when": 1714981666488,
|
"when": 1715631674334,
|
||||||
"tag": "0001_rapid_daimon_hellstrom",
|
"tag": "0001_charming_thundra",
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 2,
|
|
||||||
"version": "5",
|
|
||||||
"when": 1714982539265,
|
|
||||||
"tag": "0002_black_zaladane",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 3,
|
|
||||||
"version": "5",
|
|
||||||
"when": 1715254754561,
|
|
||||||
"tag": "0003_tough_hobgoblin",
|
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { boolean, char, doublePrecision, integer, pgEnum, pgTable, text, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
|
import { boolean, char, doublePrecision, integer, jsonb, pgEnum, pgTable, text, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
|
||||||
|
import { AreasServed, LicensedIn } from 'src/models/db.model';
|
||||||
|
|
||||||
export const PG_CONNECTION = 'PG_CONNECTION';
|
export const PG_CONNECTION = 'PG_CONNECTION';
|
||||||
export const genderEnum = pgEnum('gender', ['male', 'female']);
|
export const genderEnum = pgEnum('gender', ['male', 'female']);
|
||||||
@@ -14,11 +15,13 @@ export const users = pgTable('users', {
|
|||||||
companyWebsite: varchar('companyWebsite', { length: 255 }),
|
companyWebsite: varchar('companyWebsite', { length: 255 }),
|
||||||
companyLocation: varchar('companyLocation', { length: 255 }),
|
companyLocation: varchar('companyLocation', { length: 255 }),
|
||||||
offeredServices: text('offeredServices'),
|
offeredServices: text('offeredServices'),
|
||||||
areasServed: varchar('areasServed', { length: 100 }).array(),
|
areasServed: jsonb('areasServed').$type<AreasServed[]>(),
|
||||||
hasProfile: boolean('hasProfile'),
|
hasProfile: boolean('hasProfile'),
|
||||||
hasCompanyLogo: boolean('hasCompanyLogo'),
|
hasCompanyLogo: boolean('hasCompanyLogo'),
|
||||||
licensedIn: varchar('licensedIn', { length: 50 }).array(),
|
licensedIn: jsonb('licensedIn').$type<LicensedIn[]>(),
|
||||||
gender: genderEnum('gender'),
|
gender: genderEnum('gender'),
|
||||||
|
created: timestamp('created'),
|
||||||
|
updated: timestamp('updated'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const businesses = pgTable('businesses', {
|
export const businesses = pgTable('businesses', {
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { fstat, readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
|
||||||
import { fileURLToPath } from 'url';
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import { ImageProperty } from '../models/main.model.js';
|
|
||||||
import sharp from 'sharp';
|
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
|
import path, { join } from 'path';
|
||||||
|
import sharp from 'sharp';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
|
import { ImageProperty, Subscription } from '../models/main.model.js';
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
@@ -26,8 +25,8 @@ export class FileService {
|
|||||||
const rawData = readFileSync(filePath, 'utf8');
|
const rawData = readFileSync(filePath, 'utf8');
|
||||||
this.subscriptions = JSON.parse(rawData);
|
this.subscriptions = JSON.parse(rawData);
|
||||||
}
|
}
|
||||||
getSubscriptions() {
|
getSubscriptions(): Subscription[] {
|
||||||
return this.subscriptions
|
return this.subscriptions;
|
||||||
}
|
}
|
||||||
async storeProfilePicture(file: Express.Multer.File, userId: string) {
|
async storeProfilePicture(file: Express.Multer.File, userId: string) {
|
||||||
let quality = 50;
|
let quality = 50;
|
||||||
@@ -38,8 +37,8 @@ export class FileService {
|
|||||||
.toBuffer();
|
.toBuffer();
|
||||||
await sharp(output).toFile(`./pictures/profile/${userId}.avif`);
|
await sharp(output).toFile(`./pictures/profile/${userId}.avif`);
|
||||||
}
|
}
|
||||||
hasProfile(userId: string){
|
hasProfile(userId: string) {
|
||||||
return fs.existsSync(`./pictures/profile/${userId}.avif`)
|
return fs.existsSync(`./pictures/profile/${userId}.avif`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async storeCompanyLogo(file: Express.Multer.File, userId: string) {
|
async storeCompanyLogo(file: Express.Multer.File, userId: string) {
|
||||||
@@ -52,41 +51,41 @@ export class FileService {
|
|||||||
await sharp(output).toFile(`./pictures/logo/${userId}.avif`); // Ersetze Dateierweiterung
|
await sharp(output).toFile(`./pictures/logo/${userId}.avif`); // Ersetze Dateierweiterung
|
||||||
// await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
|
// await fs.outputFile(`./pictures/logo/${userId}`, file.buffer);
|
||||||
}
|
}
|
||||||
hasCompanyLogo(userId: string){
|
hasCompanyLogo(userId: string) {
|
||||||
return fs.existsSync(`./pictures/logo/${userId}.avif`)?true:false
|
return fs.existsSync(`./pictures/logo/${userId}.avif`) ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPropertyImages(listingId: string): Promise<string[]> {
|
async getPropertyImages(listingId: string): Promise<string[]> {
|
||||||
const result: string[] = []
|
const result: string[] = [];
|
||||||
const directory = `./pictures/property/${listingId}`
|
const directory = `./pictures/property/${listingId}`;
|
||||||
if (fs.existsSync(directory)) {
|
if (fs.existsSync(directory)) {
|
||||||
const files = await fs.readdir(directory);
|
const files = await fs.readdir(directory);
|
||||||
files.forEach(f => {
|
files.forEach(f => {
|
||||||
result.push(f)
|
result.push(f);
|
||||||
})
|
});
|
||||||
return result;
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return []
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async hasPropertyImages(listingId: string): Promise<boolean> {
|
async hasPropertyImages(listingId: string): Promise<boolean> {
|
||||||
const result: ImageProperty[] = []
|
const result: ImageProperty[] = [];
|
||||||
const directory = `./pictures/property/${listingId}`
|
const directory = `./pictures/property/${listingId}`;
|
||||||
if (fs.existsSync(directory)) {
|
if (fs.existsSync(directory)) {
|
||||||
const files = await fs.readdir(directory);
|
const files = await fs.readdir(directory);
|
||||||
return files.length>0
|
return files.length > 0;
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async storePropertyPicture(file: Express.Multer.File, listingId: string) : Promise<string> {
|
async storePropertyPicture(file: Express.Multer.File, listingId: string): Promise<string> {
|
||||||
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg'
|
const suffix = file.mimetype.includes('png') ? 'png' : 'jpg';
|
||||||
const directory = `./pictures/property/${listingId}`
|
const directory = `./pictures/property/${listingId}`;
|
||||||
fs.ensureDirSync(`${directory}`);
|
fs.ensureDirSync(`${directory}`);
|
||||||
const imageName = await this.getNextImageName(directory);
|
const imageName = await this.getNextImageName(directory);
|
||||||
//await fs.outputFile(`${directory}/${imageName}`, file.buffer);
|
//await fs.outputFile(`${directory}/${imageName}`, file.buffer);
|
||||||
await this.resizeImageToAVIF(file.buffer,150 * 1024,imageName,directory);
|
await this.resizeImageToAVIF(file.buffer, 150 * 1024, imageName, directory);
|
||||||
return `${imageName}.avif`
|
return `${imageName}.avif`;
|
||||||
}
|
}
|
||||||
async getNextImageName(directory) {
|
async getNextImageName(directory) {
|
||||||
try {
|
try {
|
||||||
@@ -103,7 +102,7 @@ export class FileService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async resizeImageToAVIF(buffer: Buffer, maxSize: number,imageName:string,directory:string) {
|
async resizeImageToAVIF(buffer: Buffer, maxSize: number, imageName: string, directory: string) {
|
||||||
let quality = 50; // AVIF kann mit niedrigeren Qualitätsstufen gute Ergebnisse erzielen
|
let quality = 50; // AVIF kann mit niedrigeren Qualitätsstufen gute Ergebnisse erzielen
|
||||||
let output;
|
let output;
|
||||||
let start = Date.now();
|
let start = Date.now();
|
||||||
@@ -114,26 +113,40 @@ export class FileService {
|
|||||||
.toBuffer();
|
.toBuffer();
|
||||||
await sharp(output).toFile(`${directory}/${imageName}.avif`); // Ersetze Dateierweiterung
|
await sharp(output).toFile(`${directory}/${imageName}.avif`); // Ersetze Dateierweiterung
|
||||||
let timeTaken = Date.now() - start;
|
let timeTaken = Date.now() - start;
|
||||||
this.logger.info(`Quality: ${quality} - Time: ${timeTaken} milliseconds`)
|
this.logger.info(`Quality: ${quality} - Time: ${timeTaken} milliseconds`);
|
||||||
}
|
}
|
||||||
getProfileImagesForUsers(userids:string){
|
getProfileImagesForUsers(userids: string) {
|
||||||
const ids = userids.split(',');
|
const ids = userids.split(',');
|
||||||
let result = {};
|
let result = {};
|
||||||
for (const id of ids){
|
for (const id of ids) {
|
||||||
result = {...result,[id]:fs.existsSync(`./pictures/profile/${id}.avif`)}
|
result = { ...result, [id]: fs.existsSync(`./pictures/profile/${id}.avif`) };
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
getCompanyLogosForUsers(userids:string){
|
getCompanyLogosForUsers(userids: string) {
|
||||||
const ids = userids.split(',');
|
const ids = userids.split(',');
|
||||||
let result = {};
|
let result = {};
|
||||||
for (const id of ids){
|
for (const id of ids) {
|
||||||
result = {...result,[id]:fs.existsSync(`./pictures/logo/${id}.avif`)}
|
result = { ...result, [id]: fs.existsSync(`./pictures/logo/${id}.avif`) };
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
deleteImage(path:string){
|
deleteImage(path: string) {
|
||||||
fs.unlinkSync(path);
|
fs.unlinkSync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteDirectoryIfExists(imagePath) {
|
||||||
|
const dirPath = `pictures/property/${imagePath}`;
|
||||||
|
try {
|
||||||
|
const exists = fs.pathExistsSync();
|
||||||
|
if (exists) {
|
||||||
|
fs.removeSync(dirPath);
|
||||||
|
console.log(`Directory ${dirPath} was deleted.`);
|
||||||
|
} else {
|
||||||
|
console.log(`Directory ${dirPath} does not exist.`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error while deleting ${dirPath}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { FileService } from '../file/file.service.js';
|
|||||||
import { ListingsService } from '../listings/listings.service.js';
|
import { ListingsService } from '../listings/listings.service.js';
|
||||||
import { SelectOptionsService } from '../select-options/select-options.service.js';
|
import { SelectOptionsService } from '../select-options/select-options.service.js';
|
||||||
|
|
||||||
import { commercials } from 'src/drizzle/schema.js';
|
import { commercials } from '../drizzle/schema.js';
|
||||||
import { CommercialPropertyListing } from 'src/models/db.model.js';
|
import { CommercialPropertyListing } from '../models/db.model.js';
|
||||||
|
|
||||||
@Controller('image')
|
@Controller('image')
|
||||||
export class ImageController {
|
export class ImageController {
|
||||||
@@ -18,11 +18,11 @@ export class ImageController {
|
|||||||
private selectOptions: SelectOptionsService,
|
private selectOptions: SelectOptionsService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('uploadPropertyPicture/:id')
|
@Post('uploadPropertyPicture/:imagePath')
|
||||||
@UseInterceptors(FileInterceptor('file'))
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File, @Param('id') id: string) {
|
async uploadPropertyPicture(@UploadedFile() file: Express.Multer.File, @Param('imagePath') imagePath: string) {
|
||||||
const imagename = await this.fileService.storePropertyPicture(file, id);
|
const imagename = await this.fileService.storePropertyPicture(file, imagePath);
|
||||||
await this.listingService.addImage(id, imagename);
|
await this.listingService.addImage(imagePath, imagename);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('uploadProfile/:id')
|
@Post('uploadProfile/:id')
|
||||||
@@ -59,16 +59,16 @@ export class ImageController {
|
|||||||
return await this.fileService.getCompanyLogosForUsers(userids);
|
return await this.fileService.getCompanyLogosForUsers(userids);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('propertyPicture/:listingid/:imagename')
|
@Delete('propertyPicture/:imagePath/:imagename')
|
||||||
async deletePropertyImagesById(@Param('listingid') listingid: string, @Param('imagename') imagename: string): Promise<any> {
|
async deletePropertyImagesById(@Param('imagePath') imagePath: string, @Param('imagename') imagename: string): Promise<any> {
|
||||||
this.fileService.deleteImage(`pictures/property/${listingid}/${imagename}`);
|
this.fileService.deleteImage(`pictures/property/${imagePath}/${imagename}`);
|
||||||
}
|
}
|
||||||
@Delete('logo/:userid/')
|
@Delete('logo/:userid/')
|
||||||
async deleteLogoImagesById(@Param('id') id: string): Promise<any> {
|
async deleteLogoImagesById(@Param('userid') userid: string): Promise<any> {
|
||||||
this.fileService.deleteImage(`pictures/property//${id}`);
|
this.fileService.deleteImage(`pictures/logo/${userid}.avif`);
|
||||||
}
|
}
|
||||||
@Delete('profile/:userid/')
|
@Delete('profile/:userid/')
|
||||||
async deleteProfileImagesById(@Param('id') id: string): Promise<any> {
|
async deleteProfileImagesById(@Param('userid') userid: string): Promise<any> {
|
||||||
this.fileService.deleteImage(`pictures/property//${id}`);
|
this.fileService.deleteImage(`pictures/profile/${userid}.avif`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,9 +40,10 @@ export class CommercialPropertyListingsController {
|
|||||||
this.logger.info(`Save Listing`);
|
this.logger.info(`Save Listing`);
|
||||||
return await this.listingsService.updateListing(listing.id, listing, commercials);
|
return await this.listingsService.updateListing(listing.id, listing, commercials);
|
||||||
}
|
}
|
||||||
@Delete(':id')
|
@Delete(':id/:imagePath')
|
||||||
deleteById(@Param('id') id: string) {
|
deleteById(@Param('id') id: string, @Param('imagePath') imagePath: string) {
|
||||||
this.listingsService.deleteListing(id, commercials);
|
this.listingsService.deleteListing(id, commercials);
|
||||||
|
this.fileService.deleteDirectoryIfExists(imagePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('imageOrder/:id')
|
@Put('imageOrder/:id')
|
||||||
|
|||||||
@@ -68,7 +68,13 @@ export class ListingsService {
|
|||||||
.where(sql`${table.id} = ${id}`);
|
.where(sql`${table.id} = ${id}`);
|
||||||
return result[0] as BusinessListing | CommercialPropertyListing;
|
return result[0] as BusinessListing | CommercialPropertyListing;
|
||||||
}
|
}
|
||||||
|
async findByImagePath(imagePath: string): Promise<CommercialPropertyListing> {
|
||||||
|
const result = await this.conn
|
||||||
|
.select()
|
||||||
|
.from(commercials)
|
||||||
|
.where(sql`${commercials.imagePath} = ${imagePath}`);
|
||||||
|
return result[0] as CommercialPropertyListing;
|
||||||
|
}
|
||||||
async findByUserId(userId: string, table: typeof businesses | typeof commercials): Promise<BusinessListing[] | CommercialPropertyListing[]> {
|
async findByUserId(userId: string, table: typeof businesses | typeof commercials): Promise<BusinessListing[] | CommercialPropertyListing[]> {
|
||||||
return (await this.conn.select().from(table).where(eq(table.userId, userId))) as BusinessListing[] | CommercialPropertyListing[];
|
return (await this.conn.select().from(table).where(eq(table.userId, userId))) as BusinessListing[] | CommercialPropertyListing[];
|
||||||
}
|
}
|
||||||
@@ -76,8 +82,6 @@ export class ListingsService {
|
|||||||
async createListing(data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
|
async createListing(data: BusinessListing | CommercialPropertyListing, table: typeof businesses | typeof commercials): Promise<BusinessListing | CommercialPropertyListing> {
|
||||||
data.created = new Date();
|
data.created = new Date();
|
||||||
data.updated = new Date();
|
data.updated = new Date();
|
||||||
data.visits = 0;
|
|
||||||
data.lastVisit = null;
|
|
||||||
const [createdListing] = await this.conn.insert(table).values(data).returning();
|
const [createdListing] = await this.conn.insert(table).values(data).returning();
|
||||||
return createdListing as BusinessListing | CommercialPropertyListing;
|
return createdListing as BusinessListing | CommercialPropertyListing;
|
||||||
}
|
}
|
||||||
@@ -116,10 +120,9 @@ export class ListingsService {
|
|||||||
await this.updateListing(listing.id, listing, commercials);
|
await this.updateListing(listing.id, listing, commercials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async addImage(id: string, imagename: string) {
|
async addImage(imagePath: string, imagename: string) {
|
||||||
const listing = (await this.findById(id, commercials)) as unknown as CommercialPropertyListing;
|
const listing = (await this.findByImagePath(imagePath)) as unknown as CommercialPropertyListing;
|
||||||
listing.imageOrder.push(imagename);
|
listing.imageOrder.push(imagename);
|
||||||
listing.imagePath = listing.id;
|
|
||||||
await this.updateListing(listing.id, listing, commercials);
|
await this.updateListing(listing.id, listing, commercials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,23 @@
|
|||||||
import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
|
import { Controller, Get, Inject, Param } from '@nestjs/common';
|
||||||
import { FileService } from '../file/file.service.js';
|
|
||||||
import { convertStringToNullUndefined } from '../utils.js';
|
|
||||||
import { ListingsService } from './listings.service.js';
|
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import { businesses, commercials } from 'src/drizzle/schema.js';
|
import { businesses, commercials } from '../drizzle/schema.js';
|
||||||
|
import { ListingsService } from './listings.service.js';
|
||||||
|
|
||||||
@Controller('listings/undefined')
|
@Controller('listings/undefined')
|
||||||
export class UnknownListingsController {
|
export class UnknownListingsController {
|
||||||
|
constructor(
|
||||||
constructor(private readonly listingsService:ListingsService,@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {
|
private readonly listingsService: ListingsService,
|
||||||
}
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
async findById(@Param('id') id:string): Promise<any> {
|
async findById(@Param('id') id: string): Promise<any> {
|
||||||
const result = await this.listingsService.findById(id,businesses);
|
const result = await this.listingsService.findById(id, businesses);
|
||||||
if (result){
|
if (result) {
|
||||||
return result
|
return result;
|
||||||
} else {
|
} else {
|
||||||
return await this.listingsService.findById(id,commercials);
|
return await this.listingsService.findById(id, commercials);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import { Body, Controller, Post } from '@nestjs/common';
|
import { Body, Controller, Post } from '@nestjs/common';
|
||||||
import { User } from 'src/models/db.model.js';
|
import { ErrorResponse, MailInfo } from 'src/models/main.model.js';
|
||||||
import { MailInfo } from 'src/models/main.model.js';
|
|
||||||
import { MailService } from './mail.service.js';
|
import { MailService } from './mail.service.js';
|
||||||
|
|
||||||
@Controller('mail')
|
@Controller('mail')
|
||||||
export class MailController {
|
export class MailController {
|
||||||
constructor(private mailService: MailService) {}
|
constructor(private mailService: MailService) {}
|
||||||
@Post()
|
@Post()
|
||||||
sendEMail(@Body() mailInfo: MailInfo): Promise<User> {
|
sendEMail(@Body() mailInfo: MailInfo): Promise<void | ErrorResponse> {
|
||||||
return this.mailService.sendInquiry(mailInfo);
|
return this.mailService.sendInquiry(mailInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import { MailerService } from '@nestjs-modules/mailer';
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import path, { join } from 'path';
|
import path, { join } from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { User } from '../models/db.model.js';
|
import { ErrorResponse, MailInfo, isEmpty } from '../models/main.model.js';
|
||||||
import { MailInfo } 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);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
@@ -15,10 +14,13 @@ export class MailService {
|
|||||||
private userService: UserService,
|
private userService: UserService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async sendInquiry(mailInfo: MailInfo): Promise<User> {
|
async sendInquiry(mailInfo: MailInfo): Promise<void | ErrorResponse> {
|
||||||
//const user = await this.authService.getUser(mailInfo.userId) as KeycloakUser;
|
//const user = await this.authService.getUser(mailInfo.userId) as KeycloakUser;
|
||||||
const user = await this.userService.getUserByMail(mailInfo.email);
|
const user = await this.userService.getUserByMail(mailInfo.email);
|
||||||
console.log(JSON.stringify(user));
|
console.log(JSON.stringify(user));
|
||||||
|
if (isEmpty(mailInfo.sender.name)) {
|
||||||
|
return { fields: [{ fieldname: 'name', message: 'Required' }] };
|
||||||
|
}
|
||||||
await this.mailerService.sendMail({
|
await this.mailerService.sendMail({
|
||||||
to: user.email,
|
to: user.email,
|
||||||
from: '"Bizmatch Team" <info@bizmatch.net>', // override default from
|
from: '"Bizmatch Team" <info@bizmatch.net>', // override default from
|
||||||
@@ -34,8 +36,9 @@ export class MailService {
|
|||||||
iname: mailInfo.sender.name,
|
iname: mailInfo.sender.name,
|
||||||
phone: mailInfo.sender.phoneNumber,
|
phone: mailInfo.sender.phoneNumber,
|
||||||
email: mailInfo.sender.email,
|
email: mailInfo.sender.email,
|
||||||
|
id: mailInfo.listing.id,
|
||||||
|
url: mailInfo.url,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,104 @@
|
|||||||
<p>Hey {{ name }},</p>
|
<!DOCTYPE html>
|
||||||
<p>You got an inquiry a</p>
|
<html lang="en">
|
||||||
<p>
|
<head>
|
||||||
{{inquiry}}
|
<meta charset="UTF-8">
|
||||||
</p>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Notification: New Buyer Lead</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.subheader {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
color: #1E90FF;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid #1E90FF;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.info:nth-child(even) {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
.info-label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.info-value {
|
||||||
|
margin-left: 10px;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">Notification: New buyer lead from the Bizmatch Network</div>
|
||||||
|
<div class="subheader">Dear {{name}},</div>
|
||||||
|
<p>You've received a message regarding your "{{title}}" listing.</p>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Buyer Information</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Contact Name:</span>
|
||||||
|
<span class="info-value">{{iname}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Contact Email:</span>
|
||||||
|
<span class="info-value">{{email}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Contact Phone:</span>
|
||||||
|
<span class="info-value">{{phone}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Comments:</span>
|
||||||
|
<span class="info-value">{{inquiry}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="section-title">Listing Information</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Headline:</span>
|
||||||
|
<span class="info-value">{{title}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Listing ID:</span>
|
||||||
|
<span class="info-value"><a href="{{url}}/listing/{{id}}">{{id}}</a></span>
|
||||||
|
</div>
|
||||||
|
{{#if internalListingNumber}}
|
||||||
|
<div class="info">
|
||||||
|
<span class="info-label">Ref ID:</span>
|
||||||
|
<span class="info-value">{{internalListingNumber}}</span>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,4 +1,24 @@
|
|||||||
export interface User {
|
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';
|
||||||
|
created?: Date;
|
||||||
|
updated?: Date;
|
||||||
|
}
|
||||||
|
export interface UserData {
|
||||||
id?: string;
|
id?: string;
|
||||||
firstname: string;
|
firstname: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
@@ -14,8 +34,10 @@ export interface User {
|
|||||||
hasProfile?: boolean;
|
hasProfile?: boolean;
|
||||||
hasCompanyLogo?: boolean;
|
hasCompanyLogo?: boolean;
|
||||||
licensedIn?: string[];
|
licensedIn?: string[];
|
||||||
|
gender?: 'male' | 'female';
|
||||||
|
created?: Date;
|
||||||
|
updated?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BusinessListing {
|
export interface BusinessListing {
|
||||||
id: string;
|
id: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
@@ -43,7 +65,7 @@ export interface BusinessListing {
|
|||||||
updated?: Date;
|
updated?: Date;
|
||||||
visits?: number;
|
visits?: number;
|
||||||
lastVisit?: Date;
|
lastVisit?: Date;
|
||||||
listingsCategory?: string;
|
listingsCategory?: 'commercialProperty' | 'business';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommercialPropertyListing {
|
export interface CommercialPropertyListing {
|
||||||
@@ -69,5 +91,13 @@ export interface CommercialPropertyListing {
|
|||||||
updated?: Date;
|
updated?: Date;
|
||||||
visits?: number;
|
visits?: number;
|
||||||
lastVisit?: Date;
|
lastVisit?: Date;
|
||||||
listingsCategory?: string;
|
listingsCategory?: 'commercialProperty' | 'business';
|
||||||
|
}
|
||||||
|
export interface AreasServed {
|
||||||
|
county: string;
|
||||||
|
state: string;
|
||||||
|
}
|
||||||
|
export interface LicensedIn {
|
||||||
|
registerNo: string;
|
||||||
|
state: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,24 +64,24 @@ export interface ListingCriteria {
|
|||||||
maxPrice: number;
|
maxPrice: number;
|
||||||
realEstateChecked: boolean;
|
realEstateChecked: boolean;
|
||||||
title: string;
|
title: string;
|
||||||
category: 'professional|broker';
|
category: 'professional' | 'broker';
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeycloakUser {
|
export interface KeycloakUser {
|
||||||
id: string;
|
id: string;
|
||||||
createdTimestamp: number;
|
createdTimestamp?: number;
|
||||||
username: string;
|
username?: string;
|
||||||
enabled: boolean;
|
enabled?: boolean;
|
||||||
totp: boolean;
|
totp?: boolean;
|
||||||
emailVerified: boolean;
|
emailVerified?: boolean;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
email: string;
|
email: string;
|
||||||
disableableCredentialTypes: any[];
|
disableableCredentialTypes?: any[];
|
||||||
requiredActions: any[];
|
requiredActions?: any[];
|
||||||
notBefore: number;
|
notBefore?: number;
|
||||||
access: Access;
|
access?: Access;
|
||||||
}
|
}
|
||||||
export interface Access {
|
export interface Access {
|
||||||
manageGroupMembership: boolean;
|
manageGroupMembership: boolean;
|
||||||
@@ -150,6 +150,7 @@ export interface MailInfo {
|
|||||||
sender: Sender;
|
sender: Sender;
|
||||||
userId: string;
|
userId: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
url: string;
|
||||||
listing?: BusinessListing;
|
listing?: BusinessListing;
|
||||||
}
|
}
|
||||||
export interface Sender {
|
export interface Sender {
|
||||||
@@ -164,3 +165,42 @@ export interface ImageProperty {
|
|||||||
code: string;
|
code: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
export interface ErrorResponse {
|
||||||
|
fields?: FieldError[];
|
||||||
|
general?: string[];
|
||||||
|
}
|
||||||
|
export interface FieldError {
|
||||||
|
fieldname: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
export function isEmpty(value: any): boolean {
|
||||||
|
// Check for undefined or null
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for empty string or string with only whitespace
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return value.trim().length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for number and NaN
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return isNaN(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's not a string or number, it's not considered empty by this function
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
export function emailToDirName(email: string): string {
|
||||||
|
// Entferne ungültige Zeichen und ersetze sie durch Unterstriche
|
||||||
|
const sanitizedEmail = email.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||||
|
|
||||||
|
// Entferne führende und nachfolgende Unterstriche
|
||||||
|
const trimmedEmail = sanitizedEmail.replace(/^_+|_+$/g, '');
|
||||||
|
|
||||||
|
// Ersetze mehrfache aufeinanderfolgende Unterstriche durch einen einzelnen Unterstrich
|
||||||
|
const normalizedEmail = trimmedEmail.replace(/_+/g, '_');
|
||||||
|
|
||||||
|
return normalizedEmail;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { Controller, Get } from '@nestjs/common';
|
|
||||||
import { FileService } from '../file/file.service.js';
|
|
||||||
|
|
||||||
@Controller('subscriptions')
|
|
||||||
export class SubscriptionsController {
|
|
||||||
constructor(private readonly fileService: FileService){}
|
|
||||||
@Get()
|
|
||||||
findAll(): any {
|
|
||||||
return this.fileService.getSubscriptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
import { Body, Controller, Get, Inject, Param, Post, Put, Query, Req } from '@nestjs/common';
|
import { Body, Controller, Get, Inject, Param, Post, Query } from '@nestjs/common';
|
||||||
import { UserService } from './user.service.js';
|
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { Logger } from 'winston';
|
|
||||||
import { User } from 'src/models/db.model.js';
|
import { User } from 'src/models/db.model.js';
|
||||||
|
import { Logger } from 'winston';
|
||||||
|
import { FileService } from '../file/file.service.js';
|
||||||
|
import { Subscription } from '../models/main.model.js';
|
||||||
|
import { UserService } from './user.service.js';
|
||||||
|
|
||||||
@Controller('user')
|
@Controller('user')
|
||||||
export class UserController {
|
export class UserController {
|
||||||
|
constructor(
|
||||||
constructor(private userService: UserService, @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger) {}
|
private userService: UserService,
|
||||||
|
private fileService: FileService,
|
||||||
|
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
findByMail(@Query('mail') mail: string): any {
|
findByMail(@Query('mail') mail: string): any {
|
||||||
@@ -38,5 +43,23 @@ export class UserController {
|
|||||||
this.logger.info(`Found users: ${JSON.stringify(foundUsers)}`);
|
this.logger.info(`Found users: ${JSON.stringify(foundUsers)}`);
|
||||||
return foundUsers;
|
return foundUsers;
|
||||||
}
|
}
|
||||||
|
@Get('states/all')
|
||||||
|
async getStates(): Promise<any[]> {
|
||||||
|
this.logger.info(`Getting all states for users`);
|
||||||
|
const result = await this.userService.getStates();
|
||||||
|
this.logger.info(`Found ${result.length} entries`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('subscriptions/:id')
|
||||||
|
async findSubscriptionsById(@Param('id') id: string): Promise<Subscription[]> {
|
||||||
|
const subscriptions = this.fileService.getSubscriptions();
|
||||||
|
const user = await this.userService.getUserById(id);
|
||||||
|
subscriptions.forEach(s => {
|
||||||
|
s.userId = user.id;
|
||||||
|
s.start = user.created;
|
||||||
|
s.modified = user.created;
|
||||||
|
});
|
||||||
|
return subscriptions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import { and, eq, ilike, or, sql } from 'drizzle-orm';
|
import { and, eq, ilike, or, sql } from 'drizzle-orm';
|
||||||
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
|
import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver.js';
|
||||||
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
||||||
import { PG_CONNECTION } from 'src/drizzle/schema.js';
|
|
||||||
import { User } from 'src/models/db.model.js';
|
|
||||||
import { Logger } from 'winston';
|
import { Logger } from 'winston';
|
||||||
import * as schema from '../drizzle/schema.js';
|
import * as schema from '../drizzle/schema.js';
|
||||||
|
import { PG_CONNECTION } from '../drizzle/schema.js';
|
||||||
import { FileService } from '../file/file.service.js';
|
import { FileService } from '../file/file.service.js';
|
||||||
|
import { User } from '../models/db.model.js';
|
||||||
import { ListingCriteria } from '../models/main.model.js';
|
import { ListingCriteria } from '../models/main.model.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -19,7 +19,8 @@ export class UserService {
|
|||||||
private getConditions(criteria: ListingCriteria): any[] {
|
private getConditions(criteria: ListingCriteria): any[] {
|
||||||
const conditions = [];
|
const conditions = [];
|
||||||
if (criteria.state) {
|
if (criteria.state) {
|
||||||
conditions.push(sql`EXISTS (SELECT 1 FROM unnest(users."areasServed") AS area WHERE area LIKE '%' || ${criteria.state} || '%')`);
|
//conditions.push(sql`EXISTS (SELECT 1 FROM unnest(users."areasServed") AS area WHERE area LIKE '%' || ${criteria.state} || '%')`);
|
||||||
|
conditions.push(sql`${schema.users.areasServed} @> ${JSON.stringify([{ state: criteria.state }])}`);
|
||||||
}
|
}
|
||||||
if (criteria.name) {
|
if (criteria.name) {
|
||||||
conditions.push(or(ilike(schema.users.firstname, `%${criteria.name}%`), ilike(schema.users.lastname, `%${criteria.name}%`)));
|
conditions.push(or(ilike(schema.users.firstname, `%${criteria.name}%`), ilike(schema.users.lastname, `%${criteria.name}%`)));
|
||||||
@@ -48,9 +49,13 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
async saveUser(user: any): Promise<User> {
|
async saveUser(user: any): Promise<User> {
|
||||||
if (user.id) {
|
if (user.id) {
|
||||||
|
user.created = new Date(user.created);
|
||||||
|
user.updated = new Date();
|
||||||
const [updateUser] = await this.conn.update(schema.users).set(user).where(eq(schema.users.id, user.id)).returning();
|
const [updateUser] = await this.conn.update(schema.users).set(user).where(eq(schema.users.id, user.id)).returning();
|
||||||
return updateUser as User;
|
return updateUser as User;
|
||||||
} else {
|
} else {
|
||||||
|
user.created = new Date();
|
||||||
|
user.updated = new Date();
|
||||||
const [newUser] = await this.conn.insert(schema.users).values(user).returning();
|
const [newUser] = await this.conn.insert(schema.users).values(user).returning();
|
||||||
return newUser as User;
|
return newUser as User;
|
||||||
}
|
}
|
||||||
@@ -74,4 +79,9 @@ export class UserService {
|
|||||||
]);
|
]);
|
||||||
return { total, data };
|
return { total, data };
|
||||||
}
|
}
|
||||||
|
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 result = await this.conn.execute(query);
|
||||||
|
return result.rows;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve & http-server ../bizmatch-server",
|
"start": "ng serve & http-server ../bizmatch-server",
|
||||||
"build": "ng build",
|
"prebuild": "node version.js",
|
||||||
"build.dev": "ng build --configuration dev",
|
"build": "node version.js && ng build",
|
||||||
|
"build.dev": "node version.js && ng build --configuration dev --output-hashing=all",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"serve:ssr:bizmatch": "node dist/bizmatch/server/server.mjs"
|
"serve:ssr:bizmatch": "node dist/bizmatch/server/server.mjs"
|
||||||
@@ -33,14 +34,16 @@
|
|||||||
"angular-mixed-cdk-drag-drop": "^2.2.3",
|
"angular-mixed-cdk-drag-drop": "^2.2.3",
|
||||||
"browser-bunyan": "^1.8.0",
|
"browser-bunyan": "^1.8.0",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
|
"dayjs": "^1.11.11",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"keycloak-js": "^23.0.7",
|
"keycloak-angular": "^15.2.1",
|
||||||
|
"keycloak-js": "^24.0.4",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"on-change": "^5.0.1",
|
"on-change": "^5.0.1",
|
||||||
"primeflex": "^3.3.1",
|
"primeflex": "^3.3.1",
|
||||||
"primeicons": "^6.0.1",
|
"primeicons": "^6.0.1",
|
||||||
"primeng": "^17.10.0",
|
"primeng": "^17.16.1",
|
||||||
"quill": "^1.3.7",
|
"quill": "^1.3.7",
|
||||||
"rxjs": "~7.8.1",
|
"rxjs": "~7.8.1",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
|||||||
@@ -6,18 +6,26 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
<footer></footer>
|
<footer></footer>
|
||||||
|
<p-confirmDialog #cd>
|
||||||
|
<ng-template pTemplate="headless" let-message>
|
||||||
|
<div class="flex flex-column align-items-center p-5 surface-overlay border-round">
|
||||||
|
<span class="font-bold text-2xl block mb-2 mt-4">
|
||||||
|
{{ message.header }}
|
||||||
|
</span>
|
||||||
|
<p class="mb-0">{{ message.message }}</p>
|
||||||
|
<div class="flex align-items-center gap-2 mt-4">
|
||||||
|
<button pButton label="OK" (click)="cd.accept()" size="small"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</p-confirmDialog>
|
||||||
</div>
|
</div>
|
||||||
<!-- @if (loadingService.isLoading$ | async) { -->
|
|
||||||
<!-- <div class="progress-spinner flex h-full align-items-center justify-content-center">
|
|
||||||
<div class="spinner-text">Please wait - we're processing your image...</div>
|
|
||||||
<p-progressSpinner></p-progressSpinner>
|
|
||||||
</div> -->
|
|
||||||
<!-- } -->
|
|
||||||
@if (loadingService.isLoading$ | async) {
|
@if (loadingService.isLoading$ | async) {
|
||||||
<div class="spinner-overlay">
|
<div class="spinner-overlay">
|
||||||
<div class="spinner-container">
|
<div class="spinner-container">
|
||||||
<p-progressSpinner></p-progressSpinner>
|
<p-progressSpinner></p-progressSpinner>
|
||||||
<div class="spinner-text" *ngIf="loadingService.loadingText$ | async as loadingText">{{loadingText}}</div>
|
<div class="spinner-text" *ngIf="loadingService.loadingText$ | async as loadingText">{{ loadingText }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -1,46 +1,67 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, ViewChild } from '@angular/core';
|
import { Component, HostListener } from '@angular/core';
|
||||||
import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from '@angular/router';
|
import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from '@angular/router';
|
||||||
import { HeaderComponent } from './components/header/header.component';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { ProgressSpinnerModule } from 'primeng/progressspinner';
|
|
||||||
import { ToastModule } from 'primeng/toast';
|
|
||||||
import { LoadingService } from './services/loading.service';
|
|
||||||
import { HomeComponent } from './pages/home/home.component';
|
|
||||||
import { filter } from 'rxjs/operators';
|
|
||||||
import { FooterComponent } from './components/footer/footer.component';
|
|
||||||
import { KeycloakService } from './services/keycloak.service';
|
|
||||||
import { KeycloakEventType } from './models/keycloak-event';
|
|
||||||
import { createGenericObject } from './utils/utils';
|
|
||||||
import onChange from 'on-change';
|
import onChange from 'on-change';
|
||||||
|
import { ConfirmationService } from 'primeng/api';
|
||||||
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
|
import { ProgressSpinnerModule } from 'primeng/progressspinner';
|
||||||
|
import { filter } from 'rxjs/operators';
|
||||||
|
import { ListingCriteria } from '../../../bizmatch-server/src/models/main.model';
|
||||||
|
import build from '../build';
|
||||||
|
import { FooterComponent } from './components/footer/footer.component';
|
||||||
|
import { HeaderComponent } from './components/header/header.component';
|
||||||
|
import { LoadingService } from './services/loading.service';
|
||||||
import { UserService } from './services/user.service';
|
import { UserService } from './services/user.service';
|
||||||
import {ListingCriteria} from '../../../bizmatch-server/src/models/main.model'
|
import { createDefaultListingCriteria } from './utils/utils';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, RouterOutlet, HeaderComponent, ProgressSpinnerModule, FooterComponent],
|
imports: [CommonModule, RouterOutlet, HeaderComponent, ProgressSpinnerModule, FooterComponent, ConfirmDialogModule],
|
||||||
|
providers: [ConfirmationService],
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss'
|
styleUrl: './app.component.scss',
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
|
build = build;
|
||||||
title = 'bizmatch';
|
title = 'bizmatch';
|
||||||
actualRoute ='';
|
actualRoute = '';
|
||||||
listingCriteria:ListingCriteria = onChange(createGenericObject<ListingCriteria>(),(path, value, previousValue, applyData)=>{
|
listingCriteria: ListingCriteria = onChange(createDefaultListingCriteria(), (path, value, previousValue, applyData) => {
|
||||||
sessionStorage.setItem('criteria',JSON.stringify(value));
|
sessionStorage.setItem('criteria', JSON.stringify(value));
|
||||||
});
|
});
|
||||||
public constructor(public loadingService: LoadingService, private router: Router,private activatedRoute: ActivatedRoute, private keycloakService:KeycloakService,private userService:UserService) {
|
public constructor(
|
||||||
this.router.events.pipe(
|
public loadingService: LoadingService,
|
||||||
filter(event => event instanceof NavigationEnd)
|
private router: Router,
|
||||||
).subscribe(() => {
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private keycloakService: KeycloakService,
|
||||||
|
private userService: UserService,
|
||||||
|
private confirmationService: ConfirmationService,
|
||||||
|
) {
|
||||||
|
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
|
||||||
let currentRoute = this.activatedRoute.root;
|
let currentRoute = this.activatedRoute.root;
|
||||||
while (currentRoute.children[0] !== undefined) {
|
while (currentRoute.children[0] !== undefined) {
|
||||||
currentRoute = currentRoute.children[0];
|
currentRoute = currentRoute.children[0];
|
||||||
}
|
}
|
||||||
// Hier haben Sie Zugriff auf den aktuellen Route-Pfad
|
// Hier haben Sie Zugriff auf den aktuellen Route-Pfad
|
||||||
this.actualRoute=currentRoute.snapshot.url[0].path
|
this.actualRoute = currentRoute.snapshot.url[0].path;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ngOnInit(){
|
ngOnInit() {}
|
||||||
|
@HostListener('window:keydown', ['$event'])
|
||||||
|
handleKeyboardEvent(event: KeyboardEvent) {
|
||||||
|
if (event.shiftKey && event.ctrlKey && event.key === 'V') {
|
||||||
|
this.showVersionDialog();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showVersionDialog() {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
target: event.target as EventTarget,
|
||||||
|
message: `App Version: ${this.build.timestamp}`,
|
||||||
|
header: 'Version Info',
|
||||||
|
icon: 'pi pi-info-circle',
|
||||||
|
accept: () => {},
|
||||||
|
reject: () => {},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,30 @@
|
|||||||
import { APP_INITIALIZER, ApplicationConfig, LOCALE_ID, importProvidersFrom } from '@angular/core';
|
import { APP_INITIALIZER, ApplicationConfig } from '@angular/core';
|
||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter, withEnabledBlockingInitialNavigation, withInMemoryScrolling } from '@angular/router';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||||
import { provideClientHydration } from '@angular/platform-browser';
|
|
||||||
import { provideAnimations } from '@angular/platform-browser/animations';
|
import { provideAnimations } from '@angular/platform-browser/animations';
|
||||||
import { HTTP_INTERCEPTORS, provideHttpClient, withFetch, withInterceptors, withInterceptorsFromDi } from '@angular/common/http';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { SelectOptionsService } from './services/select-options.service';
|
import { customKeycloakAdapter } from '../keycloak';
|
||||||
import { KeycloakService } from './services/keycloak.service';
|
import { routes } from './app.routes';
|
||||||
import { UserService } from './services/user.service';
|
|
||||||
import { LoadingInterceptor } from './interceptors/loading.interceptor';
|
import { LoadingInterceptor } from './interceptors/loading.interceptor';
|
||||||
|
import { KeycloakInitializerService } from './services/keycloak-initializer.service';
|
||||||
|
import { SelectOptionsService } from './services/select-options.service';
|
||||||
|
import { createLogger } from './utils/utils';
|
||||||
// provideClientHydration()
|
// provideClientHydration()
|
||||||
|
const logger = createLogger('ApplicationConfig');
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideHttpClient(withInterceptorsFromDi()),
|
provideHttpClient(withInterceptorsFromDi()),
|
||||||
{provide:KeycloakService},
|
{ provide: KeycloakService },
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
useFactory: initializeKeycloak,
|
// useFactory: initializeKeycloak,
|
||||||
|
//useFactory: initializeKeycloak,
|
||||||
|
useFactory: initializeKeycloak3,
|
||||||
multi: true,
|
multi: true,
|
||||||
deps: [KeycloakService],
|
//deps: [KeycloakService],
|
||||||
|
deps: [KeycloakInitializerService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: APP_INITIALIZER,
|
provide: APP_INITIALIZER,
|
||||||
@@ -28,28 +33,53 @@ export const appConfig: ApplicationConfig = {
|
|||||||
deps: [SelectOptionsService],
|
deps: [SelectOptionsService],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide:HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
useClass:LoadingInterceptor,
|
useClass: LoadingInterceptor,
|
||||||
multi:true
|
multi: true,
|
||||||
},
|
},
|
||||||
provideRouter(routes),provideAnimations(),
|
provideRouter(
|
||||||
// {provide: LOCALE_ID, useValue: 'en-US' }
|
routes,
|
||||||
]
|
withEnabledBlockingInitialNavigation(),
|
||||||
|
withInMemoryScrolling({
|
||||||
|
scrollPositionRestoration: 'enabled',
|
||||||
|
anchorScrolling: 'enabled',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
provideAnimations(),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
function initUserService(userService:UserService) {
|
function initServices(selectOptions: SelectOptionsService) {
|
||||||
return () => {
|
return async () => {
|
||||||
//selectOptions.init();
|
await selectOptions.init();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
function initServices(selectOptions:SelectOptionsService) {
|
export function initializeKeycloak3(keycloak: KeycloakInitializerService) {
|
||||||
return () => {
|
return () => keycloak.initialize();
|
||||||
selectOptions.init();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function initializeKeycloak2(keycloak: KeycloakService): () => Promise<void> {
|
||||||
|
return async () => {
|
||||||
|
const { url, realm, clientId } = environment.keycloak;
|
||||||
|
const adapter = customKeycloakAdapter(() => keycloak.getKeycloakInstance(), {});
|
||||||
|
if (window.location.search.length > 0) {
|
||||||
|
sessionStorage.setItem('SEARCH', window.location.search);
|
||||||
|
}
|
||||||
|
const { host, hostname, href, origin, pathname, port, protocol, search } = window.location;
|
||||||
|
await keycloak.init({
|
||||||
|
config: { url, realm, clientId },
|
||||||
|
initOptions: {
|
||||||
|
onLoad: 'check-sso',
|
||||||
|
silentCheckSsoRedirectUri: window.location.hostname === 'localhost' ? `${window.location.origin}/assets/silent-check-sso.html` : `${window.location.origin}/dealerweb/assets/silent-check-sso.html`,
|
||||||
|
adapter,
|
||||||
|
redirectUri: `${origin}${pathname}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
function initializeKeycloak(keycloak: KeycloakService) {
|
function initializeKeycloak(keycloak: KeycloakService) {
|
||||||
return () =>
|
return async () => {
|
||||||
keycloak.init({
|
logger.info(`###>calling keycloakService init ...`);
|
||||||
|
const authenticated = await keycloak.init({
|
||||||
config: {
|
config: {
|
||||||
url: environment.keycloak.url,
|
url: environment.keycloak.url,
|
||||||
realm: environment.keycloak.realm,
|
realm: environment.keycloak.realm,
|
||||||
@@ -57,7 +87,9 @@ function initializeKeycloak(keycloak: KeycloakService) {
|
|||||||
},
|
},
|
||||||
initOptions: {
|
initOptions: {
|
||||||
onLoad: 'check-sso',
|
onLoad: 'check-sso',
|
||||||
silentCheckSsoRedirectUri: (<any>window).location.origin + '/assets/silent-check-sso.html'
|
silentCheckSsoRedirectUri: (<any>window).location.origin + '/assets/silent-check-sso.html',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
logger.info(`+++>${authenticated}`);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
import { LogoutComponent } from './components/logout/logout.component';
|
import { LogoutComponent } from './components/logout/logout.component';
|
||||||
import { authGuard } from './guards/auth.guard';
|
import { NotFoundComponent } from './components/not-found/not-found.component';
|
||||||
|
|
||||||
|
import { AuthGuard } from './guards/auth.guard';
|
||||||
|
import { ListingCategoryGuard } from './guards/listing-category.guard';
|
||||||
import { DetailsBusinessListingComponent } from './pages/details/details-business-listing/details-business-listing.component';
|
import { DetailsBusinessListingComponent } from './pages/details/details-business-listing/details-business-listing.component';
|
||||||
import { DetailsCommercialPropertyListingComponent } from './pages/details/details-commercial-property-listing/details-commercial-property-listing.component';
|
import { DetailsCommercialPropertyListingComponent } from './pages/details/details-commercial-property-listing/details-commercial-property-listing.component';
|
||||||
import { DetailsUserComponent } from './pages/details/details-user/details-user.component';
|
import { DetailsUserComponent } from './pages/details/details-user/details-user.component';
|
||||||
@@ -46,6 +49,11 @@ export const routes: Routes = [
|
|||||||
path: 'details-commercial-property-listing/:id',
|
path: 'details-commercial-property-listing/:id',
|
||||||
component: DetailsCommercialPropertyListingComponent,
|
component: DetailsCommercialPropertyListingComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'listing/:id',
|
||||||
|
canActivate: [ListingCategoryGuard],
|
||||||
|
component: NotFoundComponent, // Dummy-Komponente, wird nie angezeigt, da der Guard weiterleitet
|
||||||
|
},
|
||||||
// #########
|
// #########
|
||||||
// User Details
|
// User Details
|
||||||
{
|
{
|
||||||
@@ -57,57 +65,62 @@ export const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'account',
|
path: 'account',
|
||||||
component: AccountComponent,
|
component: AccountComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [AuthGuard],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'account/:id',
|
||||||
|
component: AccountComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
// #########
|
// #########
|
||||||
// Create, Update Listings
|
// Create, Update Listings
|
||||||
{
|
{
|
||||||
path: 'editBusinessListing/:id',
|
path: 'editBusinessListing/:id',
|
||||||
component: EditBusinessListingComponent,
|
component: EditBusinessListingComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'createBusinessListing',
|
path: 'createBusinessListing',
|
||||||
component: EditBusinessListingComponent,
|
component: EditBusinessListingComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'editCommercialPropertyListing/:id',
|
path: 'editCommercialPropertyListing/:id',
|
||||||
component: EditCommercialPropertyListingComponent,
|
component: EditCommercialPropertyListingComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'createCommercialPropertyListing',
|
path: 'createCommercialPropertyListing',
|
||||||
component: EditCommercialPropertyListingComponent,
|
component: EditCommercialPropertyListingComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
// #########
|
// #########
|
||||||
// My Listings
|
// My Listings
|
||||||
{
|
{
|
||||||
path: 'myListings',
|
path: 'myListings',
|
||||||
component: MyListingComponent,
|
component: MyListingComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
// #########
|
// #########
|
||||||
// My Favorites
|
// My Favorites
|
||||||
{
|
{
|
||||||
path: 'myFavorites',
|
path: 'myFavorites',
|
||||||
component: FavoritesComponent,
|
component: FavoritesComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
// #########
|
// #########
|
||||||
// EMAil Us
|
// EMAil Us
|
||||||
{
|
{
|
||||||
path: 'emailUs',
|
path: 'emailUs',
|
||||||
component: EmailUsComponent,
|
component: EmailUsComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
// #########
|
// #########
|
||||||
// Logout
|
// Logout
|
||||||
{
|
{
|
||||||
path: 'logout',
|
path: 'logout',
|
||||||
component: LogoutComponent,
|
component: LogoutComponent,
|
||||||
canActivate: [authGuard],
|
canActivate: [AuthGuard],
|
||||||
},
|
},
|
||||||
// #########
|
// #########
|
||||||
// Pricing
|
// Pricing
|
||||||
|
|||||||
@@ -1,26 +1,501 @@
|
|||||||
<div class="surface-0 px-4 py-4 md:px-6 lg:px-8">
|
<div class="surface-0 px-4 py-4 md:px-6 lg:px-8">
|
||||||
<div class="surface-0">
|
<div class="surface-0">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="col-12 md:col-3 md:mb-0 mb-3">
|
<div class="col-12 md:col-3 md:mb-0 mb-3 cursor-pointer" routerLink="/home">
|
||||||
<img src="assets/images/header-logo.png" alt="footer sections" height="30" class="mr-3" />
|
<img src="assets/images/header-logo.png" alt="footer sections" height="30" class="mr-3" />
|
||||||
<div class="text-500">© 2024 Bizmatch All rights reserved.</div>
|
<div class="text-500">© 2024 Bizmatch All rights reserved.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-3">
|
<div class="col-12 md:col-3">
|
||||||
<div class="text-black mb-4 flex flex-wrap" style="max-width: 290px">BizMatch, Inc., 1001 Blucher Street, Corpus Christi, Texas 78401</div>
|
<div class="text-black mb-4 flex flex-wrap" style="max-width: 290px">BizMatch, Inc., 1001 Blucher Street, Corpus Christi, Texas 78401</div>
|
||||||
<div class="text-black mb-3"><i class="text-white pi pi-phone surface-800 border-round p-1 mr-2"></i>1-800-840-6025</div>
|
<div class="text-black mb-3"><i class="text-white pi pi-phone surface-800 border-round p-1 mr-2"></i>1-800-840-6025</div>
|
||||||
<div class="text-black mb-3"><i class="text-white pi pi-inbox surface-800 border-round p-1 mr-2"></i>bizmatch@biz-match.com</div>
|
<div class="text-black mb-3"><i class="text-white pi pi-inbox surface-800 border-round p-1 mr-2"></i>info@bizmatch.net</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-3 text-500">
|
<div class="col-12 md:col-3 text-500">
|
||||||
<div class="text-black font-bold line-height-3 mb-3">Legal</div>
|
<div class="text-black font-bold line-height-3 mb-3">Legal</div>
|
||||||
<a class="line-height-3 block cursor-pointer mb-2">Terms of use</a>
|
<a class="line-height-3 block cursor-pointer mb-2" (click)="termsVisible = true">Terms of use</a>
|
||||||
<a class="line-height-3 block cursor-pointer mb-2">Privacy statement</a>
|
<a class="line-height-3 block cursor-pointer mb-2" (click)="privacyVisible = true">Privacy statement</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-3 text-500">
|
<div class="col-12 md:col-3 text-500">
|
||||||
<div class="text-black font-bold line-height-3 mb-3">Actions</div>
|
<div class="text-black font-bold line-height-3 mb-3">Actions</div>
|
||||||
<a *ngIf="!userService.isLoggedIn()" (click)="login()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Login</a>
|
<a *ngIf="!keycloakService.isLoggedIn()" (click)="login()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Login</a>
|
||||||
<a *ngIf="userService.isLoggedIn()" [routerLink]="['/account']" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Account</a>
|
<a *ngIf="keycloakService.isLoggedIn()" [routerLink]="['/account']" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline">Account</a>
|
||||||
<a *ngIf="userService.isLoggedIn()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline" (click)="userService.logout()">Log Out</a>
|
<a *ngIf="keycloakService.isLoggedIn()" class="text-500 line-height-3 block cursor-pointer mb-2 no-underline" (click)="keycloakService.logout()">Log Out</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p-sidebar [(visible)]="privacyVisible" styleClass="w-30rem" position="right">
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<span class="font-semibold text-xl">Privacy Statement</span>
|
||||||
|
</ng-template>
|
||||||
|
<section id="content" role="main">
|
||||||
|
<article id="post-2" class="post-2 page type-page status-publish hentry pmpro-has-access">
|
||||||
|
<section class="entry-content">
|
||||||
|
<div class="container" style="padding: 3.5% 0 3.75% 0 !important">
|
||||||
|
<p>
|
||||||
|
<strong>Privacy Policy</strong><br />
|
||||||
|
We are committed to protecting your privacy. We have established this statement as a testament to our commitment to your privacy.
|
||||||
|
</p>
|
||||||
|
<p>This Privacy Policy relates to the use of any personal information you provide to us through this websites.</p>
|
||||||
|
<p>
|
||||||
|
By accepting the Privacy Policy during registration or the sending of an enquiry, you expressly consent to our collection, storage, use and disclosure of your personal information as described in this Privacy
|
||||||
|
Policy.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We may update our Privacy Policy from time to time. Our Privacy Policy was last updated in Febuary 2018 and is effective upon acceptance for new users. By continuing to use our websites or otherwise
|
||||||
|
continuing to deal with us, you accept this Privacy Policy.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Collection of personal information</strong><br />
|
||||||
|
Anyone can browse our websites without revealing any personally identifiable information.
|
||||||
|
</p>
|
||||||
|
<p>However, should you wish to contact a business for sale, a franchise opportunity or an intermediary, we will require you to provide some personal information.</p>
|
||||||
|
<p>Should you wish to advertise your services, your business (es) or your franchise opportunity, we will require you to provide some personal information.</p>
|
||||||
|
<p>By providing personal information, you are consenting to the transfer and storage of that information on our servers located in the United States.</p>
|
||||||
|
<p>We may collect and store the following personal information:</p>
|
||||||
|
<p>
|
||||||
|
Your name, email address, physical address, telephone numbers, and (depending on the service used), your business information, financial information, such as credit / payment card details;<br />
|
||||||
|
transactional information based on your activities on the site; information that you disclose in a forum on any of our websites, feedback, correspondence through our websites, and correspondence sent to
|
||||||
|
us;<br />
|
||||||
|
other information from your interaction with our websites, services, content and advertising, including computer and connection information, statistics on page views, traffic to and from the sites, ad data,
|
||||||
|
IP address and standard web log information;<br />
|
||||||
|
supplemental information from third parties (for example, if you incur a debt, we will generally conduct a credit check by obtaining additional information about you from a credit bureau, as permitted by law;
|
||||||
|
or if the information you provide cannot be verified,<br />
|
||||||
|
we may ask you to send us additional information, or to answer additional questions online to help verify your information).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>How we use your information</strong><br />
|
||||||
|
The primary reason we collect your personal information is to improve the services we deliver to you through our website. By registering or sending an enquiry through our website, you agree that we may use
|
||||||
|
your personal information to:<br />
|
||||||
|
provide the services and customer support you request;<br />
|
||||||
|
connect you with relevant parties:<br />
|
||||||
|
If you are a buyer we will pass some or all of your details on to the seller / intermediary along with any message you have typed. This allows the seller to contact you in order to pursue a possible sale of a
|
||||||
|
business;<br />
|
||||||
|
If you are a seller / intermediary, we will disclose your details where you have given us permission to do so;<br />
|
||||||
|
resolve disputes, collect fees, and troubleshoot problems;<br />
|
||||||
|
prevent potentially prohibited or illegal activities, and enforce our Terms and Conditions;<br />
|
||||||
|
customize, measure and improve our services, conduct internal market research, provide content and advertising;<br />
|
||||||
|
tell you about other Biz-Match products and services, target marketing, send you service updates, and promotional offers based on your communication preferences.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Our disclosure of your information</strong><br />
|
||||||
|
We may disclose personal information to respond to legal requirements, enforce our policies, respond to claims that a listing or other content infringes the rights of others, or protect anyone’s rights,
|
||||||
|
property, or safety.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We may also share your personal information with<br />
|
||||||
|
When you select to register an account as a business buyer, you provide your personal details and we will pass this on to a seller of a business or franchise when you request more information.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
When you select to register an account as a business broker or seller on the site, we provide a public platform on which to establish your business profile. This profile consists of pertinent facts about your
|
||||||
|
business along with your personal information; namely, the contact information you provide to facilitate contact between you and other users’ of the site. Direct email addresses and telephone numbers will not
|
||||||
|
be publicly displayed unless you specifically include it on your profile.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The information a user includes within the forums provided on the site is publicly available to other users’ of the site. Please be aware that any personal information you elect to provide in a public forum
|
||||||
|
may be used to send you unsolicited messages; we are not responsible for the personal information a user elects to disclose within their public profile, or in the private communications that users’ engage in
|
||||||
|
on the site.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
We post testimonials on the site obtained from users’. These testimonials may include the name, city, state or region and business of the user. We obtain permission from our users’ prior to posting their
|
||||||
|
testimonials on the site. We are not responsible for any personal information a user selects to include within their testimonial.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
When you elect to email a friend about the site, or a particular business, we request the third party’s email address to send this one time email. We do not share this information with any third parties for
|
||||||
|
their promotional purposes and only store the information to gauge the effectiveness of our referral program.
|
||||||
|
</p>
|
||||||
|
<p>We may share your personal information with our service providers where necessary. We employ the services of a payment processor to fulfil payment for services purchased on the site.</p>
|
||||||
|
<p>
|
||||||
|
We works with a number of partners or affiliates, where we provide marketing services for these companies. These third party agents collect your personal information to facilitate your service request and the
|
||||||
|
information submitted here is governed by their privacy policy.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Masking Policy</strong><br />
|
||||||
|
In some cases, where the third party agent collects your information, the affiliate portal may appear within a BizMatch.net frame. It is presented as a BizMatch.net page for a streamlined user interface
|
||||||
|
however the data collected on such pages is governed by the third party agent’s privacy policy.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Legal Disclosure</strong><br />
|
||||||
|
In certain circumstances, we may be legally required to disclose information collected on the site to law enforcement, government agencies or other third parties. We reserve the right to disclose information
|
||||||
|
to our service providers and to law enforcement or government agencies where a formal request such as in response to a court order, subpoena or judicial proceeding is made. Where we believe in good faith that
|
||||||
|
disclosure of information is necessary to prevent imminent physical or financial harm, or loss, or in protecting against illegal activity on the site, we reserve to disclose information.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Should the company undergo the merger, acquisition or sale of some or all of its assets, your personal information may likely be a part of the transferred assets. In such an event, your personal information
|
||||||
|
on the site, would be governed by this privacy statement; any changes to the privacy practices governing your information as a result of transfer would be relayed to you by means of a prominent notice on the
|
||||||
|
Site, or by email.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Using information from BizMatch.net website</strong><br />
|
||||||
|
In certain cases, (where you are receiving contact details of buyers interested in your business opportunity or a business opportunity you represent), you must comply with data protection laws, and give other
|
||||||
|
users a chance to remove themselves from your database and a chance to review what information you have collected about them.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>You agree to use BizMatch.net user information only for:</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
BizMatch.net transaction-related purposes that are not unsolicited commercial messages;<br />
|
||||||
|
using services offered through BizMatch.net, or<br />
|
||||||
|
other purposes that a user expressly chooses.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Marketing</strong><br />
|
||||||
|
We do not sell or rent your personal information to third parties for their marketing purposes without your explicit consent. Where you explicitly express your consent at the point of collection to receive
|
||||||
|
offers from third party partners or affiliates, we will communicate to you on their behalf. We will not pass your information on.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You will receive email marketing communications from us throughout the duration of your relationship with our websites. If you do not wish to receive marketing communications from us you may unsubscribe and /
|
||||||
|
or change your preferences at any time by following instructions included within a communication or emailing Customer Services.
|
||||||
|
</p>
|
||||||
|
<p>If you have an account with one of our websites you can also log in and click the email preferences link to unsubscribe and / or change your preferences.</p>
|
||||||
|
<p>
|
||||||
|
Please note that we reserve the right to send all website users notifications and administrative emails where necessary which are considered a part of the service. Given that these messages aren’t promotional
|
||||||
|
in nature, you will be unable to opt-out of them.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Cookies</strong><br />
|
||||||
|
A cookie is a small text file written to your hard drive that contains information about you. Cookies do not contain any personal information about users. Once you close your browser or log out of the
|
||||||
|
website, the cookie simply terminates. We use cookies so that we can personalise your experience of our websites.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you set up your browser to reject the cookie, you may still use the website however; doing so may interfere with your use of some aspects of our websites. Some of our business partners use cookies on our
|
||||||
|
site (for example, advertisers). We have no access to or control over these cookies.
|
||||||
|
</p>
|
||||||
|
<p>For more information about how BizMatch.net uses cookies please read our Cookie Policy.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Spam, spyware or spoofing</strong><br />
|
||||||
|
We and our users do not tolerate spam. Make sure to set your email preferences so we can communicate with you, as you prefer. Please add us to your safe senders list. To report spam or spoof emails, please
|
||||||
|
contact us using the contact information provided in the Contact Us section of this privacy statement.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You may not use our communication tools to send spam or otherwise send content that would breach our Terms and Conditions. We automatically scan and may manually filter messages to check for spam, viruses,
|
||||||
|
phishing attacks and other malicious activity or illegal or prohibited content. We may also store these messages for back up purposes only.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you send an email to an email address that is not registered in our community, we do not permanently store that email or use that email address for any marketing purpose. We do not rent or sell these email
|
||||||
|
addresses.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Account protection</strong><br />
|
||||||
|
Your password is the key to your account. Make sure this is stored safely. Use unique numbers, letters and special characters, and do not disclose your password to anyone. If you do share your password or
|
||||||
|
your personal information with others, remember that you are responsible for all actions taken in the name of your account. If you lose control of your password, you may lose substantial control over your
|
||||||
|
personal information and may be subject to legally binding actions taken on your behalf. Therefore, if your password has been compromised for any reason, you should immediately notify us and change your
|
||||||
|
password.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Accessing, reviewing and changing your personal information</strong><br />
|
||||||
|
You can view and amend your personal information at any time by logging in to your account online. You must promptly update your personal information if it changes or is inaccurate.
|
||||||
|
</p>
|
||||||
|
<p>If at any time you wish to close your account, please contact Customer Services and instruct us to do so. We will process your request as soon as we can.</p>
|
||||||
|
<p>You may also contact us at any time to find out what information we hold about you, what we do with it and ask us to update it for you.</p>
|
||||||
|
<p>
|
||||||
|
We do retain personal information from closed accounts to comply with law, prevent fraud, collect any fees owed, resolve disputes, troubleshoot problems, assist with any investigations, enforce our Terms and
|
||||||
|
Conditions, and take other actions otherwise permitted by law.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Security</strong><br />
|
||||||
|
Your information is stored on our servers located in the USA. We treat data as an asset that must be protected and use a variety of tools (encryption, passwords, physical security, etc.) to protect your
|
||||||
|
personal information against unauthorized access and disclosure. However, no method of security is 100% effective and while we take every measure to protect your personal information, we make no guarantees of
|
||||||
|
its absolute security.
|
||||||
|
</p>
|
||||||
|
<p>We employ the use of SSL encryption during the transmission of sensitive data across our websites.</p>
|
||||||
|
<p>
|
||||||
|
<strong>Third parties</strong><br />
|
||||||
|
Except as otherwise expressly included in this Privacy Policy, this document addresses only the use and disclosure of information we collect from you. If you disclose your information to others, whether they
|
||||||
|
are buyers or sellers on our websites or other sites throughout the internet, different rules may apply to their use or disclosure of the information you disclose to them. Dynamis does not control the privacy
|
||||||
|
policies of third parties, and you are subject to the privacy policies of those third parties where applicable.
|
||||||
|
</p>
|
||||||
|
<p>We encourage you to ask questions before you disclose your personal information to others.</p>
|
||||||
|
<p>
|
||||||
|
<strong>General</strong><br />
|
||||||
|
We may change this Privacy Policy from time to time as we add new products and applications, as we improve our current offerings, and as technologies and laws change. You can determine when this Privacy
|
||||||
|
Policy was last revised by referring to the “Last Updated” legend at the top of this page.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Any changes will become effective upon our posting of the revised Privacy Policy on our affected websites. We will provide notice to you if these changes are material and, where required by applicable law, we
|
||||||
|
will obtain your consent. This notice may be provided by email, by posting notice of the changes on our affected websites or by other means, consistent with applicable laws.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Contact Us</strong><br />
|
||||||
|
If you have any questions or comments about our privacy policy, and you can’t find the answer to your question on our help pages, please contact us using this form or email support@bizmatch.net, or write
|
||||||
|
to us at BizMatch, 715 S. Tanahua, Corpus Christi, TX 78401.)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</p-sidebar>
|
||||||
|
|
||||||
|
<p-sidebar [(visible)]="termsVisible" styleClass="w-30rem" position="right">
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<span class="font-semibold text-xl">Terms of use</span>
|
||||||
|
</ng-template>
|
||||||
|
<section id="content" role="main">
|
||||||
|
<article id="post-1" class="post-1 page type-page status-publish hentry pmpro-has-access">
|
||||||
|
<section class="entry-content">
|
||||||
|
<div class="container" style="padding: 3.5% 0 3.75% 0 !important">
|
||||||
|
<b><span>AGREEMENT BETWEEN USER AND BizMatch</span></b
|
||||||
|
><span
|
||||||
|
><p></p>
|
||||||
|
<p><span>The BizMatch Web Site is comprised of various Web pages operated by BizMatch.</span><span></span></p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>The BizMatch Web Site is offered to you conditioned on your acceptance without modification of the terms, conditions, and notices contained herein. Your use of the BizMatch Web Site constitutes your
|
||||||
|
agreement to all such terms, conditions, and notices.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b><span>MODIFICATION OF THESE TERMS OF USE</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>BizMatch reserves the right to change the terms, conditions, and notices under which the BizMatch Web Site is offered, including but not limited to the charges associated with the use of the BizMatch Web
|
||||||
|
Site.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b><span>LINKS TO THIRD PARTY SITES</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>The BizMatch Web Site may contain links to other Web Sites ("Linked Sites"). The Linked Sites are not under the control of BizMatch and BizMatch is not responsible for the contents of any Linked Site,
|
||||||
|
including without limitation any link contained in a Linked Site, or any changes or updates to a Linked Site. BizMatch is not responsible for webcasting or any other form of transmission received from any
|
||||||
|
Linked Site. BizMatch is providing these links to you only as a convenience, and the inclusion of any link does not imply endorsement by BizMatch of the site or any association with its operators.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b><span>NO UNLAWFUL OR PROHIBITED USE</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>As a condition of your use of the BizMatch Web Site, you warrant to BizMatch that you will not use the BizMatch Web Site for any purpose that is unlawful or prohibited by these terms, conditions, and
|
||||||
|
notices. You may not use the BizMatch Web Site in any manner which could damage, disable, overburden, or impair the BizMatch Web Site or interfere with any other party’s use and enjoyment of the BizMatch
|
||||||
|
Web Site. You may not obtain or attempt to obtain any materials or information through any means not intentionally made available or provided for through the BizMatch Web Sites.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b><span>USE OF COMMUNICATION SERVICES</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>The BizMatch Web Site may contain bulletin board services, chat areas, news groups, forums, communities, personal web pages, calendars, and/or other message or communication facilities designed to enable
|
||||||
|
you to communicate with the public at large or with a group (collectively, "Communication Services"), you agree to use the Communication Services only to post, send and receive messages and material that
|
||||||
|
are proper and related to the particular Communication Service. By way of example, and not as a limitation, you agree that when using a Communication Service, you will not:</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p> </p>
|
||||||
|
<p class="MsoNormal"><!--[if !supportLists]--></p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Defame, abuse, harass, stalk, threaten or otherwise violate the legal rights (such as rights of privacy and publicity) of others.</span><span></span>
|
||||||
|
</p>
|
||||||
|
<p> </p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Publish, post, upload, distribute or disseminate any inappropriate, profane, defamatory, infringing, obscene, indecent or unlawful topic, name, material or information.</span>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span
|
||||||
|
>Upload files that contain software or other material protected by intellectual property laws (or by rights of privacy of publicity) unless you own or control the rights thereto or have received all
|
||||||
|
necessary consents.</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Upload files that contain viruses, corrupted files, or any other similar software or programs that may damage the operation of another’s computer.</span>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Advertise or offer to sell or buy any goods or services for any business purpose, unless such Communication Service specifically allows such messages.</span>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Conduct or forward surveys, contests, pyramid schemes or chain letters.</span>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Download any file posted by another user of a Communication Service that you know, or reasonably should know, cannot be legally distributed in such manner.</span>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span
|
||||||
|
>Falsify or delete any author attributions, legal or other proper notices or proprietary designations or labels of the origin or source of software or other material contained in a file that is
|
||||||
|
uploaded.</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Restrict or inhibit any other user from using and enjoying the Communication Services.</span>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Violate any code of conduct or other guidelines which may be applicable for any particular Communication Service.</span>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Harvest or otherwise collect information about others, including e-mail addresses, without their consent.</span>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<!--[if !supportLists]--><span
|
||||||
|
><span>§<span> </span></span></span
|
||||||
|
><!--[endif]--><span>Violate any applicable laws or regulations.</span>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
<span
|
||||||
|
>BizMatch has no obligation to monitor the Communication Services. However, BizMatch reserves the right to review materials posted to a Communication Service and to remove any materials in its sole
|
||||||
|
discretion. BizMatch reserves the right to terminate your access to any or all of the Communication Services at any time without notice for any reason whatsoever.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>BizMatch reserves the right at all times to disclose any information as necessary to satisfy any applicable law, regulation, legal process or governmental request, or to edit, refuse to post or to remove
|
||||||
|
any information or materials, in whole or in part, in BizMatch’s sole discretion.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>Always use caution when giving out any personally identifying information about yourself or your children in any Communication Service. BizMatch does not control or endorse the content, messages or
|
||||||
|
information found in any Communication Service and, therefore, BizMatch specifically disclaims any liability with regard to the Communication Services and any actions resulting from your participation in
|
||||||
|
any Communication Service. Managers and hosts are not authorized BizMatch spokespersons, and their views do not necessarily reflect those of BizMatch.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>Materials uploaded to a Communication Service may be subject to posted limitations on usage, reproduction and/or dissemination. You are responsible for adhering to such limitations if you download the
|
||||||
|
materials.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b><span>MATERIALS PROVIDED TO BizMatch OR POSTED AT ANY BizMatch WEB SITE</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>BizMatch does not claim ownership of the materials you provide to BizMatch (including feedback and suggestions) or post, upload, input or submit to any BizMatch Web Site or its associated services
|
||||||
|
(collectively "Submissions"). However, by posting, uploading, inputting, providing or submitting your Submission you are granting BizMatch, its affiliated companies and necessary sublicensees permission
|
||||||
|
to use your Submission in connection with the operation of their Internet businesses including, without limitation, the rights to: copy, distribute, transmit, publicly display, publicly perform,
|
||||||
|
reproduce, edit, translate and reformat your Submission; and to publish your name in connection with your Submission.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>No compensation will be paid with respect to the use of your Submission, as provided herein. BizMatch is under no obligation to post or use any Submission you may provide and may remove any Submission at
|
||||||
|
any time in BizMatch’s sole discretion.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>By posting, uploading, inputting, providing or submitting your Submission you warrant and represent that you own or otherwise control all of the rights to your Submission as described in this section
|
||||||
|
including, without limitation, all the rights necessary for you to provide, post, upload, input or submit the Submissions.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b><span>LIABILITY DISCLAIMER</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>THE INFORMATION, SOFTWARE, PRODUCTS, AND SERVICES INCLUDED IN OR AVAILABLE THROUGH THE BizMatch WEB SITE MAY INCLUDE INACCURACIES OR TYPOGRAPHICAL ERRORS. CHANGES ARE PERIODICALLY ADDED TO THE
|
||||||
|
INFORMATION HEREIN. BizMatch AND/OR ITS SUPPLIERS MAY MAKE IMPROVEMENTS AND/OR CHANGES IN THE BizMatch WEB SITE AT ANY TIME. ADVICE RECEIVED VIA THE BizMatch WEB SITE SHOULD NOT BE RELIED UPON FOR
|
||||||
|
PERSONAL, MEDICAL, LEGAL OR FINANCIAL DECISIONS AND YOU SHOULD CONSULT AN APPROPRIATE PROFESSIONAL FOR SPECIFIC ADVICE TAILORED TO YOUR SITUATION.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>BizMatch AND/OR ITS SUPPLIERS MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY, RELIABILITY, AVAILABILITY, TIMELINESS, AND ACCURACY OF THE INFORMATION, SOFTWARE, PRODUCTS, SERVICES AND RELATED GRAPHICS
|
||||||
|
CONTAINED ON THE BizMatch WEB SITE FOR ANY PURPOSE. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, ALL SUCH INFORMATION, SOFTWARE, PRODUCTS, SERVICES AND RELATED GRAPHICS ARE PROVIDED "AS IS" WITHOUT
|
||||||
|
WARRANTY OR CONDITION OF ANY KIND. BizMatch AND/OR ITS SUPPLIERS HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH REGARD TO THIS INFORMATION, SOFTWARE, PRODUCTS, SERVICES AND RELATED GRAPHICS, INCLUDING
|
||||||
|
ALL IMPLIED WARRANTIES OR CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL BizMatch AND/OR ITS SUPPLIERS BE LIABLE FOR ANY DIRECT, INDIRECT, PUNITIVE, INCIDENTAL, SPECIAL, CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF USE, DATA OR PROFITS, ARISING OUT OF OR IN ANY WAY CONNECTED WITH THE USE OR PERFORMANCE OF THE BizMatch WEB SITE, WITH THE DELAY OR INABILITY
|
||||||
|
TO USE THE BizMatch WEB SITE OR RELATED SERVICES, THE PROVISION OF OR FAILURE TO PROVIDE SERVICES, OR FOR ANY INFORMATION, SOFTWARE, PRODUCTS, SERVICES AND RELATED GRAPHICS OBTAINED THROUGH THE BizMatch
|
||||||
|
WEB SITE, OR OTHERWISE ARISING OUT OF THE USE OF THE BizMatch WEB SITE, WHETHER BASED ON CONTRACT, TORT, NEGLIGENCE, STRICT LIABILITY OR OTHERWISE, EVEN IF BizMatch OR ANY OF ITS SUPPLIERS HAS BEEN
|
||||||
|
ADVISED OF THE POSSIBILITY OF DAMAGES. BECAUSE SOME STATES/JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE LIMITATION MAY NOT APPLY
|
||||||
|
TO YOU. IF YOU ARE DISSATISFIED WITH ANY PORTION OF THE BizMatch WEB SITE, OR WITH ANY OF THESE TERMS OF USE, YOUR SOLE AND EXCLUSIVE REMEDY IS TO DISCONTINUE USING THE BizMatch WEB SITE.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p><span>SERVICE CONTACT : info@bizmatch.net</span><span></span></p>
|
||||||
|
<p>
|
||||||
|
<b><span>TERMINATION/ACCESS RESTRICTION</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>BizMatch reserves the right, in its sole discretion, to terminate your access to the BizMatch Web Site and the related services or any portion thereof at any time, without notice. GENERAL To the maximum
|
||||||
|
extent permitted by law, this agreement is governed by the laws of the State of Washington, U.S.A. and you hereby consent to the exclusive jurisdiction and venue of courts in King County, Washington,
|
||||||
|
U.S.A. in all disputes arising out of or relating to the use of the BizMatch Web Site. Use of the BizMatch Web Site is unauthorized in any jurisdiction that does not give effect to all provisions of these
|
||||||
|
terms and conditions, including without limitation this paragraph. You agree that no joint venture, partnership, employment, or agency relationship exists between you and BizMatch as a result of this
|
||||||
|
agreement or use of the BizMatch Web Site. BizMatch’s performance of this agreement is subject to existing laws and legal process, and nothing contained in this agreement is in derogation of BizMatch’s
|
||||||
|
right to comply with governmental, court and law enforcement requests or requirements relating to your use of the BizMatch Web Site or information provided to or gathered by BizMatch with respect to such
|
||||||
|
use. If any part of this agreement is determined to be invalid or unenforceable pursuant to applicable law including, but not limited to, the warranty disclaimers and liability limitations set forth
|
||||||
|
above, then the invalid or unenforceable provision will be deemed superseded by a valid, enforceable provision that most closely matches the intent of the original provision and the remainder of the
|
||||||
|
agreement shall continue in effect. Unless otherwise specified herein, this agreement constitutes the entire agreement between the user and BizMatch with respect to the BizMatch Web Site and it supersedes
|
||||||
|
all prior or contemporaneous communications and proposals, whether electronic, oral or written, between the user and BizMatch with respect to the BizMatch Web Site. A printed version of this agreement and
|
||||||
|
of any notice given in electronic form shall be admissible in judicial or administrative proceedings based upon or relating to this agreement to the same extent an d subject to the same conditions as
|
||||||
|
other business documents and records originally generated and maintained in printed form. It is the express wish to the parties that this agreement and all related documents be drawn up in English.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b><span>COPYRIGHT AND TRADEMARK NOTICES:</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p><span>All contents of the BizMatch Web Site are: Copyright 2011 by Bizmatch Business Solutions and/or its suppliers. All rights reserved.</span><span></span></p>
|
||||||
|
<p>
|
||||||
|
<b><span>TRADEMARKS</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p><span>The names of actual companies and products mentioned herein may be the trademarks of their respective owners.</span><span></span></p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>The example companies, organizations, products, people and events depicted herein are fictitious. No association with any real company, organization, product, person, or event is intended or should be
|
||||||
|
inferred.</span
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p><span>Any rights not expressly granted herein are reserved.</span><span></span></p>
|
||||||
|
<p>
|
||||||
|
<b><span>NOTICES AND PROCEDURE FOR MAKING CLAIMS OF COPYRIGHT INFRINGEMENT</span></b
|
||||||
|
><span></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span
|
||||||
|
>Pursuant to Title 17, United States Code, Section 512(c)(2), notifications of claimed copyright infringement under United States copyright law should be sent to Service Provider’s Designated Agent. ALL
|
||||||
|
INQUIRIES NOT RELEVANT TO THE FOLLOWING PROCEDURE WILL RECEIVE NO RESPONSE. See Notice and Procedure for Making Claims of Copyright Infringement.</span
|
||||||
|
><span
|
||||||
|
><br />
|
||||||
|
<!--[if !supportLineBreakNewLine]--></span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<p class="MsoNormal"> </p>
|
||||||
|
<p class="MsoNormal">
|
||||||
|
We reserve the right to update or revise these Terms of Use at any time without notice. Please check the Terms of Use periodically for changes. The revised terms will be effective immediately as
|
||||||
|
soon as they are posted on the WebSite and by continuing to use the Site you agree to be bound by the revised terms<span
|
||||||
|
><br />
|
||||||
|
<!--[endif]--></span
|
||||||
|
>
|
||||||
|
</p></span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
</p-sidebar>
|
||||||
|
|||||||
@@ -1,25 +1,21 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ButtonModule } from 'primeng/button';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { CheckboxModule } from 'primeng/checkbox';
|
import { SidebarModule } from 'primeng/sidebar';
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
|
||||||
import {StyleClassModule} from 'primeng/styleclass';
|
|
||||||
import { DropdownModule } from 'primeng/dropdown';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { UserService } from '../../services/user.service';
|
|
||||||
import { SharedModule } from '../../shared/shared/shared.module';
|
import { SharedModule } from '../../shared/shared/shared.module';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'footer',
|
selector: 'footer',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule],
|
imports: [SharedModule, SidebarModule],
|
||||||
templateUrl: './footer.component.html',
|
templateUrl: './footer.component.html',
|
||||||
styleUrl: './footer.component.scss'
|
styleUrl: './footer.component.scss',
|
||||||
})
|
})
|
||||||
export class FooterComponent {
|
export class FooterComponent {
|
||||||
constructor(public userService:UserService){}
|
privacyVisible = false;
|
||||||
login(){
|
termsVisible = false;
|
||||||
this.userService.login(window.location.href);
|
constructor(public keycloakService: KeycloakService) {}
|
||||||
|
login() {
|
||||||
|
this.keycloakService.login({
|
||||||
|
redirectUri: window.location.href,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="pl-3 flex align-items-center gap-2">
|
<div class="pl-3 flex align-items-center gap-2">
|
||||||
<a routerLink="/home"><img src="assets/images/header-logo.png" height="40" alt="bizmatch" /></a>
|
<a routerLink="/home"><img src="assets/images/header-logo.png" height="40" alt="bizmatch" /></a>
|
||||||
<p-tabMenu [model]="tabItems" ariaLabelledBy="label" styleClass="flex" [activeItem]="activeItem">
|
<p-tabMenu [model]="tabItems" ariaLabelledBy="label" styleClass="flex" [activeItem]="activeItem"> </p-tabMenu>
|
||||||
</p-tabMenu>
|
|
||||||
<p-menubar [model]="menuItems"></p-menubar>
|
<p-menubar [model]="menuItems"></p-menubar>
|
||||||
<div *ngIf="user$ | async as user else empty">Welcome, {{user.firstname}}</div>
|
<p-menubar [model]="loginItems"></p-menubar>
|
||||||
<ng-template #empty>
|
@if(user){
|
||||||
</ng-template>
|
<div>Welcome, {{ user.firstName }}</div>
|
||||||
|
}
|
||||||
|
<ng-template #empty> </ng-template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -2,15 +2,16 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { faUserGear } from '@fortawesome/free-solid-svg-icons';
|
import { faUserGear } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { MenuItem } from 'primeng/api';
|
import { MenuItem } from 'primeng/api';
|
||||||
import { ButtonModule } from 'primeng/button';
|
import { ButtonModule } from 'primeng/button';
|
||||||
import { MenubarModule } from 'primeng/menubar';
|
import { MenubarModule } from 'primeng/menubar';
|
||||||
import { OverlayPanelModule } from 'primeng/overlaypanel';
|
import { OverlayPanelModule } from 'primeng/overlaypanel';
|
||||||
import { TabMenuModule } from 'primeng/tabmenu';
|
import { TabMenuModule } from 'primeng/tabmenu';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
import { KeycloakUser } from '../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { UserService } from '../../services/user.service';
|
import { map2User } from '../../utils/utils';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'header',
|
selector: 'header',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -20,18 +21,21 @@ import { UserService } from '../../services/user.service';
|
|||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent {
|
||||||
public buildVersion = environment.buildVersion;
|
public buildVersion = environment.buildVersion;
|
||||||
user$: Observable<User>;
|
user$: Observable<KeycloakUser>;
|
||||||
user: User;
|
user: KeycloakUser;
|
||||||
public tabItems: MenuItem[];
|
public tabItems: MenuItem[];
|
||||||
|
public loginItems: MenuItem[];
|
||||||
public menuItems: MenuItem[];
|
public menuItems: MenuItem[];
|
||||||
activeItem;
|
activeItem;
|
||||||
faUserGear = faUserGear;
|
faUserGear = faUserGear;
|
||||||
constructor(public userService: UserService, private router: Router) {}
|
constructor(public keycloakService: KeycloakService, private router: Router) {}
|
||||||
|
|
||||||
ngOnInit() {
|
async ngOnInit() {
|
||||||
this.user$ = this.userService.getUserObservable();
|
const token = await this.keycloakService.getToken();
|
||||||
this.user$.subscribe(u => {
|
this.user = map2User(token);
|
||||||
this.user = u;
|
//this.user$ = this.keycloakService
|
||||||
|
// this.user$.subscribe(u => {
|
||||||
|
// this.user = u;
|
||||||
this.menuItems = [
|
this.menuItems = [
|
||||||
{
|
{
|
||||||
label: 'User Actions',
|
label: 'User Actions',
|
||||||
@@ -41,75 +45,86 @@ export class HeaderComponent {
|
|||||||
label: 'Account',
|
label: 'Account',
|
||||||
icon: 'pi pi-user',
|
icon: 'pi pi-user',
|
||||||
routerLink: `/account`,
|
routerLink: `/account`,
|
||||||
visible: this.isUserLogedIn(),
|
visible: this.keycloakService.isLoggedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Create Listing',
|
label: 'Create Listing',
|
||||||
icon: 'pi pi-plus-circle',
|
icon: 'pi pi-plus-circle',
|
||||||
routerLink: '/createBusinessListing',
|
routerLink: '/createBusinessListing',
|
||||||
visible: this.isUserLogedIn(),
|
visible: this.keycloakService.isLoggedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'My Listings',
|
label: 'My Listings',
|
||||||
icon: 'pi pi-list',
|
icon: 'pi pi-list',
|
||||||
routerLink: '/myListings',
|
routerLink: '/myListings',
|
||||||
visible: this.isUserLogedIn(),
|
visible: this.keycloakService.isLoggedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'My Favorites',
|
label: 'My Favorites',
|
||||||
icon: 'pi pi-star',
|
icon: 'pi pi-star',
|
||||||
routerLink: '/myFavorites',
|
routerLink: '/myFavorites',
|
||||||
visible: this.isUserLogedIn(),
|
visible: this.keycloakService.isLoggedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'EMail Us',
|
label: 'EMail Us',
|
||||||
icon: 'fa-regular fa-envelope',
|
icon: 'fa-regular fa-envelope',
|
||||||
routerLink: '/emailUs',
|
routerLink: '/emailUs',
|
||||||
visible: this.isUserLogedIn(),
|
visible: this.keycloakService.isLoggedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Logout',
|
label: 'Logout',
|
||||||
icon: 'fa-solid fa-right-from-bracket',
|
icon: 'fa-solid fa-right-from-bracket',
|
||||||
routerLink: '/logout',
|
routerLink: '/logout',
|
||||||
visible: this.isUserLogedIn(),
|
visible: this.keycloakService.isLoggedIn(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Login',
|
label: 'Login',
|
||||||
icon: 'fa-solid fa-right-from-bracket',
|
icon: 'fa-solid fa-right-from-bracket',
|
||||||
command: () => this.login(),
|
command: () => this.login(),
|
||||||
visible: !this.isUserLogedIn(),
|
visible: !this.keycloakService.isLoggedIn(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
// });
|
||||||
this.tabItems = [
|
this.tabItems = [
|
||||||
{
|
{
|
||||||
label: 'Businesses for Sale',
|
label: 'Businesses for Sale',
|
||||||
routerLink: '/businessListings',
|
routerLink: '/businessListings',
|
||||||
state: {},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Commercial Property',
|
label: 'Commercial Property',
|
||||||
routerLink: '/commercialPropertyListings',
|
routerLink: '/commercialPropertyListings',
|
||||||
state: {},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Professionals/Brokers Directory',
|
label: 'Professionals/Brokers Directory',
|
||||||
routerLink: '/brokerListings',
|
routerLink: '/brokerListings',
|
||||||
state: {},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
this.loginItems = [
|
||||||
|
{
|
||||||
|
label: 'Login',
|
||||||
|
command: () => this.login(),
|
||||||
|
visible: !this.keycloakService.isLoggedIn(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Register',
|
||||||
|
command: () => this.register(),
|
||||||
|
visible: !this.keycloakService.isLoggedIn(),
|
||||||
|
},
|
||||||
|
];
|
||||||
this.activeItem = this.tabItems[0];
|
this.activeItem = this.tabItems[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateWithState(dest: string, state: any) {
|
navigateWithState(dest: string, state: any) {
|
||||||
this.router.navigate([dest], { state: state });
|
this.router.navigate([dest], { state: state });
|
||||||
}
|
}
|
||||||
isUserLogedIn() {
|
|
||||||
return this.userService?.isLoggedIn();
|
|
||||||
}
|
|
||||||
login() {
|
login() {
|
||||||
this.userService.login(window.location.href);
|
this.keycloakService.login({
|
||||||
|
redirectUri: window.location.href,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
register() {
|
||||||
|
this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { UserService } from '../../services/user.service';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'logout',
|
selector: 'logout',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule,RouterModule],
|
imports: [CommonModule, RouterModule],
|
||||||
template:``
|
template: ``,
|
||||||
})
|
})
|
||||||
export class LogoutComponent {
|
export class LogoutComponent {
|
||||||
constructor(private userService:UserService){
|
constructor(public keycloakService: KeycloakService) {
|
||||||
userService.logout();
|
sessionStorage.removeItem('USERID');
|
||||||
|
keycloakService.logout(window.location.origin + '/home');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<p>not-found works!</p>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-not-found',
|
||||||
|
standalone: true,
|
||||||
|
template: '<h2>Page not found</h2>',
|
||||||
|
})
|
||||||
|
export class NotFoundComponent {}
|
||||||
@@ -1,38 +1,41 @@
|
|||||||
import { CanMatchFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
import { Injectable } from '@angular/core';
|
||||||
import { inject } from '@angular/core';
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||||
|
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
|
||||||
// Services
|
import { KeycloakInitializerService } from '../services/keycloak-initializer.service';
|
||||||
import { UserService } from '../services/user.service';
|
import { createLogger } from '../utils/utils';
|
||||||
|
const logger = createLogger('AuthGuard');
|
||||||
export const authGuard: CanMatchFn = async (route, segments): Promise<boolean | UrlTree> => {
|
@Injectable({
|
||||||
const router = inject(Router);
|
providedIn: 'root',
|
||||||
const userService = inject(UserService);
|
})
|
||||||
|
export class AuthGuard extends KeycloakAuthGuard {
|
||||||
const authenticated: boolean = userService.isLoggedIn();
|
constructor(protected override readonly router: Router, protected readonly keycloak: KeycloakService, private keycloakInitializer: KeycloakInitializerService) {
|
||||||
if (!authenticated) {
|
super(router, keycloak);
|
||||||
console.log(window.location.origin)
|
|
||||||
console.log(window.location.href)
|
|
||||||
await userService.login(`${window.location.origin}${segments['url']}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the user Keycloak roles and the required from the route
|
async isAccessAllowed(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean | UrlTree> {
|
||||||
const roles: string[] = userService.getUserRoles();//keycloakService.getUserRoles(true);
|
logger.info(`--->AuthGuard`);
|
||||||
const requiredRoles = route.data?.['roles'];
|
while (!this.keycloakInitializer.initialized) {
|
||||||
|
logger.info(`Waiting 100 msec`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
// Force the user to log in if currently unauthenticated.
|
||||||
|
const authenticated = this.keycloak.isLoggedIn();
|
||||||
|
if (!this.authenticated && !authenticated) {
|
||||||
|
await this.keycloak.login({
|
||||||
|
redirectUri: window.location.origin + state.url,
|
||||||
|
});
|
||||||
|
// return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Allow the user to proceed if no additional roles are required to access the route
|
// Get the roles required from the route.
|
||||||
|
const requiredRoles = route.data['roles'];
|
||||||
|
|
||||||
|
// Allow the user to proceed if no additional roles are required to access the route.
|
||||||
if (!Array.isArray(requiredRoles) || requiredRoles.length === 0) {
|
if (!Array.isArray(requiredRoles) || requiredRoles.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow the user to proceed if ALL of the required roles are present
|
// Allow the user to proceed if all the required roles are present.
|
||||||
const authorized = requiredRoles.every((role) => roles.includes(role));
|
return requiredRoles.every(role => this.roles.includes(role));
|
||||||
// Allow the user to proceed if ONE of the required roles is present
|
|
||||||
//const authorized = requiredRoles.some((role) => roles.includes(role));
|
|
||||||
|
|
||||||
if (authorized) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Display my custom HTTP 403 access denied page
|
|
||||||
return router.createUrlTree(['/access']);
|
|
||||||
};
|
|
||||||
|
|||||||
35
bizmatch/src/app/guards/listing-category.guard.ts
Normal file
35
bizmatch/src/app/guards/listing-category.guard.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { catchError, map } from 'rxjs/operators';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ListingCategoryGuard implements CanActivate {
|
||||||
|
private apiBaseUrl = environment.apiBaseUrl;
|
||||||
|
constructor(private http: HttpClient, private router: Router) {}
|
||||||
|
|
||||||
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
|
||||||
|
const id = route.paramMap.get('id');
|
||||||
|
const url = `${this.apiBaseUrl}/bizmatch/listings/undefined/${id}`;
|
||||||
|
|
||||||
|
return this.http.get<any>(url).pipe(
|
||||||
|
map(response => {
|
||||||
|
const category = response.listingsCategory;
|
||||||
|
if (category === 'business') {
|
||||||
|
return this.router.createUrlTree([`/details-business-listing/${id}`]);
|
||||||
|
} else if (category === 'commercialProperty') {
|
||||||
|
return this.router.createUrlTree([`/details-commercial-property-listing/${id}`]);
|
||||||
|
} else {
|
||||||
|
return this.router.createUrlTree(['/not-found']);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(() => {
|
||||||
|
return of(this.router.createUrlTree(['/not-found']));
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,77 +1,95 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
/**
|
||||||
import {
|
* @license
|
||||||
HttpInterceptor,
|
* Copyright Mauricio Gemelli Vigolo and contributors.
|
||||||
HttpRequest,
|
*
|
||||||
HttpHandler,
|
* Use of this source code is governed by a MIT-style license that can be
|
||||||
HttpEvent,
|
* found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
|
||||||
HttpInterceptorFn,
|
*/
|
||||||
HttpHandlerFn,
|
|
||||||
} from '@angular/common/http';
|
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { Observable, combineLatest, from, of } from 'rxjs';
|
import { Observable, combineLatest, from, of } from 'rxjs';
|
||||||
import { mergeMap } from 'rxjs/operators';
|
import { mergeMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { KeycloakService } from '../services/keycloak.service';
|
|
||||||
import { ExcludedUrlRegex } from '../models/keycloak-options';
|
import { ExcludedUrlRegex } from '../models/keycloak-options';
|
||||||
|
import { KeycloakService } from '../services/keycloak.service';
|
||||||
|
|
||||||
export const keycloakBearerInterceptor: HttpInterceptorFn = (req, next) => {
|
/**
|
||||||
//return next(req);
|
* This interceptor includes the bearer by default in all HttpClient requests.
|
||||||
const keycloak = inject(KeycloakService);
|
*
|
||||||
const { enableBearerInterceptor, excludedUrls } = keycloak;
|
* If you need to exclude some URLs from adding the bearer, please, take a look
|
||||||
if (!enableBearerInterceptor) {
|
* at the {@link KeycloakOptions} bearerExcludedUrls property.
|
||||||
return next(req);
|
*/
|
||||||
}
|
@Injectable()
|
||||||
|
export class KeycloakBearerInterceptor implements HttpInterceptor {
|
||||||
|
constructor(private keycloak: KeycloakService) {}
|
||||||
|
|
||||||
const shallPass: boolean =
|
/**
|
||||||
!keycloak.shouldAddToken(req) ||
|
* Calls to update the keycloak token if the request should update the token.
|
||||||
excludedUrls.findIndex((item) => isUrlExcluded(req, item)) > -1;
|
*
|
||||||
if (shallPass) {
|
* @param req http request from @angular http module.
|
||||||
return next(req);
|
* @returns
|
||||||
}
|
* A promise boolean for the token update or noop result.
|
||||||
|
*/
|
||||||
return combineLatest([
|
private async conditionallyUpdateToken(req: HttpRequest<unknown>): Promise<boolean> {
|
||||||
from(conditionallyUpdateToken(req)),
|
|
||||||
of(keycloak.isLoggedIn()),
|
|
||||||
]).pipe(
|
|
||||||
mergeMap(([_, isLoggedIn]) =>
|
|
||||||
isLoggedIn ? handleRequestWithTokenHeader(req, next) : next(req)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function isUrlExcluded(
|
|
||||||
{ method, url }: HttpRequest<unknown>,
|
|
||||||
{ urlPattern, httpMethods }: ExcludedUrlRegex
|
|
||||||
): boolean {
|
|
||||||
const httpTest =
|
|
||||||
httpMethods.length === 0 ||
|
|
||||||
httpMethods.join().indexOf(method.toUpperCase()) > -1;
|
|
||||||
|
|
||||||
const urlTest = urlPattern.test(url);
|
|
||||||
|
|
||||||
return httpTest && urlTest;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRequestWithTokenHeader(
|
|
||||||
req: HttpRequest<unknown>,
|
|
||||||
next: HttpHandlerFn
|
|
||||||
): Observable<HttpEvent<unknown>> {
|
|
||||||
return this.keycloak.addTokenToHeader(req.headers).pipe(
|
|
||||||
mergeMap((headersWithBearer:string) => {
|
|
||||||
const kcReq = req.clone({
|
|
||||||
headers: req.headers.set('Authorization', headersWithBearer)
|
|
||||||
});//req.clone({ headers: headersWithBearer });
|
|
||||||
return next(kcReq);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function conditionallyUpdateToken(
|
|
||||||
req: HttpRequest<unknown>
|
|
||||||
): Promise<boolean> {
|
|
||||||
if (this.keycloak.shouldUpdateToken(req)) {
|
if (this.keycloak.shouldUpdateToken(req)) {
|
||||||
return await this.keycloak.updateToken();
|
return await this.keycloak.updateToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* Checks if the url is excluded from having the Bearer Authorization
|
||||||
|
* header added.
|
||||||
|
*
|
||||||
|
* @param req http request from @angular http module.
|
||||||
|
* @param excludedUrlRegex contains the url pattern and the http methods,
|
||||||
|
* excluded from adding the bearer at the Http Request.
|
||||||
|
*/
|
||||||
|
private isUrlExcluded({ method, url }: HttpRequest<unknown>, { urlPattern, httpMethods }: ExcludedUrlRegex): boolean {
|
||||||
|
const httpTest = httpMethods.length === 0 || httpMethods.join().indexOf(method.toUpperCase()) > -1;
|
||||||
|
|
||||||
|
const urlTest = urlPattern.test(url);
|
||||||
|
|
||||||
|
return httpTest && urlTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercept implementation that checks if the request url matches the excludedUrls.
|
||||||
|
* If not, adds the Authorization header to the request if the user is logged in.
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param next
|
||||||
|
*/
|
||||||
|
public intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
|
const { enableBearerInterceptor, excludedUrls } = this.keycloak;
|
||||||
|
if (!enableBearerInterceptor) {
|
||||||
|
return next.handle(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
const shallPass: boolean = !this.keycloak.shouldAddToken(req) || excludedUrls.findIndex(item => this.isUrlExcluded(req, item)) > -1;
|
||||||
|
if (shallPass) {
|
||||||
|
return next.handle(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
return combineLatest([from(this.conditionallyUpdateToken(req)), of(this.keycloak.isLoggedIn())]).pipe(mergeMap(([_, isLoggedIn]) => (isLoggedIn ? this.handleRequestWithTokenHeader(req, next) : next.handle(req))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the token of the current user to the Authorization header
|
||||||
|
*
|
||||||
|
* @param req
|
||||||
|
* @param next
|
||||||
|
*/
|
||||||
|
private handleRequestWithTokenHeader(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
|
return this.keycloak.addTokenToHeader(req.headers).pipe(
|
||||||
|
mergeMap(headersWithBearer => {
|
||||||
|
const kcReq = req.clone({ headers: headersWithBearer });
|
||||||
|
return next.handle(kcReq);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,24 +1,22 @@
|
|||||||
import { HttpEvent, HttpHandler, HttpInterceptor, HttpInterceptorFn, HttpRequest } from '@angular/common/http';
|
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable, tap } from 'rxjs';
|
import { Observable, tap } from 'rxjs';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import { LoadingService } from '../services/loading.service';
|
import { LoadingService } from '../services/loading.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LoadingInterceptor implements HttpInterceptor {
|
export class LoadingInterceptor implements HttpInterceptor {
|
||||||
constructor(private loadingService:LoadingService) { }
|
constructor(private loadingService: LoadingService) {}
|
||||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||||
console.log("Intercepting Requests")
|
|
||||||
const requestId = `HTTP-${v4()}`;
|
const requestId = `HTTP-${v4()}`;
|
||||||
this.loadingService.startLoading(requestId,request.url);
|
this.loadingService.startLoading(requestId, request.url);
|
||||||
|
|
||||||
// return next.handle(request);
|
|
||||||
return next.handle(request).pipe(
|
return next.handle(request).pipe(
|
||||||
tap({
|
tap({
|
||||||
finalize: () => this.loadingService.stopLoading(requestId), // Stoppt den Ladevorgang, wenn die Anfrage abgeschlossen ist
|
finalize: () => this.loadingService.stopLoading(requestId), // Stoppt den Ladevorgang, wenn die Anfrage abgeschlossen ist
|
||||||
// Beachte, dass 'error' und 'complete' hier entfernt wurden, da 'finalize' in allen Fällen aufgerufen wird,
|
// Beachte, dass 'error' und 'complete' hier entfernt wurden, da 'finalize' in allen Fällen aufgerufen wird,
|
||||||
// egal ob die Anfrage erfolgreich war, einen Fehler geworfen hat oder abgeschlossen wurde.
|
// egal ob die Anfrage erfolgreich war, einen Fehler geworfen hat oder abgeschlossen wurde.
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
export enum KeycloakEventType {
|
|
||||||
/**
|
|
||||||
* Called if there was an error during authentication.
|
|
||||||
*/
|
|
||||||
OnAuthError,
|
|
||||||
/**
|
|
||||||
* Called if the user is logged out
|
|
||||||
* (will only be called if the session status iframe is enabled, or in Cordova mode).
|
|
||||||
*/
|
|
||||||
OnAuthLogout,
|
|
||||||
/**
|
|
||||||
* Called if there was an error while trying to refresh the token.
|
|
||||||
*/
|
|
||||||
OnAuthRefreshError,
|
|
||||||
/**
|
|
||||||
* Called when the token is refreshed.
|
|
||||||
*/
|
|
||||||
OnAuthRefreshSuccess,
|
|
||||||
/**
|
|
||||||
* Called when a user is successfully authenticated.
|
|
||||||
*/
|
|
||||||
OnAuthSuccess,
|
|
||||||
/**
|
|
||||||
* Called when the adapter is initialized.
|
|
||||||
*/
|
|
||||||
OnReady,
|
|
||||||
/**
|
|
||||||
* Called when the access token is expired. If a refresh token is available the token
|
|
||||||
* can be refreshed with updateToken, or in cases where it is not (that is, with implicit flow)
|
|
||||||
* you can redirect to login screen to obtain a new access token.
|
|
||||||
*/
|
|
||||||
OnTokenExpired,
|
|
||||||
/**
|
|
||||||
* Called when a AIA has been requested by the application.
|
|
||||||
*/
|
|
||||||
OnActionUpdate
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure of an event triggered by Keycloak, contains it's type
|
|
||||||
* and arguments (if any).
|
|
||||||
*/
|
|
||||||
export interface KeycloakEvent {
|
|
||||||
/**
|
|
||||||
* Event type as described at {@link KeycloakEventType}.
|
|
||||||
*/
|
|
||||||
type: KeycloakEventType;
|
|
||||||
/**
|
|
||||||
* Arguments from the keycloak-js event function.
|
|
||||||
*/
|
|
||||||
args?: unknown;
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Mauricio Gemelli Vigolo and contributors.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by a MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { HttpRequest } from '@angular/common/http';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP Methods
|
|
||||||
*/
|
|
||||||
export type HttpMethods =
|
|
||||||
| 'GET'
|
|
||||||
| 'POST'
|
|
||||||
| 'PUT'
|
|
||||||
| 'DELETE'
|
|
||||||
| 'OPTIONS'
|
|
||||||
| 'HEAD'
|
|
||||||
| 'PATCH';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ExcludedUrl type may be used to specify the url and the HTTP method that
|
|
||||||
* should not be intercepted by the KeycloakBearerInterceptor.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* const excludedUrl: ExcludedUrl[] = [
|
|
||||||
* {
|
|
||||||
* url: 'reports/public'
|
|
||||||
* httpMethods: ['GET']
|
|
||||||
* }
|
|
||||||
* ]
|
|
||||||
*
|
|
||||||
* In the example above for URL reports/public and HTTP Method GET the
|
|
||||||
* bearer will not be automatically added.
|
|
||||||
*
|
|
||||||
* If the url is informed but httpMethod is undefined, then the bearer
|
|
||||||
* will not be added for all HTTP Methods.
|
|
||||||
*/
|
|
||||||
export interface ExcludedUrl {
|
|
||||||
url: string;
|
|
||||||
httpMethods?: HttpMethods[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to ExcludedUrl, contains the HTTP methods and a regex to
|
|
||||||
* include the url patterns.
|
|
||||||
* This interface is used internally by the KeycloakService.
|
|
||||||
*/
|
|
||||||
export interface ExcludedUrlRegex {
|
|
||||||
urlPattern: RegExp;
|
|
||||||
httpMethods?: HttpMethods[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* keycloak-angular initialization options.
|
|
||||||
*/
|
|
||||||
export interface KeycloakOptions {
|
|
||||||
/**
|
|
||||||
* Configs to init the keycloak-js library. If undefined, will look for a keycloak.json file
|
|
||||||
* at root of the project.
|
|
||||||
* If not undefined, can be a string meaning the url to the keycloak.json file or an object
|
|
||||||
* of {@link Keycloak.KeycloakConfig}. Use this configuration if you want to specify the keycloak server,
|
|
||||||
* realm, clientId. This is usefull if you have different configurations for production, stage
|
|
||||||
* and development environments. Hint: Make use of Angular environment configuration.
|
|
||||||
*/
|
|
||||||
config?: string | Keycloak.KeycloakConfig;
|
|
||||||
/**
|
|
||||||
* Options to initialize the Keycloak adapter, matches the options as provided by Keycloak itself.
|
|
||||||
*/
|
|
||||||
initOptions?: Keycloak.KeycloakInitOptions;
|
|
||||||
/**
|
|
||||||
* By default all requests made by Angular HttpClient will be intercepted in order to
|
|
||||||
* add the bearer in the Authorization Http Header. However, if this is a not desired
|
|
||||||
* feature, the enableBearerInterceptor must be false.
|
|
||||||
*
|
|
||||||
* Briefly, if enableBearerInterceptor === false, the bearer will not be added
|
|
||||||
* to the authorization header.
|
|
||||||
*
|
|
||||||
* The default value is true.
|
|
||||||
*/
|
|
||||||
enableBearerInterceptor?: boolean;
|
|
||||||
/**
|
|
||||||
* Forces the execution of loadUserProfile after the keycloak initialization considering that the
|
|
||||||
* user logged in.
|
|
||||||
* This option is recommended if is desirable to have the user details at the beginning,
|
|
||||||
* so after the login, the loadUserProfile function will be called and its value cached.
|
|
||||||
*
|
|
||||||
* The default value is true.
|
|
||||||
*/
|
|
||||||
loadUserProfileAtStartUp?: boolean;
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* String Array to exclude the urls that should not have the Authorization Header automatically
|
|
||||||
* added. This library makes use of Angular Http Interceptor, to automatically add the Bearer
|
|
||||||
* token to the request.
|
|
||||||
*/
|
|
||||||
bearerExcludedUrls?: (string | ExcludedUrl)[];
|
|
||||||
/**
|
|
||||||
* This value will be used as the Authorization Http Header name. The default value is
|
|
||||||
* **Authorization**. If the backend expects requests to have a token in a different header, you
|
|
||||||
* should change this value, i.e: **JWT-Authorization**. This will result in a Http Header
|
|
||||||
* Authorization as "JWT-Authorization: bearer <token>".
|
|
||||||
*/
|
|
||||||
authorizationHeaderName?: string;
|
|
||||||
/**
|
|
||||||
* This value will be included in the Authorization Http Header param. The default value is
|
|
||||||
* **Bearer**, which will result in a Http Header Authorization as "Authorization: Bearer <token>".
|
|
||||||
*
|
|
||||||
* If any other value is needed by the backend in the authorization header, you should change this
|
|
||||||
* value.
|
|
||||||
*
|
|
||||||
* Warning: this value must be in compliance with the keycloak server instance and the adapter.
|
|
||||||
*/
|
|
||||||
bearerPrefix?: string;
|
|
||||||
/**
|
|
||||||
* This value will be used to determine whether or not the token needs to be updated. If the token
|
|
||||||
* will expire is fewer seconds than the updateMinValidity value, then it will be updated.
|
|
||||||
*
|
|
||||||
* The default value is 20.
|
|
||||||
*/
|
|
||||||
updateMinValidity?: number;
|
|
||||||
/**
|
|
||||||
* A function that will tell the KeycloakBearerInterceptor whether to add the token to the request
|
|
||||||
* or to leave the request as it is. If the returned value is `true`, the request will have the token
|
|
||||||
* present on it. If it is `false`, the token will be left off the request.
|
|
||||||
*
|
|
||||||
* The default is a function that always returns `true`.
|
|
||||||
*/
|
|
||||||
shouldAddToken?: (request: HttpRequest<unknown>) => boolean;
|
|
||||||
/**
|
|
||||||
* A function that will tell the KeycloakBearerInterceptor if the token should be considered for
|
|
||||||
* updating as a part of the request being made. If the returned value is `true`, the request will
|
|
||||||
* check the token's expiry time and if it is less than the number of seconds configured by
|
|
||||||
* updateMinValidity then it will be updated before the request is made. If the returned value is
|
|
||||||
* false, the token will not be updated.
|
|
||||||
*
|
|
||||||
* The default is a function that always returns `true`.
|
|
||||||
*/
|
|
||||||
shouldUpdateToken?: (request: HttpRequest<unknown>) => boolean;
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,9 @@
|
|||||||
<div class="flex justify-content-between align-items-center align-content-center">
|
<div class="flex justify-content-between align-items-center align-content-center">
|
||||||
<div class="font-medium text-3xl text-900 mb-3">{{ listing?.title }}</div>
|
<div class="font-medium text-3xl text-900 mb-3">{{ listing?.title }}</div>
|
||||||
<!-- <button pButton pRipple type="button" label="Go back to listings" icon="pi pi-user-plus" class="mr-3 p-button-rounded"></button> -->
|
<!-- <button pButton pRipple type="button" label="Go back to listings" icon="pi pi-user-plus" class="mr-3 p-button-rounded"></button> -->
|
||||||
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
@if(historyService.canGoBack){
|
||||||
|
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="historyService.goBack()"></p-button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
|
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
|
||||||
@if(listing){
|
@if(listing){
|
||||||
@@ -12,8 +14,8 @@
|
|||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-500 w-full md:w-2 font-medium flex align-self-start">Description</div>
|
<div class="text-500 w-full md:w-2 font-medium flex">Description</div>
|
||||||
<div class="text-900 w-full md:w-10 line-height-3" [innerHTML]="description"></div>
|
<div class="text-900 w-full md:w-10 line-height-3 flex flex-column" [innerHTML]="description"></div>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Category</div>
|
<div class="text-500 w-full md:w-2 font-medium">Category</div>
|
||||||
@@ -45,19 +47,27 @@
|
|||||||
<div class="text-500 w-full md:w-2 font-medium">Employees</div>
|
<div class="text-500 w-full md:w-2 font-medium">Employees</div>
|
||||||
<div class="text-900 w-full md:w-10">{{ listing.employees }}</div>
|
<div class="text-900 w-full md:w-10">{{ listing.employees }}</div>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium">Support & Training</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.supportAndTraining }}</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
|
<div class="text-500 w-full md:w-2 font-medium">Reason for Sale</div>
|
||||||
|
<div class="text-900 w-full md:w-10">{{ listing.reasonForSale }}</div>
|
||||||
|
</li>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Broker licensing</div>
|
<div class="text-500 w-full md:w-2 font-medium">Broker licensing</div>
|
||||||
<div class="text-900 w-full md:w-10">{{ listing.brokerLicencing }}</div>
|
<div class="text-900 w-full md:w-10">{{ listing.brokerLicencing }}</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@if(listing && user && (user.id===listing?.userId || isAdmin())){
|
@if(listing && listingUser && (listingUser?.email===user?.email || isAdmin())){
|
||||||
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editBusinessListing', listing.id]"></button>
|
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editBusinessListing', listing.id]"></button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<div class="surface-card p-4 border-round p-fluid">
|
<div class="surface-card p-4 border-round p-fluid">
|
||||||
<div class="font-medium text-xl text-primary text-900 mb-3">Contact The Author of This Listing</div>
|
<div class="font-medium text-xl text-primary text-900 mb-3">Contact the Author of this Listing</div>
|
||||||
<div class="font-italic text-sm text-900 mb-5">Please Include your contact info below:</div>
|
<div class="font-italic text-sm text-900 mb-5">Please include your contact info below</div>
|
||||||
<div class="grid formgrid p-fluid">
|
<div class="grid formgrid p-fluid">
|
||||||
<div class="field mb-4 col-12 md:col-6">
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
<label for="name" class="font-medium text-900">Your Name</label>
|
<label for="name" class="font-medium text-900">Your Name</label>
|
||||||
@@ -80,7 +90,14 @@
|
|||||||
<label for="notes" class="font-medium text-900">Questions/Comments</label>
|
<label for="notes" class="font-medium text-900">Questions/Comments</label>
|
||||||
<textarea id="notes" pInputTextarea [autoResize]="true" [rows]="5" [(ngModel)]="mailinfo.sender.comments"></textarea>
|
<textarea id="notes" pInputTextarea [autoResize]="true" [rows]="5" [(ngModel)]="mailinfo.sender.comments"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
|
@if(listingUser){
|
||||||
|
<div class="surface-border mb-4 col-12 flex align-items-center">
|
||||||
|
Listing by <a routerLink="/details-user/{{ listingUser.id }}" class="mr-2">{{ listingUser.firstname }} {{ listingUser.lastname }}</a>
|
||||||
|
@if(listingUser.hasCompanyLogo){
|
||||||
|
<img src="{{ env.imageBaseUrl }}/pictures/logo/{{ listingUser.id }}.avif?_ts={{ ts }}" class="mr-5 lg:mb-0" style="height: 30px; max-width: 100px" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<button pButton pRipple label="Submit" icon="pi pi-file" class="w-auto" (click)="mail()"></button>
|
<button pButton pRipple label="Submit" icon="pi pi-file" class="w-auto" (click)="mail()"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Location } from '@angular/common';
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import onChange from 'on-change';
|
import onChange from 'on-change';
|
||||||
import { MessageService } from 'primeng/api';
|
import { MessageService } from 'primeng/api';
|
||||||
import { GalleriaModule } from 'primeng/galleria';
|
import { GalleriaModule } from 'primeng/galleria';
|
||||||
import { lastValueFrom } from 'rxjs';
|
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 { ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { KeycloakUser, ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
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';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
@@ -48,8 +49,12 @@ export class DetailsBusinessListingComponent {
|
|||||||
criteria: ListingCriteria;
|
criteria: ListingCriteria;
|
||||||
mailinfo: MailInfo;
|
mailinfo: MailInfo;
|
||||||
environment = environment;
|
environment = environment;
|
||||||
user: User;
|
user: KeycloakUser;
|
||||||
|
listingUser: User;
|
||||||
description: SafeHtml;
|
description: SafeHtml;
|
||||||
|
private history: string[] = [];
|
||||||
|
ts = new Date().getTime();
|
||||||
|
env = environment;
|
||||||
constructor(
|
constructor(
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private listingsService: ListingsService,
|
private listingsService: ListingsService,
|
||||||
@@ -59,27 +64,30 @@ export class DetailsBusinessListingComponent {
|
|||||||
private mailService: MailService,
|
private mailService: MailService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
private location: Location,
|
public historyService: HistoryService,
|
||||||
|
public keycloakService: KeycloakService,
|
||||||
) {
|
) {
|
||||||
this.mailinfo = { sender: {}, userId: '', email: '' };
|
this.router.events.subscribe(event => {
|
||||||
this.userService.getUserObservable().subscribe(user => {
|
if (event instanceof NavigationEnd) {
|
||||||
this.user = user;
|
this.history.push(event.urlAfterRedirects);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
this.mailinfo = { sender: {}, userId: '', email: '', url: environment.mailinfoUrl };
|
||||||
|
this.user;
|
||||||
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business'));
|
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business'));
|
||||||
|
this.listingUser = await this.userService.getById(this.listing.userId);
|
||||||
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
||||||
}
|
}
|
||||||
back() {
|
|
||||||
this.location.back();
|
|
||||||
}
|
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
return this.userService.hasAdminRole();
|
return this.keycloakService.getUserRoles(true).includes('ADMIN');
|
||||||
}
|
}
|
||||||
async mail() {
|
async mail() {
|
||||||
this.mailinfo.email = this.user.email;
|
this.mailinfo.email = this.listingUser.email;
|
||||||
this.mailinfo.userId = this.listing.userId;
|
this.mailinfo.userId = this.listing.userId;
|
||||||
this.mailinfo.listing = this.listing;
|
this.mailinfo.listing = this.listing;
|
||||||
await this.mailService.mail(this.mailinfo);
|
await this.mailService.mail(this.mailinfo);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<div class="surface-ground h-full">
|
<div class="surface-ground h-full">
|
||||||
<div class="px-6 py-5">
|
<div class="px-6 py-5">
|
||||||
<div class="surface-card p-4 shadow-2 border-round">
|
<div class="surface-card p-4 shadow-2 border-round">
|
||||||
<div class="flex justify-content-between align-items-center align-content-center">
|
<div class="flex justify-content-between align-items-center align-content-center mb-2">
|
||||||
<div class="font-medium text-3xl text-900 mb-3">{{ listing?.title }}</div>
|
<div class="font-medium text-3xl text-900 mb-3">{{ listing?.title }}</div>
|
||||||
<!-- <button pButton pRipple type="button" label="Go back to listings" icon="pi pi-user-plus" class="mr-3 p-button-rounded"></button> -->
|
<!-- <button pButton pRipple type="button" label="Go back to listings" icon="pi pi-user-plus" class="mr-3 p-button-rounded"></button> -->
|
||||||
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
@if(historyService.canGoBack){
|
||||||
|
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="historyService.goBack()"></p-button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
|
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
|
||||||
@if(listing){
|
@if(listing){
|
||||||
@@ -12,7 +14,7 @@
|
|||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
<ul class="list-none p-0 m-0 border-top-1 border-300">
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-500 w-full md:w-2 font-medium flex align-self-start">Description</div>
|
<div class="text-500 w-full md:w-2 font-medium flex">Description</div>
|
||||||
<div class="text-900 w-full md:w-10 line-height-3" [innerHTML]="description"></div>
|
<div class="text-900 w-full md:w-10 line-height-3" [innerHTML]="description"></div>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
<li class="flex align-items-center py-3 px-2 flex-wrap surface-ground">
|
||||||
@@ -40,24 +42,26 @@
|
|||||||
<div class="text-900 w-full md:w-10">{{ listing.price | currency }}</div>
|
<div class="text-900 w-full md:w-10">{{ listing.price | currency }}</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p-galleria [value]="propertyImages" [showIndicators]="true" [showThumbnails]="false" [responsiveOptions]="responsiveOptions" [containerStyle]="{ 'max-width': '640px' }" [numVisible]="5">
|
|
||||||
<ng-template pTemplate="item" let-item>
|
@if(listing && listingUser && (listingUser?.email===user?.email || isAdmin())){
|
||||||
<img src="pictures/property/{{ listing.imagePath }}/{{ item }}" style="width: 100%" />
|
|
||||||
</ng-template>
|
|
||||||
</p-galleria>
|
|
||||||
@if(listing && user && (user.id===listing?.userId || isAdmin())){
|
|
||||||
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editCommercialPropertyListing', listing.id]"></button>
|
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editCommercialPropertyListing', listing.id]"></button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (mailinfo){
|
|
||||||
<div class="col-12 md:col-6">
|
<div class="col-12 md:col-6">
|
||||||
|
<p-galleria [value]="propertyImages" [showIndicators]="true" [showThumbnails]="false" [responsiveOptions]="responsiveOptions" [containerStyle]="{ 'max-width': '640px' }" [numVisible]="5">
|
||||||
|
<ng-template pTemplate="item" let-item>
|
||||||
|
<img src="{{ env.imageBaseUrl }}/pictures/property/{{ listing.imagePath }}/{{ item }}" style="width: 100%" />
|
||||||
|
</ng-template>
|
||||||
|
</p-galleria>
|
||||||
|
@if (mailinfo){
|
||||||
<div class="surface-card p-4 border-round p-fluid">
|
<div class="surface-card p-4 border-round p-fluid">
|
||||||
<div class="font-medium text-xl text-primary text-900 mb-3">Contact The Author of This Listing</div>
|
<div class="font-medium text-xl text-primary text-900 mb-3">Contact the Author of this Listing</div>
|
||||||
<div class="font-italic text-sm text-900 mb-5">Please Include your contact info below:</div>
|
<div class="font-italic text-sm text-900 mb-5">Please include your contact info below</div>
|
||||||
<div class="grid formgrid p-fluid">
|
<div class="grid formgrid p-fluid">
|
||||||
<div class="field mb-4 col-12 md:col-6">
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
<label for="name" class="font-medium text-900">Your Name</label>
|
<label for="name" class="font-medium text-900">Your Name</label>
|
||||||
<input id="name" type="text" pInputText [(ngModel)]="mailinfo.sender.name" />
|
<input [ngClass]="{ 'ng-invalid': containsError('name'), 'ng-dirty': containsError('name') }" id="name" type="text" pInputText [(ngModel)]="mailinfo.sender.name" />
|
||||||
</div>
|
</div>
|
||||||
<div class="field mb-4 col-12 md:col-6">
|
<div class="field mb-4 col-12 md:col-6">
|
||||||
<label for="email" class="font-medium text-900">Your Email</label>
|
<label for="email" class="font-medium text-900">Your Email</label>
|
||||||
@@ -76,13 +80,20 @@
|
|||||||
<label for="notes" class="font-medium text-900">Questions/Comments</label>
|
<label for="notes" class="font-medium text-900">Questions/Comments</label>
|
||||||
<textarea id="notes" pInputTextarea [autoResize]="true" [rows]="5" [(ngModel)]="mailinfo.sender.comments"></textarea>
|
<textarea id="notes" pInputTextarea [autoResize]="true" [rows]="5" [(ngModel)]="mailinfo.sender.comments"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
|
@if(listingUser){
|
||||||
|
<div class="surface-border mb-4 col-12 flex align-items-center">
|
||||||
|
Listing by <a routerLink="/details-user/{{ listingUser.id }}" class="mr-2">{{ listingUser.firstname }} {{ listingUser.lastname }}</a>
|
||||||
|
@if(listingUser.hasCompanyLogo){
|
||||||
|
<img src="{{ env.imageBaseUrl }}/pictures/logo/{{ listingUser.id }}.avif?_ts={{ ts }}" class="mr-5 lg:mb-0" style="height: 30px; max-width: 100px" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<button pButton pRipple label="Submit" icon="pi pi-file" class="w-auto" (click)="mail()"></button>
|
<button pButton pRipple label="Submit" icon="pi pi-file" class="w-auto" (click)="mail()"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import { Location } from '@angular/common';
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import onChange from 'on-change';
|
import onChange from 'on-change';
|
||||||
import { MessageService } from 'primeng/api';
|
import { MessageService } from 'primeng/api';
|
||||||
import { GalleriaModule } from 'primeng/galleria';
|
import { GalleriaModule } from 'primeng/galleria';
|
||||||
import { lastValueFrom } from 'rxjs';
|
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 { ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { ErrorResponse, KeycloakUser, ListingCriteria, MailInfo } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
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';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.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';
|
||||||
import { getCriteriaStateObject, getSessionStorageHandler } from '../../../utils/utils';
|
import { getCriteriaStateObject, getSessionStorageHandler, map2User } from '../../../utils/utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-details-commercial-property-listing',
|
selector: 'app-details-commercial-property-listing',
|
||||||
@@ -49,8 +50,12 @@ export class DetailsCommercialPropertyListingComponent {
|
|||||||
mailinfo: MailInfo;
|
mailinfo: MailInfo;
|
||||||
propertyImages: string[] = [];
|
propertyImages: string[] = [];
|
||||||
environment = environment;
|
environment = environment;
|
||||||
user: User;
|
user: KeycloakUser;
|
||||||
|
listingUser: User;
|
||||||
description: SafeHtml;
|
description: SafeHtml;
|
||||||
|
ts = new Date().getTime();
|
||||||
|
env = environment;
|
||||||
|
errorResponse: ErrorResponse;
|
||||||
constructor(
|
constructor(
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private listingsService: ListingsService,
|
private listingsService: ListingsService,
|
||||||
@@ -60,31 +65,38 @@ export class DetailsCommercialPropertyListingComponent {
|
|||||||
private mailService: MailService,
|
private mailService: MailService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
private location: Location,
|
public historyService: HistoryService,
|
||||||
|
public keycloakService: KeycloakService,
|
||||||
) {
|
) {
|
||||||
this.mailinfo = { sender: {}, userId: '', email: '' };
|
this.mailinfo = { sender: {}, userId: '', email: '', url: environment.mailinfoUrl };
|
||||||
this.userService.getUserObservable().subscribe(user => {
|
|
||||||
this.user = user;
|
|
||||||
});
|
|
||||||
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
const token = await this.keycloakService.getToken();
|
||||||
|
this.user = map2User(token);
|
||||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
|
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
|
||||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
||||||
|
this.listingUser = await this.userService.getById(this.listing.userId);
|
||||||
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
this.description = this.sanitizer.bypassSecurityTrustHtml(this.listing.description);
|
||||||
}
|
}
|
||||||
back() {
|
|
||||||
this.location.back();
|
|
||||||
}
|
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
return this.userService.hasAdminRole();
|
return this.keycloakService.getUserRoles(true).includes('ADMIN');
|
||||||
}
|
}
|
||||||
async mail() {
|
async mail() {
|
||||||
this.mailinfo.email = this.user.email;
|
this.mailinfo.email = this.listingUser.email;
|
||||||
this.mailinfo.userId = this.listing.userId;
|
this.mailinfo.userId = this.listing.userId;
|
||||||
this.mailinfo.listing = this.listing;
|
this.mailinfo.listing = this.listing;
|
||||||
await this.mailService.mail(this.mailinfo);
|
const result = await this.mailService.mail(this.mailinfo);
|
||||||
|
if (result) {
|
||||||
|
this.errorResponse = result as ErrorResponse;
|
||||||
|
} else {
|
||||||
|
this.errorResponse = null;
|
||||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Your message has been sent to the creator of the listing', life: 3000 });
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Your message has been sent to the creator of the listing', life: 3000 });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
containsError(fieldname: string) {
|
||||||
|
return this.errorResponse?.fields.map(f => f.fieldname).includes(fieldname);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,11 @@
|
|||||||
<div class="px-6 py-5">
|
<div class="px-6 py-5">
|
||||||
@if (user){
|
@if (user){
|
||||||
<div class="surface-card p-4 shadow-2 border-round">
|
<div class="surface-card p-4 shadow-2 border-round">
|
||||||
<!-- <div class="flex justify-content-between align-items-center align-content-center">
|
|
||||||
<div class="font-medium text-3xl text-900 mb-3">{{listing?.title}}</div>
|
|
||||||
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
|
||||||
</div> -->
|
|
||||||
<div class="surface-section px-6 pt-5">
|
<div class="surface-section px-6 pt-5">
|
||||||
<div class="flex align-items-start flex-column lg:flex-row lg:justify-content-between">
|
<div class="flex align-items-start flex-column lg:flex-row lg:justify-content-between">
|
||||||
<div class="flex align-items-start flex-column md:flex-row">
|
<div class="flex align-items-start flex-column md:flex-row">
|
||||||
@if(user.hasProfile){
|
@if(user.hasProfile){
|
||||||
<img src="pictures//profile/{{ user.id }}.avif" class="mr-5 mb-3 lg:mb-0" style="width: 90px" />
|
<img src="{{ env.imageBaseUrl }}/pictures//profile/{{ user.id }}.avif?_ts={{ ts }}" class="mr-5 mb-3 lg:mb-0" style="width: 90px" />
|
||||||
} @else {
|
} @else {
|
||||||
<img src="assets/images/person_placeholder.jpg" class="mr-5 mb-3 lg:mb-0" style="width: 90px" />
|
<img src="assets/images/person_placeholder.jpg" class="mr-5 mb-3 lg:mb-0" style="width: 90px" />
|
||||||
}
|
}
|
||||||
@@ -24,17 +20,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mr-5 mt-3">
|
<div class="mr-5 mt-3">
|
||||||
<span class="font-medium text-500">For Sale</span>
|
<span class="font-medium text-500">For Sale</span>
|
||||||
<div class="text-700 mt-2">12</div>
|
<div class="text-700 mt-2">{{ businessListings.length + commercialPropListings.length }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mr-5 mt-3">
|
<!-- <div class="mr-5 mt-3">
|
||||||
<span class="font-medium text-500">Sold</span>
|
<span class="font-medium text-500">Sold</span>
|
||||||
<div class="text-700 mt-2">8</div>
|
<div class="text-700 mt-2">8</div>
|
||||||
</div>
|
</div> -->
|
||||||
<div class="flex align-items-center mt-3">
|
<div class="flex align-items-center mt-3">
|
||||||
<!-- <span class="font-medium text-500">Logo</span> -->
|
<!-- <span class="font-medium text-500">Logo</span> -->
|
||||||
<div>
|
<div>
|
||||||
@if(user.hasCompanyLogo){
|
@if(user.hasCompanyLogo){
|
||||||
<img src="pictures/logo/{{ user.id }}.avif" class="mr-5 lg:mb-0" style="height: 60px; max-width: 100px" />
|
<img src="{{ env.imageBaseUrl }}/pictures/logo/{{ user.id }}.avif?_ts={{ ts }}" class="mr-5 lg:mb-0" style="height: 60px; max-width: 100px" />
|
||||||
}
|
}
|
||||||
<!-- <img *ngIf="!user.hasCompanyLogo" src="assets/images/placeholder.png"
|
<!-- <img *ngIf="!user.hasCompanyLogo" src="assets/images/placeholder.png"
|
||||||
class="mr-5 lg:mb-0" style="height:60px;max-width:100px" /> -->
|
class="mr-5 lg:mb-0" style="height:60px;max-width:100px" /> -->
|
||||||
@@ -44,7 +40,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="back()"></p-button>
|
@if(historyService.canGoBack){
|
||||||
|
<p-button icon="pi pi-times" [rounded]="true" severity="danger" (click)="historyService.goBack()"></p-button>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-700 line-height-3 text-l font-semibold">{{ user.description }}</p>
|
<p class="mt-2 text-700 line-height-3 text-l font-semibold">{{ user.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,10 +72,10 @@
|
|||||||
<div class="text-900 w-full md:w-10" [innerHTML]="offeredServices"></div>
|
<div class="text-900 w-full md:w-10" [innerHTML]="offeredServices"></div>
|
||||||
</li>
|
</li>
|
||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Areas we serve</div>
|
<div class="text-500 w-full md:w-2 font-medium">Areas (Counties) we serve</div>
|
||||||
<div class="text-900 w-full md:w-10">
|
<div class="text-900 w-full md:w-10">
|
||||||
@for (area of user.areasServed; track area) {
|
@for (area of user.areasServed; track area) {
|
||||||
<p-tag styleClass="mr-2" [value]="area" [rounded]="true"></p-tag>
|
<p-tag styleClass="mr-2" value="{{ area.county }}-{{ area.state }}" [rounded]="true"></p-tag>
|
||||||
}
|
}
|
||||||
<!-- <p-tag styleClass="mr-2" severity="success" value="Javascript" [rounded]="true"></p-tag>
|
<!-- <p-tag styleClass="mr-2" severity="success" value="Javascript" [rounded]="true"></p-tag>
|
||||||
<p-tag styleClass="mr-2" severity="danger" value="Python" [rounded]="true"></p-tag>
|
<p-tag styleClass="mr-2" severity="danger" value="Python" [rounded]="true"></p-tag>
|
||||||
@@ -87,8 +85,8 @@
|
|||||||
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
<li class="flex align-items-center py-3 px-2 flex-wrap">
|
||||||
<div class="text-500 w-full md:w-2 font-medium">Licensed In</div>
|
<div class="text-500 w-full md:w-2 font-medium">Licensed In</div>
|
||||||
<div class="text-900 w-full md:w-10">
|
<div class="text-900 w-full md:w-10">
|
||||||
@for (license of userLicensedIn; track license) {
|
@for (license of user.licensedIn; track license) {
|
||||||
<div>{{ license.name }} : {{ license.value }}</div>
|
<p-tag styleClass="mr-2" value="{{ license.registerNo }}-{{ license.state }}" [rounded]="true" severity="success"></p-tag>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -123,7 +121,7 @@
|
|||||||
<div class="p-3 border-1 surface-border border-round surface-card">
|
<div class="p-3 border-1 surface-border border-round surface-card">
|
||||||
<div class="text-900 mb-2 flex align-items-center">
|
<div class="text-900 mb-2 flex align-items-center">
|
||||||
@if (listing.imageOrder?.length>0){
|
@if (listing.imageOrder?.length>0){
|
||||||
<img src="pictures/property/{{ listing.imagePath }}/{{ listing.imageOrder[0] }}" class="mr-3" style="width: 45px; height: 45px" />
|
<img src="{{ env.imageBaseUrl }}/pictures/property/{{ listing.imagePath }}/{{ listing.imageOrder[0] }}?_ts={{ ts }}" class="mr-3" style="width: 45px; height: 45px" />
|
||||||
} @else {
|
} @else {
|
||||||
<img src="assets/images/placeholder_properties.jpg" class="mr-3" style="width: 45px; height: 45px" />
|
<img src="assets/images/placeholder_properties.jpg" class="mr-3" style="width: 45px; height: 45px" />
|
||||||
}
|
}
|
||||||
@@ -140,8 +138,8 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if( user?.id===(user$| async)?.id || isAdmin()){
|
@if( user?.email===keycloakUser?.email || isAdmin()){
|
||||||
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/account']"></button>
|
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/account', user.id]"></button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import { Location } from '@angular/common';
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { MessageService } from 'primeng/api';
|
import { MessageService } from 'primeng/api';
|
||||||
import { GalleriaModule } from 'primeng/galleria';
|
import { GalleriaModule } from 'primeng/galleria';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { KeyValue, ListingCriteria } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { KeycloakUser, ListingCriteria } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
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';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.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';
|
||||||
|
import { map2User } from '../../../utils/utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-details-user',
|
selector: 'app-details-user',
|
||||||
@@ -25,14 +27,16 @@ import { SharedModule } from '../../../shared/shared/shared.module';
|
|||||||
export class DetailsUserComponent {
|
export class DetailsUserComponent {
|
||||||
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
|
||||||
user: User;
|
user: User;
|
||||||
user$: Observable<User>;
|
user$: Observable<KeycloakUser>;
|
||||||
|
keycloakUser: KeycloakUser;
|
||||||
environment = environment;
|
environment = environment;
|
||||||
criteria: ListingCriteria;
|
criteria: ListingCriteria;
|
||||||
businessListings: BusinessListing[];
|
businessListings: BusinessListing[];
|
||||||
commercialPropListings: CommercialPropertyListing[];
|
commercialPropListings: CommercialPropertyListing[];
|
||||||
companyOverview: SafeHtml;
|
companyOverview: SafeHtml;
|
||||||
offeredServices: SafeHtml;
|
offeredServices: SafeHtml;
|
||||||
userLicensedIn: KeyValue[];
|
ts = new Date().getTime();
|
||||||
|
env = environment;
|
||||||
constructor(
|
constructor(
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
@@ -42,26 +46,25 @@ export class DetailsUserComponent {
|
|||||||
public selectOptions: SelectOptionsService,
|
public selectOptions: SelectOptionsService,
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
private imageService: ImageService,
|
private imageService: ImageService,
|
||||||
private location: Location,
|
public historyService: HistoryService,
|
||||||
|
public keycloakService: KeycloakService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.user = await this.userService.getById(this.id);
|
this.user = await this.userService.getById(this.id);
|
||||||
this.userLicensedIn = this.user.licensedIn.map(l => {
|
this.user.email;
|
||||||
return { name: l.split('|')[0], value: l.split('|')[1] };
|
|
||||||
});
|
|
||||||
const results = await Promise.all([await this.listingsService.getListingByUserId(this.id, 'business'), await this.listingsService.getListingByUserId(this.id, 'commercialProperty')]);
|
const results = await Promise.all([await this.listingsService.getListingByUserId(this.id, 'business'), await this.listingsService.getListingByUserId(this.id, 'commercialProperty')]);
|
||||||
// Zuweisen der Ergebnisse zu den Member-Variablen der Klasse
|
// Zuweisen der Ergebnisse zu den Member-Variablen der Klasse
|
||||||
this.businessListings = results[0];
|
this.businessListings = results[0];
|
||||||
this.commercialPropListings = results[1];
|
this.commercialPropListings = results[1];
|
||||||
this.user$ = this.userService.getUserObservable();
|
//this.user$ = this.userService.getUserObservable();
|
||||||
|
const token = await this.keycloakService.getToken();
|
||||||
|
this.keycloakUser = map2User(token);
|
||||||
this.companyOverview = this.sanitizer.bypassSecurityTrustHtml(this.user.companyOverview);
|
this.companyOverview = this.sanitizer.bypassSecurityTrustHtml(this.user.companyOverview);
|
||||||
this.offeredServices = this.sanitizer.bypassSecurityTrustHtml(this.user.offeredServices);
|
this.offeredServices = this.sanitizer.bypassSecurityTrustHtml(this.user.offeredServices);
|
||||||
}
|
}
|
||||||
back() {
|
|
||||||
this.location.back();
|
|
||||||
}
|
|
||||||
isAdmin() {
|
isAdmin() {
|
||||||
return this.userService.hasAdminRole();
|
return this.keycloakService.getUserRoles(true).includes('ADMIN');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
<div class="align-items-center flex-grow-1 justify-content-between hidden lg:flex absolute lg:static w-full left-0 top-100 px-6 lg:px-0 shadow-2 lg:shadow-none z-2">
|
<div class="align-items-center flex-grow-1 justify-content-between hidden lg:flex absolute lg:static w-full left-0 top-100 px-6 lg:px-0 shadow-2 lg:shadow-none z-2">
|
||||||
<section></section>
|
<section></section>
|
||||||
<div class="flex justify-content-between lg:block border-top-1 lg:border-top-none border-gray-800 py-3 lg:py-0 mt-3 lg:mt-0">
|
<div class="flex justify-content-between lg:block border-top-1 lg:border-top-none border-gray-800 py-3 lg:py-0 mt-3 lg:mt-0">
|
||||||
@if(userService.isLoggedIn()){
|
@if(keycloakService.isLoggedIn()){
|
||||||
<p-button label="Account" class="ml-3 font-bold" [outlined]="true" severity="secondary" [routerLink]="['/account']"></p-button>
|
<p-button label="Account" class="ml-3 font-bold" [outlined]="true" severity="secondary" [routerLink]="['/account']"></p-button>
|
||||||
} @else {
|
} @else {
|
||||||
<p-button label="Log In" class="ml-3 font-bold" [outlined]="true" severity="secondary" (click)="login()"></p-button>
|
<p-button label="Log In" class="ml-3 font-bold" [outlined]="true" severity="secondary" (click)="login()"></p-button>
|
||||||
|
<p-button label="Register" class="ml-3 font-bold" [outlined]="true" severity="secondary" (click)="register()"></p-button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,9 +46,29 @@
|
|||||||
</div>
|
</div>
|
||||||
<div [ngClass]="{ 'mt-5': activeTabAction === 'business', 'mt-11': activeTabAction === 'commercialProperty', 'mt-22': activeTabAction === 'broker' }">
|
<div [ngClass]="{ 'mt-5': activeTabAction === 'business', 'mt-11': activeTabAction === 'commercialProperty', 'mt-22': activeTabAction === 'broker' }">
|
||||||
<div class="flex flex-column align-items-right gap-3 px-2 py-3 my-3 surface-border">
|
<div class="flex flex-column align-items-right gap-3 px-2 py-3 my-3 surface-border">
|
||||||
<p-dropdown [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value" [showClear]="true" placeholder="State" [style]="{ width: '200px' }"></p-dropdown>
|
<p-dropdown
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
|
[options]="states"
|
||||||
|
[(ngModel)]="criteria.state"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="State"
|
||||||
|
[style]="{ width: '200px' }"
|
||||||
|
></p-dropdown>
|
||||||
@if(activeTabAction === 'business'){
|
@if(activeTabAction === 'business'){
|
||||||
<p-dropdown [options]="selectOptions.typesOfBusiness" [(ngModel)]="criteria.type" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Category" [style]="{ width: '200px' }"></p-dropdown>
|
<p-dropdown
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
|
[options]="selectOptions.typesOfBusiness"
|
||||||
|
[(ngModel)]="criteria.type"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="Category"
|
||||||
|
[style]="{ width: '200px' }"
|
||||||
|
></p-dropdown>
|
||||||
} @if(activeTabAction === 'commercialProperty'){
|
} @if(activeTabAction === 'commercialProperty'){
|
||||||
<p-dropdown
|
<p-dropdown
|
||||||
[options]="selectOptions.typesOfCommercialProperty"
|
[options]="selectOptions.typesOfCommercialProperty"
|
||||||
@@ -73,7 +94,7 @@
|
|||||||
pRipple
|
pRipple
|
||||||
label="Create Your Listing"
|
label="Create Your Listing"
|
||||||
class="block mt-7 mb-7 lg:mb-0 p-button-rounded p-button-success p-button-lg font-medium"
|
class="block mt-7 mb-7 lg:mb-0 p-button-rounded p-button-success p-button-lg font-medium"
|
||||||
[routerLink]="userService.isLoggedIn() ? '/createBusinessListing' : '/pricing'"
|
[routerLink]="keycloakService.isLoggedIn() ? '/createBusinessListing' : '/pricing'"
|
||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,18 +2,16 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import onChange from 'on-change';
|
import onChange from 'on-change';
|
||||||
import { ButtonModule } from 'primeng/button';
|
import { ButtonModule } from 'primeng/button';
|
||||||
import { CheckboxModule } from 'primeng/checkbox';
|
import { CheckboxModule } from 'primeng/checkbox';
|
||||||
import { DropdownModule } from 'primeng/dropdown';
|
import { DropdownModule } from 'primeng/dropdown';
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
import { StyleClassModule } from 'primeng/styleclass';
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { User } from '../../../../../bizmatch-server/src/models/db.model';
|
|
||||||
import { ListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
import { ListingCriteria } from '../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { ListingsService } from '../../services/listings.service';
|
import { ListingsService } from '../../services/listings.service';
|
||||||
import { SelectOptionsService } from '../../services/select-options.service';
|
import { SelectOptionsService } from '../../services/select-options.service';
|
||||||
import { UserService } from '../../services/user.service';
|
|
||||||
import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from '../../utils/utils';
|
import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from '../../utils/utils';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
@@ -28,14 +26,12 @@ export class HomeComponent {
|
|||||||
maxPrice: string;
|
maxPrice: string;
|
||||||
minPrice: string;
|
minPrice: string;
|
||||||
criteria: ListingCriteria;
|
criteria: ListingCriteria;
|
||||||
user$: Observable<User>;
|
|
||||||
states = [];
|
states = [];
|
||||||
public constructor(private router: Router, private activatedRoute: ActivatedRoute, public selectOptions: SelectOptionsService, public userService: UserService, private listingsService: ListingsService) {
|
public constructor(private router: Router, private activatedRoute: ActivatedRoute, public selectOptions: SelectOptionsService, public keycloakService: KeycloakService, private listingsService: ListingsService) {
|
||||||
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
resetCriteria(this.criteria);
|
resetCriteria(this.criteria);
|
||||||
}
|
}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
this.user$ = this.userService.getUserObservable();
|
|
||||||
if (this.activeTabAction === 'business' || this.activeTabAction === 'commercialProperty') {
|
if (this.activeTabAction === 'business' || this.activeTabAction === 'commercialProperty') {
|
||||||
const statesResult = await this.listingsService.getAllStates(this.activeTabAction);
|
const statesResult = await this.listingsService.getAllStates(this.activeTabAction);
|
||||||
this.states = statesResult.map(s => s.state).map(ls => ({ name: this.selectOptions.getState(ls as string), value: ls }));
|
this.states = statesResult.map(s => s.state).map(ls => ({ name: this.selectOptions.getState(ls as string), value: ls }));
|
||||||
@@ -58,6 +54,11 @@ export class HomeComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
login() {
|
login() {
|
||||||
this.userService.login(window.location.href);
|
this.keycloakService.login({
|
||||||
|
redirectUri: window.location.href,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
register() {
|
||||||
|
this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,20 +3,10 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="grid p-4 align-items-center">
|
<div class="grid p-4 align-items-center">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<p-dropdown
|
<p-dropdown [filter]="true" filterBy="name" [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Location" [style]="{ width: '100%' }">
|
||||||
[filter]="true"
|
|
||||||
filterBy="name"
|
|
||||||
[options]="selectOptions.states"
|
|
||||||
[(ngModel)]="criteria.state"
|
|
||||||
optionLabel="name"
|
|
||||||
optionValue="value"
|
|
||||||
[showClear]="true"
|
|
||||||
placeholder="Location"
|
|
||||||
[style]="{ width: '100%' }"
|
|
||||||
>
|
|
||||||
<ng-template let-state pTemplate="item">
|
<ng-template let-state pTemplate="item">
|
||||||
<div class="flex align-items-center gap-2">
|
<div class="flex align-items-center gap-2">
|
||||||
<div>{{ state.name }}</div>
|
<div>{{ state.name }} ({{ state.count }})</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</p-dropdown>
|
</p-dropdown>
|
||||||
@@ -42,7 +32,7 @@
|
|||||||
<div class="surface-card p-4 flex flex-column align-items-center md:flex-row md:align-items-stretch h-full">
|
<div class="surface-card p-4 flex flex-column align-items-center md:flex-row md:align-items-stretch h-full">
|
||||||
<span>
|
<span>
|
||||||
@if(user.hasProfile){
|
@if(user.hasProfile){
|
||||||
<img src="pictures/profile/{{ user.id }}.avif" class="w-5rem" />
|
<img src="{{ env.imageBaseUrl }}/pictures/profile/{{ user.id }}.avif?_ts={{ ts }}" class="w-5rem" />
|
||||||
} @else {
|
} @else {
|
||||||
<img src="assets/images/person_placeholder.jpg" class="w-5rem" />
|
<img src="assets/images/person_placeholder.jpg" class="w-5rem" />
|
||||||
}
|
}
|
||||||
@@ -55,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-3 text-right flex justify-content-between align-items-center">
|
<div class="px-4 py-3 text-right flex justify-content-between align-items-center">
|
||||||
@if(user.hasCompanyLogo){
|
@if(user.hasCompanyLogo){
|
||||||
<img src="pictures/logo/{{ user.id }}.avif" class="rounded-image" />
|
<img src="{{ env.imageBaseUrl }}/pictures/logo/{{ user.id }}.avif?_ts={{ ts }}" class="rounded-image" />
|
||||||
} @else {
|
} @else {
|
||||||
<img src="assets/images/placeholder.png" class="rounded-image" />
|
<img src="assets/images/placeholder.png" class="rounded-image" />
|
||||||
}
|
}
|
||||||
@@ -65,7 +55,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
|
<div class="mb-2 surface-200 flex align-items-center justify-content-center paginator-bar">
|
||||||
<div class="mx-1 text-color">Total number of Professionals/Brokers: {{ totalRecords }}</div>
|
<div class="mx-1 text-color">Total number of Professionals/Brokers: {{ totalRecords }}</div>
|
||||||
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
|
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from
|
|||||||
NgOptimizedImage,
|
NgOptimizedImage,
|
||||||
],
|
],
|
||||||
templateUrl: './broker-listings.component.html',
|
templateUrl: './broker-listings.component.html',
|
||||||
styleUrl: './broker-listings.component.scss',
|
styleUrls: ['./broker-listings.component.scss', '../../pages.scss'],
|
||||||
})
|
})
|
||||||
export class BrokerListingsComponent {
|
export class BrokerListingsComponent {
|
||||||
environment = environment;
|
environment = environment;
|
||||||
@@ -58,6 +58,7 @@ export class BrokerListingsComponent {
|
|||||||
rows: number = 12;
|
rows: number = 12;
|
||||||
totalRecords: number = 0;
|
totalRecords: number = 0;
|
||||||
ts = new Date().getTime();
|
ts = new Date().getTime();
|
||||||
|
env = environment;
|
||||||
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -71,16 +72,18 @@ export class BrokerListingsComponent {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
this.criteria.type = undefined;
|
|
||||||
this.route.data.subscribe(async () => {
|
this.route.data.subscribe(async () => {
|
||||||
if (this.router.getCurrentNavigation().extras.state) {
|
if (this.router.getCurrentNavigation().extras.state) {
|
||||||
resetCriteria(this.criteria);
|
resetCriteria(this.criteria);
|
||||||
|
} else {
|
||||||
|
this.first = this.criteria.page * this.criteria.length;
|
||||||
|
this.rows = this.criteria.length;
|
||||||
}
|
}
|
||||||
this.init();
|
this.init();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const statesResult = await this.listingsService.getAllStates('business');
|
const statesResult = await this.userService.getAllStates();
|
||||||
this.states = statesResult.map(ls => ({ name: this.selectOptions.getState(ls.state as string), value: ls.state, count: ls.count }));
|
this.states = statesResult.map(ls => ({ name: this.selectOptions.getState(ls.state as string), value: ls.state, count: ls.count }));
|
||||||
}
|
}
|
||||||
async init() {
|
async init() {
|
||||||
|
|||||||
@@ -3,18 +3,7 @@
|
|||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="grid p-4 align-items-center">
|
<div class="grid p-4 align-items-center">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<p-dropdown
|
<p-dropdown [filter]="true" filterBy="name" [options]="states" [(ngModel)]="criteria.state" optionLabel="name" optionValue="value" [showClear]="true" placeholder="State" [style]="{ width: '100%' }">
|
||||||
[filter]="true"
|
|
||||||
filterBy="name"
|
|
||||||
[options]="states"
|
|
||||||
[(ngModel)]="criteria.state"
|
|
||||||
optionLabel="criteria.location"
|
|
||||||
optionLabel="name"
|
|
||||||
optionValue="value"
|
|
||||||
[showClear]="true"
|
|
||||||
placeholder="State"
|
|
||||||
[style]="{ width: '100%' }"
|
|
||||||
>
|
|
||||||
<ng-template let-state pTemplate="item">
|
<ng-template let-state pTemplate="item">
|
||||||
<div class="flex align-items-center gap-2">
|
<div class="flex align-items-center gap-2">
|
||||||
<div>{{ state.name }} ({{ state.count }})</div>
|
<div>{{ state.name }} ({{ state.count }})</div>
|
||||||
@@ -48,7 +37,6 @@
|
|||||||
</p-inputGroup>
|
</p-inputGroup>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1" pTooltip="Real Estate excluded/included" tooltipPosition="top">
|
<div class="col-1" pTooltip="Real Estate excluded/included" tooltipPosition="top">
|
||||||
<!-- <p-toggleButton [(ngModel)]="checked1" onLabel="Sustainable" offLabel="Unsustainable" onIcon="pi pi-check" offIcon="pi pi-times" styleClass="mb-3 lg:mt-0 mr-4 flex-shrink-0 w-12rem"></p-toggleButton> -->
|
|
||||||
<p-toggleButton [(ngModel)]="criteria.realEstateChecked" onLabel="RE incl." offLabel="RE excl."></p-toggleButton>
|
<p-toggleButton [(ngModel)]="criteria.realEstateChecked" onLabel="RE incl." offLabel="RE excl."></p-toggleButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-1">
|
<div class="col-1">
|
||||||
@@ -62,8 +50,8 @@
|
|||||||
<div class="grid">
|
<div class="grid">
|
||||||
@for (listing of listings; track listing.id) {
|
@for (listing of listings; track listing.id) {
|
||||||
<div class="col-12 lg:col-3 p-3">
|
<div class="col-12 lg:col-3 p-3">
|
||||||
<div class="shadow-2 border-round surface-card mb-3 h-full flex-column justify-content-between flex">
|
<div class="shadow-2 border-round surface-card h-full flex-column justify-content-between flex">
|
||||||
<div class="p-4 h-full flex flex-column">
|
<div class="p-4 flex flex-column relative">
|
||||||
<div class="flex align-items-center">
|
<div class="flex align-items-center">
|
||||||
<span [class]="selectOptions.getBgColorType(listing.type)" class="inline-flex border-circle align-items-center justify-content-center mr-3" style="width: 38px; height: 38px">
|
<span [class]="selectOptions.getBgColorType(listing.type)" class="inline-flex border-circle align-items-center justify-content-center mr-3" style="width: 38px; height: 38px">
|
||||||
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="pi text-xl"></i>
|
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="pi text-xl"></i>
|
||||||
@@ -76,8 +64,10 @@
|
|||||||
<p class="mt-0 mb-1 text-700 line-height-3">Net profit: {{ listing.cashFlow | currency }}</p>
|
<p class="mt-0 mb-1 text-700 line-height-3">Net profit: {{ listing.cashFlow | currency }}</p>
|
||||||
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{ selectOptions.getState(listing.state) }}</p>
|
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{ selectOptions.getState(listing.state) }}</p>
|
||||||
<p class="mt-0 mb-1 text-700 line-height-3">Established: {{ listing.established }}</p>
|
<p class="mt-0 mb-1 text-700 line-height-3">Established: {{ listing.established }}</p>
|
||||||
<div class="mt-auto ml-auto">
|
<div class="icon-pos">
|
||||||
<img src="pictures/logo/{{ listing.userId }}.avif" (error)="imageErrorHandler(listing)" class="rounded-image" />
|
<a routerLink="/details-user/{{ listing.userId }}" class="mr-2"
|
||||||
|
><img src="{{ env.imageBaseUrl }}/pictures/logo/{{ listing.userId }}.avif?_ts={{ ts }}" (error)="imageErrorHandler(listing)" class="rounded-image"
|
||||||
|
/></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 py-3 surface-100 text-left">
|
<div class="px-4 py-3 surface-100 text-left">
|
||||||
@@ -87,7 +77,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
|
<div class="mb-2 surface-200 flex align-items-center justify-content-center paginator-bar">
|
||||||
<div class="mx-1 text-color">Total number of Listings: {{ totalRecords }}</div>
|
<div class="mx-1 text-color">Total number of Listings: {{ totalRecords }}</div>
|
||||||
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
|
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,13 +10,17 @@
|
|||||||
}
|
}
|
||||||
::ng-deep p-paginator div {
|
::ng-deep p-paginator div {
|
||||||
background-color: var(--surface-200) !important;
|
background-color: var(--surface-200) !important;
|
||||||
// background-color: var(--surface-400) !important;
|
}
|
||||||
|
.icon-pos {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 1.5rem; /* Gleich dem Padding des Containers */
|
||||||
|
right: 1.5rem; /* Gleich dem Padding des Containers */
|
||||||
}
|
}
|
||||||
.rounded-image {
|
.rounded-image {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
// width: 100px;
|
// width: 100px;
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
height: 45px;
|
height: 35px;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
padding: 1px 1px;
|
padding: 1px 1px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from
|
|||||||
TooltipModule,
|
TooltipModule,
|
||||||
],
|
],
|
||||||
templateUrl: './business-listings.component.html',
|
templateUrl: './business-listings.component.html',
|
||||||
styleUrl: './business-listings.component.scss',
|
styleUrls: ['./business-listings.component.scss', '../../pages.scss'],
|
||||||
})
|
})
|
||||||
export class BusinessListingsComponent {
|
export class BusinessListingsComponent {
|
||||||
environment = environment;
|
environment = environment;
|
||||||
@@ -56,6 +56,7 @@ export class BusinessListingsComponent {
|
|||||||
ts = new Date().getTime();
|
ts = new Date().getTime();
|
||||||
first: number = 0;
|
first: number = 0;
|
||||||
rows: number = 12;
|
rows: number = 12;
|
||||||
|
env = environment;
|
||||||
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
public category: 'business' | 'commercialProperty' | 'professionals_brokers' | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -68,10 +69,12 @@ export class BusinessListingsComponent {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
this.criteria.type = undefined;
|
|
||||||
this.route.data.subscribe(async () => {
|
this.route.data.subscribe(async () => {
|
||||||
if (this.router.getCurrentNavigation().extras.state) {
|
if (this.router.getCurrentNavigation().extras.state) {
|
||||||
resetCriteria(this.criteria);
|
resetCriteria(this.criteria);
|
||||||
|
} else {
|
||||||
|
this.first = this.criteria.page * this.criteria.length;
|
||||||
|
this.rows = this.criteria.length;
|
||||||
}
|
}
|
||||||
this.init();
|
this.init();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<article class="flex flex-column md:flex-row w-full gap-3 p-3 surface-card">
|
<article class="flex flex-column md:flex-row w-full gap-3 p-3 surface-card">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@if (listing.imageOrder?.length>0){
|
@if (listing.imageOrder?.length>0){
|
||||||
<img src="pictures/property/{{ listing.imagePath }}/{{ listing.imageOrder[0] }}" alt="Image" class="border-round w-full h-full md:w-12rem md:h-9rem" />
|
<img src="{{ env.imageBaseUrl }}/pictures/property/{{ listing.imagePath }}/{{ listing.imageOrder[0] }}?_ts={{ ts }}" alt="Image" class="border-round w-full h-full md:w-12rem md:h-9rem" />
|
||||||
} @else {
|
} @else {
|
||||||
<img src="assets/images/placeholder_properties.jpg" alt="Image" class="border-round w-full h-full md:w-12rem md:h-9rem" />
|
<img src="assets/images/placeholder_properties.jpg" alt="Image" class="border-round w-full h-full md:w-12rem md:h-9rem" />
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
|
<div class="mb-2 surface-200 flex align-items-center justify-content-center paginator-bar">
|
||||||
<!-- @if(listings && listings.length>12){ -->
|
<!-- @if(listings && listings.length>12){ -->
|
||||||
<div class="mx-1 text-color">Total number of Listings: {{ totalRecords }}</div>
|
<div class="mx-1 text-color">Total number of Listings: {{ totalRecords }}</div>
|
||||||
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
|
<p-paginator (onPageChange)="onPageChange($event)" [first]="first" [rows]="rows" [totalRecords]="totalRecords" [rowsPerPageOptions]="[12, 24, 48]"></p-paginator>
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
}
|
}
|
||||||
.search{
|
.search {
|
||||||
background-color: #343F69;
|
background-color: #343f69;
|
||||||
}
|
}
|
||||||
::ng-deep p-paginator div {
|
::ng-deep p-paginator div {
|
||||||
background-color: var(--surface-200) !important;
|
background-color: var(--surface-200) !important;
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
// width: 100px;
|
// width: 100px;
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
height: 45px;
|
height: 45px;
|
||||||
border: 1px solid rgba(0,0,0,0.2);
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
padding: 1px 1px;
|
padding: 1px 1px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@ import { getCriteriaStateObject, getSessionStorageHandler, resetCriteria } from
|
|||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, StyleClassModule, ToggleButtonModule, RouterModule, PaginatorModule, InputGroupModule],
|
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, StyleClassModule, ToggleButtonModule, RouterModule, PaginatorModule, InputGroupModule],
|
||||||
templateUrl: './commercial-property-listings.component.html',
|
templateUrl: './commercial-property-listings.component.html',
|
||||||
styleUrl: './commercial-property-listings.component.scss',
|
styleUrls: ['./commercial-property-listings.component.scss', '../../pages.scss'],
|
||||||
})
|
})
|
||||||
export class CommercialPropertyListingsComponent {
|
export class CommercialPropertyListingsComponent {
|
||||||
environment = environment;
|
environment = environment;
|
||||||
@@ -42,7 +42,7 @@ export class CommercialPropertyListingsComponent {
|
|||||||
state: string;
|
state: string;
|
||||||
totalRecords: number = 0;
|
totalRecords: number = 0;
|
||||||
ts = new Date().getTime();
|
ts = new Date().getTime();
|
||||||
|
env = environment;
|
||||||
constructor(
|
constructor(
|
||||||
public selectOptions: SelectOptionsService,
|
public selectOptions: SelectOptionsService,
|
||||||
private listingsService: ListingsService,
|
private listingsService: ListingsService,
|
||||||
@@ -53,10 +53,12 @@ export class CommercialPropertyListingsComponent {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
this.criteria = onChange(getCriteriaStateObject(), getSessionStorageHandler);
|
||||||
this.criteria.type = undefined;
|
|
||||||
this.route.data.subscribe(async () => {
|
this.route.data.subscribe(async () => {
|
||||||
if (this.router.getCurrentNavigation().extras.state) {
|
if (this.router.getCurrentNavigation().extras.state) {
|
||||||
resetCriteria(this.criteria);
|
resetCriteria(this.criteria);
|
||||||
|
} else {
|
||||||
|
this.first = this.criteria.page * this.criteria.length;
|
||||||
|
this.rows = this.criteria.length;
|
||||||
}
|
}
|
||||||
this.init();
|
this.init();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,12 +47,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a (click)="logout()" routerLinkActive="text-blue-500" pRipple class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline">
|
||||||
(click)="userService.logout()"
|
|
||||||
routerLinkActive="text-blue-500"
|
|
||||||
pRipple
|
|
||||||
class="flex align-items-center cursor-pointer p-3 border-round text-800 hover:surface-200 transition-duration-150 transition-colors no-underline"
|
|
||||||
>
|
|
||||||
<fa-icon [icon]="faRightFromBracket" class="mr-2 flex"></fa-icon>
|
<fa-icon [icon]="faRightFromBracket" class="mr-2 flex"></fa-icon>
|
||||||
<span class="font-medium hidden md:block">Logout</span>
|
<span class="font-medium hidden md:block">Logout</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,37 +1,27 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { ButtonModule } from 'primeng/button';
|
|
||||||
import { CheckboxModule } from 'primeng/checkbox';
|
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
|
||||||
import { StyleClassModule } from 'primeng/styleclass';
|
|
||||||
import { SelectOptionsService } from '../../services/select-options.service';
|
|
||||||
import { DropdownModule } from 'primeng/dropdown';
|
|
||||||
import { FormsModule } from '@angular/forms';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ToggleButtonModule } from 'primeng/togglebutton';
|
import { Component } from '@angular/core';
|
||||||
import { TagModule } from 'primeng/tag';
|
import { NavigationEnd, Router, RouterModule } from '@angular/router';
|
||||||
import data from '../../../assets/data/listings.json';
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||||
import { ActivatedRoute, NavigationEnd, Router, RouterModule } from '@angular/router';
|
|
||||||
import { InputTextareaModule } from 'primeng/inputtextarea';
|
|
||||||
import { ChipModule } from 'primeng/chip';
|
|
||||||
import { DividerModule } from 'primeng/divider';
|
|
||||||
import { RippleModule } from 'primeng/ripple';
|
|
||||||
import { faEnvelope } from '@fortawesome/free-regular-svg-icons';
|
import { faEnvelope } from '@fortawesome/free-regular-svg-icons';
|
||||||
import { faRightFromBracket } from '@fortawesome/free-solid-svg-icons';
|
import { faRightFromBracket } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { UserService } from '../../services/user.service';
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
import { DividerModule } from 'primeng/divider';
|
||||||
|
import { RippleModule } from 'primeng/ripple';
|
||||||
|
import { StyleClassModule } from 'primeng/styleclass';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'menu-account',
|
selector: 'menu-account',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, StyleClassModule, ButtonModule, DividerModule, RouterModule, RippleModule, FontAwesomeModule ],
|
imports: [CommonModule, StyleClassModule, ButtonModule, DividerModule, RouterModule, RippleModule, FontAwesomeModule],
|
||||||
templateUrl: './menu-account.component.html',
|
templateUrl: './menu-account.component.html',
|
||||||
styleUrl: './menu-account.component.scss'
|
styleUrl: './menu-account.component.scss',
|
||||||
})
|
})
|
||||||
export class MenuAccountComponent {
|
export class MenuAccountComponent {
|
||||||
activeLink: string;
|
activeLink: string;
|
||||||
faEnvelope=faEnvelope;
|
faEnvelope = faEnvelope;
|
||||||
faRightFromBracket=faRightFromBracket;
|
faRightFromBracket = faRightFromBracket;
|
||||||
constructor(private router: Router,public userService:UserService) {
|
constructor(private router: Router, public keycloakService: KeycloakService) {
|
||||||
// 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 => {
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
@@ -39,4 +29,8 @@ export class MenuAccountComponent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
logout() {
|
||||||
|
sessionStorage.removeItem('USERID');
|
||||||
|
this.keycloakService.logout(window.location.origin + '/home');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,13 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
:host ::ng-deep .paginator-bar {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0px;
|
||||||
|
& > button {
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* Schatten-/Unschärfe-Effekt */
|
||||||
|
}
|
||||||
|
& .p-paginator {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { SharedModule } from '../../shared/shared/shared.module';
|
import { SharedModule } from '../../shared/shared/shared.module';
|
||||||
import { UserService } from '../../services/user.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pricing',
|
selector: 'app-pricing',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule],
|
imports: [SharedModule],
|
||||||
templateUrl: './pricing.component.html',
|
templateUrl: './pricing.component.html',
|
||||||
styleUrl: './pricing.component.scss'
|
styleUrl: './pricing.component.scss',
|
||||||
})
|
})
|
||||||
export class PricingComponent {
|
export class PricingComponent {
|
||||||
constructor(private userService:UserService){}
|
constructor(public keycloakService: KeycloakService) {}
|
||||||
register(){
|
register() {
|
||||||
this.userService.register(`${window.location.origin}/account`);
|
this.keycloakService.register({ redirectUri: `${window.location.origin}/account` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,354 +1,191 @@
|
|||||||
<section id="content" role="main">
|
<section id="content" role="main">
|
||||||
<article
|
<article id="post-2" class="post-2 page type-page status-publish hentry pmpro-has-access">
|
||||||
id="post-2"
|
|
||||||
class="post-2 page type-page status-publish hentry pmpro-has-access"
|
|
||||||
>
|
|
||||||
<section class="entry-content">
|
<section class="entry-content">
|
||||||
<div class="container" style="padding: 3.5% 0 3.75% 0 !important">
|
<div class="container" style="padding: 3.5% 0 3.75% 0 !important">
|
||||||
<p>
|
<p>
|
||||||
<strong>Privacy Policy</strong><br />
|
<strong>Privacy Policy</strong><br />
|
||||||
We are committed to protecting your privacy. We have established this
|
We are committed to protecting your privacy. We have established this statement as a testament to our commitment to your privacy.
|
||||||
statement as a testament to our commitment to your privacy.
|
|
||||||
</p>
|
</p>
|
||||||
|
<p>This Privacy Policy relates to the use of any personal information you provide to us through this websites.</p>
|
||||||
<p>
|
<p>
|
||||||
This Privacy Policy relates to the use of any personal information you
|
By accepting the Privacy Policy during registration or the sending of an enquiry, you expressly consent to our collection, storage, use and disclosure of your personal information as described in this Privacy
|
||||||
provide to us through this websites.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
By accepting the Privacy Policy during registration or the sending of
|
|
||||||
an enquiry, you expressly consent to our collection, storage, use and
|
|
||||||
disclosure of your personal information as described in this Privacy
|
|
||||||
Policy.
|
Policy.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We may update our Privacy Policy from time to time. Our Privacy Policy
|
We may update our Privacy Policy from time to time. Our Privacy Policy was last updated in Febuary 2018 and is effective upon acceptance for new users. By continuing to use our websites or otherwise continuing
|
||||||
was last updated in Febuary 2018 and is effective upon acceptance for
|
|
||||||
new users. By continuing to use our websites or otherwise continuing
|
|
||||||
to deal with us, you accept this Privacy Policy.
|
to deal with us, you accept this Privacy Policy.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Collection of personal information</strong><br />
|
<strong>Collection of personal information</strong><br />
|
||||||
Anyone can browse our websites without revealing any personally
|
Anyone can browse our websites without revealing any personally identifiable information.
|
||||||
identifiable information.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
However, should you wish to contact a business for sale, a franchise
|
|
||||||
opportunity or an intermediary, we will require you to provide some
|
|
||||||
personal information.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Should you wish to advertise your services, your business (es) or your
|
|
||||||
franchise opportunity, we will require you to provide some personal
|
|
||||||
information.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
By providing personal information, you are consenting to the transfer
|
|
||||||
and storage of that information on our servers located in the United
|
|
||||||
States.
|
|
||||||
</p>
|
</p>
|
||||||
|
<p>However, should you wish to contact a business for sale, a franchise opportunity or an intermediary, we will require you to provide some personal information.</p>
|
||||||
|
<p>Should you wish to advertise your services, your business (es) or your franchise opportunity, we will require you to provide some personal information.</p>
|
||||||
|
<p>By providing personal information, you are consenting to the transfer and storage of that information on our servers located in the United States.</p>
|
||||||
<p>We may collect and store the following personal information:</p>
|
<p>We may collect and store the following personal information:</p>
|
||||||
<p>
|
<p>
|
||||||
Your name, email address, physical address, telephone numbers, and
|
Your name, email address, physical address, telephone numbers, and (depending on the service used), your business information, financial information, such as credit / payment card details;<br />
|
||||||
(depending on the service used), your business information, financial
|
transactional information based on your activities on the site; information that you disclose in a forum on any of our websites, feedback, correspondence through our websites, and correspondence sent to us;<br />
|
||||||
information, such as credit / payment card details;<br />
|
other information from your interaction with our websites, services, content and advertising, including computer and connection information, statistics on page views, traffic to and from the sites, ad data, IP
|
||||||
transactional information based on your activities on the site;
|
address and standard web log information;<br />
|
||||||
information that you disclose in a forum on any of our websites,
|
supplemental information from third parties (for example, if you incur a debt, we will generally conduct a credit check by obtaining additional information about you from a credit bureau, as permitted by law;
|
||||||
feedback, correspondence through our websites, and correspondence sent
|
or if the information you provide cannot be verified,<br />
|
||||||
to us;<br />
|
we may ask you to send us additional information, or to answer additional questions online to help verify your information).
|
||||||
other information from your interaction with our websites, services,
|
|
||||||
content and advertising, including computer and connection
|
|
||||||
information, statistics on page views, traffic to and from the sites,
|
|
||||||
ad data, IP address and standard web log information;<br />
|
|
||||||
supplemental information from third parties (for example, if you incur
|
|
||||||
a debt, we will generally conduct a credit check by obtaining
|
|
||||||
additional information about you from a credit bureau, as permitted by
|
|
||||||
law; or if the information you provide cannot be verified,<br />
|
|
||||||
we may ask you to send us additional information, or to answer
|
|
||||||
additional questions online to help verify your information).
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>How we use your information</strong><br />
|
<strong>How we use your information</strong><br />
|
||||||
The primary reason we collect your personal information is to improve
|
The primary reason we collect your personal information is to improve the services we deliver to you through our website. By registering or sending an enquiry through our website, you agree that we may use your
|
||||||
the services we deliver to you through our website. By registering or
|
|
||||||
sending an enquiry through our website, you agree that we may use your
|
|
||||||
personal information to:<br />
|
personal information to:<br />
|
||||||
provide the services and customer support you request;<br />
|
provide the services and customer support you request;<br />
|
||||||
connect you with relevant parties:<br />
|
connect you with relevant parties:<br />
|
||||||
If you are a buyer we will pass some or all of your details on to the
|
If you are a buyer we will pass some or all of your details on to the seller / intermediary along with any message you have typed. This allows the seller to contact you in order to pursue a possible sale of a
|
||||||
seller / intermediary along with any message you have typed. This
|
business;<br />
|
||||||
allows the seller to contact you in order to pursue a possible sale of
|
If you are a seller / intermediary, we will disclose your details where you have given us permission to do so;<br />
|
||||||
a business;<br />
|
|
||||||
If you are a seller / intermediary, we will disclose your details
|
|
||||||
where you have given us permission to do so;<br />
|
|
||||||
resolve disputes, collect fees, and troubleshoot problems;<br />
|
resolve disputes, collect fees, and troubleshoot problems;<br />
|
||||||
prevent potentially prohibited or illegal activities, and enforce our
|
prevent potentially prohibited or illegal activities, and enforce our Terms and Conditions;<br />
|
||||||
Terms and Conditions;<br />
|
customize, measure and improve our services, conduct internal market research, provide content and advertising;<br />
|
||||||
customize, measure and improve our services, conduct internal market
|
tell you about other Biz-Match products and services, target marketing, send you service updates, and promotional offers based on your communication preferences.
|
||||||
research, provide content and advertising;<br />
|
|
||||||
tell you about other Biz-Match products and services, target
|
|
||||||
marketing, send you service updates, and promotional offers based on
|
|
||||||
your communication preferences.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Our disclosure of your information</strong><br />
|
<strong>Our disclosure of your information</strong><br />
|
||||||
We may disclose personal information to respond to legal requirements,
|
We may disclose personal information to respond to legal requirements, enforce our policies, respond to claims that a listing or other content infringes the rights of others, or protect anyone’s rights,
|
||||||
enforce our policies, respond to claims that a listing or other
|
|
||||||
content infringes the rights of others, or protect anyone’s rights,
|
|
||||||
property, or safety.
|
property, or safety.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We may also share your personal information with<br />
|
We may also share your personal information with<br />
|
||||||
When you select to register an account as a business buyer, you
|
When you select to register an account as a business buyer, you provide your personal details and we will pass this on to a seller of a business or franchise when you request more information.
|
||||||
provide your personal details and we will pass this on to a seller of
|
|
||||||
a business or franchise when you request more information.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
When you select to register an account as a business broker or seller
|
When you select to register an account as a business broker or seller on the site, we provide a public platform on which to establish your business profile. This profile consists of pertinent facts about your
|
||||||
on the site, we provide a public platform on which to establish your
|
business along with your personal information; namely, the contact information you provide to facilitate contact between you and other users’ of the site. Direct email addresses and telephone numbers will not
|
||||||
business profile. This profile consists of pertinent facts about your
|
be publicly displayed unless you specifically include it on your profile.
|
||||||
business along with your personal information; namely, the contact
|
|
||||||
information you provide to facilitate contact between you and other
|
|
||||||
users’ of the site. Direct email addresses and telephone numbers will
|
|
||||||
not be publicly displayed unless you specifically include it on your
|
|
||||||
profile.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The information a user includes within the forums provided on the site
|
The information a user includes within the forums provided on the site is publicly available to other users’ of the site. Please be aware that any personal information you elect to provide in a public forum may
|
||||||
is publicly available to other users’ of the site. Please be aware
|
be used to send you unsolicited messages; we are not responsible for the personal information a user elects to disclose within their public profile, or in the private communications that users’ engage in on the
|
||||||
that any personal information you elect to provide in a public forum
|
site.
|
||||||
may be used to send you unsolicited messages; we are not responsible
|
|
||||||
for the personal information a user elects to disclose within their
|
|
||||||
public profile, or in the private communications that users’ engage in
|
|
||||||
on the site.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We post testimonials on the site obtained from users’. These
|
We post testimonials on the site obtained from users’. These testimonials may include the name, city, state or region and business of the user. We obtain permission from our users’ prior to posting their
|
||||||
testimonials may include the name, city, state or region and business
|
testimonials on the site. We are not responsible for any personal information a user selects to include within their testimonial.
|
||||||
of the user. We obtain permission from our users’ prior to posting
|
|
||||||
their testimonials on the site. We are not responsible for any
|
|
||||||
personal information a user selects to include within their
|
|
||||||
testimonial.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
When you elect to email a friend about the site, or a particular
|
When you elect to email a friend about the site, or a particular business, we request the third party’s email address to send this one time email. We do not share this information with any third parties for
|
||||||
business, we request the third party’s email address to send this one
|
their promotional purposes and only store the information to gauge the effectiveness of our referral program.
|
||||||
time email. We do not share this information with any third parties
|
|
||||||
for their promotional purposes and only store the information to gauge
|
|
||||||
the effectiveness of our referral program.
|
|
||||||
</p>
|
</p>
|
||||||
|
<p>We may share your personal information with our service providers where necessary. We employ the services of a payment processor to fulfil payment for services purchased on the site.</p>
|
||||||
<p>
|
<p>
|
||||||
We may share your personal information with our service providers
|
We works with a number of partners or affiliates, where we provide marketing services for these companies. These third party agents collect your personal information to facilitate your service request and the
|
||||||
where necessary. We employ the services of a payment processor to
|
information submitted here is governed by their privacy policy.
|
||||||
fulfil payment for services purchased on the site.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
We works with a number of partners or affiliates, where we provide
|
|
||||||
marketing services for these companies. These third party agents
|
|
||||||
collect your personal information to facilitate your service request
|
|
||||||
and the information submitted here is governed by their privacy
|
|
||||||
policy.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Masking Policy</strong><br />
|
<strong>Masking Policy</strong><br />
|
||||||
In some cases, where the third party agent collects your information,
|
In some cases, where the third party agent collects your information, the affiliate portal may appear within a BizMatch.net frame. It is presented as a BizMatch.net page for a streamlined user interface however
|
||||||
the affiliate portal may appear within a BizMatch.net frame. It is
|
the data collected on such pages is governed by the third party agent’s privacy policy.
|
||||||
presented as a BizMatch.net page for a streamlined user interface
|
|
||||||
however the data collected on such pages is governed by the third
|
|
||||||
party agent’s privacy policy.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Legal Disclosure</strong><br />
|
<strong>Legal Disclosure</strong><br />
|
||||||
In certain circumstances, we may be legally required to disclose
|
In certain circumstances, we may be legally required to disclose information collected on the site to law enforcement, government agencies or other third parties. We reserve the right to disclose information to
|
||||||
information collected on the site to law enforcement, government
|
our service providers and to law enforcement or government agencies where a formal request such as in response to a court order, subpoena or judicial proceeding is made. Where we believe in good faith that
|
||||||
agencies or other third parties. We reserve the right to disclose
|
disclosure of information is necessary to prevent imminent physical or financial harm, or loss, or in protecting against illegal activity on the site, we reserve to disclose information.
|
||||||
information to our service providers and to law enforcement or
|
|
||||||
government agencies where a formal request such as in response to a
|
|
||||||
court order, subpoena or judicial proceeding is made. Where we believe
|
|
||||||
in good faith that disclosure of information is necessary to prevent
|
|
||||||
imminent physical or financial harm, or loss, or in protecting against
|
|
||||||
illegal activity on the site, we reserve to disclose information.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Should the company undergo the merger, acquisition or sale of some or
|
Should the company undergo the merger, acquisition or sale of some or all of its assets, your personal information may likely be a part of the transferred assets. In such an event, your personal information on
|
||||||
all of its assets, your personal information may likely be a part of
|
the site, would be governed by this privacy statement; any changes to the privacy practices governing your information as a result of transfer would be relayed to you by means of a prominent notice on the Site,
|
||||||
the transferred assets. In such an event, your personal information on
|
or by email.
|
||||||
the site, would be governed by this privacy statement; any changes to
|
|
||||||
the privacy practices governing your information as a result of
|
|
||||||
transfer would be relayed to you by means of a prominent notice on the
|
|
||||||
Site, or by email.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Using information from BizMatch.net website</strong><br />
|
<strong>Using information from BizMatch.net website</strong><br />
|
||||||
In certain cases, (where you are receiving contact details of buyers
|
In certain cases, (where you are receiving contact details of buyers interested in your business opportunity or a business opportunity you represent), you must comply with data protection laws, and give other
|
||||||
interested in your business opportunity or a business opportunity you
|
users a chance to remove themselves from your database and a chance to review what information you have collected about them.
|
||||||
represent), you must comply with data protection laws, and give other
|
|
||||||
users a chance to remove themselves from your database and a chance to
|
|
||||||
review what information you have collected about them.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong
|
<strong>You agree to use BizMatch.net user information only for:</strong>
|
||||||
>You agree to use BizMatch.net user information only for:</strong
|
|
||||||
>
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
BizMatch.net transaction-related purposes that are not unsolicited
|
BizMatch.net transaction-related purposes that are not unsolicited commercial messages;<br />
|
||||||
commercial messages;<br />
|
|
||||||
using services offered through BizMatch.net, or<br />
|
using services offered through BizMatch.net, or<br />
|
||||||
other purposes that a user expressly chooses.
|
other purposes that a user expressly chooses.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Marketing</strong><br />
|
<strong>Marketing</strong><br />
|
||||||
We do not sell or rent your personal information to third parties for
|
We do not sell or rent your personal information to third parties for their marketing purposes without your explicit consent. Where you explicitly express your consent at the point of collection to receive
|
||||||
their marketing purposes without your explicit consent. Where you
|
offers from third party partners or affiliates, we will communicate to you on their behalf. We will not pass your information on.
|
||||||
explicitly express your consent at the point of collection to receive
|
|
||||||
offers from third party partners or affiliates, we will communicate to
|
|
||||||
you on their behalf. We will not pass your information on.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
You will receive email marketing communications from us throughout the
|
You will receive email marketing communications from us throughout the duration of your relationship with our websites. If you do not wish to receive marketing communications from us you may unsubscribe and /
|
||||||
duration of your relationship with our websites. If you do not wish to
|
or change your preferences at any time by following instructions included within a communication or emailing Customer Services.
|
||||||
receive marketing communications from us you may unsubscribe and / or
|
|
||||||
change your preferences at any time by following instructions included
|
|
||||||
within a communication or emailing Customer Services.
|
|
||||||
</p>
|
</p>
|
||||||
|
<p>If you have an account with one of our websites you can also log in and click the email preferences link to unsubscribe and / or change your preferences.</p>
|
||||||
<p>
|
<p>
|
||||||
If you have an account with one of our websites you can also log in
|
Please note that we reserve the right to send all website users notifications and administrative emails where necessary which are considered a part of the service. Given that these messages aren’t promotional
|
||||||
and click the email preferences link to unsubscribe and / or change
|
in nature, you will be unable to opt-out of them.
|
||||||
your preferences.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Please note that we reserve the right to send all website users
|
|
||||||
notifications and administrative emails where necessary which are
|
|
||||||
considered a part of the service. Given that these messages aren’t
|
|
||||||
promotional in nature, you will be unable to opt-out of them.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Cookies</strong><br />
|
<strong>Cookies</strong><br />
|
||||||
A cookie is a small text file written to your hard drive that contains
|
A cookie is a small text file written to your hard drive that contains information about you. Cookies do not contain any personal information about users. Once you close your browser or log out of the website,
|
||||||
information about you. Cookies do not contain any personal information
|
the cookie simply terminates. We use cookies so that we can personalise your experience of our websites.
|
||||||
about users. Once you close your browser or log out of the website,
|
|
||||||
the cookie simply terminates. We use cookies so that we can
|
|
||||||
personalise your experience of our websites.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you set up your browser to reject the cookie, you may still use the
|
If you set up your browser to reject the cookie, you may still use the website however; doing so may interfere with your use of some aspects of our websites. Some of our business partners use cookies on our
|
||||||
website however; doing so may interfere with your use of some aspects
|
site (for example, advertisers). We have no access to or control over these cookies.
|
||||||
of our websites. Some of our business partners use cookies on our site
|
|
||||||
(for example, advertisers). We have no access to or control over these
|
|
||||||
cookies.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
For more information about how BizMatch.net uses cookies please read
|
|
||||||
our Cookie Policy.
|
|
||||||
</p>
|
</p>
|
||||||
|
<p>For more information about how BizMatch.net uses cookies please read our Cookie Policy.</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Spam, spyware or spoofing</strong><br />
|
<strong>Spam, spyware or spoofing</strong><br />
|
||||||
We and our users do not tolerate spam. Make sure to set your email
|
We and our users do not tolerate spam. Make sure to set your email preferences so we can communicate with you, as you prefer. Please add us to your safe senders list. To report spam or spoof emails, please
|
||||||
preferences so we can communicate with you, as you prefer. Please add
|
contact us using the contact information provided in the Contact Us section of this privacy statement.
|
||||||
us to your safe senders list. To report spam or spoof emails, please
|
|
||||||
contact us using the contact information provided in the Contact Us
|
|
||||||
section of this privacy statement.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
You may not use our communication tools to send spam or otherwise send
|
You may not use our communication tools to send spam or otherwise send content that would breach our Terms and Conditions. We automatically scan and may manually filter messages to check for spam, viruses,
|
||||||
content that would breach our Terms and Conditions. We automatically
|
phishing attacks and other malicious activity or illegal or prohibited content. We may also store these messages for back up purposes only.
|
||||||
scan and may manually filter messages to check for spam, viruses,
|
|
||||||
phishing attacks and other malicious activity or illegal or prohibited
|
|
||||||
content. We may also store these messages for back up purposes only.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you send an email to an email address that is not registered in our
|
If you send an email to an email address that is not registered in our community, we do not permanently store that email or use that email address for any marketing purpose. We do not rent or sell these email
|
||||||
community, we do not permanently store that email or use that email
|
|
||||||
address for any marketing purpose. We do not rent or sell these email
|
|
||||||
addresses.
|
addresses.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Account protection</strong><br />
|
<strong>Account protection</strong><br />
|
||||||
Your password is the key to your account. Make sure this is stored
|
Your password is the key to your account. Make sure this is stored safely. Use unique numbers, letters and special characters, and do not disclose your password to anyone. If you do share your password or your
|
||||||
safely. Use unique numbers, letters and special characters, and do not
|
personal information with others, remember that you are responsible for all actions taken in the name of your account. If you lose control of your password, you may lose substantial control over your personal
|
||||||
disclose your password to anyone. If you do share your password or
|
information and may be subject to legally binding actions taken on your behalf. Therefore, if your password has been compromised for any reason, you should immediately notify us and change your password.
|
||||||
your personal information with others, remember that you are
|
|
||||||
responsible for all actions taken in the name of your account. If you
|
|
||||||
lose control of your password, you may lose substantial control over
|
|
||||||
your personal information and may be subject to legally binding
|
|
||||||
actions taken on your behalf. Therefore, if your password has been
|
|
||||||
compromised for any reason, you should immediately notify us and
|
|
||||||
change your password.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong
|
<strong>Accessing, reviewing and changing your personal information</strong><br />
|
||||||
>Accessing, reviewing and changing your personal information</strong
|
You can view and amend your personal information at any time by logging in to your account online. You must promptly update your personal information if it changes or is inaccurate.
|
||||||
><br />
|
|
||||||
You can view and amend your personal information at any time by
|
|
||||||
logging in to your account online. You must promptly update your
|
|
||||||
personal information if it changes or is inaccurate.
|
|
||||||
</p>
|
</p>
|
||||||
|
<p>If at any time you wish to close your account, please contact Customer Services and instruct us to do so. We will process your request as soon as we can.</p>
|
||||||
|
<p>You may also contact us at any time to find out what information we hold about you, what we do with it and ask us to update it for you.</p>
|
||||||
<p>
|
<p>
|
||||||
If at any time you wish to close your account, please contact Customer
|
We do retain personal information from closed accounts to comply with law, prevent fraud, collect any fees owed, resolve disputes, troubleshoot problems, assist with any investigations, enforce our Terms and
|
||||||
Services and instruct us to do so. We will process your request as
|
Conditions, and take other actions otherwise permitted by law.
|
||||||
soon as we can.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
You may also contact us at any time to find out what information we
|
|
||||||
hold about you, what we do with it and ask us to update it for you.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
We do retain personal information from closed accounts to comply with
|
|
||||||
law, prevent fraud, collect any fees owed, resolve disputes,
|
|
||||||
troubleshoot problems, assist with any investigations, enforce our
|
|
||||||
Terms and Conditions, and take other actions otherwise permitted by
|
|
||||||
law.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Security</strong><br />
|
<strong>Security</strong><br />
|
||||||
Your information is stored on our servers located in the USA. We treat
|
Your information is stored on our servers located in the USA. We treat data as an asset that must be protected and use a variety of tools (encryption, passwords, physical security, etc.) to protect your
|
||||||
data as an asset that must be protected and use a variety of tools
|
personal information against unauthorized access and disclosure. However, no method of security is 100% effective and while we take every measure to protect your personal information, we make no guarantees of
|
||||||
(encryption, passwords, physical security, etc.) to protect your
|
its absolute security.
|
||||||
personal information against unauthorized access and disclosure.
|
|
||||||
However, no method of security is 100% effective and while we take
|
|
||||||
every measure to protect your personal information, we make no
|
|
||||||
guarantees of its absolute security.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
We employ the use of SSL encryption during the transmission of
|
|
||||||
sensitive data across our websites.
|
|
||||||
</p>
|
</p>
|
||||||
|
<p>We employ the use of SSL encryption during the transmission of sensitive data across our websites.</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Third parties</strong><br />
|
<strong>Third parties</strong><br />
|
||||||
Except as otherwise expressly included in this Privacy Policy, this
|
Except as otherwise expressly included in this Privacy Policy, this document addresses only the use and disclosure of information we collect from you. If you disclose your information to others, whether they
|
||||||
document addresses only the use and disclosure of information we
|
are buyers or sellers on our websites or other sites throughout the internet, different rules may apply to their use or disclosure of the information you disclose to them. Dynamis does not control the privacy
|
||||||
collect from you. If you disclose your information to others, whether
|
policies of third parties, and you are subject to the privacy policies of those third parties where applicable.
|
||||||
they are buyers or sellers on our websites or other sites throughout
|
|
||||||
the internet, different rules may apply to their use or disclosure of
|
|
||||||
the information you disclose to them. Dynamis does not control the
|
|
||||||
privacy policies of third parties, and you are subject to the privacy
|
|
||||||
policies of those third parties where applicable.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
We encourage you to ask questions before you disclose your personal
|
|
||||||
information to others.
|
|
||||||
</p>
|
</p>
|
||||||
|
<p>We encourage you to ask questions before you disclose your personal information to others.</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>General</strong><br />
|
<strong>General</strong><br />
|
||||||
We may change this Privacy Policy from time to time as we add new
|
We may change this Privacy Policy from time to time as we add new products and applications, as we improve our current offerings, and as technologies and laws change. You can determine when this Privacy Policy
|
||||||
products and applications, as we improve our current offerings, and as
|
was last revised by referring to the “Last Updated” legend at the top of this page.
|
||||||
technologies and laws change. You can determine when this Privacy
|
|
||||||
Policy was last revised by referring to the “Last Updated” legend at
|
|
||||||
the top of this page.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Any changes will become effective upon our posting of the revised
|
Any changes will become effective upon our posting of the revised Privacy Policy on our affected websites. We will provide notice to you if these changes are material and, where required by applicable law, we
|
||||||
Privacy Policy on our affected websites. We will provide notice to you
|
will obtain your consent. This notice may be provided by email, by posting notice of the changes on our affected websites or by other means, consistent with applicable laws.
|
||||||
if these changes are material and, where required by applicable law,
|
|
||||||
we will obtain your consent. This notice may be provided by email, by
|
|
||||||
posting notice of the changes on our affected websites or by other
|
|
||||||
means, consistent with applicable laws.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Contact Us</strong><br />
|
<strong>Contact Us</strong><br />
|
||||||
If you have any questions or comments about our privacy policy, and
|
If you have any questions or comments about our privacy policy, and you can’t find the answer to your question on our help pages, please contact us using this form or email support@bizmatch.net, or write to
|
||||||
you can’t find the answer to your question on our help pages, please
|
|
||||||
contact us using this form or email support@bizmatch.net, or write to
|
|
||||||
us at BizMatch, 715 S. Tanahua, Corpus Christi, TX 78401.)
|
us at BizMatch, 715 S. Tanahua, Corpus Christi, TX 78401.)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -62,26 +62,52 @@
|
|||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="areasServed" class="block font-medium text-900 mb-2">Areas We Serve</label>
|
<label for="areasServed" class="block font-medium text-900 mb-2">Areas We Serve</label>
|
||||||
<textarea id="areasServed" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="user.areasServed"></textarea>
|
@for (areasServed of user.areasServed; track areasServed){
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label for="companyOverview" class="block font-medium text-900 mb-2">Licensed In</label>
|
|
||||||
@for (licensedIn of userLicensedIn; track licensedIn.value){
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="flex col-12 md:col-6">
|
<div class="flex col-12 md:col-6">
|
||||||
<p-dropdown
|
<p-dropdown
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
id="states"
|
id="states"
|
||||||
[options]="selectOptions?.states"
|
[options]="selectOptions?.states"
|
||||||
[(ngModel)]="licensedIn.name"
|
[(ngModel)]="areasServed.state"
|
||||||
optionLabel="name"
|
optionLabel="name"
|
||||||
optionValue="value"
|
optionValue="value"
|
||||||
[showClear]="true"
|
|
||||||
placeholder="State"
|
placeholder="State"
|
||||||
[ngStyle]="{ width: '100%' }"
|
[ngStyle]="{ width: '100%' }"
|
||||||
></p-dropdown>
|
></p-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex col-12 md:col-6">
|
<div class="flex col-12 md:col-6">
|
||||||
<input id="companyWebsite" type="text" pInputText [(ngModel)]="licensedIn.value" placeholder="Licence Number" />
|
<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>
|
||||||
|
<!-- <button pButton pRipple label="Add Licence" class="w-auto" (click)="addLicence()"></button> -->
|
||||||
|
</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>
|
||||||
}
|
}
|
||||||
@@ -102,7 +128,10 @@
|
|||||||
<span class="font-medium text-900 mb-2">Company Logo</span>
|
<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>
|
<span class="font-medium text-xs mb-2">(is shown in every offer)</span>
|
||||||
@if(user?.hasCompanyLogo){
|
@if(user?.hasCompanyLogo){
|
||||||
|
<div class="image-wrap">
|
||||||
<img src="{{ companyLogoUrl }}" class="rounded-profile" />
|
<img src="{{ companyLogoUrl }}" class="rounded-profile" />
|
||||||
|
<fa-icon [icon]="faTrash" (click)="deleteConfirm('logo')"></fa-icon>
|
||||||
|
</div>
|
||||||
<!-- <img src="profile/{{ user.id }}.avif" class="rounded-profile" /> -->
|
<!-- <img src="profile/{{ user.id }}.avif" class="rounded-profile" /> -->
|
||||||
} @else {
|
} @else {
|
||||||
<img src="assets/images/placeholder.png" class="rounded-profile" />
|
<img src="assets/images/placeholder.png" class="rounded-profile" />
|
||||||
@@ -123,7 +152,10 @@
|
|||||||
<div class="flex flex-column align-items-center flex-or">
|
<div class="flex flex-column align-items-center flex-or">
|
||||||
<span class="font-medium text-900 mb-2">Your Profile Picture</span>
|
<span class="font-medium text-900 mb-2">Your Profile Picture</span>
|
||||||
@if(user?.hasProfile){
|
@if(user?.hasProfile){
|
||||||
|
<div class="image-wrap">
|
||||||
<img src="{{ profileUrl }}" class="rounded-profile" />
|
<img src="{{ profileUrl }}" class="rounded-profile" />
|
||||||
|
<fa-icon [icon]="faTrash" (click)="deleteConfirm('profile')"></fa-icon>
|
||||||
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
<img src="assets/images/person_placeholder.jpg" class="rounded-profile" />
|
<img src="assets/images/person_placeholder.jpg" class="rounded-profile" />
|
||||||
}
|
}
|
||||||
@@ -202,3 +234,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p-confirmDialog></p-confirmDialog>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
border: 1px solid #6b7280;
|
border: 1px solid #6b7280;
|
||||||
padding: 1px 1px;
|
padding: 1px 1px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
.rounded-profile {
|
.rounded-profile {
|
||||||
// @extend .rounded-logo;
|
// @extend .rounded-logo;
|
||||||
max-width: 100px;
|
max-width: 100px;
|
||||||
@@ -15,6 +15,22 @@
|
|||||||
padding: 1px 1px;
|
padding: 1px 1px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
.wfull{
|
.wfull {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.image-wrap {
|
||||||
|
position: relative; /* Ermöglicht die absolute Positionierung des Icons bezogen auf diesen Container */
|
||||||
|
display: inline-block; /* Erlaubt die Inline-Anordnung, falls mehrere Bilder vorhanden sind */
|
||||||
|
}
|
||||||
|
/* Stil für das FontAwesome Icon */
|
||||||
|
.image-wrap fa-icon {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px; /* Positioniert das Icon am oberen Rand des Bildes */
|
||||||
|
right: 5px; /* Positioniert das Icon am rechten Rand des Bildes */
|
||||||
|
color: #fff; /* Weiße Farbe für das Icon */
|
||||||
|
background-color: rgba(0, 0, 0, 0.5); /* Halbtransparenter Hintergrund für bessere Sichtbarkeit */
|
||||||
|
padding: 5px; /* Ein wenig Platz um das Icon */
|
||||||
|
cursor: pointer; /* Verwandelt den Cursor in eine Hand, um Interaktivität anzudeuten */
|
||||||
|
border-radius: 8px; /* Optional: Abrunden der linken unteren Ecke für ästhetische Zwecke */
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { HttpEventType } from '@angular/common/http';
|
import { HttpEventType } from '@angular/common/http';
|
||||||
import { ChangeDetectorRef, Component, ViewChild } from '@angular/core';
|
import { ChangeDetectorRef, Component, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { AngularCropperjsModule } from 'angular-cropperjs';
|
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||||
import { MessageService } from 'primeng/api';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
import { DialogModule } from 'primeng/dialog';
|
import { DialogModule } from 'primeng/dialog';
|
||||||
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||||
import { EditorModule } from 'primeng/editor';
|
import { EditorModule } from 'primeng/editor';
|
||||||
@@ -10,7 +13,7 @@ import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
|||||||
import { SelectButtonModule } from 'primeng/selectbutton';
|
import { SelectButtonModule } from 'primeng/selectbutton';
|
||||||
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, KeyValue, Subscription } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { AutoCompleteCompleteEvent, Invoice, Subscription } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component';
|
import { ImageCropperComponent, stateOptions } from '../../../components/image-cropper/image-cropper.component';
|
||||||
import { GeoService } from '../../../services/geo.service';
|
import { GeoService } from '../../../services/geo.service';
|
||||||
@@ -20,12 +23,13 @@ import { SelectOptionsService } from '../../../services/select-options.service';
|
|||||||
import { SubscriptionsService } from '../../../services/subscriptions.service';
|
import { SubscriptionsService } from '../../../services/subscriptions.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';
|
||||||
|
import { map2User } from '../../../utils/utils';
|
||||||
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account',
|
selector: 'app-account',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [SharedModule, FileUploadModule, EditorModule, AngularCropperjsModule, DialogModule, SelectButtonModule, DynamicDialogModule],
|
imports: [SharedModule, FileUploadModule, EditorModule, AngularCropperjsModule, DialogModule, SelectButtonModule, DynamicDialogModule, ConfirmDialogModule],
|
||||||
providers: [MessageService, DialogService],
|
providers: [MessageService, DialogService, ConfirmationService],
|
||||||
templateUrl: './account.component.html',
|
templateUrl: './account.component.html',
|
||||||
styleUrl: './account.component.scss',
|
styleUrl: './account.component.scss',
|
||||||
})
|
})
|
||||||
@@ -36,14 +40,15 @@ export class AccountComponent {
|
|||||||
user: User;
|
user: User;
|
||||||
subscriptions: Array<Subscription>;
|
subscriptions: Array<Subscription>;
|
||||||
userSubscriptions: Array<Subscription> = [];
|
userSubscriptions: Array<Subscription> = [];
|
||||||
maxFileSize = 1000000;
|
maxFileSize = 15000000;
|
||||||
companyLogoUrl: string;
|
companyLogoUrl: string;
|
||||||
profileUrl: string;
|
profileUrl: string;
|
||||||
type: 'company' | 'profile';
|
type: 'company' | 'profile';
|
||||||
dialogRef: DynamicDialogRef | undefined;
|
dialogRef: DynamicDialogRef | undefined;
|
||||||
environment = environment;
|
environment = environment;
|
||||||
editorModules = TOOLBAR_OPTIONS;
|
editorModules = TOOLBAR_OPTIONS;
|
||||||
userLicensedIn: KeyValue[];
|
env = environment;
|
||||||
|
faTrash = faTrash;
|
||||||
constructor(
|
constructor(
|
||||||
public userService: UserService,
|
public userService: UserService,
|
||||||
private subscriptionService: SubscriptionsService,
|
private subscriptionService: SubscriptionsService,
|
||||||
@@ -55,43 +60,43 @@ export class AccountComponent {
|
|||||||
private loadingService: LoadingService,
|
private loadingService: LoadingService,
|
||||||
private imageUploadService: ImageService,
|
private imageUploadService: ImageService,
|
||||||
public dialogService: DialogService,
|
public dialogService: DialogService,
|
||||||
|
private confirmationService: ConfirmationService,
|
||||||
|
private imageService: ImageService,
|
||||||
|
private keycloakService: KeycloakService,
|
||||||
) {}
|
) {}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const keycloakUser = this.userService.getKeycloakUser();
|
if (this.id) {
|
||||||
|
this.user = await this.userService.getById(this.id);
|
||||||
|
} else {
|
||||||
|
const token = await this.keycloakService.getToken();
|
||||||
|
const keycloakUser = map2User(token);
|
||||||
const email = keycloakUser.email;
|
const email = keycloakUser.email;
|
||||||
try {
|
try {
|
||||||
this.user = await this.userService.getByMail(email);
|
this.user = await this.userService.getByMail(email);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.user = { email, firstname: keycloakUser.firstname, lastname: keycloakUser.lastname };
|
this.user = { email, firstname: keycloakUser.firstName, lastname: keycloakUser.lastName, areasServed: [], licensedIn: [], companyOverview: '', offeredServices: '' };
|
||||||
this.user = await this.userService.save(this.user);
|
this.user = await this.userService.save(this.user);
|
||||||
}
|
}
|
||||||
this.userLicensedIn = this.user.licensedIn
|
|
||||||
? this.user.licensedIn.map(l => {
|
|
||||||
return { name: l.split('|')[0], value: l.split('|')[1] };
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions());
|
|
||||||
if (!this.user.licensedIn || this.user.licensedIn?.length === 0) {
|
|
||||||
this.user.licensedIn = [''];
|
|
||||||
}
|
}
|
||||||
this.profileUrl = this.user.hasProfile ? `pictures/profile/${this.user.id}.avif` : `/assets/images/placeholder.png`;
|
|
||||||
this.companyLogoUrl = this.user.hasCompanyLogo ? `pictures/logo/${this.user.id}.avif` : `/assets/images/placeholder.png`;
|
this.userSubscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.id));
|
||||||
|
this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${this.user.id}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
||||||
|
this.companyLogoUrl = this.user.hasCompanyLogo ? `${this.env.imageBaseUrl}/pictures/logo/${this.user.id}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
|
||||||
}
|
}
|
||||||
printInvoice(invoice: Invoice) {}
|
printInvoice(invoice: Invoice) {}
|
||||||
|
|
||||||
async updateProfile(user: User) {
|
async updateProfile(user: User) {
|
||||||
this.user.licensedIn = this.userLicensedIn.map(l => `${l.name}|${l.value}`);
|
|
||||||
await this.userService.save(this.user);
|
await this.userService.save(this.user);
|
||||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Acount changes have been persisted', life: 3000 });
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Acount changes have been persisted', life: 3000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
onUploadCompanyLogo(event: any) {
|
onUploadCompanyLogo(event: any) {
|
||||||
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
||||||
this.companyLogoUrl = `pictures/logo/${this.user.id}${uniqueSuffix}`;
|
this.companyLogoUrl = `${this.env.imageBaseUrl}/pictures/logo/${this.user.id}${uniqueSuffix}`;
|
||||||
}
|
}
|
||||||
onUploadProfilePicture(event: any) {
|
onUploadProfilePicture(event: any) {
|
||||||
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
const uniqueSuffix = '?_ts=' + new Date().getTime();
|
||||||
this.profileUrl = `pictures/profile/${this.user.id}${uniqueSuffix}`;
|
this.profileUrl = `${this.env.imageBaseUrl}/pictures/profile/${this.user.id}${uniqueSuffix}`;
|
||||||
}
|
}
|
||||||
setImageToFallback(event: Event) {
|
setImageToFallback(event: Event) {
|
||||||
(event.target as HTMLImageElement).src = `/assets/images/placeholder.png`; // Pfad zum Platzhalterbild
|
(event.target as HTMLImageElement).src = `/assets/images/placeholder.png`; // Pfad zum Platzhalterbild
|
||||||
@@ -104,12 +109,17 @@ export class AccountComponent {
|
|||||||
this.suggestions = result.map(r => `${r.city} - ${r.state_code}`).slice(0, 5);
|
this.suggestions = result.map(r => `${r.city} - ${r.state_code}`).slice(0, 5);
|
||||||
}
|
}
|
||||||
addLicence() {
|
addLicence() {
|
||||||
this.userLicensedIn.push({ name: '', value: '' });
|
this.user.licensedIn.push({ registerNo: '', state: '' });
|
||||||
}
|
}
|
||||||
removeLicence() {
|
removeLicence() {
|
||||||
this.userLicensedIn.splice(this.user.licensedIn.length - 2, 1);
|
this.user.licensedIn.splice(this.user.licensedIn.length - 1, 1);
|
||||||
|
}
|
||||||
|
addArea() {
|
||||||
|
this.user.areasServed.push({ county: '', state: '' });
|
||||||
|
}
|
||||||
|
removeArea() {
|
||||||
|
this.user.areasServed.splice(this.user.areasServed.length - 1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
select(event: any, type: 'company' | 'profile') {
|
select(event: any, type: 'company' | 'profile') {
|
||||||
const imageUrl = URL.createObjectURL(event.files[0]);
|
const imageUrl = URL.createObjectURL(event.files[0]);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -142,11 +152,12 @@ export class AccountComponent {
|
|||||||
this.loadingService.stopLoading('uploadImage');
|
this.loadingService.stopLoading('uploadImage');
|
||||||
if (this.type === 'company') {
|
if (this.type === 'company') {
|
||||||
this.user.hasCompanyLogo = true; //
|
this.user.hasCompanyLogo = true; //
|
||||||
this.companyLogoUrl = `${environment.apiBaseUrl}/logo/${this.user.id}.avif?_ts=${new Date().getTime()}`;
|
this.companyLogoUrl = `${this.env.imageBaseUrl}/pictures/logo/${this.user.id}.avif?_ts=${new Date().getTime()}`;
|
||||||
} else {
|
} else {
|
||||||
this.user.hasProfile = true;
|
this.user.hasProfile = true;
|
||||||
this.profileUrl = `${environment.apiBaseUrl}/profile/${this.user.id}.avif?_ts=${new Date().getTime()}`;
|
this.profileUrl = `${this.env.imageBaseUrl}/pictures/profile/${this.user.id}.avif?_ts=${new Date().getTime()}`;
|
||||||
}
|
}
|
||||||
|
await this.userService.save(this.user);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error => console.error('Fehler beim Upload:', error),
|
error => console.error('Fehler beim Upload:', error),
|
||||||
@@ -155,4 +166,31 @@ export class AccountComponent {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
deleteConfirm(type: 'profile' | 'logo') {
|
||||||
|
this.confirmationService.confirm({
|
||||||
|
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.id), this.userService.save(this.user)]);
|
||||||
|
} else {
|
||||||
|
this.user.hasCompanyLogo = false;
|
||||||
|
await Promise.all([this.imageService.deleteLogoImagesById(this.user.id), 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');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="description" class="block font-medium text-900 mb-2">Description</label>
|
<label for="description" class="block font-medium text-900 mb-2">Description</label>
|
||||||
<!-- <textarea id="description" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.description"></textarea> -->
|
|
||||||
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
|
<p-editor [(ngModel)]="listing.description" [style]="{ height: '320px' }" [modules]="editorModules">
|
||||||
<ng-template pTemplate="header"></ng-template>
|
<ng-template pTemplate="header"></ng-template>
|
||||||
</p-editor>
|
</p-editor>
|
||||||
@@ -36,13 +35,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<label for="type" class="block font-medium text-900 mb-2">Type of business</label>
|
<label for="type" class="block font-medium text-900 mb-2">Type of business</label>
|
||||||
<p-dropdown id="type" [options]="typesOfBusiness" [(ngModel)]="listing.type" optionLabel="name" optionValue="value" [showClear]="true" placeholder="Type of business" [style]="{ width: '100%' }"></p-dropdown>
|
<p-dropdown
|
||||||
|
id="type"
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
|
[options]="typesOfBusiness"
|
||||||
|
[(ngModel)]="listing.type"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="Type of business"
|
||||||
|
[style]="{ width: '100%' }"
|
||||||
|
></p-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="listingCategory" class="block font-medium text-900 mb-2">State</label>
|
<label for="listingCategory" class="block font-medium text-900 mb-2">State</label>
|
||||||
<p-dropdown
|
<p-dropdown
|
||||||
id="listingCategory"
|
id="listingCategory"
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
[options]="selectOptions?.states"
|
[options]="selectOptions?.states"
|
||||||
[(ngModel)]="listing.state"
|
[(ngModel)]="listing.state"
|
||||||
optionLabel="name"
|
optionLabel="name"
|
||||||
@@ -66,27 +78,27 @@
|
|||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="price" class="block font-medium text-900 mb-2">Price</label>
|
<label for="price" class="block font-medium text-900 mb-2">Price</label>
|
||||||
<!-- <p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price" ></p-inputNumber> -->
|
<!-- <p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price" ></p-inputNumber> -->
|
||||||
<app-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></app-inputNumber>
|
<p-inputNumber mode="currency" currency="USD" locale="en-US" inputId="price" [(ngModel)]="listing.price"></p-inputNumber>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
|
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
|
||||||
<app-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></app-inputNumber>
|
<p-inputNumber mode="currency" currency="USD" inputId="salesRevenue" [(ngModel)]="listing.salesRevenue"></p-inputNumber>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
|
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
|
||||||
<app-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></app-inputNumber>
|
<p-inputNumber mode="currency" currency="USD" inputId="cashFlow" [(ngModel)]="listing.cashFlow"></p-inputNumber>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="employees" class="block font-medium text-900 mb-2">Years Established Since</label>
|
<label for="established" class="block font-medium text-900 mb-2">Years Established Since</label>
|
||||||
<app-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established"></app-inputNumber>
|
<p-inputNumber mode="decimal" inputId="established" [(ngModel)]="listing.established" [useGrouping]="false"></p-inputNumber>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="employees" class="block font-medium text-900 mb-2">Employees</label>
|
<label for="employees" class="block font-medium text-900 mb-2">Employees</label>
|
||||||
<app-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees"></app-inputNumber>
|
<p-inputNumber mode="decimal" inputId="employees" [(ngModel)]="listing.employees" [useGrouping]="false"></p-inputNumber>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
@@ -119,7 +131,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="internalListingNumber" class="block font-medium text-900 mb-2">Internal Listing Number</label>
|
<label for="internalListingNumber" class="block font-medium text-900 mb-2">Internal Listing Number</label>
|
||||||
<app-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber"></app-inputNumber>
|
<p-inputNumber mode="decimal" inputId="internalListingNumber" type="text" [(ngModel)]="listing.internalListingNumber" [useGrouping]="false"></p-inputNumber>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
|||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
import { createGenericObject, getListingType, routeListingWithState } from '../../../utils/utils';
|
import { createDefaultBusinessListing, map2User, routeListingWithState } from '../../../utils/utils';
|
||||||
|
|
||||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { AngularCropperjsModule } from 'angular-cropperjs';
|
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||||
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
|
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
import { CarouselModule } from 'primeng/carousel';
|
import { CarouselModule } from 'primeng/carousel';
|
||||||
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
@@ -99,6 +100,7 @@ export class EditBusinessListingComponent {
|
|||||||
public dialogService: DialogService,
|
public dialogService: DialogService,
|
||||||
private confirmationService: ConfirmationService,
|
private confirmationService: ConfirmationService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
private keycloakService: KeycloakService,
|
||||||
) {
|
) {
|
||||||
this.router.events.subscribe(event => {
|
this.router.events.subscribe(event => {
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
@@ -115,21 +117,24 @@ export class EditBusinessListingComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
const token = await this.keycloakService.getToken();
|
||||||
|
const keycloakUser = map2User(token);
|
||||||
if (this.mode === 'edit') {
|
if (this.mode === 'edit') {
|
||||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business'));
|
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'business'));
|
||||||
} else {
|
} else {
|
||||||
this.listing = createGenericObject<BusinessListing>();
|
this.listing = createDefaultBusinessListing();
|
||||||
this.listing.listingsCategory = 'business';
|
this.listing.userId = await this.userService.getId(keycloakUser.email);
|
||||||
this.listing.userId = await this.userService.getId();
|
if (this.data) {
|
||||||
this.listing.title = this.data?.title;
|
this.listing.title = this.data?.title;
|
||||||
this.listing.description = this.data?.description;
|
this.listing.description = this.data?.description;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
||||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
this.listing = await this.listingsService.save(this.listing, getListingType(this.listing));
|
this.listing = await this.listingsService.save(this.listing, this.listing.listingsCategory);
|
||||||
this.router.navigate(['editBusinessListing', this.listing.id]);
|
this.router.navigate(['editBusinessListing', this.listing.id]);
|
||||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
<label for="type" class="block font-medium text-900 mb-2">Property Category</label>
|
<label for="type" class="block font-medium text-900 mb-2">Property Category</label>
|
||||||
<p-dropdown
|
<p-dropdown
|
||||||
id="type"
|
id="type"
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
[options]="typesOfCommercialProperty"
|
[options]="typesOfCommercialProperty"
|
||||||
[(ngModel)]="listing.type"
|
[(ngModel)]="listing.type"
|
||||||
optionLabel="name"
|
optionLabel="name"
|
||||||
@@ -50,7 +52,18 @@
|
|||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="states" class="block font-medium text-900 mb-2">State</label>
|
<label for="states" class="block font-medium text-900 mb-2">State</label>
|
||||||
<p-dropdown id="states" [options]="selectOptions?.states" [(ngModel)]="listing.state" optionLabel="name" optionValue="value" [showClear]="true" placeholder="State" [style]="{ width: '100%' }"></p-dropdown>
|
<p-dropdown
|
||||||
|
[filter]="true"
|
||||||
|
filterBy="name"
|
||||||
|
id="states"
|
||||||
|
[options]="selectOptions?.states"
|
||||||
|
[(ngModel)]="listing.state"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="value"
|
||||||
|
[showClear]="true"
|
||||||
|
placeholder="State"
|
||||||
|
[style]="{ width: '100%' }"
|
||||||
|
></p-dropdown>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-4 col-12 md:col-6">
|
<div class="mb-4 col-12 md:col-6">
|
||||||
<label for="city" class="block font-medium text-900 mb-2">City</label>
|
<label for="city" class="block font-medium text-900 mb-2">City</label>
|
||||||
@@ -103,7 +116,7 @@
|
|||||||
@for (image of propertyImages; track image) {
|
@for (image of propertyImages; track image) {
|
||||||
<span cdkDropList mixedCdkDropList>
|
<span cdkDropList mixedCdkDropList>
|
||||||
<div cdkDrag mixedCdkDragSizeHelper class="image-wrap">
|
<div cdkDrag mixedCdkDragSizeHelper class="image-wrap">
|
||||||
<img src="pictures/property/{{ listing.imagePath }}/{{ image }}" [alt]="image" class="shadow-2" cdkDrag />
|
<img src="{{ env.imageBaseUrl }}/pictures/property/{{ listing.imagePath }}/{{ image }}?_ts={{ ts }}" [alt]="image" class="shadow-2" cdkDrag />
|
||||||
<fa-icon [icon]="faTrash" (click)="deleteConfirm(image)"></fa-icon>
|
<fa-icon [icon]="faTrash" (click)="deleteConfirm(image)"></fa-icon>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
justify-content: flex-start; /* Startet die Anordnung der Elemente am Anfang des Containers */
|
justify-content: flex-start; /* Startet die Anordnung der Elemente am Anfang des Containers */
|
||||||
align-items: flex-start; /* Ausrichtung der Elemente am Anfang der Querachse */
|
align-items: flex-start; /* Ausrichtung der Elemente am Anfang der Querachse */
|
||||||
padding: 10px; /* Abstand zwischen den Inhalten des Containers und dessen Rand */
|
padding: 10px; /* Abstand zwischen den Inhalten des Containers und dessen Rand */
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-container span {
|
.image-container span {
|
||||||
flex-flow: row;
|
flex-flow: row;
|
||||||
@@ -32,26 +32,26 @@
|
|||||||
// cursor: pointer; /* Verwandelt den Cursor in eine Hand, um Interaktivität anzudeuten */
|
// cursor: pointer; /* Verwandelt den Cursor in eine Hand, um Interaktivität anzudeuten */
|
||||||
// }
|
// }
|
||||||
|
|
||||||
.image-wrap {
|
.image-wrap {
|
||||||
position: relative; /* Ermöglicht die absolute Positionierung des Icons bezogen auf diesen Container */
|
position: relative; /* Ermöglicht die absolute Positionierung des Icons bezogen auf diesen Container */
|
||||||
display: inline-block; /* Erlaubt die Inline-Anordnung, falls mehrere Bilder vorhanden sind */
|
display: inline-block; /* Erlaubt die Inline-Anordnung, falls mehrere Bilder vorhanden sind */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stil für das Bild */
|
/* Stil für das Bild */
|
||||||
.image-wrap img {
|
.image-wrap img {
|
||||||
max-height: 150px;
|
max-height: 150px;
|
||||||
width: auto;
|
width: auto;
|
||||||
display: block; /* Verhindert unerwünschten Abstand unter dem Bild */
|
display: block; /* Verhindert unerwünschten Abstand unter dem Bild */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stil für das FontAwesome Icon */
|
/* Stil für das FontAwesome Icon */
|
||||||
.image-wrap fa-icon {
|
.image-wrap fa-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 15px; /* Positioniert das Icon am oberen Rand des Bildes */
|
top: 15px; /* Positioniert das Icon am oberen Rand des Bildes */
|
||||||
right: 15px; /* Positioniert das Icon am rechten Rand des Bildes */
|
right: 15px; /* Positioniert das Icon am rechten Rand des Bildes */
|
||||||
color: #fff; /* Weiße Farbe für das Icon */
|
color: #fff; /* Weiße Farbe für das Icon */
|
||||||
background-color: rgba(0,0,0,0.5); /* Halbtransparenter Hintergrund für bessere Sichtbarkeit */
|
background-color: rgba(0, 0, 0, 0.5); /* Halbtransparenter Hintergrund für bessere Sichtbarkeit */
|
||||||
padding: 5px; /* Ein wenig Platz um das Icon */
|
padding: 5px; /* Ein wenig Platz um das Icon */
|
||||||
cursor: pointer; /* Verwandelt den Cursor in eine Hand, um Interaktivität anzudeuten */
|
cursor: pointer; /* Verwandelt den Cursor in eine Hand, um Interaktivität anzudeuten */
|
||||||
border-radius: 8px; /* Optional: Abrunden der linken unteren Ecke für ästhetische Zwecke */
|
border-radius: 8px; /* Optional: Abrunden der linken unteren Ecke für ästhetische Zwecke */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,14 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
|||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
import { createGenericObject, getListingType, routeListingWithState } from '../../../utils/utils';
|
import { createDefaultCommercialPropertyListing, map2User, routeListingWithState } from '../../../utils/utils';
|
||||||
|
|
||||||
import { DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
|
import { DragDropModule, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||||
import { HttpEventType } from '@angular/common/http';
|
import { HttpEventType } from '@angular/common/http';
|
||||||
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
import { faTrash } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { AngularCropperjsModule } from 'angular-cropperjs';
|
import { AngularCropperjsModule } from 'angular-cropperjs';
|
||||||
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
|
import { MixedCdkDragDropModule } from 'angular-mixed-cdk-drag-drop';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
import { CarouselModule } from 'primeng/carousel';
|
import { CarouselModule } from 'primeng/carousel';
|
||||||
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
import { ConfirmDialogModule } from 'primeng/confirmdialog';
|
||||||
@@ -17,6 +18,7 @@ import { DialogModule } from 'primeng/dialog';
|
|||||||
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
import { DialogService, DynamicDialogModule, DynamicDialogRef } from 'primeng/dynamicdialog';
|
||||||
import { EditorModule } from 'primeng/editor';
|
import { EditorModule } from 'primeng/editor';
|
||||||
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
import { FileUpload, FileUploadModule } from 'primeng/fileupload';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { BusinessListing, CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { AutoCompleteCompleteEvent, ImageProperty } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { AutoCompleteCompleteEvent, ImageProperty } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
@@ -90,6 +92,8 @@ export class EditCommercialPropertyListingComponent {
|
|||||||
data: BusinessListing;
|
data: BusinessListing;
|
||||||
userId: string;
|
userId: string;
|
||||||
typesOfCommercialProperty = [];
|
typesOfCommercialProperty = [];
|
||||||
|
env = environment;
|
||||||
|
ts = new Date().getTime();
|
||||||
constructor(
|
constructor(
|
||||||
public selectOptions: SelectOptionsService,
|
public selectOptions: SelectOptionsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
@@ -103,6 +107,7 @@ export class EditCommercialPropertyListingComponent {
|
|||||||
public dialogService: DialogService,
|
public dialogService: DialogService,
|
||||||
private confirmationService: ConfirmationService,
|
private confirmationService: ConfirmationService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
private keycloakService: KeycloakService,
|
||||||
) {
|
) {
|
||||||
// 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 => {
|
||||||
@@ -120,20 +125,25 @@ export class EditCommercialPropertyListingComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
const token = await this.keycloakService.getToken();
|
||||||
|
const keycloakUser = map2User(token);
|
||||||
if (this.mode === 'edit') {
|
if (this.mode === 'edit') {
|
||||||
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
|
this.listing = await lastValueFrom(this.listingsService.getListingById(this.id, 'commercialProperty'));
|
||||||
} else {
|
} else {
|
||||||
this.listing = createGenericObject<CommercialPropertyListing>();
|
this.listing = createDefaultCommercialPropertyListing();
|
||||||
this.listing.userId = await this.userService.getId();
|
this.listing.userId = await this.userService.getId(keycloakUser.email);
|
||||||
|
this.listing.imagePath = uuidv4();
|
||||||
|
if (this.data) {
|
||||||
this.listing.title = this.data?.title;
|
this.listing.title = this.data?.title;
|
||||||
this.listing.description = this.data?.description;
|
this.listing.description = this.data?.description;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/image/uploadPropertyPicture/${this.listing.id}`;
|
||||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async save() {
|
async save() {
|
||||||
this.listing = await this.listingsService.save(this.listing, getListingType(this.listing));
|
this.listing = await this.listingsService.save(this.listing, this.listing.listingsCategory);
|
||||||
this.router.navigate(['editCommercialPropertyListing', this.listing.id]);
|
this.router.navigate(['editCommercialPropertyListing', this.listing.id]);
|
||||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing changes have been persisted', life: 3000 });
|
||||||
}
|
}
|
||||||
@@ -166,7 +176,7 @@ export class EditCommercialPropertyListingComponent {
|
|||||||
if (cropper) {
|
if (cropper) {
|
||||||
this.loadingService.startLoading('uploadImage');
|
this.loadingService.startLoading('uploadImage');
|
||||||
cropper.getCroppedCanvas().toBlob(async blob => {
|
cropper.getCroppedCanvas().toBlob(async blob => {
|
||||||
this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.id).subscribe(
|
this.imageService.uploadImage(blob, 'uploadPropertyPicture', this.listing.imagePath).subscribe(
|
||||||
async event => {
|
async event => {
|
||||||
if (event.type === HttpEventType.Response) {
|
if (event.type === HttpEventType.Response) {
|
||||||
console.log('Upload abgeschlossen', event.body);
|
console.log('Upload abgeschlossen', event.body);
|
||||||
@@ -194,7 +204,8 @@ export class EditCommercialPropertyListingComponent {
|
|||||||
rejectIcon: 'none',
|
rejectIcon: 'none',
|
||||||
|
|
||||||
accept: async () => {
|
accept: async () => {
|
||||||
await this.imageService.deleteListingImage(this.listing.id, imageName);
|
this.listing.imageOrder = this.listing.imageOrder.filter(item => item !== imageName);
|
||||||
|
await Promise.all([this.imageService.deleteListingImage(this.listing.imagePath, imageName), this.listingsService.save(this.listing, 'commercialProperty')]);
|
||||||
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
|
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Image deleted' });
|
||||||
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
this.propertyImages = await this.listingsService.getPropertyImages(this.listing.id);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { KeycloakUser, ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.service';
|
||||||
import { UserService } from '../../../services/user.service';
|
|
||||||
import { SharedModule } from '../../../shared/shared/shared.module';
|
import { SharedModule } from '../../../shared/shared/shared.module';
|
||||||
|
import { map2User } from '../../../utils/utils';
|
||||||
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
|
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -15,14 +15,13 @@ import { MenuAccountComponent } from '../../menu-account/menu-account.component'
|
|||||||
styleUrl: './favorites.component.scss',
|
styleUrl: './favorites.component.scss',
|
||||||
})
|
})
|
||||||
export class FavoritesComponent {
|
export class FavoritesComponent {
|
||||||
user: User;
|
user: KeycloakUser;
|
||||||
listings: Array<ListingType> = []; //= dataListings as unknown as Array<BusinessListing>;
|
listings: Array<ListingType> = []; //= dataListings as unknown as Array<BusinessListing>;
|
||||||
favorites: Array<ListingType>;
|
favorites: Array<ListingType>;
|
||||||
constructor(public userService: UserService, private listingsService: ListingsService, public selectOptions: SelectOptionsService) {
|
constructor(public keycloakService: KeycloakService, private listingsService: ListingsService, public selectOptions: SelectOptionsService) {}
|
||||||
this.user = this.userService.getKeycloakUser();
|
|
||||||
}
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
// this.listings=await lastValueFrom(this.listingsService.getAllListings());
|
const token = await this.keycloakService.getToken();
|
||||||
|
this.user = map2User(token);
|
||||||
this.favorites = this.listings.filter(l => l.favoritesForUser?.includes(this.user.id));
|
this.favorites = this.listings.filter(l => l.favoritesForUser?.includes(this.user.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,9 @@
|
|||||||
<td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td>
|
<td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td>
|
||||||
<td>{{ selectOptions.getState(listing.state) }}</td>
|
<td>{{ selectOptions.getState(listing.state) }}</td>
|
||||||
<td>
|
<td>
|
||||||
@if(isBusinessListing(listing)){
|
@if(listing.listingsCategory==='business'){
|
||||||
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editBusinessListing', listing.id]"></button>
|
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editBusinessListing', listing.id]"></button>
|
||||||
} @if(isCommercialPropertyListing(listing)){
|
} @if(listing.listingsCategory==='commercialProperty'){
|
||||||
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]"></button>
|
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editCommercialPropertyListing', listing.id]"></button>
|
||||||
}
|
}
|
||||||
<button pButton pRipple icon="pi pi-trash" class="p-button-rounded p-button-warning" (click)="confirm($event, listing)"></button>
|
<button pButton pRipple icon="pi pi-trash" class="p-button-rounded p-button-warning" (click)="confirm($event, listing)"></button>
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||||
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
|
import { CommercialPropertyListing, User } from '../../../../../../bizmatch-server/src/models/db.model';
|
||||||
import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
import { ListingType } from '../../../../../../bizmatch-server/src/models/main.model';
|
||||||
import { ListingsService } from '../../../services/listings.service';
|
import { ListingsService } from '../../../services/listings.service';
|
||||||
import { SelectOptionsService } from '../../../services/select-options.service';
|
import { SelectOptionsService } from '../../../services/select-options.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';
|
||||||
import { getListingType, isBusinessListing, isCommercialPropertyListing } from '../../../utils/utils';
|
import { map2User } from '../../../utils/utils';
|
||||||
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
|
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-my-listing',
|
selector: 'app-my-listing',
|
||||||
@@ -20,10 +21,9 @@ export class MyListingComponent {
|
|||||||
listings: Array<ListingType> = []; //dataListings as unknown as Array<BusinessListing>;
|
listings: Array<ListingType> = []; //dataListings as unknown as Array<BusinessListing>;
|
||||||
myListings: Array<ListingType>;
|
myListings: Array<ListingType>;
|
||||||
user: User;
|
user: User;
|
||||||
isBusinessListing = isBusinessListing;
|
|
||||||
isCommercialPropertyListing = isCommercialPropertyListing;
|
|
||||||
constructor(
|
constructor(
|
||||||
public userService: UserService,
|
public userService: UserService,
|
||||||
|
public keycloakService: KeycloakService,
|
||||||
private listingsService: ListingsService,
|
private listingsService: ListingsService,
|
||||||
private cdRef: ChangeDetectorRef,
|
private cdRef: ChangeDetectorRef,
|
||||||
public selectOptions: SelectOptionsService,
|
public selectOptions: SelectOptionsService,
|
||||||
@@ -31,7 +31,9 @@ export class MyListingComponent {
|
|||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
) {}
|
) {}
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
const keycloakUser = this.userService.getKeycloakUser();
|
// const keycloakUser = this.userService.getKeycloakUser();
|
||||||
|
const token = await this.keycloakService.getToken();
|
||||||
|
const keycloakUser = map2User(token);
|
||||||
const email = keycloakUser.email;
|
const email = keycloakUser.email;
|
||||||
this.user = await this.userService.getByMail(email);
|
this.user = await this.userService.getByMail(email);
|
||||||
const result = await Promise.all([await this.listingsService.getListingByUserId(this.user.id, 'business'), await this.listingsService.getListingByUserId(this.user.id, 'commercialProperty')]);
|
const result = await Promise.all([await this.listingsService.getListingByUserId(this.user.id, 'business'), await this.listingsService.getListingByUserId(this.user.id, 'commercialProperty')]);
|
||||||
@@ -39,7 +41,11 @@ export class MyListingComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteListing(listing: ListingType) {
|
async deleteListing(listing: ListingType) {
|
||||||
await this.listingsService.deleteListing(listing.id, getListingType(listing));
|
if (listing.listingsCategory === 'business') {
|
||||||
|
await this.listingsService.deleteBusinessListing(listing.id);
|
||||||
|
} else {
|
||||||
|
await this.listingsService.deleteCommercialPropertyListing(listing.id, (<CommercialPropertyListing>listing).imagePath);
|
||||||
|
}
|
||||||
const result = await Promise.all([await this.listingsService.getListingByUserId(this.user.id, 'business'), await this.listingsService.getListingByUserId(this.user.id, 'commercialProperty')]);
|
const result = await Promise.all([await this.listingsService.getListingByUserId(this.user.id, 'business'), await this.listingsService.getListingByUserId(this.user.id, 'commercialProperty')]);
|
||||||
this.myListings = [...result[0], ...result[1]];
|
this.myListings = [...result[0], ...result[1]];
|
||||||
}
|
}
|
||||||
|
|||||||
15
bizmatch/src/app/resolvers/auth.resolver.ts
Normal file
15
bizmatch/src/app/resolvers/auth.resolver.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { inject } from '@angular/core';
|
||||||
|
import { ResolveFn } from '@angular/router';
|
||||||
|
import { KeycloakService } from '../services/keycloak.service';
|
||||||
|
|
||||||
|
export const authResolver: ResolveFn<boolean> = async (route, state) => {
|
||||||
|
const keycloakService: KeycloakService = inject(KeycloakService);
|
||||||
|
|
||||||
|
if (!keycloakService.isLoggedIn()) {
|
||||||
|
await keycloakService.login({
|
||||||
|
redirectUri: window.location.href,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
33
bizmatch/src/app/services/history.service.ts
Normal file
33
bizmatch/src/app/services/history.service.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { NavigationEnd, Router } from '@angular/router';
|
||||||
|
import { filter } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class HistoryService {
|
||||||
|
private previousUrl: string | undefined;
|
||||||
|
private currentUrl: string | undefined;
|
||||||
|
|
||||||
|
constructor(private router: Router, private location: Location) {
|
||||||
|
this.setupRouterListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupRouterListener(): void {
|
||||||
|
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe((event: NavigationEnd) => {
|
||||||
|
this.previousUrl = this.currentUrl;
|
||||||
|
this.currentUrl = event.urlAfterRedirects;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get canGoBack(): boolean {
|
||||||
|
return !!this.previousUrl || window.history.length > 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
goBack(): void {
|
||||||
|
if (this.canGoBack) {
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +1,44 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { environment } from '../../environments/environment';
|
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { ImageType } from '../../../../bizmatch-server/src/models/main.model';
|
import { ImageType } from '../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class ImageService {
|
export class ImageService {
|
||||||
|
|
||||||
private apiBaseUrl = environment.apiBaseUrl;
|
private apiBaseUrl = environment.apiBaseUrl;
|
||||||
|
|
||||||
constructor(private http: HttpClient) { }
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
uploadImage(imageBlob: Blob,type:'uploadPropertyPicture'|'uploadCompanyLogo'|'uploadProfile',id:string) {
|
uploadImage(imageBlob: Blob, type: 'uploadPropertyPicture' | 'uploadCompanyLogo' | 'uploadProfile', imagePath: string) {
|
||||||
const uploadUrl = `${this.apiBaseUrl}/bizmatch/image/${type}/${id}`;
|
const uploadUrl = `${this.apiBaseUrl}/bizmatch/image/${type}/${imagePath}`;
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', imageBlob, 'image.png');
|
formData.append('file', imageBlob, 'image.png');
|
||||||
|
|
||||||
return this.http.post(uploadUrl, formData,{
|
return this.http.post(uploadUrl, formData, {
|
||||||
// headers: this.headers,
|
// headers: this.headers,
|
||||||
//reportProgress: true,
|
//reportProgress: true,
|
||||||
observe: 'events',
|
observe: 'events',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
async deleteUserImage(userid:string,type:ImageType,name?:string){
|
async deleteUserImage(userid: string, type: ImageType, name?: string) {
|
||||||
return await lastValueFrom(this.http.delete<[]>(`${this.apiBaseUrl}/bizmatch/image/${type.delete}${userid}`));
|
return await lastValueFrom(this.http.delete<[]>(`${this.apiBaseUrl}/bizmatch/image/${type.delete}${userid}`));
|
||||||
}
|
}
|
||||||
async deleteListingImage(listingid:string,name?:string){
|
async deleteListingImage(imagePath: string, name?: string) {
|
||||||
return await lastValueFrom(this.http.delete<[]>(`${this.apiBaseUrl}/bizmatch/image/propertyPicture/${listingid}/${name}`));
|
return await lastValueFrom(this.http.delete<[]>(`${this.apiBaseUrl}/bizmatch/image/propertyPicture/${imagePath}/${name}`));
|
||||||
}
|
}
|
||||||
async getProfileImagesForUsers(userids:string[]){
|
async getProfileImagesForUsers(userids: string[]) {
|
||||||
return await lastValueFrom(this.http.get<[]>(`${this.apiBaseUrl}/bizmatch/image/profileImages/${userids.join(',')}`));
|
return await lastValueFrom(this.http.get<[]>(`${this.apiBaseUrl}/bizmatch/image/profileImages/${userids.join(',')}`));
|
||||||
}
|
}
|
||||||
async getCompanyLogosForUsers(userids:string[]){
|
async getCompanyLogosForUsers(userids: string[]) {
|
||||||
return await lastValueFrom(this.http.get<[]>(`${this.apiBaseUrl}/bizmatch/image/companyLogos/${userids.join(',')}`));
|
return await lastValueFrom(this.http.get<[]>(`${this.apiBaseUrl}/bizmatch/image/companyLogos/${userids.join(',')}`));
|
||||||
}
|
}
|
||||||
|
async deleteLogoImagesById(userid: string) {
|
||||||
|
await lastValueFrom(this.http.delete<[]>(`${this.apiBaseUrl}/bizmatch/image/logo/${userid}`));
|
||||||
|
}
|
||||||
|
async deleteProfileImagesById(userid: string) {
|
||||||
|
await lastValueFrom(this.http.delete<[]>(`${this.apiBaseUrl}/bizmatch/image/profile/${userid}`));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
64
bizmatch/src/app/services/keycloak-initializer.service.ts
Normal file
64
bizmatch/src/app/services/keycloak-initializer.service.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { KeycloakService } from 'keycloak-angular';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
import { createLogger } from '../utils/utils';
|
||||||
|
const logger = createLogger('KeycloakInitializerService');
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class KeycloakInitializerService {
|
||||||
|
public initialized = false;
|
||||||
|
|
||||||
|
constructor(private keycloakService: KeycloakService) {}
|
||||||
|
|
||||||
|
async initialize(): Promise<boolean> {
|
||||||
|
return new Promise<boolean>(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
await this.keycloakService.init({
|
||||||
|
config: {
|
||||||
|
url: environment.keycloak.url,
|
||||||
|
realm: environment.keycloak.realm,
|
||||||
|
clientId: environment.keycloak.clientId,
|
||||||
|
},
|
||||||
|
initOptions: {
|
||||||
|
onLoad: 'check-sso',
|
||||||
|
silentCheckSsoRedirectUri: (<any>window).location.origin + '/assets/silent-check-sso.html',
|
||||||
|
// flow: 'implicit',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.initialized = true;
|
||||||
|
resolve(true);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// if (this.initialized) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// logger.info(`###>calling keycloakService init ...`);
|
||||||
|
// const authenticated = await this.keycloakService.init({
|
||||||
|
// config: {
|
||||||
|
// url: environment.keycloak.url,
|
||||||
|
// realm: environment.keycloak.realm,
|
||||||
|
// clientId: environment.keycloak.clientId,
|
||||||
|
// },
|
||||||
|
// initOptions: {
|
||||||
|
// onLoad: 'check-sso',
|
||||||
|
// silentCheckSsoRedirectUri: (<any>window).location.origin + '/assets/silent-check-sso.html',
|
||||||
|
// // flow: 'implicit',
|
||||||
|
// },
|
||||||
|
// // initOptions: {
|
||||||
|
// // pkceMethod: 'S256',
|
||||||
|
// // redirectUri: environment.keycloak.redirectUri,
|
||||||
|
// // checkLoginIframe: false,
|
||||||
|
// // },
|
||||||
|
// });
|
||||||
|
// logger.info(`+++>authenticated: ${authenticated}`);
|
||||||
|
// const token = await this.keycloakService.getToken();
|
||||||
|
// logger.info(`--->${token}`);
|
||||||
|
|
||||||
|
// this.initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// isInitialized(): boolean {
|
||||||
|
// return this.initialized;
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -1,561 +0,0 @@
|
|||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Mauricio Gemelli Vigolo and contributors.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by a MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://github.com/mauriciovigolo/keycloak-angular/blob/main/LICENSE.md
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpHeaders, HttpRequest } from '@angular/common/http';
|
|
||||||
|
|
||||||
import { Subject, from } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import Keycloak from 'keycloak-js';
|
|
||||||
|
|
||||||
|
|
||||||
import { ExcludedUrl, ExcludedUrlRegex, KeycloakOptions } from '../models/keycloak-options';
|
|
||||||
import { KeycloakEvent, KeycloakEventType } from '../models/keycloak-event';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Service to expose existent methods from the Keycloak JS adapter, adding new
|
|
||||||
* functionalities to improve the use of keycloak in Angular v > 4.3 applications.
|
|
||||||
*
|
|
||||||
* This class should be injected in the application bootstrap, so the same instance will be used
|
|
||||||
* along the web application.
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
export class KeycloakService {
|
|
||||||
/**
|
|
||||||
* Keycloak-js instance.
|
|
||||||
*/
|
|
||||||
private _instance: Keycloak.KeycloakInstance;
|
|
||||||
/**
|
|
||||||
* User profile as KeycloakProfile interface.
|
|
||||||
*/
|
|
||||||
private _userProfile: Keycloak.KeycloakProfile;
|
|
||||||
/**
|
|
||||||
* Flag to indicate if the bearer will not be added to the authorization header.
|
|
||||||
*/
|
|
||||||
private _enableBearerInterceptor: boolean;
|
|
||||||
/**
|
|
||||||
* When the implicit flow is choosen there must exist a silentRefresh, as there is
|
|
||||||
* no refresh token.
|
|
||||||
*/
|
|
||||||
private _silentRefresh: boolean;
|
|
||||||
/**
|
|
||||||
* Indicates that the user profile should be loaded at the keycloak initialization,
|
|
||||||
* just after the login.
|
|
||||||
*/
|
|
||||||
private _loadUserProfileAtStartUp: boolean;
|
|
||||||
/**
|
|
||||||
* The bearer prefix that will be appended to the Authorization Header.
|
|
||||||
*/
|
|
||||||
private _bearerPrefix: string;
|
|
||||||
/**
|
|
||||||
* Value that will be used as the Authorization Http Header name.
|
|
||||||
*/
|
|
||||||
private _authorizationHeaderName: string;
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* The excluded urls patterns that must skip the KeycloakBearerInterceptor.
|
|
||||||
*/
|
|
||||||
private _excludedUrls: ExcludedUrlRegex[];
|
|
||||||
/**
|
|
||||||
* Observer for the keycloak events
|
|
||||||
*/
|
|
||||||
private _keycloakEvents$: Subject<KeycloakEvent> =
|
|
||||||
new Subject<KeycloakEvent>();
|
|
||||||
/**
|
|
||||||
* The amount of required time remaining before expiry of the token before the token will be refreshed.
|
|
||||||
*/
|
|
||||||
private _updateMinValidity: number;
|
|
||||||
/**
|
|
||||||
* Returns true if the request should have the token added to the headers by the KeycloakBearerInterceptor.
|
|
||||||
*/
|
|
||||||
shouldAddToken: (request: HttpRequest<unknown>) => boolean;
|
|
||||||
/**
|
|
||||||
* Returns true if the request being made should potentially update the token.
|
|
||||||
*/
|
|
||||||
shouldUpdateToken: (request: HttpRequest<unknown>) => boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds the keycloak-js events to the keycloakEvents Subject
|
|
||||||
* which is a good way to monitor for changes, if needed.
|
|
||||||
*
|
|
||||||
* The keycloakEvents returns the keycloak-js event type and any
|
|
||||||
* argument if the source function provides any.
|
|
||||||
*/
|
|
||||||
private bindsKeycloakEvents(): void {
|
|
||||||
this._instance.onAuthError = (errorData) => {
|
|
||||||
this._keycloakEvents$.next({
|
|
||||||
args: errorData,
|
|
||||||
type: KeycloakEventType.OnAuthError
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this._instance.onAuthLogout = () => {
|
|
||||||
this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthLogout });
|
|
||||||
};
|
|
||||||
|
|
||||||
this._instance.onAuthRefreshSuccess = () => {
|
|
||||||
this._keycloakEvents$.next({
|
|
||||||
type: KeycloakEventType.OnAuthRefreshSuccess
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this._instance.onAuthRefreshError = () => {
|
|
||||||
this._keycloakEvents$.next({
|
|
||||||
type: KeycloakEventType.OnAuthRefreshError
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this._instance.onAuthSuccess = () => {
|
|
||||||
this._keycloakEvents$.next({ type: KeycloakEventType.OnAuthSuccess });
|
|
||||||
};
|
|
||||||
|
|
||||||
this._instance.onTokenExpired = () => {
|
|
||||||
this._keycloakEvents$.next({
|
|
||||||
type: KeycloakEventType.OnTokenExpired
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this._instance.onActionUpdate = (state) => {
|
|
||||||
this._keycloakEvents$.next({
|
|
||||||
args: state,
|
|
||||||
type: KeycloakEventType.OnActionUpdate
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
this._instance.onReady = (authenticated) => {
|
|
||||||
this._keycloakEvents$.next({
|
|
||||||
args: authenticated,
|
|
||||||
type: KeycloakEventType.OnReady
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads all bearerExcludedUrl content in a uniform type: ExcludedUrl,
|
|
||||||
* so it becomes easier to handle.
|
|
||||||
*
|
|
||||||
* @param bearerExcludedUrls array of strings or ExcludedUrl that includes
|
|
||||||
* the url and HttpMethod.
|
|
||||||
*/
|
|
||||||
private loadExcludedUrls(
|
|
||||||
bearerExcludedUrls: (string | ExcludedUrl)[]
|
|
||||||
): ExcludedUrlRegex[] {
|
|
||||||
const excludedUrls: ExcludedUrlRegex[] = [];
|
|
||||||
for (const item of bearerExcludedUrls) {
|
|
||||||
let excludedUrl: ExcludedUrlRegex;
|
|
||||||
if (typeof item === 'string') {
|
|
||||||
excludedUrl = { urlPattern: new RegExp(item, 'i'), httpMethods: [] };
|
|
||||||
} else {
|
|
||||||
excludedUrl = {
|
|
||||||
urlPattern: new RegExp(item.url, 'i'),
|
|
||||||
httpMethods: item.httpMethods
|
|
||||||
};
|
|
||||||
}
|
|
||||||
excludedUrls.push(excludedUrl);
|
|
||||||
}
|
|
||||||
return excludedUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the class values initialization.
|
|
||||||
*
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
private initServiceValues({
|
|
||||||
enableBearerInterceptor = true,
|
|
||||||
loadUserProfileAtStartUp = false,
|
|
||||||
bearerExcludedUrls = [],
|
|
||||||
authorizationHeaderName = 'Authorization',
|
|
||||||
bearerPrefix = 'Bearer',
|
|
||||||
initOptions,
|
|
||||||
updateMinValidity = 20,
|
|
||||||
shouldAddToken = () => true,
|
|
||||||
shouldUpdateToken = () => true
|
|
||||||
}: KeycloakOptions): void {
|
|
||||||
this._enableBearerInterceptor = enableBearerInterceptor;
|
|
||||||
this._loadUserProfileAtStartUp = loadUserProfileAtStartUp;
|
|
||||||
this._authorizationHeaderName = authorizationHeaderName;
|
|
||||||
this._bearerPrefix = bearerPrefix.trim().concat(' ');
|
|
||||||
this._excludedUrls = this.loadExcludedUrls(bearerExcludedUrls);
|
|
||||||
this._silentRefresh = initOptions ? initOptions.flow === 'implicit' : false;
|
|
||||||
this._updateMinValidity = updateMinValidity;
|
|
||||||
this.shouldAddToken = shouldAddToken;
|
|
||||||
this.shouldUpdateToken = shouldUpdateToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keycloak initialization. It should be called to initialize the adapter.
|
|
||||||
* Options is an object with 2 main parameters: config and initOptions. The first one
|
|
||||||
* will be used to create the Keycloak instance. The second one are options to initialize the
|
|
||||||
* keycloak instance.
|
|
||||||
*
|
|
||||||
* @param options
|
|
||||||
* Config: may be a string representing the keycloak URI or an object with the
|
|
||||||
* following content:
|
|
||||||
* - url: Keycloak json URL
|
|
||||||
* - realm: realm name
|
|
||||||
* - clientId: client id
|
|
||||||
*
|
|
||||||
* initOptions:
|
|
||||||
* Options to initialize the Keycloak adapter, matches the options as provided by Keycloak itself.
|
|
||||||
*
|
|
||||||
* enableBearerInterceptor:
|
|
||||||
* Flag to indicate if the bearer will added to the authorization header.
|
|
||||||
*
|
|
||||||
* loadUserProfileInStartUp:
|
|
||||||
* Indicates that the user profile should be loaded at the keycloak initialization,
|
|
||||||
* just after the login.
|
|
||||||
*
|
|
||||||
* bearerExcludedUrls:
|
|
||||||
* String Array to exclude the urls that should not have the Authorization Header automatically
|
|
||||||
* added.
|
|
||||||
*
|
|
||||||
* authorizationHeaderName:
|
|
||||||
* This value will be used as the Authorization Http Header name.
|
|
||||||
*
|
|
||||||
* bearerPrefix:
|
|
||||||
* This value will be included in the Authorization Http Header param.
|
|
||||||
*
|
|
||||||
* tokenUpdateExcludedHeaders:
|
|
||||||
* Array of Http Header key/value maps that should not trigger the token to be updated.
|
|
||||||
*
|
|
||||||
* updateMinValidity:
|
|
||||||
* This value determines if the token will be refreshed based on its expiration time.
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* A Promise with a boolean indicating if the initialization was successful.
|
|
||||||
*/
|
|
||||||
public async init(options: KeycloakOptions = {}) {
|
|
||||||
this.initServiceValues(options);
|
|
||||||
const { config, initOptions } = options;
|
|
||||||
|
|
||||||
this._instance = new Keycloak(config);
|
|
||||||
this.bindsKeycloakEvents();
|
|
||||||
|
|
||||||
const authenticated = await this._instance.init(initOptions);
|
|
||||||
|
|
||||||
if (authenticated && this._loadUserProfileAtStartUp) {
|
|
||||||
await this.loadUserProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
return authenticated;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirects to login form on (options is an optional object with redirectUri and/or
|
|
||||||
* prompt fields).
|
|
||||||
*
|
|
||||||
* @param options
|
|
||||||
* Object, where:
|
|
||||||
* - redirectUri: Specifies the uri to redirect to after login.
|
|
||||||
* - prompt:By default the login screen is displayed if the user is not logged-in to Keycloak.
|
|
||||||
* To only authenticate to the application if the user is already logged-in and not display the
|
|
||||||
* login page if the user is not logged-in, set this option to none. To always require
|
|
||||||
* re-authentication and ignore SSO, set this option to login .
|
|
||||||
* - maxAge: Used just if user is already authenticated. Specifies maximum time since the
|
|
||||||
* authentication of user happened. If user is already authenticated for longer time than
|
|
||||||
* maxAge, the SSO is ignored and he will need to re-authenticate again.
|
|
||||||
* - loginHint: Used to pre-fill the username/email field on the login form.
|
|
||||||
* - action: If value is 'register' then user is redirected to registration page, otherwise to
|
|
||||||
* login page.
|
|
||||||
* - locale: Specifies the desired locale for the UI.
|
|
||||||
* @returns
|
|
||||||
* A void Promise if the login is successful and after the user profile loading.
|
|
||||||
*/
|
|
||||||
public async login(options: Keycloak.KeycloakLoginOptions = {}) {
|
|
||||||
await this._instance.login(options);
|
|
||||||
|
|
||||||
if (this._loadUserProfileAtStartUp) {
|
|
||||||
await this.loadUserProfile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirects to logout.
|
|
||||||
*
|
|
||||||
* @param redirectUri
|
|
||||||
* Specifies the uri to redirect to after logout.
|
|
||||||
* @returns
|
|
||||||
* A void Promise if the logout was successful, cleaning also the userProfile.
|
|
||||||
*/
|
|
||||||
public async logout(redirectUri?: string) {
|
|
||||||
const options = {
|
|
||||||
redirectUri
|
|
||||||
};
|
|
||||||
|
|
||||||
await this._instance.logout(options);
|
|
||||||
this._userProfile = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirects to registration form. Shortcut for login with option
|
|
||||||
* action = 'register'. Options are same as for the login method but 'action' is set to
|
|
||||||
* 'register'.
|
|
||||||
*
|
|
||||||
* @param options
|
|
||||||
* login options
|
|
||||||
* @returns
|
|
||||||
* A void Promise if the register flow was successful.
|
|
||||||
*/
|
|
||||||
public async register(
|
|
||||||
options: Keycloak.KeycloakLoginOptions = { action: 'register' }
|
|
||||||
) {
|
|
||||||
await this._instance.register(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the user has access to the specified role. It will look for roles in
|
|
||||||
* realm and the given resource, but will not check if the user is logged in for better performance.
|
|
||||||
*
|
|
||||||
* @param role
|
|
||||||
* role name
|
|
||||||
* @param resource
|
|
||||||
* resource name. If not specified, `clientId` is used
|
|
||||||
* @returns
|
|
||||||
* A boolean meaning if the user has the specified Role.
|
|
||||||
*/
|
|
||||||
isUserInRole(role: string, resource?: string): boolean {
|
|
||||||
let hasRole: boolean;
|
|
||||||
hasRole = this._instance.hasResourceRole(role, resource);
|
|
||||||
if (!hasRole) {
|
|
||||||
hasRole = this._instance.hasRealmRole(role);
|
|
||||||
}
|
|
||||||
return hasRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the roles of the logged user. The realmRoles parameter, with default value
|
|
||||||
* true, will return the resource roles and realm roles associated with the logged user. If set to false
|
|
||||||
* it will only return the resource roles. The resource parameter, if specified, will return only resource roles
|
|
||||||
* associated with the given resource.
|
|
||||||
*
|
|
||||||
* @param realmRoles
|
|
||||||
* Set to false to exclude realm roles (only client roles)
|
|
||||||
* @param resource
|
|
||||||
* resource name If not specified, returns roles from all resources
|
|
||||||
* @returns
|
|
||||||
* Array of Roles associated with the logged user.
|
|
||||||
*/
|
|
||||||
getUserRoles(realmRoles: boolean = true, resource?: string): string[] {
|
|
||||||
let roles: string[] = [];
|
|
||||||
|
|
||||||
if (this._instance.resourceAccess) {
|
|
||||||
Object.keys(this._instance.resourceAccess).forEach((key) => {
|
|
||||||
if (resource && resource !== key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceAccess = this._instance.resourceAccess[key];
|
|
||||||
const clientRoles = resourceAccess['roles'] || [];
|
|
||||||
roles = roles.concat(clientRoles);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (realmRoles && this._instance.realmAccess) {
|
|
||||||
const realmRoles = this._instance.realmAccess['roles'] || [];
|
|
||||||
roles.push(...realmRoles);
|
|
||||||
}
|
|
||||||
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if user is logged in.
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* A boolean that indicates if the user is logged in.
|
|
||||||
*/
|
|
||||||
isLoggedIn(): boolean {
|
|
||||||
if (!this._instance) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._instance.authenticated;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the token has less than minValidity seconds left before
|
|
||||||
* it expires.
|
|
||||||
*
|
|
||||||
* @param minValidity
|
|
||||||
* Seconds left. (minValidity) is optional. Default value is 0.
|
|
||||||
* @returns
|
|
||||||
* Boolean indicating if the token is expired.
|
|
||||||
*/
|
|
||||||
isTokenExpired(minValidity: number = 0): boolean {
|
|
||||||
return this._instance.isTokenExpired(minValidity);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the token expires within _updateMinValidity seconds the token is refreshed. If the
|
|
||||||
* session status iframe is enabled, the session status is also checked.
|
|
||||||
* Returns a promise telling if the token was refreshed or not. If the session is not active
|
|
||||||
* anymore, the promise is rejected.
|
|
||||||
*
|
|
||||||
* @param minValidity
|
|
||||||
* Seconds left. (minValidity is optional, if not specified updateMinValidity - default 20 is used)
|
|
||||||
* @returns
|
|
||||||
* Promise with a boolean indicating if the token was succesfully updated.
|
|
||||||
*/
|
|
||||||
public async updateToken(minValidity = this._updateMinValidity) {
|
|
||||||
// TODO: this is a workaround until the silent refresh (issue #43)
|
|
||||||
// is not implemented, avoiding the redirect loop.
|
|
||||||
if (this._silentRefresh) {
|
|
||||||
if (this.isTokenExpired()) {
|
|
||||||
throw new Error(
|
|
||||||
'Failed to refresh the token, or the session is expired'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._instance) {
|
|
||||||
throw new Error('Keycloak Angular library is not initialized.');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await this._instance.updateToken(minValidity);
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the user profile.
|
|
||||||
* Returns promise to set functions to be invoked if the profile was loaded
|
|
||||||
* successfully, or if the profile could not be loaded.
|
|
||||||
*
|
|
||||||
* @param forceReload
|
|
||||||
* If true will force the loadUserProfile even if its already loaded.
|
|
||||||
* @returns
|
|
||||||
* A promise with the KeycloakProfile data loaded.
|
|
||||||
*/
|
|
||||||
public async loadUserProfile(forceReload = false) {
|
|
||||||
if (this._userProfile && !forceReload) {
|
|
||||||
return this._userProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._instance.authenticated) {
|
|
||||||
throw new Error(
|
|
||||||
'The user profile was not loaded as the user is not logged in.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (this._userProfile = await this._instance.loadUserProfile());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the authenticated token, calling updateToken to get a refreshed one if necessary.
|
|
||||||
*/
|
|
||||||
public async getToken() {
|
|
||||||
return this._instance.token;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the logged username.
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* The logged username.
|
|
||||||
*/
|
|
||||||
public getUsername() {
|
|
||||||
if (!this._userProfile) {
|
|
||||||
throw new Error('User not logged in or user profile was not loaded.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._userProfile.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear authentication state, including tokens. This can be useful if application
|
|
||||||
* has detected the session was expired, for example if updating token fails.
|
|
||||||
* Invoking this results in onAuthLogout callback listener being invoked.
|
|
||||||
*/
|
|
||||||
clearToken(): void {
|
|
||||||
this._instance.clearToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a valid token in header. The key & value format is:
|
|
||||||
* Authorization Bearer <token>.
|
|
||||||
* If the headers param is undefined it will create the Angular headers object.
|
|
||||||
*
|
|
||||||
* @param headers
|
|
||||||
* Updated header with Authorization and Keycloak token.
|
|
||||||
* @returns
|
|
||||||
* An observable with with the HTTP Authorization header and the current token.
|
|
||||||
*/
|
|
||||||
public addTokenToHeader(headers: HttpHeaders = new HttpHeaders()) {
|
|
||||||
return from(this.getToken()).pipe(
|
|
||||||
map((token) =>
|
|
||||||
token
|
|
||||||
? headers.set(
|
|
||||||
this._authorizationHeaderName,
|
|
||||||
this._bearerPrefix + token
|
|
||||||
)
|
|
||||||
: headers
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the original Keycloak instance, if you need any customization that
|
|
||||||
* this Angular service does not support yet. Use with caution.
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* The KeycloakInstance from keycloak-js.
|
|
||||||
*/
|
|
||||||
getKeycloakInstance(): Keycloak.KeycloakInstance {
|
|
||||||
return this._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* Returns the excluded URLs that should not be considered by
|
|
||||||
* the http interceptor which automatically adds the authorization header in the Http Request.
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* The excluded urls that must not be intercepted by the KeycloakBearerInterceptor.
|
|
||||||
*/
|
|
||||||
get excludedUrls(): ExcludedUrlRegex[] {
|
|
||||||
return this._excludedUrls;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flag to indicate if the bearer will be added to the authorization header.
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* Returns if the bearer interceptor was set to be disabled.
|
|
||||||
*/
|
|
||||||
get enableBearerInterceptor(): boolean {
|
|
||||||
return this._enableBearerInterceptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keycloak subject to monitor the events triggered by keycloak-js.
|
|
||||||
* The following events as available (as described at keycloak docs -
|
|
||||||
* https://www.keycloak.org/docs/latest/securing_apps/index.html#callback-events):
|
|
||||||
* - OnAuthError
|
|
||||||
* - OnAuthLogout
|
|
||||||
* - OnAuthRefreshError
|
|
||||||
* - OnAuthRefreshSuccess
|
|
||||||
* - OnAuthSuccess
|
|
||||||
* - OnReady
|
|
||||||
* - OnTokenExpire
|
|
||||||
* In each occurrence of any of these, this subject will return the event type,
|
|
||||||
* described at {@link KeycloakEventType} enum and the function args from the keycloak-js
|
|
||||||
* if provided any.
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
* A subject with the {@link KeycloakEvent} which describes the event type and attaches the
|
|
||||||
* function args.
|
|
||||||
*/
|
|
||||||
get keycloakEvents$(): Subject<KeycloakEvent> {
|
|
||||||
return this._keycloakEvents$;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,9 +12,6 @@ export class ListingsService {
|
|||||||
private apiBaseUrl = environment.apiBaseUrl;
|
private apiBaseUrl = environment.apiBaseUrl;
|
||||||
constructor(private http: HttpClient) {}
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
// getAllListings():Observable<ListingType[]>{
|
|
||||||
// return this.http.get<ListingType[]>(`${this.apiBaseUrl}/bizmatch/business-listings`);
|
|
||||||
// }
|
|
||||||
async getListings(criteria: ListingCriteria, listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty'): Promise<ResponseBusinessListingArray | ResponseCommercialPropertyListingArray> {
|
async getListings(criteria: ListingCriteria, listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty'): Promise<ResponseBusinessListingArray | ResponseCommercialPropertyListingArray> {
|
||||||
const result = await lastValueFrom(this.http.post<ResponseBusinessListingArray | ResponseCommercialPropertyListingArray>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/search`, criteria));
|
const result = await lastValueFrom(this.http.post<ResponseBusinessListingArray | ResponseCommercialPropertyListingArray>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/search`, criteria));
|
||||||
return result;
|
return result;
|
||||||
@@ -34,12 +31,16 @@ export class ListingsService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async getAllStates(listingsCategory?: 'business' | 'commercialProperty'): Promise<StatesResult[]> {
|
async getAllStates(listingsCategory?: 'business' | 'commercialProperty'): Promise<StatesResult[]> {
|
||||||
const result = lastValueFrom(this.http.get<StatesResult[]>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/states/all`));
|
const result = lastValueFrom(this.http.get<StatesResult[]>(`${this.apiBaseUrl}/bizmatch/listings/business/states/all`));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
async deleteListing(id: string, listingsCategory: 'business' | 'professionals_brokers' | 'commercialProperty') {
|
async deleteBusinessListing(id: string) {
|
||||||
await lastValueFrom(this.http.delete<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/${listingsCategory}/${id}`));
|
await lastValueFrom(this.http.delete<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/business/${id}`));
|
||||||
}
|
}
|
||||||
|
async deleteCommercialPropertyListing(id: string, imagePath: string) {
|
||||||
|
await lastValueFrom(this.http.delete<ListingType>(`${this.apiBaseUrl}/bizmatch/listings/commercialProperty/${id}/${imagePath}`));
|
||||||
|
}
|
||||||
|
|
||||||
async getPropertyImages(id: string): Promise<string[]> {
|
async getPropertyImages(id: string): Promise<string[]> {
|
||||||
return await lastValueFrom(this.http.get<string[]>(`${this.apiBaseUrl}/bizmatch/image/${id}`));
|
return await lastValueFrom(this.http.get<string[]>(`${this.apiBaseUrl}/bizmatch/image/${id}`));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { environment } from '../../environments/environment';
|
import { Injectable } from '@angular/core';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { MailInfo } from '../../../../bizmatch-server/src/models/main.model';
|
import { ErrorResponse, MailInfo } from '../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class MailService {
|
export class MailService {
|
||||||
|
|
||||||
private apiBaseUrl = environment.apiBaseUrl;
|
private apiBaseUrl = environment.apiBaseUrl;
|
||||||
constructor(private http: HttpClient) { }
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
async mail(mailinfo:MailInfo):Promise<void>{
|
async mail(mailinfo: MailInfo): Promise<void | ErrorResponse> {
|
||||||
await lastValueFrom(this.http.post(`${this.apiBaseUrl}/bizmatch/mail`,mailinfo));
|
return await lastValueFrom(this.http.post(`${this.apiBaseUrl}/bizmatch/mail`, mailinfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { environment } from '../../environments/environment';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Subscription } from '../../../../bizmatch-server/src/models/main.model';
|
import { Subscription } from '../../../../bizmatch-server/src/models/main.model';
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class SubscriptionsService {
|
export class SubscriptionsService {
|
||||||
private apiBaseUrl = environment.apiBaseUrl;
|
private apiBaseUrl = environment.apiBaseUrl;
|
||||||
constructor(private http: HttpClient) { }
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
getAllSubscriptions():Observable<Subscription[]>{
|
getAllSubscriptions(id: string): Observable<Subscription[]> {
|
||||||
return this.http.get<Subscription[]>(`${this.apiBaseUrl}/bizmatch/subscriptions`);
|
return this.http.get<Subscription[]>(`${this.apiBaseUrl}/bizmatch/user/subscriptions/${id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,107 +1,18 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable, Signal, computed, effect, signal } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { jwtDecode } from 'jwt-decode';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { Observable, distinctUntilChanged, filter, from, lastValueFrom, map } from 'rxjs';
|
|
||||||
import urlcat from 'urlcat';
|
import urlcat from 'urlcat';
|
||||||
import { User } from '../../../../bizmatch-server/src/models/db.model';
|
import { User } from '../../../../bizmatch-server/src/models/db.model';
|
||||||
import { JwtToken, ListingCriteria, ResponseUsersArray } from '../../../../bizmatch-server/src/models/main.model';
|
import { ListingCriteria, ResponseUsersArray, StatesResult } from '../../../../bizmatch-server/src/models/main.model';
|
||||||
import { environment } from '../../environments/environment';
|
import { environment } from '../../environments/environment';
|
||||||
import { KeycloakService } from './keycloak.service';
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class UserService {
|
export class UserService {
|
||||||
private apiBaseUrl = environment.apiBaseUrl;
|
private apiBaseUrl = environment.apiBaseUrl;
|
||||||
// -----------------------------
|
|
||||||
// Keycloak services
|
|
||||||
// -----------------------------
|
|
||||||
private user$ = new Observable<User>();
|
|
||||||
private user: User;
|
|
||||||
public $isLoggedIn: Signal<boolean>;
|
|
||||||
constructor(public keycloak: KeycloakService, private http: HttpClient) {
|
|
||||||
this.user$ = from(this.keycloak.getToken()).pipe(
|
|
||||||
filter(t => !!t),
|
|
||||||
distinctUntilChanged(),
|
|
||||||
map(t => this.map2User(t)),
|
|
||||||
// tap(u => {
|
|
||||||
// logger.info('Logged in user:', u);
|
|
||||||
// this.analyticsService.identify(u);
|
|
||||||
// }),
|
|
||||||
);
|
|
||||||
this.$isLoggedIn = signal(false);
|
|
||||||
this.$isLoggedIn = computed(() => {
|
|
||||||
return keycloak.isLoggedIn();
|
|
||||||
});
|
|
||||||
|
|
||||||
effect(async () => {
|
constructor(private http: HttpClient) {}
|
||||||
if (this.$isLoggedIn()) {
|
|
||||||
this.updateTokenDetails();
|
|
||||||
} else {
|
|
||||||
this.user = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async refreshToken(): Promise<void> {
|
|
||||||
try {
|
|
||||||
await this.keycloak.updateToken(10); // Versuche, den Token zu erneuern
|
|
||||||
await this.updateTokenDetails(); // Aktualisiere den Token und seine Details
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Fehler beim Token-Refresh', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private async updateTokenDetails(): Promise<void> {
|
|
||||||
const token = await this.keycloak.getToken();
|
|
||||||
this.user = this.map2User(token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private map2User(jwt: string): User {
|
|
||||||
const token = jwtDecode<JwtToken>(jwt);
|
|
||||||
return {
|
|
||||||
id: token.user_id,
|
|
||||||
firstname: token.given_name,
|
|
||||||
lastname: token.family_name,
|
|
||||||
email: token.email,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoggedIn(): boolean {
|
|
||||||
return this.$isLoggedIn();
|
|
||||||
}
|
|
||||||
getKeycloakUser(): User {
|
|
||||||
return this.user;
|
|
||||||
}
|
|
||||||
getUserObservable(): Observable<User> {
|
|
||||||
return this.user$;
|
|
||||||
}
|
|
||||||
async getId(): Promise<string> {
|
|
||||||
if (sessionStorage.getItem('USERID')) {
|
|
||||||
return sessionStorage.getItem('USERID');
|
|
||||||
} else {
|
|
||||||
const user = await this.getByMail(this.user.email);
|
|
||||||
sessionStorage.setItem('USERID', user.id);
|
|
||||||
return user.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logout() {
|
|
||||||
sessionStorage.removeItem('USERID');
|
|
||||||
this.keycloak.logout(window.location.origin + '/home');
|
|
||||||
}
|
|
||||||
async login(url: string) {
|
|
||||||
await this.keycloak.login({
|
|
||||||
redirectUri: url,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
getUserRoles() {
|
|
||||||
return this.keycloak.getUserRoles(true);
|
|
||||||
}
|
|
||||||
hasAdminRole() {
|
|
||||||
return this.keycloak.getUserRoles(true).includes('ADMIN');
|
|
||||||
}
|
|
||||||
register(url: string) {
|
|
||||||
this.keycloak.register({ redirectUri: url });
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// DB services
|
// DB services
|
||||||
@@ -119,4 +30,16 @@ export class UserService {
|
|||||||
async search(criteria?: ListingCriteria): Promise<ResponseUsersArray> {
|
async search(criteria?: ListingCriteria): Promise<ResponseUsersArray> {
|
||||||
return await lastValueFrom(this.http.post<ResponseUsersArray>(`${this.apiBaseUrl}/bizmatch/user/search`, criteria));
|
return await lastValueFrom(this.http.post<ResponseUsersArray>(`${this.apiBaseUrl}/bizmatch/user/search`, criteria));
|
||||||
}
|
}
|
||||||
|
async getAllStates(): Promise<any> {
|
||||||
|
return await lastValueFrom(this.http.get<StatesResult[]>(`${this.apiBaseUrl}/bizmatch/user/states/all`));
|
||||||
|
}
|
||||||
|
async getId(email: string): Promise<string> {
|
||||||
|
if (sessionStorage.getItem('USERID')) {
|
||||||
|
return sessionStorage.getItem('USERID');
|
||||||
|
} else {
|
||||||
|
const user = await this.getByMail(email);
|
||||||
|
sessionStorage.setItem('USERID', user.id);
|
||||||
|
return user.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,83 @@
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ConsoleFormattedStream, INFO, createLogger as _createLogger, stdSerializers } from 'browser-bunyan';
|
import { ConsoleFormattedStream, INFO, createLogger as _createLogger, stdSerializers } from 'browser-bunyan';
|
||||||
|
import { jwtDecode } from 'jwt-decode';
|
||||||
import { BusinessListing, CommercialPropertyListing } from '../../../../bizmatch-server/src/models/db.model';
|
import { BusinessListing, CommercialPropertyListing } from '../../../../bizmatch-server/src/models/db.model';
|
||||||
import { ListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
|
import { JwtToken, KeycloakUser, ListingCriteria } from '../../../../bizmatch-server/src/models/main.model';
|
||||||
|
|
||||||
export function createGenericObject<T>(): T {
|
export function createDefaultCommercialPropertyListing(): CommercialPropertyListing {
|
||||||
// Ein leeres Objekt vom Typ T erstellen
|
return {
|
||||||
const ergebnis: Partial<T> = {};
|
id: undefined,
|
||||||
|
userId: '',
|
||||||
// Für ein reales Interface funktioniert diese direkte Iteration nicht,
|
type: null,
|
||||||
// da Interfaces zur Compile-Zeit entfernt werden. Stattdessen könnten Sie
|
title: '',
|
||||||
// ein Dummy-Objekt oder spezifische Typtransformationen verwenden.
|
description: '',
|
||||||
// Hier nur als Pseudocode dargestellt, um die Idee zu vermitteln:
|
city: '',
|
||||||
for (const key in ergebnis) {
|
state: '',
|
||||||
ergebnis[key] = null; // oder undefined, je nach Bedarf
|
price: null,
|
||||||
}
|
favoritesForUser: [],
|
||||||
|
hideImage: false,
|
||||||
return ergebnis as T;
|
draft: false,
|
||||||
|
zipCode: null,
|
||||||
|
county: '',
|
||||||
|
email: '',
|
||||||
|
website: '',
|
||||||
|
phoneNumber: '',
|
||||||
|
imageOrder: [],
|
||||||
|
imagePath: '',
|
||||||
|
created: null,
|
||||||
|
updated: null,
|
||||||
|
visits: null,
|
||||||
|
lastVisit: null,
|
||||||
|
listingsCategory: 'commercialProperty',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function createDefaultBusinessListing(): BusinessListing {
|
||||||
|
return {
|
||||||
|
id: undefined,
|
||||||
|
userId: '',
|
||||||
|
type: null,
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
city: '',
|
||||||
|
state: '',
|
||||||
|
price: null,
|
||||||
|
favoritesForUser: [],
|
||||||
|
draft: false,
|
||||||
|
realEstateIncluded: false,
|
||||||
|
leasedLocation: false,
|
||||||
|
franchiseResale: false,
|
||||||
|
salesRevenue: null,
|
||||||
|
cashFlow: null,
|
||||||
|
supportAndTraining: '',
|
||||||
|
employees: null,
|
||||||
|
established: null,
|
||||||
|
internalListingNumber: null,
|
||||||
|
reasonForSale: '',
|
||||||
|
brokerLicencing: '',
|
||||||
|
internals: '',
|
||||||
|
created: null,
|
||||||
|
updated: null,
|
||||||
|
visits: null,
|
||||||
|
lastVisit: null,
|
||||||
|
listingsCategory: 'business',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export function createDefaultListingCriteria(): ListingCriteria {
|
||||||
|
return {
|
||||||
|
start: 0,
|
||||||
|
length: 12,
|
||||||
|
page: 0,
|
||||||
|
pageCount: 0,
|
||||||
|
type: 0,
|
||||||
|
state: '',
|
||||||
|
minPrice: 0,
|
||||||
|
maxPrice: 0,
|
||||||
|
realEstateChecked: false,
|
||||||
|
title: '',
|
||||||
|
category: 'broker',
|
||||||
|
name: '',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createLogger(name: string, level: number = INFO, options: any = {}) {
|
export function createLogger(name: string, level: number = INFO, options: any = {}) {
|
||||||
return _createLogger({
|
return _createLogger({
|
||||||
name,
|
name,
|
||||||
@@ -33,20 +93,11 @@ export const getSessionStorageHandler = function (path, value, previous, applyDa
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function getCriteriaStateObject() {
|
export function getCriteriaStateObject() {
|
||||||
const initialState = createGenericObject<ListingCriteria>();
|
const initialState = createDefaultListingCriteria();
|
||||||
const storedState = sessionStorage.getItem('criteria');
|
const storedState = sessionStorage.getItem('criteria');
|
||||||
return storedState ? JSON.parse(storedState) : initialState;
|
return storedState ? JSON.parse(storedState) : initialState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getListingType(listing: BusinessListing | CommercialPropertyListing): 'business' | 'commercialProperty' {
|
|
||||||
return listing?.type < 100 ? 'business' : 'commercialProperty';
|
|
||||||
}
|
|
||||||
export function isBusinessListing(listing: BusinessListing | CommercialPropertyListing): listing is BusinessListing {
|
|
||||||
return listing?.type < 100;
|
|
||||||
}
|
|
||||||
export function isCommercialPropertyListing(listing: BusinessListing | CommercialPropertyListing): listing is CommercialPropertyListing {
|
|
||||||
return listing?.type >= 100;
|
|
||||||
}
|
|
||||||
export function routeListingWithState(router: Router, value: string, data: any) {
|
export function routeListingWithState(router: Router, value: string, data: any) {
|
||||||
if (value === 'business') {
|
if (value === 'business') {
|
||||||
router.navigate(['createBusinessListing'], { state: { data } });
|
router.navigate(['createBusinessListing'], { state: { data } });
|
||||||
@@ -54,13 +105,28 @@ export function routeListingWithState(router: Router, value: string, data: any)
|
|||||||
router.navigate(['createCommercialPropertyListing'], { state: { data } });
|
router.navigate(['createCommercialPropertyListing'], { state: { data } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetCriteria(criteria: ListingCriteria) {
|
export function resetCriteria(criteria: ListingCriteria) {
|
||||||
criteria.type = null;
|
criteria.type = null;
|
||||||
criteria.state = null;
|
criteria.state = null;
|
||||||
criteria.minPrice = null;
|
criteria.minPrice = null;
|
||||||
criteria.maxPrice = null;
|
criteria.maxPrice = null;
|
||||||
criteria.start = null;
|
criteria.start = 0;
|
||||||
criteria.length = null;
|
criteria.length = 12;
|
||||||
criteria.realEstateChecked = null;
|
criteria.realEstateChecked = null;
|
||||||
criteria.title = null;
|
criteria.title = null;
|
||||||
|
criteria.name = null;
|
||||||
|
}
|
||||||
|
export function map2User(jwt: string): KeycloakUser {
|
||||||
|
if (jwt) {
|
||||||
|
const token = jwtDecode<JwtToken>(jwt);
|
||||||
|
return {
|
||||||
|
id: token.user_id,
|
||||||
|
firstName: token.given_name,
|
||||||
|
lastName: token.family_name,
|
||||||
|
email: token.email,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
bizmatch/src/build.ts
Normal file
6
bizmatch/src/build.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Build information, automatically generated by `the_build_script` :zwinkern:
|
||||||
|
const build = {
|
||||||
|
timestamp: "GER: 16.05.2024 22:55 | TX: 05/16/2024 3:55 PM"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default build;
|
||||||
@@ -1,16 +1,12 @@
|
|||||||
export const environment_base = {
|
export const environment_base = {
|
||||||
// url:'http://localhost:4200',
|
|
||||||
apiBaseUrl: 'http://localhost:3000',
|
apiBaseUrl: 'http://localhost:3000',
|
||||||
// apiAuthorizationHeader: 'Basic dGJjOnFVWDdoT25vR3hBMk4zNHVvVFZ1',
|
imageBaseUrl: 'https://dev.bizmatch.net',
|
||||||
buildVersion: '<BUILD_VERSION>',
|
buildVersion: '<BUILD_VERSION>',
|
||||||
// keycloak: {
|
mailinfoUrl: 'https://dev.bizmatch.net',
|
||||||
// url: 'http://localhost:8080',
|
|
||||||
// realm: 'bizmatch',
|
|
||||||
// clientId: 'bizmatch-angular-client'
|
|
||||||
// }
|
|
||||||
keycloak: {
|
keycloak: {
|
||||||
url: 'https://auth.bizmatch.net',
|
url: 'https://auth.bizmatch.net',
|
||||||
realm: 'bizmatch-dev',
|
realm: 'bizmatch-dev',
|
||||||
clientId: 'bizmatch-dev'
|
clientId: 'bizmatch-dev',
|
||||||
}
|
redirectUri: 'https://dev.bizmatch.net',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {environment_base} from './environment.base'
|
import { environment_base } from './environment.base';
|
||||||
|
|
||||||
export const environment = environment_base
|
export const environment = environment_base;
|
||||||
|
|
||||||
environment.apiBaseUrl = "https://api-dev.bizmatch.net"
|
environment.apiBaseUrl = 'https://api-dev.bizmatch.net';
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {environment_base} from './environment.base'
|
import { environment_base } from './environment.base';
|
||||||
|
|
||||||
export const environment = environment_base
|
export const environment = environment_base;
|
||||||
|
environment.mailinfoUrl = 'http://localhost:4200';
|
||||||
environment.keycloak.clientId="dev"
|
environment.imageBaseUrl = 'http://localhost:4200';
|
||||||
environment.keycloak.realm="dev"
|
environment.keycloak.clientId = 'dev';
|
||||||
|
environment.keycloak.realm = 'dev';
|
||||||
|
environment.keycloak.redirectUri = 'http://localhost:4200';
|
||||||
|
|||||||
@@ -1,28 +1,34 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Bizmatch - Find Business for sale </title>
|
<title>Bizmatch - Find Business for sale</title>
|
||||||
<meta name="description" content="Find or Sell Businesses and Restaurants">
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||||
<meta charset="utf-8">
|
<meta http-equiv="Pragma" content="no-cache" />
|
||||||
|
<meta http-equiv="Expires" content="0" />
|
||||||
|
<meta name="description" content="Find or Sell Businesses and Restaurants" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<meta name="robots" content="index, follow">
|
<meta name="robots" content="index, follow" />
|
||||||
<meta name="googlebot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
|
<meta name="googlebot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
|
||||||
<meta name="bingbot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
|
<meta name="bingbot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
|
||||||
<meta property="og:locale" content="en_US">
|
<meta property="og:locale" content="en_US" />
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:title" content="front-page - BizMatch">
|
<meta property="og:title" content="front-page - BizMatch" />
|
||||||
<meta property="og:description" content="We are dedicated to providing a simple to use way for people in business to get in contact with each other. We are dedicated to providing a simple to use way for people in business to get in contact with each other. Who can use our site: Brokers: We provide an avenue for business and […]">
|
<meta
|
||||||
<meta property="og:site_name" content="BizMatch">
|
property="og:description"
|
||||||
<meta property="article:modified_time" content="2016-11-17T15:57:10+00:00">
|
content="We are dedicated to providing a simple to use way for people in business to get in contact with each other. We are dedicated to providing a simple to use way for people in business to get in contact with each other. Who can use our site: Brokers: We provide an avenue for business and […]"
|
||||||
<meta property="og:image:width" content="1200">
|
/>
|
||||||
<meta property="og:image:height" content="630">
|
<meta property="og:site_name" content="BizMatch" />
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta property="article:modified_time" content="2016-11-17T15:57:10+00:00" />
|
||||||
<base href="/">
|
<meta property="og:image:width" content="1200" />
|
||||||
<link rel="icon" href="assets/cropped-Favicon-32x32.png" sizes="32x32">
|
<meta property="og:image:height" content="630" />
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<base href="/" />
|
||||||
|
<link rel="icon" href="assets/cropped-Favicon-32x32.png" sizes="32x32" />
|
||||||
<!-- <link rel="icon" href="cropped-Favicon-192x192.png" sizes="192x192"> -->
|
<!-- <link rel="icon" href="cropped-Favicon-192x192.png" sizes="192x192"> -->
|
||||||
<!-- <link rel="apple-touch-icon" href="cropped-Favicon-180x180.png"> -->
|
<!-- <link rel="apple-touch-icon" href="cropped-Favicon-180x180.png"> -->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
86
bizmatch/src/keycloak.ts
Normal file
86
bizmatch/src/keycloak.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { KeycloakAdapter, KeycloakInstance, KeycloakLoginOptions, KeycloakLogoutOptions, KeycloakRegisterOptions } from 'keycloak-js';
|
||||||
|
import { createLogger } from './app/utils/utils';
|
||||||
|
|
||||||
|
const logger = createLogger('keycloak');
|
||||||
|
export type OptionsOrProvider<T> = Partial<T> | (() => Partial<T>);
|
||||||
|
/**
|
||||||
|
* Create and immediately resolve a KeycloakPromise
|
||||||
|
*/
|
||||||
|
const createPromise = () => new Promise<void>(resolve => resolve());
|
||||||
|
/**
|
||||||
|
* Resolve OptionsOrProvider: if it's an function, execute it, otherwise return it.
|
||||||
|
*/
|
||||||
|
const resolveOptions = <T>(opt: OptionsOrProvider<T>): Partial<T> => (typeof opt === 'function' ? opt() : opt);
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Update options with the overrides given as OptionsOrProvider
|
||||||
|
*
|
||||||
|
* @param options
|
||||||
|
* @param overrides
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const updateOptions = <T>(options: T, overrides: OptionsOrProvider<T>): T => Object.assign(options ?? <T>{}, resolveOptions(overrides));
|
||||||
|
/**
|
||||||
|
* Keycloak adapter that supports options customization.
|
||||||
|
*
|
||||||
|
* All options can either be given as lazily evaluated provider functions (that will be evaluated
|
||||||
|
* right before navigating) or eagerly evaluated objects. These options will have precedence
|
||||||
|
* over options passed by keycloak-js.
|
||||||
|
*
|
||||||
|
* Cf. https://www.keycloak.org/docs/15.0/securing_apps/#custom-adapters
|
||||||
|
*
|
||||||
|
* Actual implementation copied more or less verbatim from
|
||||||
|
* https://github.com/keycloak/keycloak-js-bower/blob/10.0.2/dist/keycloak.js#L1136
|
||||||
|
*
|
||||||
|
* @param kc Function that returns a Keycloak instance
|
||||||
|
* @param loginOptions login options
|
||||||
|
* @param logoutOptions logout options
|
||||||
|
* @param registerOptions register options
|
||||||
|
* @returns KeycloakAdapter
|
||||||
|
*/
|
||||||
|
export function customKeycloakAdapter(
|
||||||
|
kc: () => KeycloakInstance,
|
||||||
|
loginOptions: OptionsOrProvider<KeycloakLoginOptions> = {},
|
||||||
|
logoutOptions: OptionsOrProvider<KeycloakLogoutOptions> = {},
|
||||||
|
registerOptions: OptionsOrProvider<KeycloakRegisterOptions> = {},
|
||||||
|
): KeycloakAdapter {
|
||||||
|
return {
|
||||||
|
login: (options?: KeycloakLoginOptions): Promise<void> => {
|
||||||
|
updateOptions(options, loginOptions);
|
||||||
|
logger.info('Executing login. Options: ', options);
|
||||||
|
window.location.replace(kc().createLoginUrl(options));
|
||||||
|
return createPromise();
|
||||||
|
},
|
||||||
|
logout: (options?: KeycloakLogoutOptions): Promise<void> => {
|
||||||
|
updateOptions(options, logoutOptions);
|
||||||
|
logger.info('Executing logout. Options: ', options);
|
||||||
|
window.location.replace(kc().createLogoutUrl(options));
|
||||||
|
return createPromise();
|
||||||
|
},
|
||||||
|
register: (options?: KeycloakRegisterOptions): Promise<void> => {
|
||||||
|
updateOptions(options, registerOptions);
|
||||||
|
logger.info('Executing register. Options: ', options);
|
||||||
|
window.location.replace(kc().createRegisterUrl(options));
|
||||||
|
return createPromise();
|
||||||
|
},
|
||||||
|
accountManagement: (): Promise<void> => {
|
||||||
|
const accountUrl = kc().createAccountUrl();
|
||||||
|
logger.info('Executing account management');
|
||||||
|
if (typeof accountUrl !== 'undefined') {
|
||||||
|
window.location.href = accountUrl;
|
||||||
|
} else {
|
||||||
|
throw new Error('Not supported by the OIDC server');
|
||||||
|
}
|
||||||
|
return createPromise();
|
||||||
|
},
|
||||||
|
redirectUri: (options: { redirectUri: string }) => {
|
||||||
|
if (options?.redirectUri) {
|
||||||
|
return options.redirectUri;
|
||||||
|
}
|
||||||
|
if (kc().redirectUri) {
|
||||||
|
return kc().redirectUri;
|
||||||
|
}
|
||||||
|
return window.location.href;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
@import "primeng/resources/primeng.css";
|
@import 'primeng/resources/primeng.css';
|
||||||
@import 'primeicons/primeicons.css';
|
@import 'primeicons/primeicons.css';
|
||||||
// @import 'primeflex/primeflex.scss';
|
// @import 'primeflex/primeflex.scss';
|
||||||
@import "primeflex/primeflex.css";
|
@import 'primeflex/primeflex.css';
|
||||||
@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
|
@import url('https://fonts.googleapis.com/css?family=Open+Sans&display=swap');
|
||||||
@import "primeng/resources/themes/lara-light-blue/theme.css";
|
@import 'primeng/resources/themes/lara-light-blue/theme.css';
|
||||||
@import "@fortawesome/fontawesome-free/css/all.min.css";
|
@import '@fortawesome/fontawesome-free/css/all.min.css';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--text-color-secondary:rgba(255, 255, 255);
|
--text-color-secondary: rgba(255, 255, 255);
|
||||||
--wrapper-width:1491px;
|
--wrapper-width: 1491px;
|
||||||
// --secondary-color: #ffffff; /* Setzt die secondary Farbe auf weiß */
|
// --secondary-color: #ffffff; /* Setzt die secondary Farbe auf weiß */
|
||||||
}
|
}
|
||||||
.p-button.p-button-secondary.p-button-outlined{
|
.p-button.p-button-secondary.p-button-outlined {
|
||||||
color: #ffffff;;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
@@ -35,7 +35,11 @@ h6 {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
body, input, button, select, textarea {
|
body,
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
// font-family: 'Open Sans', sans-serif;
|
// font-family: 'Open Sans', sans-serif;
|
||||||
font-family: var(--font-family);
|
font-family: var(--font-family);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
@@ -71,8 +75,11 @@ p-menubarsub ul {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
.p-editor-container .ql-toolbar{
|
.p-editor-container .ql-toolbar {
|
||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
border-top-right-radius: 6px;
|
border-top-right-radius: 6px;
|
||||||
border-top-left-radius: 6px;
|
border-top-left-radius: 6px;
|
||||||
}
|
}
|
||||||
|
.p-dropdown-panel .p-dropdown-header .p-dropdown-filter {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
|||||||
35
bizmatch/version.js
Normal file
35
bizmatch/version.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#! /usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
const dayjs = require('dayjs');
|
||||||
|
const timezone = require('dayjs/plugin/timezone');
|
||||||
|
const utc = require('dayjs/plugin/utc');
|
||||||
|
var localizedFormat = require('dayjs/plugin/localizedFormat');
|
||||||
|
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
|
const write = (content, path) => {
|
||||||
|
const writePath = path || `${process.cwd()}/src/build.ts`;
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(
|
||||||
|
writePath,
|
||||||
|
'// Build information, automatically generated by `the_build_script` :zwinkern:\n' + 'const build = ' + JSON.stringify(content, null, 4).replace(/\"([^(\")"]+)\":/g, '$1:') + ';\n\nexport default build;',
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const package = require(`${process.cwd()}/package.json`);
|
||||||
|
(() => {
|
||||||
|
console.log('start build.js script ...');
|
||||||
|
// Generate `build` object
|
||||||
|
const build = {};
|
||||||
|
const acDate = new Date();
|
||||||
|
const german = dayjs(acDate).tz('Europe/Berlin').format('DD.MM.YYYY HH:mm');
|
||||||
|
const texan = dayjs(acDate).tz('America/Chicago').format('L LT');
|
||||||
|
build.timestamp = `GER: ${german} | TX: ${texan}`;
|
||||||
|
// Write Build information to file
|
||||||
|
write(build);
|
||||||
|
console.log('build.js script finished ...');
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user