initial release

This commit is contained in:
2024-02-29 10:23:41 -06:00
commit 5146c8e919
210 changed files with 11040 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,331 @@
{
"hash": "67af9537",
"configHash": "34f84833",
"lockfileHash": "c6a666e0",
"browserHash": "ac877f75",
"optimized": {
"@angular/common": {
"src": "../../../../../node_modules/@angular/common/fesm2022/common.mjs",
"file": "@angular_common.js",
"fileHash": "28f818f0",
"needsInterop": false
},
"@angular/common/http": {
"src": "../../../../../node_modules/@angular/common/fesm2022/http.mjs",
"file": "@angular_common_http.js",
"fileHash": "3e7d92bd",
"needsInterop": false
},
"@angular/core": {
"src": "../../../../../node_modules/@angular/core/fesm2022/core.mjs",
"file": "@angular_core.js",
"fileHash": "accc9b8c",
"needsInterop": false
},
"@angular/forms": {
"src": "../../../../../node_modules/@angular/forms/fesm2022/forms.mjs",
"file": "@angular_forms.js",
"fileHash": "928647a5",
"needsInterop": false
},
"@angular/platform-browser": {
"src": "../../../../../node_modules/@angular/platform-browser/fesm2022/platform-browser.mjs",
"file": "@angular_platform-browser.js",
"fileHash": "9b924e7c",
"needsInterop": false
},
"@angular/platform-browser/animations": {
"src": "../../../../../node_modules/@angular/platform-browser/fesm2022/animations.mjs",
"file": "@angular_platform-browser_animations.js",
"fileHash": "7495a616",
"needsInterop": false
},
"@angular/router": {
"src": "../../../../../node_modules/@angular/router/fesm2022/router.mjs",
"file": "@angular_router.js",
"fileHash": "735c693a",
"needsInterop": false
},
"@fortawesome/angular-fontawesome": {
"src": "../../../../../node_modules/@fortawesome/angular-fontawesome/fesm2022/angular-fontawesome.mjs",
"file": "@fortawesome_angular-fontawesome.js",
"fileHash": "0cf8f8a4",
"needsInterop": false
},
"@fortawesome/free-regular-svg-icons": {
"src": "../../../../../node_modules/@fortawesome/free-regular-svg-icons/index.mjs",
"file": "@fortawesome_free-regular-svg-icons.js",
"fileHash": "a6d77e90",
"needsInterop": false
},
"@fortawesome/free-solid-svg-icons": {
"src": "../../../../../node_modules/@fortawesome/free-solid-svg-icons/index.mjs",
"file": "@fortawesome_free-solid-svg-icons.js",
"fileHash": "248c3041",
"needsInterop": false
},
"browser-bunyan": {
"src": "../../../../../node_modules/browser-bunyan/lib/index.m.js",
"file": "browser-bunyan.js",
"fileHash": "3556293f",
"needsInterop": false
},
"jwt-decode": {
"src": "../../../../../node_modules/jwt-decode/build/esm/index.js",
"file": "jwt-decode.js",
"fileHash": "31be6951",
"needsInterop": false
},
"keycloak-js": {
"src": "../../../../../node_modules/keycloak-js/dist/keycloak.mjs",
"file": "keycloak-js.js",
"fileHash": "a0ad62ab",
"needsInterop": false
},
"on-change": {
"src": "../../../../../node_modules/on-change/index.js",
"file": "on-change.js",
"fileHash": "78a0866f",
"needsInterop": false
},
"primeng/api": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-api.mjs",
"file": "primeng_api.js",
"fileHash": "4db75e0f",
"needsInterop": false
},
"primeng/button": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-button.mjs",
"file": "primeng_button.js",
"fileHash": "1190de19",
"needsInterop": false
},
"primeng/checkbox": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-checkbox.mjs",
"file": "primeng_checkbox.js",
"fileHash": "9193d0d3",
"needsInterop": false
},
"primeng/chip": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-chip.mjs",
"file": "primeng_chip.js",
"fileHash": "d481740a",
"needsInterop": false
},
"primeng/confirmdialog": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-confirmdialog.mjs",
"file": "primeng_confirmdialog.js",
"fileHash": "27802851",
"needsInterop": false
},
"primeng/confirmpopup": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-confirmpopup.mjs",
"file": "primeng_confirmpopup.js",
"fileHash": "07fa7368",
"needsInterop": false
},
"primeng/divider": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-divider.mjs",
"file": "primeng_divider.js",
"fileHash": "d9ac7514",
"needsInterop": false
},
"primeng/dropdown": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-dropdown.mjs",
"file": "primeng_dropdown.js",
"fileHash": "a52c3507",
"needsInterop": false
},
"primeng/fileupload": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-fileupload.mjs",
"file": "primeng_fileupload.js",
"fileHash": "57b31a6c",
"needsInterop": false
},
"primeng/inputnumber": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-inputnumber.mjs",
"file": "primeng_inputnumber.js",
"fileHash": "2c9bde1c",
"needsInterop": false
},
"primeng/inputtext": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-inputtext.mjs",
"file": "primeng_inputtext.js",
"fileHash": "b03c8c78",
"needsInterop": false
},
"primeng/inputtextarea": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-inputtextarea.mjs",
"file": "primeng_inputtextarea.js",
"fileHash": "890a3704",
"needsInterop": false
},
"primeng/menubar": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-menubar.mjs",
"file": "primeng_menubar.js",
"fileHash": "573b8558",
"needsInterop": false
},
"primeng/overlaypanel": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-overlaypanel.mjs",
"file": "primeng_overlaypanel.js",
"fileHash": "cbfd82db",
"needsInterop": false
},
"primeng/paginator": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-paginator.mjs",
"file": "primeng_paginator.js",
"fileHash": "f1a55a1a",
"needsInterop": false
},
"primeng/progressspinner": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-progressspinner.mjs",
"file": "primeng_progressspinner.js",
"fileHash": "f063f0c5",
"needsInterop": false
},
"primeng/ripple": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-ripple.mjs",
"file": "primeng_ripple.js",
"fileHash": "97e2f125",
"needsInterop": false
},
"primeng/styleclass": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-styleclass.mjs",
"file": "primeng_styleclass.js",
"fileHash": "37c3b954",
"needsInterop": false
},
"primeng/table": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-table.mjs",
"file": "primeng_table.js",
"fileHash": "7aeb3809",
"needsInterop": false
},
"primeng/tabmenu": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-tabmenu.mjs",
"file": "primeng_tabmenu.js",
"fileHash": "19fb3789",
"needsInterop": false
},
"primeng/toast": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-toast.mjs",
"file": "primeng_toast.js",
"fileHash": "68ec89d2",
"needsInterop": false
},
"primeng/togglebutton": {
"src": "../../../../../node_modules/primeng/fesm2022/primeng-togglebutton.mjs",
"file": "primeng_togglebutton.js",
"fileHash": "522ccb3c",
"needsInterop": false
},
"rxjs": {
"src": "../../../../../node_modules/rxjs/dist/esm5/index.js",
"file": "rxjs.js",
"fileHash": "823e3e71",
"needsInterop": false
},
"rxjs/operators": {
"src": "../../../../../node_modules/rxjs/dist/esm5/operators/index.js",
"file": "rxjs_operators.js",
"fileHash": "b5e6296c",
"needsInterop": false
}
},
"chunks": {
"chunk-I7D7PQME": {
"file": "chunk-I7D7PQME.js"
},
"chunk-KMR4QVQU": {
"file": "chunk-KMR4QVQU.js"
},
"chunk-IU2G34K3": {
"file": "chunk-IU2G34K3.js"
},
"chunk-VYYSRRQP": {
"file": "chunk-VYYSRRQP.js"
},
"chunk-SRY2GXMH": {
"file": "chunk-SRY2GXMH.js"
},
"chunk-2HEVCKUT": {
"file": "chunk-2HEVCKUT.js"
},
"chunk-FLWDL4KY": {
"file": "chunk-FLWDL4KY.js"
},
"chunk-B3BEBK2H": {
"file": "chunk-B3BEBK2H.js"
},
"chunk-7NEKFKVF": {
"file": "chunk-7NEKFKVF.js"
},
"chunk-RKMPS52T": {
"file": "chunk-RKMPS52T.js"
},
"chunk-2QWPUIZJ": {
"file": "chunk-2QWPUIZJ.js"
},
"chunk-XGK4THRG": {
"file": "chunk-XGK4THRG.js"
},
"chunk-DFUOJE3F": {
"file": "chunk-DFUOJE3F.js"
},
"chunk-NBIWWQDH": {
"file": "chunk-NBIWWQDH.js"
},
"chunk-6M7HYOYM": {
"file": "chunk-6M7HYOYM.js"
},
"chunk-3SQF7L7O": {
"file": "chunk-3SQF7L7O.js"
},
"chunk-MI5BQO2G": {
"file": "chunk-MI5BQO2G.js"
},
"chunk-BH46VVEZ": {
"file": "chunk-BH46VVEZ.js"
},
"chunk-KAUMDUK6": {
"file": "chunk-KAUMDUK6.js"
},
"chunk-6ZBHNYAL": {
"file": "chunk-6ZBHNYAL.js"
},
"chunk-SLXWKEKP": {
"file": "chunk-SLXWKEKP.js"
},
"chunk-O5UWSVSE": {
"file": "chunk-O5UWSVSE.js"
},
"chunk-ESXUKNGR": {
"file": "chunk-ESXUKNGR.js"
},
"chunk-SVLVPO4L": {
"file": "chunk-SVLVPO4L.js"
},
"chunk-MDVT4WFW": {
"file": "chunk-MDVT4WFW.js"
},
"chunk-UANYWW5J": {
"file": "chunk-UANYWW5J.js"
},
"chunk-3Q7TKPWY": {
"file": "chunk-3Q7TKPWY.js"
},
"chunk-SG3BCSKH": {
"file": "chunk-SG3BCSKH.js"
},
"chunk-SAVXX6OM": {
"file": "chunk-SAVXX6OM.js"
},
"chunk-PQ7O3X3G": {
"file": "chunk-PQ7O3X3G.js"
},
"chunk-ASLTLD6L": {
"file": "chunk-ASLTLD6L.js"
}
}
}

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

16
bizmatch/.editorconfig Normal file
View File

@@ -0,0 +1,16 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

45
bizmatch/.eslintrc.json Normal file
View File

@@ -0,0 +1,45 @@
{
"env": {
"es2021": true,
"browser": true
},
"extends": [
"airbnb-base",
"airbnb-typescript",
"plugin:@typescript-eslint/recommended",
"eslint-config-prettier",
"plugin:cypress/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module",
"project": ["./tsconfig.json"]
},
"plugins": ["@typescript-eslint"],
"rules": {
"import/no-unresolved": ["off"],
"import/prefer-default-export": ["off"],
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": ["error"],
"@typescript-eslint/lines-between-class-members": ["off"],
"no-param-reassign": ["off"],
"max-classes-per-file": ["off"],
"no-shadow": ["off"],
"class-methods-use-this": ["off"],
"react/jsx-filename-extension": ["off"],
"import/no-cycle": ["off"],
"radix": ["off"],
"no-promise-executor-return": ["off"],
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "enumMember",
"format": ["UPPER_CASE", "PascalCase"]
}
],
"no-restricted-syntax": ["error", "ForInStatement", "LabeledStatement", "WithStatement"],
"spaced-comment": ["off"],
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }]
}
}

4
bizmatch/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
"recommendations": ["angular.ng-template"]
}

20
bizmatch/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ng serve",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: start",
"url": "http://localhost:4200/"
},
{
"name": "ng test",
"type": "chrome",
"request": "launch",
"preLaunchTask": "npm: test",
"url": "http://localhost:9876/debug.html"
}
]
}

42
bizmatch/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,42 @@
{
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "start",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
},
{
"type": "npm",
"script": "test",
"isBackground": true,
"problemMatcher": {
"owner": "typescript",
"pattern": "$tsc",
"background": {
"activeOnStart": true,
"beginsPattern": {
"regexp": "(.*?)"
},
"endsPattern": {
"regexp": "bundle generation complete"
}
}
}
}
]
}

27
bizmatch/README.md Normal file
View File

@@ -0,0 +1,27 @@
# Bizmatch
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.1.2.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.

116
bizmatch/angular.json Normal file
View File

@@ -0,0 +1,116 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"bizmatch": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss",
"skipTests": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/bizmatch",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
},
"dev": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
],
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "bizmatch:build:production"
},
"development": {
"buildTarget": "bizmatch:build:development"
}
},
"defaultConfiguration": "development",
"options": {"proxyConfig": "proxy.conf.json"}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "bizmatch:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
},
"cli": {
"analytics": false
}
}

108
bizmatch/angular_copy.json Normal file
View File

@@ -0,0 +1,108 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"bizmatch": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss",
"skipTests": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/bizmatch",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [],
"server": "src/main.server.ts",
"prerender": true,
"ssr": {
"entry": "server.ts"
}
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "bizmatch:build:production"
},
"development": {
"buildTarget": "bizmatch:build:development"
}
},
"defaultConfiguration": "development",
"options": {"proxyConfig": "proxy.conf.json"}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "bizmatch:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
[
{
"id":"1",
"userId":"14a05316-cb85-4c67-86bc-4a2083ff6af7",
"listingsCategory": "business",
"title": "Industrial Service Company In Corpus Christi For Sale - 1954",
"summary": ["Asking price: $5,500,000","Sales revenue: $1,200,000","Net profit: $650,000"],
"description": ["This company services a wide variety of industries. Asking price includes Business and the Real Estate and is approx 30,000 sq ft with room for expansion including approx 5 acres. Absentee run business."],
"type": "Industrial Services",
"location": "Texas",
"price":5500000,
"salesRevenue":1200000,
"cashFlow":650000,
"brokerLicencing":"TREC Broker #516788",
"established":1954,
"realEstateIncluded":false,
"favoritesForUser":["e0811669-c7eb-4e5e-a699-e8334d5c5b01"]
},
{
"id":"2",
"userId":"e0811669-c7eb-4e5e-a699-e8334d5c5b01",
"listingsCategory": "business",
"title": "Coastal Bend Manufacturing Business Plastic Injection For Sale - 1950",
"summary": ["Asking price: $165,000","Sales revenue: Undisclosed","Net profit: Undisclosed"],
"description": [""],
"type": "Manufacturing",
"location": "Texas",
"price":165000,
"salesRevenue":null,
"cashFlow":null,
"brokerLicencing":"TREC Broker #516788",
"established":1950,
"realEstateIncluded":false,
"favoritesForUser":["828cc120-51e9-4baa-9a33-a82608fe66b4"]
},
{
"id":"3",
"userId":"e0811669-c7eb-4e5e-a699-e8334d5c5b01",
"listingsCategory": "business",
"title": "Corner Property On Everhart South-side Corpus Christi For Sale - 1944",
"summary": ["Asking price: $830,000","Sales revenue: Undisclosed","Net profit: Undisclosed"],
"description": [""],
"type": "Real Estate",
"location": "Texas",
"price":830000,
"salesRevenue":null,
"cashFlow":null,
"brokerLicencing":"TREC Broker #516788",
"established":1944,
"realEstateIncluded":false,
"favoritesForUser":[]
},
{
"id":"4",
"userId":"828cc120-51e9-4baa-9a33-a82608fe66b4",
"listingsCategory": "business",
"title": "Corpus Christi Dessert Business For Sale - 1941",
"summary": ["Asking price: $124,900","Sales revenue: $225,000","Net profit: $50,000"],
"description": [""],
"type": "Food and Restaurant",
"location": "Texas",
"price":830000,
"salesRevenue":225000,
"cashFlow":50000,
"brokerLicencing":"TREC Broker #516788",
"established":1941,
"realEstateIncluded":false,
"favoritesForUser":[]
}
]

View File

@@ -0,0 +1,21 @@
{
"id":"1",
"firstname":"Andreas",
"lastname":"Knuth",
"email":"andreas.knuth@gmail.com",
"nickname":"aknuth",
"displayName":"Andreas Knuth",
"subscriptions":[{
"id":"1",
"level":"Business Broker",
"start":"2024-02-12T21:54:20.603Z",
"modified":"2024-02-12T21:54:20.603Z",
"end":"9999-02-12T21:54:20.603Z",
"status":"active",
"invoices":[{
"date":"2024-02-12T21:54:20.603Z",
"id":"C991853B99",
"price":0
}]
}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 48 KiB

View File

@@ -0,0 +1,7 @@
<html>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

61
bizmatch/package.json Normal file
View File

@@ -0,0 +1,61 @@
{
"name": "bizmatch",
"version": "0.0.1",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"build.dev": "ng build --configuration dev",
"watch": "ng build --watch --configuration development",
"test": "ng test",
"serve:ssr:bizmatch": "node dist/bizmatch/server/server.mjs"
},
"private": true,
"dependencies": {
"@angular/animations": "^17.2.2",
"@angular/common": "^17.2.2",
"@angular/compiler": "^17.2.2",
"@angular/core": "^17.2.2",
"@angular/forms": "^17.2.2",
"@angular/platform-browser": "^17.2.2",
"@angular/platform-browser-dynamic": "^17.2.2",
"@angular/platform-server": "^17.2.2",
"@angular/router": "^17.2.2",
"@fortawesome/angular-fontawesome": "^0.14.1",
"@fortawesome/fontawesome-free": "^6.5.1",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@types/uuid": "^9.0.8",
"browser-bunyan": "^1.8.0",
"express": "^4.18.2",
"jwt-decode": "^4.0.0",
"keycloak-js": "^23.0.7",
"memoize-one": "^6.0.0",
"on-change": "^5.0.1",
"primeflex": "^3.3.1",
"primeicons": "^6.0.1",
"primeng": "^17.6.0",
"rxjs": "~7.8.1",
"tslib": "^2.3.0",
"urlcat": "^3.1.0",
"uuid": "^9.0.1",
"zone.js": "~0.14.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.2.1",
"@angular/cli": "^17.2.1",
"@angular/compiler-cli": "^17.2.2",
"@types/express": "^4.17.21",
"@types/jasmine": "~5.1.4",
"@types/node": "^20.11.20",
"jasmine-core": "~5.1.2",
"karma": "~6.4.2",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.1",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.3.3"
}
}

6
bizmatch/proxy.conf.json Normal file
View File

@@ -0,0 +1,6 @@
{
"/api": {
"target": "http://localhost:3000",
"secure": false
}
}

56
bizmatch/server.ts Normal file
View File

@@ -0,0 +1,56 @@
// import { APP_BASE_HREF } from '@angular/common';
// import { CommonEngine } from '@angular/ssr';
// import express from 'express';
// import { fileURLToPath } from 'node:url';
// import { dirname, join, resolve } from 'node:path';
// import bootstrap from './src/main.server';
// // The Express app is exported so that it can be used by serverless Functions.
// export function app(): express.Express {
// const server = express();
// const serverDistFolder = dirname(fileURLToPath(import.meta.url));
// const browserDistFolder = resolve(serverDistFolder, '../browser');
// const indexHtml = join(serverDistFolder, 'index.server.html');
// const commonEngine = new CommonEngine();
// server.set('view engine', 'html');
// server.set('views', browserDistFolder);
// // Example Express Rest API endpoints
// // server.get('/api/**', (req, res) => { });
// // Serve static files from /browser
// server.get('*.*', express.static(browserDistFolder, {
// maxAge: '1y'
// }));
// // All regular routes use the Angular engine
// server.get('*', (req, res, next) => {
// const { protocol, originalUrl, baseUrl, headers } = req;
// commonEngine
// .render({
// bootstrap,
// documentFilePath: indexHtml,
// url: `${protocol}://${headers.host}${originalUrl}`,
// publicPath: browserDistFolder,
// providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
// })
// .then((html) => res.send(html))
// .catch((err) => next(err));
// });
// return server;
// }
// function run(): void {
// const port = process.env['PORT'] || 4000;
// // Start up the Node server
// const server = app();
// server.listen(port, () => {
// console.log(`Node Express server listening on http://localhost:${port}`);
// });
// }
// run();

View File

@@ -0,0 +1,14 @@
<div class="container">
<div class="content">
@if (actualRoute !=='home' && actualRoute !=='pricing'){
<header></header>
}
<router-outlet></router-outlet>
@if (loadingService.isLoading$ | async) {
<div class="progress-spinner flex h-full align-items-center justify-content-center">
<p-progressSpinner></p-progressSpinner>
</div>
}
</div>
<footer></footer>
</div>

View File

@@ -0,0 +1,10 @@
.container {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.content {
flex: 1;
/* Optional: Padding für den Inhalt, um sicherzustellen, dass er nicht direkt am Footer klebt */
// padding-bottom: 20px;
}

View File

@@ -0,0 +1,59 @@
import { CommonModule } from '@angular/common';
import { Component, ViewChild } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from '@angular/router';
import { HeaderComponent } from './components/header/header.component';
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 { ListingCriteria, User } from './models/main.model';
import { createGenericObject } from './utils/utils';
import onChange from 'on-change';
import { UserService } from './services/user.service';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, RouterOutlet, HeaderComponent, ProgressSpinnerModule, FooterComponent],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
title = 'bizmatch';
actualRoute ='';
user:User;
listingCriteria:ListingCriteria = onChange(createGenericObject<ListingCriteria>(),(path, value, previousValue, applyData)=>{
sessionStorage.setItem('criteria',JSON.stringify(value));
});
public constructor(public loadingService: LoadingService, private router: Router,private activatedRoute: ActivatedRoute, private keycloakService:KeycloakService,private userService:UserService) {
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe(() => {
let currentRoute = this.activatedRoute.root;
while (currentRoute.children[0] !== undefined) {
currentRoute = currentRoute.children[0];
}
// Hier haben Sie Zugriff auf den aktuellen Route-Pfad
this.actualRoute=currentRoute.snapshot.url[0].path
});
// keycloakService.keycloakEvents$.subscribe({
// next(event) {
// if (event.type == KeycloakEventType.OnTokenExpired) {
// keycloakService.updateToken(20);
// }
// if (event.type == KeycloakEventType.OnActionUpdate) {
// }
// }
// });
}
ngOnInit(){
this.user = this.userService.getUser();
}
}

View File

@@ -0,0 +1,11 @@
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';
const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering()
]
};
export const config = mergeApplicationConfig(appConfig, serverConfig);

View File

@@ -0,0 +1,56 @@
import { APP_INITIALIZER, ApplicationConfig, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideClientHydration } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { HTTP_INTERCEPTORS, provideHttpClient, withFetch, withInterceptorsFromDi } from '@angular/common/http';
import { environment } from '../environments/environment';
import { SelectOptionsService } from './services/select-options.service';
import { KeycloakService } from './services/keycloak.service';
import { UserService } from './services/user.service';
// provideClientHydration()
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptorsFromDi()),
{provide:KeycloakService},
{
provide: APP_INITIALIZER,
useFactory: initializeKeycloak,
multi: true,
deps: [KeycloakService],
},
{
provide: APP_INITIALIZER,
useFactory: initServices,
multi: true,
deps: [SelectOptionsService],
},
provideRouter(routes),provideAnimations()
]
};
function initUserService(userService:UserService) {
return () => {
//selectOptions.init();
}
}
function initServices(selectOptions:SelectOptionsService) {
return () => {
selectOptions.init();
}
}
function initializeKeycloak(keycloak: KeycloakService) {
return () =>
keycloak.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'
},
});
}

View File

@@ -0,0 +1,75 @@
import { Routes } from '@angular/router';
import { ListingsComponent } from './pages/listings/listings.component';
import { HomeComponent } from './pages/home/home.component';
import { DetailsComponent } from './pages/details/details.component';
import { AccountComponent } from './pages/subscription/account/account.component';
import { EditListingComponent } from './pages/subscription/edit-listing/edit-listing.component';
import { MyListingComponent } from './pages/subscription/my-listing/my-listing.component';
import { FavoritesComponent } from './pages/subscription/favorites/favorites.component';
import { EmailUsComponent } from './pages/subscription/email-us/email-us.component';
import { authGuard } from './guards/auth.guard';
import { PricingComponent } from './pages/pricing/pricing.component';
import { LogoutComponent } from './components/logout/logout.component';
export const routes: Routes = [
{
path: 'listings/:type',
component: ListingsComponent,
},
// Umleitung von /listing zu /listing/business
{
path: 'listings',
pathMatch: 'full',
redirectTo: 'listings/business',
runGuardsAndResolvers:'always'
},
{
path: 'home',
component: HomeComponent,
},
{
path: 'details/:id',
component: DetailsComponent,
},
{
path: 'account',
component: AccountComponent,
canActivate: [authGuard],
},
{
path: 'editListing/:id',
component: EditListingComponent,
canActivate: [authGuard],
},
{
path: 'createListing',
component: EditListingComponent,
canActivate: [authGuard],
},
{
path: 'myListings',
component: MyListingComponent,
canActivate: [authGuard],
},
{
path: 'myFavorites',
component: FavoritesComponent,
canActivate: [authGuard],
},
{
path: 'emailUs',
component: EmailUsComponent,
canActivate: [authGuard],
},
{
path: 'logout',
component: LogoutComponent,
canActivate: [authGuard],
},
{
path: 'pricing',
component: PricingComponent
},
{ path: '**', redirectTo: 'home' },
];

View File

@@ -0,0 +1,27 @@
<div class="surface-0 px-4 py-4 md:px-6 lg:px-8">
<div class="surface-0">
<div class="grid">
<div class="col-12 md:col-3 md:mb-0 mb-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-gray-300 font-bold text-5xl">Bastion</div> -->
</div>
<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-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&#64;biz-match.com</div>
</div>
<div class="col-12 md:col-3 text-500">
<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">Privacy statement</a>
</div>
<div class="col-12 md:col-3 text-500">
<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="userService.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>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,6 @@
:host{
height: 192px;
}
div {
font-size: small;
}

View File

@@ -0,0 +1,26 @@
import { CommonModule } from '@angular/common';
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 { KeyValue } from '../../models/main.model';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { UserService } from '../../services/user.service';
import { SharedModule } from '../../shared/shared/shared.module';
@Component({
selector: 'footer',
standalone: true,
imports: [SharedModule],
templateUrl: './footer.component.html',
styleUrl: './footer.component.scss'
})
export class FooterComponent {
constructor(public userService:UserService){}
login(){
this.userService.login(window.location.href);
}
}

View File

@@ -0,0 +1,11 @@
<div class="wrapper">
<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>
<p-tabMenu [model]="tabItems" ariaLabelledBy="label" styleClass="flex" [activeItem]="activeItem">
</p-tabMenu>
<p-menubar [model]="menuItems"></p-menubar>
<div *ngIf="user$ | async as user else empty">Welcome, {{user.firstname}}</div>
<ng-template #empty>
</ng-template>
</div>
</div>

View File

@@ -0,0 +1,13 @@
::ng-deep p-menubarsub{
margin-left: auto;
}
::ng-deep .p-tabmenu .p-tabmenu-nav .p-tabmenuitem .p-menuitem-link{
border:1px solid #ffffff;
}
::ng-deep .p-tabmenu .p-tabmenu-nav .p-tabmenuitem.p-highlight .p-menuitem-link {
border-bottom: 2px solid #3B82F6 !important;
}
::ng-deep .p-menubar{
border:unset;
background: unset;
}

View File

@@ -0,0 +1,115 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { MenuItem } from 'primeng/api';
import { ButtonModule } from 'primeng/button';
import { MenubarModule } from 'primeng/menubar';
import { OverlayPanelModule } from 'primeng/overlaypanel';
import { environment } from '../../../environments/environment';
import { UserService } from '../../services/user.service';
import { User } from '../../models/main.model';
import { TabMenuModule } from 'primeng/tabmenu';
import { Observable } from 'rxjs';
import { faUserGear } from '@fortawesome/free-solid-svg-icons';
import { Router } from '@angular/router';
@Component({
selector: 'header',
standalone: true,
imports: [CommonModule, MenubarModule, ButtonModule, OverlayPanelModule, TabMenuModule ],
templateUrl: './header.component.html',
styleUrl: './header.component.scss'
})
export class HeaderComponent {
public buildVersion = environment.buildVersion;
user:User;
user$:Observable<User>
public tabItems: MenuItem[];
public menuItems: MenuItem[];
activeItem
faUserGear=faUserGear
constructor(public userService: UserService,private router: Router) {
}
ngOnInit(){
this.user$=this.userService.getUserObservable();
this.tabItems = [
{
label: 'Businesses for Sale',
routerLink: '/listings/business',
fragment:''
},
{
label: 'Professionals/Brokers Directory',
routerLink: '/listings/professionals_brokers',
fragment:''
},
{
label: 'Investment Property',
routerLink: '/listings/investment',
fragment:''
}
];
this.menuItems = [
{
label: 'User Actions',
icon: 'fas fa-cog',
items: [
{
label: 'Account',
icon: 'pi pi-user',
routerLink: '/account',
visible: this.isUserLoogedIn()
},
{
label: 'Create Listing',
icon: 'pi pi-plus-circle',
routerLink: "/createListing",
visible: this.isUserLoogedIn()
},
{
label: 'My Listings',
icon: 'pi pi-list',
routerLink:"/myListings",
visible: this.isUserLoogedIn()
},
{
label: 'My Favorites',
icon: 'pi pi-star',
routerLink:"/myFavorites",
visible: this.isUserLoogedIn()
},
{
label: 'EMail Us',
icon: 'fa-regular fa-envelope',
routerLink:"/emailUs",
visible: this.isUserLoogedIn()
},
{
label: 'Logout',
icon: 'fa-solid fa-right-from-bracket',
routerLink:"/logout",
visible: this.isUserLoogedIn()
},
{
label: 'Login',
icon: 'fa-solid fa-right-from-bracket',
//routerLink:"/account",
command: () => this.login(),
visible: !this.isUserLoogedIn()
},
]
}
]
this.activeItem=this.tabItems[0];
}
navigateWithState(dest: string, state: any) {
this.router.navigate([dest], { state: state });
}
isUserLoogedIn(){
return this.userService?.isLoggedIn();
}
login(){
this.userService.login(window.location.href);
}
}

View File

@@ -0,0 +1 @@
<p>logout works!</p>

View File

@@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { UserService } from '../../services/user.service';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
@Component({
selector: 'logout',
standalone: true,
imports: [CommonModule,RouterModule],
template:``
})
export class LogoutComponent {
constructor(private userService:UserService){
userService.logout();
}
}

View File

@@ -0,0 +1,38 @@
import { CanMatchFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
import { inject } from '@angular/core';
// Services
import { UserService } from '../services/user.service';
export const authGuard: CanMatchFn = async (route, segments): Promise<boolean | UrlTree> => {
const router = inject(Router);
const userService = inject(UserService);
const authenticated: boolean = userService.isLoggedIn();
if (!authenticated) {
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
const roles: string[] = userService.getUserRoles();//keycloakService.getUserRoles(true);
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) {
return true;
}
// Allow the user to proceed if ALL of the required roles are present
const authorized = requiredRoles.every((role) => 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']);
};

View File

@@ -0,0 +1,77 @@
import { Injectable, inject } from '@angular/core';
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptorFn,
HttpHandlerFn,
} from '@angular/common/http';
import { Observable, combineLatest, from, of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { KeycloakService } from '../services/keycloak.service';
import { ExcludedUrlRegex } from '../models/keycloak-options';
export const keycloakBearerInterceptor: HttpInterceptorFn = (req, next) => {
//return next(req);
const keycloak = inject(KeycloakService);
const { enableBearerInterceptor, excludedUrls } = keycloak;
if (!enableBearerInterceptor) {
return next(req);
}
const shallPass: boolean =
!keycloak.shouldAddToken(req) ||
excludedUrls.findIndex((item) => isUrlExcluded(req, item)) > -1;
if (shallPass) {
return next(req);
}
return combineLatest([
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)) {
return await this.keycloak.updateToken();
}
return true;
}

View File

@@ -0,0 +1,21 @@
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { tap } from 'rxjs';
import { v4 } from 'uuid';
import { LoadingService } from '../services/loading.service';
export const loadingInterceptor: HttpInterceptorFn = (req, next) => {
const loadingService = inject(LoadingService);
const requestId = `HTTP-${v4()}`;
loadingService.startLoading(requestId);
return next(req).pipe(
tap({
finalize: () => loadingService.stopLoading(requestId),
error: () => loadingService.stopLoading(requestId),
complete: () => loadingService.stopLoading(requestId),
})
);
};

View File

@@ -0,0 +1,52 @@
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;
}

View File

@@ -0,0 +1,142 @@
/**
* @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;
}

View File

@@ -0,0 +1 @@
../../../../common-models/src/main.model.ts

View File

@@ -0,0 +1,146 @@
<div class="surface-ground h-full">
<div class="px-6 py-5">
<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>
<!-- <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>
</div>
<!-- <div class="text-500 mb-5">Egestas sed tempus urna et pharetra pharetra massa massa ultricies.</div> -->
<div class="grid">
<div class="col-12 md:col-6">
<ul class="list-none p-0 m-0 border-top-1 border-300">
@if (listing && (listing.listingsCategory==='business' || listing.listingsCategory==='professionals_brokers')){
<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">Summary</div>
<div class="w-full md:w-10">
@for (summary of listing.summary; track summary; let idx = $index; let last = $last) {
<div class="text-900">{{summary}}</div>
@if (!last) {
<br/>
}
}
</div>
</li>
}
<li class="flex align-items-center py-3 px-2 flex-wrap">
<div class="text-500 w-full md:w-2 font-medium">Description</div>
<div class="text-900 w-full md:w-10 line-height-3">{{listing?.description}}</div>
</li>
@if (listing && (listing.listingsCategory==='business')){
<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-900 w-full md:w-10">
<p-chip [label]="selectOptions.getBusiness(listing.type)"></p-chip>
</div>
</li>
<li class="flex align-items-center py-3 px-2 flex-wrap ">
<div class="text-500 w-full md:w-2 font-medium">Located in</div>
<div class="text-900 w-full md:w-10">{{selectOptions.getLocation(listing.location)}}</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">Asking Price</div>
<div class="text-900 w-full md:w-10">{{listing.price | currency}}</div>
</li>
<li class="flex align-items-center py-3 px-2 flex-wrap ">
<div class="text-500 w-full md:w-2 font-medium">Real Estate Included</div>
<div class="text-900 w-full md:w-10">{{listing.realEstateIncluded?'Yes':'No'}}</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">Sales revenue</div>
<div class="text-900 w-full md:w-10">{{listing.salesRevenue | currency}}</div>
</li>
<li class="flex align-items-center py-3 px-2 flex-wrap ">
<div class="text-500 w-full md:w-2 font-medium">Cash flow</div>
<div class="text-900 w-full md:w-10">{{listing.cashFlow | currency}}</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">Employees</div>
<div class="text-900 w-full md:w-10">{{listing.employees}}</div>
</li>
<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-900 w-full md:w-10">{{listing.brokerLicencing}}</div>
</li>
}
@if (listing && (listing.listingsCategory==='professionals_brokers')){
<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">Located in</div>
<div class="text-900 w-full md:w-10">{{selectOptions.getLocation(listing.location)}}</div>
</li>
<li class="flex align-items-center py-3 px-2 flex-wrap ">
<div class="text-500 w-full md:w-2 font-medium">Address</div>
<div class="text-900 w-full md:w-10">{{listing.address}}</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">EMail</div>
<div class="text-900 w-full md:w-10">{{listing.email}}</div>
</li>
<li class="flex align-items-center py-3 px-2 flex-wrap ">
<div class="text-500 w-full md:w-2 font-medium">Website</div>
<div class="text-900 w-full md:w-10">{{listing.website}}</div>
</li>
<li class="flex align-items-center py-3 px-2 flex-wrap ">
<div class="text-500 w-full md:w-2 font-medium">Category</div>
<div class="text-900 w-full md:w-10">{{listing.category}}</div>
</li>
}
@if (listing && (listing.listingsCategory==='investment')){
<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">Located in</div>
<div class="text-900 w-full md:w-10">{{selectOptions.getLocation(listing.location)}}</div>
</li>
<li class="flex align-items-center py-3 px-2 flex-wrap">
<div class="text-500 w-full md:w-2 font-medium">EMail</div>
<div class="text-900 w-full md:w-10">{{listing.email}}</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">Website</div>
<div class="text-900 w-full md:w-10">{{listing.website}}</div>
</li>
<li class="flex align-items-center py-3 px-2 flex-wrap">
<div class="text-500 w-full md:w-2 font-medium">Phone Number</div>
<div class="text-900 w-full md:w-10">{{listing.phoneNumber}}</div>
</li>
}
</ul>
@if(listing && user && (user.id===listing?.userId || isAdmin())){
<button pButton pRipple label="Edit" icon="pi pi-file-edit" class="w-auto" [routerLink]="['/editListing',listing.id]"></button>
}
</div>
<div class="col-12 md:col-6">
<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-italic text-sm text-900 mb-5">Please Include your contact info below:</div>
<div class="grid formgrid p-fluid">
<div class="field mb-4 col-12 md:col-6">
<label for="company_name" class="font-medium text-900">Your Name</label>
<input id="company_name" type="text" pInputText>
</div>
<div class="field mb-4 col-12 md:col-6">
<label for="invoice_id" class="font-medium text-900">Your Email</label>
<input id="invoice_id" type="text" pInputText>
</div>
<div class="field mb-4 col-12 md:col-6">
<label for="customer_name" class="font-medium text-900">Phone Number</label>
<input id="customer_name" type="text" pInputText>
</div>
<div class="field mb-4 col-12 md:col-6">
<label for="customer_email" class="font-medium text-900">Country/State</label>
<input id="customer_email" type="text" pInputText>
</div>
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
<div class="field mb-4 col-12">
<label for="notes" class="font-medium text-900">Questions/Comments</label>
<textarea id="notes" pInputTextarea [autoResize]="true" [rows]="5"></textarea>
</div>
<div class="surface-border border-top-1 opacity-50 mb-4 col-12"></div>
</div>
<button pButton pRipple label="Submit" icon="pi pi-file" class="w-auto"></button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,50 @@
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 { BusinessListing, InvestmentsListing, KeyValue, ListingCriteria, ProfessionalsBrokersListing, User } from '../../models/main.model';
import { SelectOptionsService } from '../../services/select-options.service';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { TagModule } from 'primeng/tag';
import data from '../../../assets/data/listings.json';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { ChipModule } from 'primeng/chip';
import { lastValueFrom } from 'rxjs';
import { ListingsService } from '../../services/listings.service';
import { UserService } from '../../services/user.service';
import onChange from 'on-change';
import { getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
@Component({
selector: 'app-details',
standalone: true,
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, ChipModule,InputTextareaModule,RouterModule],
templateUrl: './details.component.html',
styleUrl: './details.component.scss'
})
export class DetailsComponent {
// listings: Array<BusinessListing>;
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
listing: BusinessListing|ProfessionalsBrokersListing|InvestmentsListing;
user:User;
criteria:ListingCriteria
constructor(private activatedRoute: ActivatedRoute,private listingsService:ListingsService,private router:Router,private userService:UserService,public selectOptions: SelectOptionsService){
this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler);
}
async ngOnInit(){
this.user = this.userService.getUser();
this.listing=await lastValueFrom(this.listingsService.getListingById(this.id));
}
back(){
this.router.navigate(['listings',this.criteria.listingsCategory])
}
isAdmin(){
return this.userService.hasAdminRole();
}
}

View File

@@ -0,0 +1,93 @@
<div class="container">
<div class="wrapper">
<div class="py-3 px-6 flex align-items-center justify-content-between relative">
<a routerLink="/home"><img src="../../../assets/images/header-logo.png" alt="Image" height="50" ></a>
<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>
<ul
class="list-none p-0 m-0 flex lg:align-items-center text-blue-900 select-none flex-column lg:flex-row cursor-pointer">
<li>
<a pRipple
class="flex px-0 lg:px-5 py-3 hover:text-blue-600 font-medium transition-colors transition-duration-150">
<span>Corporate</span>
</a>
</li>
<li>
<a pRipple
class="flex px-0 lg:px-5 py-3 hover:text-blue-600 font-medium transition-colors transition-duration-150">
<span>Resources</span>
</a>
</li>
<li>
<a pRipple
class="flex px-0 lg:px-5 py-3 hover:text-blue-600 font-medium transition-colors transition-duration-150">
<span>Pricing</span>
</a>
</li>
</ul>
<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">
<!-- <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" (click)="account()"></p-button>
</div>
</div>
</div>
<div class="px-4 py-8 md:px-6 lg:px-8">
<div class="flex flex-wrap">
<div class="w-12 lg:w-6 p-4">
<h1 class="text-6xl font-bold text-blue-900 mt-0 mb-3">Find businesses for sale</h1>
<p class="text-3xl text-blue-600 mt-0 mb-5">Arcu cursus euismod quis viverra nibh cras. Amet justo
donec
enim diam vulputate ut.</p>
<ul class="list-none p-0 m-0">
<li class="mb-3 flex align-items-center"><i
class="pi pi-compass text-yellow-500 text-xl mr-2"></i><span
class="text-blue-600 line-height-3">Senectus et netus et malesuada fames.</span></li>
<li class="mb-3 flex align-items-center"><i
class="pi pi-map text-yellow-500 text-xl mr-2"></i><span
class="text-blue-600 line-height-3">Orci a scelerisque purus semper eget.</span></li>
<li class="mb-3 flex align-items-center"><i
class="pi pi-calendar text-yellow-500 text-xl mr-2"></i><span
class="text-blue-600 line-height-3">Aenean sed adipiscing diam donec adipiscing
tristique.</span></li>
</ul>
</div>
<div class="w-12 lg:w-6 text-center lg:text-right flex">
<div class="mt-5">
<ul class="flex flex-column align-items-left gap-3 px-2 py-3 list-none surface-border">
<li><button pButton pRipple icon="pi pi-user" (click)="activeTabAction = 'business'"
label="Businesses"
[ngClass]="{'p-button-text text-700': activeTabAction !== 'business'}"></button></li>
<li><button pButton pRipple icon="pi pi-globe" (click)="activeTabAction = 'professionals_brokers'"
label="Professionals/Brokers Directory"
[ngClass]="{'p-button-text text-700': activeTabAction != 'professionals_brokers'}"></button></li>
<li><button pButton pRipple icon="pi pi-shield" (click)="activeTabAction = 'investment'"
label="Investment Property"
[ngClass]="{'p-button-text text-700': activeTabAction != 'investment'}"></button>
</li>
</ul>
</div>
<div class="mt-5">
<div class="flex flex-column align-items-right gap-3 px-2 py-3 my-3 surface-border">
<p-dropdown [options]="selectOptions.typesOfBusiness" [(ngModel)]="criteria.type" optionLabel="name"
optionValue="value" [showClear]="true" placeholder="Category"
[style]="{ width: '200px'}"></p-dropdown>
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.minPrice" optionLabel="name" optionValue="value"
[showClear]="true" placeholder="Min Price" [style]="{ width: '200px'}"></p-dropdown>
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.maxPrice" optionLabel="name" optionValue="value"
[showClear]="true" placeholder="Max Price" [style]="{ width: '200px'}"></p-dropdown>
<button pButton pRipple label="Find" class="ml-3 font-bold"
[style]="{ width: '170px'}" (click)="search()"></button>
</div>
</div>
</div>
<div class="w-12 flex justify-content-center">
<button type="button" pButton pRipple label="Create Your Listing"
class="block mt-7 mb-7 lg:mb-0 p-button-rounded p-button-success p-button-lg font-medium" [routerLink]="userService.isLoggedIn()?'/createListing':'/pricing'"></button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,17 @@
:host {
height: 100%
}
.container {
background-image: url(../../../assets/images/index-bg.webp);
//background-image: url(../../../assets/images/corpusChristiSkyline.jpg);
background-size: cover;
background-position: center;
height: 100vh;
}
.combo_lp{
width: 200px;
}
.p-button-white{
color:aliceblue
}

View File

@@ -0,0 +1,45 @@
import { Component } from '@angular/core';
import { DropdownModule } from 'primeng/dropdown';
import { KeyValue, ListingCriteria } from '../../models/main.model';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { StyleClassModule } from 'primeng/styleclass';
import { ButtonModule } from 'primeng/button';
import { CheckboxModule } from 'primeng/checkbox';
import { InputTextModule } from 'primeng/inputtext';
import { SelectOptionsService } from '../../services/select-options.service';
import { UserService } from '../../services/user.service';
import onChange from 'on-change';
import { getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
@Component({
selector: 'app-home',
standalone: true,
imports: [CommonModule, StyleClassModule,ButtonModule, CheckboxModule,InputTextModule,DropdownModule,FormsModule, RouterModule],
templateUrl: './home.component.html',
styleUrl: './home.component.scss'
})
export class HomeComponent {
activeTabAction = 'business';
type:string;
maxPrice:string;
minPrice:string;
criteria:ListingCriteria
public constructor(private router: Router,private activatedRoute: ActivatedRoute, public selectOptions:SelectOptionsService, public userService:UserService) {
this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler);
}
ngOnInit(){
}
search(){
this.router.navigate([`listings/${this.activeTabAction}`])
}
account(){
setTimeout(()=>{
this.router.navigate([`account`])
},10);
}
}

View File

@@ -0,0 +1,130 @@
<div id="sky-line" class="hidden-lg-down">
</div>
<div class="search">
<div class="wrapper">
<div class="grid p-4 align-items-center">
@if (listingCategory==='business'){
<div class="col-2">
<p-dropdown [options]="selectOptions.typesOfBusiness" [(ngModel)]="criteria.type" optionLabel="name"
optionValue="value" [showClear]="true" placeholder="Categorie of Business"
[style]="{ width: '100%'}"></p-dropdown>
</div>
<div class="col-2">
<p-dropdown [options]="locations" [(ngModel)]="location" optionLabel="criteria.location" optionLabel="name" optionValue="value"
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
</div>
<div class="col-2">
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.minPrice" optionLabel="name"
optionValue="value" [showClear]="true" placeholder="Min Price"
[style]="{ width: '100%'}"></p-dropdown>
</div>
<div class="col-2">
<p-dropdown [options]="selectOptions.prices" [(ngModel)]="criteria.maxPrice" optionLabel="name"
optionValue="value" [showClear]="true" placeholder="Max Price"
[style]="{ width: '100%'}"></p-dropdown>
</div>
<div class="col-3">
<!-- <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="Real Estate not included"
offLabel="Real Estate included"></p-toggleButton>
</div>
}
@if (listingCategory==='investment'){
<div class="col-2">
<p-dropdown [options]="locations" [(ngModel)]="criteria.location" optionLabel="name" optionValue="value"
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
</div>
}
@if (listingCategory==='professionals_brokers'){
<div class="col-2">
<p-dropdown [options]="selectOptions.categories" [(ngModel)]="criteria.category" optionLabel="name"
optionValue="value" [showClear]="true" placeholder="Category"
[style]="{ width: '100%'}"></p-dropdown>
</div>
<div class="col-2">
<p-dropdown [options]="locations" [(ngModel)]="criteria.location" optionLabel="name" optionValue="value"
[showClear]="true" placeholder="Location" [style]="{ width: '100%'}"></p-dropdown>
</div>
}
<div [ngClass]="{'col-offset-9':type==='investment','col-offset-7':type==='professionals_brokers'}" class="col-1">
<p-button label="Refine" (click)="search()"></p-button>
</div>
</div>
</div>
</div>
<div class="surface-200 h-full">
<div class="wrapper">
<div class="grid">
@for (listing of filteredListings; track listing.id) {
<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="p-4 h-full flex flex-column">
@if (listing.listingsCategory==='business'){
<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">
<i [class]="selectOptions.getIconAndTextColorType(listing.type)" class="pi text-xl"></i>
</span>
<span class="text-900 font-medium text-2xl">{{selectOptions.getBusiness(listing.type)}}</span>
</div>
}
@if (listing.listingsCategory==='professionals_brokers'){
<div class="flex align-items-center">
<span [class]="selectOptions.getBgColor(listing.category)" class="inline-flex border-circle align-items-center justify-content-center mr-3"
style="width:38px;height:38px">
<i [class]="selectOptions.getIconAndTextColor(listing.category)" class="text-xl"></i>
</span>
<span class="text-900 font-medium text-2xl">{{selectOptions.getCategory(listing.category)}}</span>
</div>
}
@if (listing.listingsCategory==='investment'){
<div class="flex align-items-center">
<span
class="inline-flex border-circle align-items-center justify-content-center bg-green-100 mr-3"
style="width:38px;height:38px">
<i class="pi pi-globe text-xl text-green-600"></i>
</span>
<span class="text-900 font-medium text-2xl">Investment</span>
</div>
}
<div class="text-900 my-3 text-xl font-medium">{{listing.title}}</div>
@if (listing.listingsCategory==='business'){
<p class="mt-0 mb-1 text-700 line-height-3">Asking price: {{listing.price | currency}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Sales revenue: {{listing.salesRevenue | 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.getLocation(listing.location)}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Established: {{listing.established}}</p>
}
@if (listing.listingsCategory==='professionals_brokers'){
<!-- <p class="mt-0 mb-1 text-700 line-height-3">Category: {{listing.category}}</p> -->
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getLocation(listing.location)}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">EMail: {{listing.email}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Website: {{listing.website}}</p>
}
@if (listing.listingsCategory==='investment'){
<p class="mt-0 mb-1 text-700 line-height-3">Location: {{selectOptions.getLocation(listing.location)}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">EMail: {{listing.email}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Website: {{listing.website}}</p>
<p class="mt-0 mb-1 text-700 line-height-3">Phone Number: {{listing.phoneNumber}}</p>
}
<div class="mt-auto ml-auto">
<img *ngIf="!listing.hideImage" src="{{environment.apiBaseUrl}}/profile_{{listing.userId}}" (error)="imageErrorHandler(listing)" class="rounded-image"/>
</div>
</div>
<div class="px-4 py-3 surface-100 text-right">
<button pButton pRipple icon="pi pi-arrow-right" iconPos="right" label="View Full Listing"
class="p-button-rounded p-button-success" [routerLink]="['/details',listing.id]"></button>
</div>
</div>
</div>
}
</div>
<div class="mb-2 surface-200 flex align-items-center justify-content-center">
<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>
</div>
</div>
</div>

View File

@@ -0,0 +1,22 @@
#sky-line {
background-image: url(../../../assets/images/bw-sky.jpg);
height: 204px;
background-position: bottom;
background-size: cover;
margin-bottom: -1px;
}
.search{
background-color: #343F69;
}
::ng-deep p-paginator div {
background-color: var(--surface-200) !important;
// background-color: var(--surface-400) !important;
}
.rounded-image {
border-radius: 6px;
width: 100px;
height: 25px;
border: 1px solid rgba(0,0,0,0.2);
padding: 1px 1px;
object-fit: contain;
}

View File

@@ -0,0 +1,96 @@
import { ChangeDetectorRef, 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 { BusinessListing, InvestmentsListing, KeyValue, ListingCriteria, ListingType, PageEvent, ProfessionalsBrokersListing, } from '../../models/main.model';
import { SelectOptionsService } from '../../services/select-options.service';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { ListingsService } from '../../services/listings.service';
import { Observable, lastValueFrom } from 'rxjs';
import { PaginatorModule } from 'primeng/paginator';
import onChange from 'on-change';
import { createGenericObject, getCriteriaStateObject, getSessionStorageHandler } from '../../utils/utils';
import { InitEditableRow } from 'primeng/table';
import { environment } from '../../../environments/environment';
@Component({
selector: 'app-listings',
standalone: true,
imports: [CommonModule, StyleClassModule, ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, StyleClassModule, ToggleButtonModule, RouterModule, PaginatorModule],
templateUrl: './listings.component.html',
styleUrls: ['./listings.component.scss', '../pages.scss']
})
export class ListingsComponent {
environment=environment;
listings: Array<ListingType>;
filteredListings: Array<ListingType>;
criteria:ListingCriteria;
realEstateChecked: boolean;
category: string;
maxPrice: string;
minPrice: string;
type:string;
locations = [];
locationsSet = new Set();
location:string;
first: number = 0;
rows: number = 12;
totalRecords:number = 0;
public listingCategory: 'business' | 'professionals_brokers' | 'investment' | undefined;
constructor(public selectOptions: SelectOptionsService, private listingsService:ListingsService,private activatedRoute: ActivatedRoute, private router:Router, private cdRef:ChangeDetectorRef) {
this.criteria = onChange(getCriteriaStateObject(),getSessionStorageHandler);
this.router.getCurrentNavigation()
this.activatedRoute.snapshot
this.activatedRoute.params.subscribe(params => {
if (this.activatedRoute.snapshot.fragment===''){
this.criteria = onChange(createGenericObject<ListingCriteria>(),getSessionStorageHandler)
this.first=0;
}
this.listingCategory = (<any>params).type;
this.criteria.listingsCategory=this.listingCategory;
this.init()
})
}
async ngOnInit(){
}
async init(){
this.listings=await this.listingsService.getListings(this.criteria);
this.setLocations();
this.filteredListings=[...this.listings];
this.totalRecords=this.listings.length
this.filteredListings=[...this.listings].splice(this.first,this.rows);
this.cdRef.markForCheck();
this.cdRef.detectChanges();
}
setLocations(){
this.locationsSet=new Set();
this.listings.forEach(l=>{
if (l.location){
this.locationsSet.add(l.location)
}
})
this.locations = [...this.locationsSet].map((ls) =>({name:this.selectOptions.getLocation(ls as string),value:ls}))
}
async search() {
this.listings= await this.listingsService.getListings(this.criteria);
this.setLocations();
this.totalRecords=this.listings.length
this.filteredListings =[...this.listings].splice(this.first,this.rows);
this.cdRef.markForCheck();
this.cdRef.detectChanges();
}
onPageChange(event: any) {
this.first = event.first;
this.rows = event.rows;
this.filteredListings=[...this.listings].splice(this.first,this.rows);
}
imageErrorHandler(listing: ListingType) {
listing.hideImage = true; // Bild ausblenden, wenn es nicht geladen werden kann
}
}

View File

@@ -0,0 +1,42 @@
<div class="surface-ground ">
<div class="p-fluid flex flex-column lg:flex-row">
<ul class="list-none m-0 p-0 flex flex-row lg:flex-column justify-content-evenly md:justify-content-between lg:justify-content-start mb-5 lg:pr-8 lg:mb-0">
<li>
<a routerLink="/account" 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" >
<i class="pi pi-user md:mr-2"></i>
<span class="font-medium hidden md:block">Account</span>
</a>
</li>
<li>
<a routerLink="/createListing" 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">
<i class="pi pi-plus-circle md:mr-2"></i>
<span class="font-medium hidden md:block">Create Listing</span>
</a>
</li>
<li>
<a routerLink="/myListings" 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">
<i class="pi pi-list md:mr-2"></i>
<span class="font-medium hidden md:block">My Listings</span>
</a>
</li>
<li>
<a routerLink="/myFavorites" 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">
<i class="pi pi-star md:mr-2"></i>
<span class="font-medium hidden md:block">My Favorites</span>
</a>
</li>
<li>
<a routerLink="/emailUs" 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]="faEnvelope" class="mr-2 flex"></fa-icon>
<span class="font-medium hidden md:block">Email Us</span>
</a>
</li>
<li>
<a (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>
<span class="font-medium hidden md:block">Logout</span>
</a>
</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,3 @@
ul {
text-wrap: nowrap;
}

View File

@@ -0,0 +1,43 @@
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 { BusinessListing, KeyValue } from '../../models/main.model';
import { SelectOptionsService } from '../../services/select-options.service';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { TagModule } from 'primeng/tag';
import data from '../../../assets/data/listings.json';
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 { faRightFromBracket } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { UserService } from '../../services/user.service';
@Component({
selector: 'menu-account',
standalone: true,
imports: [CommonModule, StyleClassModule, ButtonModule, DividerModule, RouterModule, RippleModule, FontAwesomeModule ],
templateUrl: './menu-account.component.html',
styleUrl: './menu-account.component.scss'
})
export class MenuAccountComponent {
activeLink: string;
faEnvelope=faEnvelope;
faRightFromBracket=faRightFromBracket;
constructor(private router: Router,public userService:UserService) {
// Abonniere Router-Events, um den aktiven Link zu ermitteln
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.activeLink = event.url;
}
});
}
}

View File

@@ -0,0 +1,6 @@
.wrapper {
width: 1491px;
max-width: 100%;
height: 100%;
margin: auto;
}

View File

@@ -0,0 +1,85 @@
<div class="container">
<div class="wrapper">
<div class="py-3 px-6 flex flex-column align-items-center justify-content-between relative">
<a routerLink="/home"><img src="../../../assets/images/header-logo.png" alt="Image" height="50" ></a>
<div class="px-4 py-8 md:px-6 lg:px-8 bg-no-repeat bg-cover" >
<div class="flex flex-wrap">
<div class="w-full lg:w-6 lg:pr-8">
<div class="text-900 font-bold text-6xl text-blue-900 mb-4">Pricing</div>
<div class="text-700 text-xl text-blue-600 line-height-3 mb-4 lg:mb-0">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Velitnumquam eligendi quos.</div>
</div>
<div class="w-full md:w-6 lg:w-3">
<ul class="list-none p-0 m-0">
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
<i class="pi pi-check text-green-500 mr-3"></i>
<span>Arcu vitae elementum</span>
</li>
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
<i class="pi pi-check text-green-500 mr-3"></i>
<span>Dui faucibus in ornare</span>
</li>
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
<i class="pi pi-check text-green-500 mr-3"></i>
<span>Morbi tincidunt augue</span>
</li>
</ul>
</div>
<div class="w-full md:w-6 lg:w-3 md:pl-5">
<ul class="list-none p-0 m-0">
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
<i class="pi pi-check text-green-500 mr-3"></i>
<span>Duis ultricies lacus sed</span>
</li>
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
<i class="pi pi-check text-green-500 mr-3"></i>
<span>Imperdiet proin</span>
</li>
<li class="flex align-items-center my-4 bg-white-alpha-40 shadow-2 py-1 px-2 border-round-lg w-max">
<i class="pi pi-check text-green-500 mr-3"></i>
<span>Nisi scelerisque</span>
</li>
</ul>
</div>
</div>
<div class="flex flex-wrap mt-5 -mx-3">
<div class="w-full lg:w-4 p-3">
<div class="shadow-2 p-3 h-full bg-primary" style="border-radius: 6px">
<div class="font-medium text-xl mb-5">Free Forever</div>
<div class="font-bold text-5xl mb-5">Free</div>
<button (click)="register()" type="button" pRipple class="font-medium appearance-none border-none p-2 surface-0 text-primary hover:surface-100 p-component lg:w-full border-rounded cursor-pointer transition-colors transition-duration-150" style="border-radius: 6px">
<span>Create Account</span>
</button>
<p class="text-sm line-height-3 mb-0 mt-5">Lorem ipsum dolor sit, amet consectetur adipisicing elit.</p>
</div>
</div>
<div class="w-full lg:w-4 p-3">
<div class="shadow-2 p-3 h-full surface-card" style="border-radius: 6px">
<div class="font-medium text-xl mb-5 text-900 ">Monthly</div>
<div class="flex align-items-center mb-5">
<span class="text-900 font-bold text-5xl">$29</span>
<span class="font-medium text-500 ml-2">per month</span>
</div>
<button (click)="register()" pButton pRipple label="Proceed Monthly" icon="pi pi-arrow-right" iconPos="right" class="lg:w-full font-medium p-2" style="border-radius: 6px"></button>
<p class="text-sm line-height-3 mb-0 mt-5">Nec ultrices dui sapien eget. Amet nulla facilisi morbi tempus.</p>
</div>
</div>
<div class="w-full lg:w-4 p-3">
<div class="shadow-2 p-3 h-full flex flex-column surface-card" style="border-radius: 6px">
<div class="flex flex-row justify-content-between mb-5 align-items-center">
<div class="text-900 text-xl font-medium">Yearly</div>
<span class="bg-orange-100 500 text-orange-500 font-semibold px-2 py-1 border-round">🎉 Save 20%</span>
</div>
<div class="flex align-items-center mb-5">
<span class="text-900 font-bold text-5xl">$275</span>
<span class="font-medium text-500 ml-2">per year</span>
</div>
<button (click)="register()" pButton pRipple label="Proceed Yearly" icon="pi pi-arrow-right" iconPos="right" class="lg:w-full font-medium p-2" style="border-radius: 6px"></button>
<p class="text-sm line-height-3 mb-0 mt-5">Placerat in egestas erat imperdiet sed euismod nisi porta.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
:host {
height: 100%
}
.container {
background-image: url(../../../assets/images/index-bg.jpg), url(../../../assets/images/pricing-4.svg);
//background-image: url(../../../assets/images/corpusChristiSkyline.jpg);
background-size: cover;
background-position: center;
height: 100vh;
}

View File

@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { SharedModule } from '../../shared/shared/shared.module';
import { UserService } from '../../services/user.service';
@Component({
selector: 'app-pricing',
standalone: true,
imports: [SharedModule],
templateUrl: './pricing.component.html',
styleUrl: './pricing.component.scss'
})
export class PricingComponent {
constructor(private userService:UserService){}
register(){
this.userService.register(`${window.location.origin}/account`);
}
}

View File

@@ -0,0 +1,108 @@
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
<div class="p-fluid flex flex-column lg:flex-row">
<menu-account></menu-account>
<p-toast></p-toast>
<div class="surface-card p-5 shadow-2 border-round flex-auto">
<div class="text-900 font-semibold text-lg mt-3">Account Details</div>
<p-divider></p-divider>
<div class="flex gap-5 flex-column-reverse md:flex-row">
<div class="flex-auto p-fluid">
<div class="mb-4">
<label for="email" class="block font-medium text-900 mb-2">Username</label>
<input id="email" type="text" [disabled]="true" pInputText [(ngModel)]="user.username">
<p class="font-italic text-sm line-height-1">Usernames cannot be changed.</p>
</div>
<div class="mb-4">
<label for="state" class="block font-medium text-900 mb-2">First Name</label>
<input id="state" type="text" pInputText [(ngModel)]="user.firstname">
</div>
<div class="mb-4">
<label for="state" class="block font-medium text-900 mb-2">Last Name</label>
<input id="state" type="text" pInputText [(ngModel)]="user.lastname">
</div>
<div class="mb-4">
<label for="state" class="block font-medium text-900 mb-2">E-mail (required)</label>
<input id="state" type="text" pInputText [(ngModel)]="user.email">
</div>
<div class="mb-4">
<label for="state" class="block font-medium text-900 mb-2">New Password</label>
<p class="font-italic text-sm line-height-1">If you would like to change the password type a new one. Otherwise leave this blank.</p>
<input id="state" type="text" pInputText>
<p class="font-italic text-sm line-height-1">Password repetition</p>
<input id="state" type="text" pInputText>
</div>
<div>
<button pButton pRipple label="Update Profile" class="w-auto" (click)="updateProfile(user)"></button>
</div>
</div>
<div class="flex flex-column align-items-center flex-or">
<span class="font-medium text-900 mb-2">Profile Picture</span>
<img [src]="imageUrl" (error)="setImageToFallback($event)" class="rounded-image"/>
<!-- <p-image src="http://localhost:3000/public/user.png" alt="Image" width="10rem" [preview]="true"></p-image> -->
<!-- <button pButton type="button" icon="pi pi-pencil" class="p-button-rounded -mt-4"></button> -->
<p-fileUpload mode="basic" chooseLabel="Upload" name="file" [url]="uploadUrl" accept="image/*" [maxFileSize]="maxFileSize" (onUpload)="onUpload($event)" [auto]="true" styleClass="p-button-outlined p-button-plain p-button-rounded mt-4"></p-fileUpload>
</div>
</div>
<div class="text-900 font-semibold text-lg mt-3">Membership Level</div>
<p-divider></p-divider>
<p-table [value]="userSubscriptions" [tableStyle]="{ 'min-width': '50rem' }" dataKey="id">
<ng-template pTemplate="header">
<tr>
<th style="width: 5rem"></th>
<th>ID</th>
<th>Level</th>
<th>Start Date</th>
<th>Date Modified</th>
<th>End Date</th>
<th>Status</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-subscription let-expanded="expanded">
<tr>
<td>
<button type="button" pButton pRipple [pRowToggler]="subscription" class="p-button-text p-button-rounded p-button-plain" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
</td>
<td>{{ subscription.id }}</td>
<td>{{ subscription.level }}</td>
<td>{{ subscription.start | date }}</td>
<td>{{ subscription.modified | date }}</td>
<td>{{ subscription.end | date }}</td>
<td>{{ subscription.status }}</td>
</tr>
</ng-template>
<ng-template pTemplate="rowexpansion" let-subscription>
<tr>
<td colspan="7">
<div class="p-3">
<p-table [value]="subscription.invoices" dataKey="id">
<ng-template pTemplate="header">
<tr>
<th style="width: 5rem"></th>
<th>ID</th>
<th>Date</th>
<th>Price</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-invoice>
<tr>
<td>
<button pButton pRipple icon="pi pi-print" class="p-button-rounded p-button-success mr-2" (click)="printInvoice(invoice)"></button>
</td>
<td>{{ invoice.id }}</td>
<td>{{ invoice.date | date}}</td>
<td>{{ invoice.price | currency}}</td>
<td></td>
<td></td>
</tr>
</ng-template>
</p-table>
</div>
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
</div>

View File

@@ -0,0 +1,9 @@
.rounded-image {
border-radius: 6px;
width: 120px;
height: 30px;
border: 1px solid #6b7280;
padding: 1px 1px;
object-fit: contain;
}

View File

@@ -0,0 +1,64 @@
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 { BusinessListing, Invoice, KeyValue, Subscription, User } from '../../../models/main.model';
import { SelectOptionsService } from '../../../services/select-options.service';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { TagModule } from 'primeng/tag';
import { ActivatedRoute } from '@angular/router';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { ChipModule } from 'primeng/chip';
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
import { DividerModule } from 'primeng/divider';
import { TableModule } from 'primeng/table';
import { HttpClient } from '@angular/common/http';
import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module';
import { SubscriptionsService } from '../../../services/subscriptions.service';
import { lastValueFrom } from 'rxjs';
import { MessageService } from 'primeng/api';
import { environment } from '../../../../environments/environment';
import { FileUploadModule } from 'primeng/fileupload';
@Component({
selector: 'app-account',
standalone: true,
// imports: [CommonModule, StyleClassModule, MenuAccountComponent, DividerModule,ButtonModule, TableModule, InputTextModule, DropdownModule, FormsModule, ChipModule,InputTextareaModule ],
imports: [SharedModule,FileUploadModule],
providers:[MessageService],
templateUrl: './account.component.html',
styleUrl: './account.component.scss'
})
export class AccountComponent {
user:User;
subscriptions:Array<Subscription>;
userSubscriptions:Array<Subscription>=[];
uploadUrl:string;
maxFileSize=1000000;
imageUrl:string;
constructor(public userService: UserService, private subscriptionService: SubscriptionsService,private messageService: MessageService) {
this.user=this.userService.getUser()
}
async ngOnInit(){
this.imageUrl = `${environment.apiBaseUrl}/profile_${this.user.id}`
this.userSubscriptions=await lastValueFrom(this.subscriptionService.getAllSubscriptions());
this.uploadUrl = `${environment.apiBaseUrl}/bizmatch/account/uploadPhoto/${this.user.id}`;
}
printInvoice(invoice:Invoice){}
updateProfile(user:User){
this.messageService.add({ severity: 'warn', summary: 'Information', detail: 'This function is not yet available, please send an email to info@bizmatch.net for changes to your customer data', life: 15000 });
}
onUpload(event:any){
const uniqueSuffix = '?_ts=' + new Date().getTime();
this.imageUrl = `${environment.apiBaseUrl}/profile_${this.user.id}${uniqueSuffix}` //`http://IhrServer:Port/${newImagePath}${uniqueSuffix}`;
}
setImageToFallback(event: Event) {
(event.target as HTMLImageElement).src = `/assets/images/placeholder.png`; // Pfad zum Platzhalterbild
}
}

View File

@@ -0,0 +1,156 @@
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8">
<div class="p-fluid flex flex-column lg:flex-row">
<menu-account></menu-account>
<p-toast></p-toast>
<div class="surface-card p-5 shadow-2 border-round flex-auto">
<div class="text-900 font-semibold text-lg mt-3">{{mode==='create'?'New':'Edit'}} Listing</div>
<p-divider></p-divider>
<div class="flex gap-5 flex-column-reverse md:flex-row">
<div class="flex-auto p-fluid">
<div class="mb-4">
<label for="listingCategory" class="block font-medium text-900 mb-2">Listing category</label>
<p-dropdown id="listingCategory" [options]="selectOptions?.listingCategories" [(ngModel)]="listing.listingsCategory" optionLabel="name"
optionValue="value" placeholder="Listing category" [disabled]="mode==='edit'"
[style]="{ width: '100%'}"></p-dropdown>
</div>
<div class="mb-4">
<label for="email" class="block font-medium text-900 mb-2">Title of Listing</label>
<input id="email" type="text" pInputText [(ngModel)]="listing.title">
</div>
@if (listing.listingsCategory==='business' || listing.listingsCategory==='professionals_brokers'){
<div>
<div class="mb-4">
<label for="summary" class="block font-medium text-900 mb-2">Summary (Brief description)</label>
<textarea id="summary" type="text" pInputTextarea rows="5" [autoResize]="true" [ngModel]="listing.summary | arrayToString:'\n\n'" (ngModelChange)="updateSummary($event)"></textarea>
</div>
</div>
}
<div>
<div class="mb-4">
<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>
</div>
</div>
@if (listing.listingsCategory==='business'){
<div class="mb-4">
<label for="listingCategory" class="block font-medium text-900 mb-2">Type of business</label>
<p-dropdown id="listingCategory" [options]="selectOptions?.typesOfBusiness" [(ngModel)]="listing.type" optionLabel="name"
optionValue="value" [showClear]="true" placeholder="Type of business"
[style]="{ width: '100%'}"></p-dropdown>
</div>
}
<div class="mb-4">
<label for="listingCategory" class="block font-medium text-900 mb-2">Location</label>
<p-dropdown id="listingCategory" [options]="selectOptions?.locations" [(ngModel)]="listing.location" optionLabel="name"
optionValue="value" [showClear]="true" placeholder="Location"
[style]="{ width: '100%'}"></p-dropdown>
</div>
@if (listing.listingsCategory==='professionals_brokers'){
<div>
<div class="mb-4">
<label for="address" class="block font-medium text-900 mb-2">Address</label>
<input id="address" type="text" pInputText [(ngModel)]="listing.address">
</div>
</div>
}
@if (listing.listingsCategory==='professionals_brokers' || listing.listingsCategory==='investment'){
<div>
<div class="mb-4">
<label for="email" class="block font-medium text-900 mb-2">Email</label>
<input id="address" type="text" pInputText [(ngModel)]="listing.email">
</div>
</div>
<div>
<div class="mb-4">
<label for="website" class="block font-medium text-900 mb-2">Website</label>
<input id="address" type="text" pInputText [(ngModel)]="listing.website">
</div>
</div>
}
@if (listing.listingsCategory==='professionals_brokers'){
<div class="mb-4">
<label for="category" class="block font-medium text-900 mb-2">Category</label>
<p-dropdown id="category" [options]="selectOptions?.categories" [(ngModel)]="listing.category" optionLabel="name"
optionValue="value" [showClear]="true" placeholder="Category"
[style]="{ width: '100%'}"></p-dropdown>
</div>
}
@if (listing.listingsCategory==='investment'){
<div>
<div class="mb-4">
<label for="phoneNumber" class="block font-medium text-900 mb-2">Phone Number</label>
<input id="phoneNumber" type="text" pInputText [(ngModel)]="listing.phoneNumber">
</div>
</div>
}
</div>
</div>
<p-divider></p-divider>
<div class="flex gap-5 flex-column-reverse md:flex-row">
<div class="flex-auto p-fluid">
@if (listing.listingsCategory==='business'){
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="price" class="block font-medium text-900 mb-2">Price</label>
<!-- <input id="price" type="text" pInputText [(ngModel)]="listing.price"> -->
<p-inputNumber mode="currency" currency="USD" inputId="price" type="text" [(ngModel)]="listing.price"></p-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6 flex align-items-end justify-content-center">
<p-checkbox [binary]="true" [(ngModel)]="listing.realEstateIncluded"></p-checkbox>
<span class="ml-2 text-900">Real Estate Included</span>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="salesRevenue" class="block font-medium text-900 mb-2">Sales Revenue</label>
<!-- <input id="salesRevenue" type="text" pInputText [(ngModel)]="listing.salesRevenue"> -->
<p-inputNumber mode="currency" currency="USD" inputId="salesRevenue" type="text" [(ngModel)]="listing.salesRevenue"></p-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="cashFlow" class="block font-medium text-900 mb-2">Cash Flow</label>
<!-- <input id="cashFlow" type="text" pInputText [(ngModel)]="listing.cashFlow"> -->
<p-inputNumber mode="currency" currency="USD" inputId="cashFlow" type="text" [(ngModel)]="listing.cashFlow"></p-inputNumber>
</div>
</div>
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="netProfit" class="block font-medium text-900 mb-2">Net Profit</label>
<!-- <input id="netProfit" type="text" pInputText [(ngModel)]="listing.netProfit"> -->
<p-inputNumber mode="currency" currency="USD" inputId="netProfit" type="text" [(ngModel)]="listing.netProfit"></p-inputNumber>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="employees" class="block font-medium text-900 mb-2">Employees</label>
<!-- <input id="employees" type="text" pInputText [(ngModel)]="listing.employees"> -->
<p-inputNumber mode="employees" mode="decimal" inputId="employees" type="text" [(ngModel)]="listing.employees"></p-inputNumber>
</div>
</div>
<div class="mb-4">
<label for="inventory" class="block font-medium text-900 mb-2">Inventory</label>
<textarea id="inventory" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.inventory"></textarea>
</div>
<div class="mb-4">
<label for="reasonForSale" class="block font-medium text-900 mb-2">Reason for Sale</label>
<textarea id="reasonForSale" type="text" pInputTextarea rows="5" [autoResize]="true" [(ngModel)]="listing.reasonForSale"></textarea>
</div>
<div class="mb-4">
<label for="brokerLicensing" class="block font-medium text-900 mb-2">Broker Licensing</label>
<input id="brokerLicensing" type="text" pInputText [(ngModel)]="listing.brokerLicencing">
</div>
<div class="mb-4">
<label for="internalListing" class="block font-medium text-900 mb-2">Internal Listing (Will not be shown on the listing, for your records only.)</label>
<input id="internalListing" type="text" pInputText [(ngModel)]="listing.internals">
</div>
}
<div>
@if (mode==='create'){
<button pButton pRipple label="Post Listing" class="w-auto" (click)="create()"></button>
} @else {
<button pButton pRipple label="Update Listing" class="w-auto" (click)="update(listing.id)"></button>
}
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,77 @@
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 { BusinessListing, InvestmentsListing, Invoice, KeyValue, ProfessionalsBrokersListing, User } from '../../../models/main.model';
import { SelectOptionsService } from '../../../services/select-options.service';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { TagModule } from 'primeng/tag';
import data from '../../../../assets/data/user.json';
import dataListings from '../../../../assets/data/listings.json';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { ChipModule } from 'primeng/chip';
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
import { DividerModule } from 'primeng/divider';
import { TableModule } from 'primeng/table';
import { createGenericObject } from '../../../utils/utils';
import { ListingsService } from '../../../services/listings.service';
import { lastValueFrom } from 'rxjs';
import { InputNumberModule } from 'primeng/inputnumber';
import { ArrayToStringPipe } from '../../../pipes/array-to-string.pipe';
import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module';
import { MessageService } from 'primeng/api';
@Component({
selector: 'create-listing',
standalone: true,
imports: [SharedModule,ArrayToStringPipe],
providers:[MessageService],
templateUrl: './edit-listing.component.html',
styleUrl: './edit-listing.component.scss'
})
export class EditListingComponent {
listingCategory:'Business'|'Professionals/Brokers Directory'|'Investment Property';
category:string;
location:string;
mode:'edit'|'create';
separator:'\n\n'
listing:BusinessListing|ProfessionalsBrokersListing|InvestmentsListing = createGenericObject<BusinessListing>();
private id: string | undefined = this.activatedRoute.snapshot.params['id'] as string | undefined;
user:User;
constructor(public selectOptions:SelectOptionsService,private router: Router,private activatedRoute: ActivatedRoute,private listingsService:ListingsService,public userService: UserService,private messageService: MessageService){
this.user=this.userService.getUser();
// Abonniere Router-Events, um den aktiven Link zu ermitteln
this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.mode = event.url==='/createListing'?'create':'edit';
}
});
}
async ngOnInit(){
if (this.mode==='edit'){
this.listing=await lastValueFrom(this.listingsService.getListingById(this.id));
} else {
this.listing=createGenericObject<BusinessListing>();
this.listing.userId=this.user.id
this.listing.listingsCategory='business';
}
}
updateSummary(value: string): void {
const lines = value.split('\n');
(<BusinessListing>this.listing).summary = lines.filter(l=>l.trim().length>0);
}
async update(id:string){
await this.listingsService.update(this.listing,this.listing.id);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing has been updated', life: 3000 });
}
async create(){
await this.listingsService.create(this.listing);
this.messageService.add({ severity: 'info', summary: 'Confirmed', detail: 'Listing has been created', life: 3000 });
}
}

View File

@@ -0,0 +1,35 @@
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8 h-full">
<div class="p-fluid flex flex-column lg:flex-row">
<menu-account></menu-account>
<div class="surface-card p-5 shadow-2 border-round flex-auto">
<div class="text-900 font-semibold text-lg mt-3">Contact Us</div>
<p-divider></p-divider>
<div class="flex gap-5 flex-column-reverse md:flex-row">
<div class="flex-auto p-fluid">
<div class="grid">
<div class="mb-4 col-12 md:col-6">
<label for="name" class="block font-medium text-900 mb-2">Your name</label>
<input id="name" type="text" pInputText>
</div>
<div class="mb-4 col-12 md:col-6">
<label for="email" class="block font-medium text-900 mb-2">Your Email</label>
<input id="email" type="text" pInputText>
</div>
</div>
<div class="mb-4">
<label for="phone" class="block font-medium text-900 mb-2">Your Phone</label>
<input id="phone" type="text" pInputText>
</div>
<div class="mb-4">
<label for="help" class="block font-medium text-900 mb-2">How can we help you ?</label>
<textarea id="help" type="text" pInputTextarea rows="5" [autoResize]="true"></textarea>
</div>
<div>
<button pButton pRipple label="Submit" class="w-auto"></button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,29 @@
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 { BusinessListing, Invoice, KeyValue, User } from '../../../models/main.model';
import { SelectOptionsService } from '../../../services/select-options.service';
import { DropdownModule } from 'primeng/dropdown';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ToggleButtonModule } from 'primeng/togglebutton';
import { TagModule } from 'primeng/tag';
import data from '../../../../assets/data/user.json';
import { ActivatedRoute } from '@angular/router';
import { InputTextareaModule } from 'primeng/inputtextarea';
import { ChipModule } from 'primeng/chip';
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
import { DividerModule } from 'primeng/divider';
import { TableModule } from 'primeng/table';
@Component({
selector: 'app-email-us',
standalone: true,
imports: [CommonModule, StyleClassModule, MenuAccountComponent, DividerModule,ButtonModule, CheckboxModule, InputTextModule, DropdownModule, FormsModule, ChipModule,InputTextareaModule],
templateUrl: './email-us.component.html',
styleUrl: './email-us.component.scss'
})
export class EmailUsComponent {
}

View File

@@ -0,0 +1,30 @@
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8 h-full">
<div class="p-fluid flex flex-column lg:flex-row">
<menu-account></menu-account>
<div class="surface-card p-5 shadow-2 border-round flex-auto">
<div class="text-900 font-semibold text-lg mt-3">My Favorites</div>
<p-divider></p-divider>
<p-table [value]="favorites" [tableStyle]="{ 'min-width': '50rem' }" dataKey="id" [paginator]="true" [rows]="10" [rowsPerPageOptions]="[10, 20, 50]" [showCurrentPageReport]="true" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries">
<ng-template pTemplate="header">
<tr>
<th class="wide-column">Title</th>
<th>Category</th>
<th>Located in</th>
<th></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-listing>
<tr>
<td class="wide-column line-height-3">{{ listing.title }}</td>
<td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td>
<td>{{ selectOptions.getLocation(listing.location) }}</td>
<td>
<button pButton pRipple icon="pi pi-eye" class="p-button-rounded p-button-success mr-2" [routerLink]="['/details',listing.id]"></button>
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
</div>

View File

@@ -0,0 +1,3 @@
.wide-column{
width: 40%;
}

View File

@@ -0,0 +1,29 @@
import { Component } from '@angular/core';
import { MenuAccountComponent } from '../../menu-account/menu-account.component';
import dataListings from '../../../../assets/data/listings.json';
import { BusinessListing, User } from '../../../models/main.model';
import { SharedModule } from '../../../shared/shared/shared.module';
import { UserService } from '../../../services/user.service';
import { lastValueFrom } from 'rxjs';
import { ListingsService } from '../../../services/listings.service';
import { SelectOptionsService } from '../../../services/select-options.service';
@Component({
selector: 'app-favorites',
standalone: true,
imports: [MenuAccountComponent, SharedModule],
templateUrl: './favorites.component.html',
styleUrl: './favorites.component.scss'
})
export class FavoritesComponent {
user: User;
listings: Array<BusinessListing> //= dataListings as unknown as Array<BusinessListing>;
favorites: Array<BusinessListing>
constructor(public userService: UserService, private listingsService:ListingsService, public selectOptions:SelectOptionsService){
this.user=this.userService.getUser();
}
async ngOnInit(){
this.listings=await lastValueFrom(this.listingsService.getAllListings());
this.favorites=this.listings.filter(l=>l.favoritesForUser?.includes(this.user.id));
}
}

View File

@@ -0,0 +1,33 @@
<div class="surface-ground px-4 py-8 md:px-6 lg:px-8 h-full">
<div class="p-fluid flex flex-column lg:flex-row">
<menu-account></menu-account>
<p-toast></p-toast>
<p-confirmPopup></p-confirmPopup>
<div class="surface-card p-5 shadow-2 border-round flex-auto">
<div class="text-900 font-semibold text-lg mt-3">My Listings</div>
<p-divider></p-divider>
<p-table [value]="myListings" [tableStyle]="{ 'min-width': '50rem' }" dataKey="id" [paginator]="true" [rows]="10" [rowsPerPageOptions]="[10, 20, 50]" [showCurrentPageReport]="true" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries">
<ng-template pTemplate="header">
<tr>
<th class="wide-column">Title</th>
<th>Category</th>
<th>Located in</th>
<th></th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-listing>
<tr>
<td class="wide-column line-height-3">{{ listing.title }}</td>
<td>{{ selectOptions.getListingsCategory(listing.listingsCategory) }}</td>
<td>{{ selectOptions.getLocation(listing.location) }}</td>
<td>
<button pButton pRipple icon="pi pi-pencil" class="p-button-rounded p-button-success mr-2" [routerLink]="['/editListing',listing.id]"></button>
<button pButton pRipple icon="pi pi-trash" class="p-button-rounded p-button-warning" (click)="confirm($event,listing)"></button>
</td>
</tr>
</ng-template>
</p-table>
</div>
</div>
</div>

View File

@@ -0,0 +1,3 @@
.wide-column{
width: 40%;
}

Some files were not shown because too many files have changed in this diff Show More