├── frontend
├── android
│ ├── app
│ │ ├── .gitignore
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── drawable-land-hdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-land-mdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-hdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-mdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── drawable-land-xhdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-land-xxhdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-land-xxxhdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-xhdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-xxhdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── drawable-port-xxxhdpi
│ │ │ │ │ │ └── splash.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ ├── ic_launcher.png
│ │ │ │ │ │ ├── ic_launcher_round.png
│ │ │ │ │ │ └── ic_launcher_foreground.png
│ │ │ │ │ ├── values
│ │ │ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ │ │ ├── strings.xml
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ ├── xml
│ │ │ │ │ │ ├── config.xml
│ │ │ │ │ │ └── file_paths.xml
│ │ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ │ ├── ic_launcher.xml
│ │ │ │ │ │ └── ic_launcher_round.xml
│ │ │ │ │ └── layout
│ │ │ │ │ │ └── activity_main.xml
│ │ │ │ ├── assets
│ │ │ │ │ ├── capacitor.config.json
│ │ │ │ │ └── capacitor.plugins.json
│ │ │ │ └── java
│ │ │ │ │ └── io
│ │ │ │ │ └── ionic
│ │ │ │ │ └── starter
│ │ │ │ │ └── MainActivity.java
│ │ │ ├── test
│ │ │ │ └── java
│ │ │ │ │ └── com
│ │ │ │ │ └── getcapacitor
│ │ │ │ │ └── myapp
│ │ │ │ │ └── ExampleUnitTest.java
│ │ │ └── androidTest
│ │ │ │ └── java
│ │ │ │ └── com
│ │ │ │ └── getcapacitor
│ │ │ │ └── myapp
│ │ │ │ └── ExampleInstrumentedTest.java
│ │ ├── capacitor.build.gradle
│ │ └── proguard-rules.pro
│ ├── .idea
│ │ ├── .gitignore
│ │ ├── compiler.xml
│ │ └── misc.xml
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── settings.gradle
│ ├── variables.gradle
│ ├── build.gradle
│ ├── capacitor.settings.gradle
│ └── gradle.properties
├── src
│ ├── app
│ │ ├── shared
│ │ │ ├── components
│ │ │ │ ├── enquiry-badge
│ │ │ │ │ ├── enquiry-badge.component.scss
│ │ │ │ │ ├── enquiry-badge.component.html
│ │ │ │ │ ├── enquiry-badge.component.spec.ts
│ │ │ │ │ └── enquiry-badge.component.ts
│ │ │ │ ├── property-badge
│ │ │ │ │ ├── property-badge.component.scss
│ │ │ │ │ ├── property-badge.component.html
│ │ │ │ │ └── property-badge.component.spec.ts
│ │ │ │ ├── action-popup
│ │ │ │ │ ├── action-popup.component.scss
│ │ │ │ │ └── action-popup.component.spec.ts
│ │ │ │ ├── alert-card
│ │ │ │ │ ├── alert-card.component.html
│ │ │ │ │ ├── alert-card.component.ts
│ │ │ │ │ ├── alert-card.component.scss
│ │ │ │ │ └── alert-card.component.spec.ts
│ │ │ │ ├── contact-form
│ │ │ │ │ ├── contact-form.component.scss
│ │ │ │ │ └── contact-form.component.spec.ts
│ │ │ │ ├── modal-search
│ │ │ │ │ ├── modal-search.component.scss
│ │ │ │ │ ├── modal-search.component.spec.ts
│ │ │ │ │ └── modal-search.component.html
│ │ │ │ ├── footer
│ │ │ │ │ ├── footer.component.scss
│ │ │ │ │ ├── footer.component.ts
│ │ │ │ │ ├── footer.component.html
│ │ │ │ │ └── footer.component.spec.ts
│ │ │ │ └── div-horizontal-slide
│ │ │ │ │ ├── div-horizontal-slide.component.scss
│ │ │ │ │ ├── div-horizontal-slide.component.html
│ │ │ │ │ ├── div-horizontal-slide.component.spec.ts
│ │ │ │ │ └── div-horizontal-slide.component.ts
│ │ │ ├── interface
│ │ │ │ ├── map.ts
│ │ │ │ ├── google.ts
│ │ │ │ ├── notification.ts
│ │ │ │ ├── user.ts
│ │ │ │ ├── api-response.ts
│ │ │ │ ├── property.ts
│ │ │ │ └── enquiry.ts
│ │ │ ├── enums
│ │ │ │ ├── notification.ts
│ │ │ │ ├── enquiry.ts
│ │ │ │ └── property.ts
│ │ │ ├── directives
│ │ │ │ └── custom-validators.directive.spec.ts
│ │ │ └── services
│ │ │ │ └── storage
│ │ │ │ └── storage.service.spec.ts
│ │ ├── enquiries
│ │ │ ├── enquiries-reply-modal
│ │ │ │ ├── enquiries-reply-modal.component.scss
│ │ │ │ ├── enquiries-reply-modal.component.html
│ │ │ │ ├── enquiries-reply-modal.component.ts
│ │ │ │ └── enquiries-reply-modal.component.spec.ts
│ │ │ ├── enquiries-list
│ │ │ │ ├── enquiries-list.component.scss
│ │ │ │ ├── enquiries-list.component.spec.ts
│ │ │ │ └── enquiries-list.component.html
│ │ │ ├── enquiries.service.spec.ts
│ │ │ ├── enquiries-routing.module.ts
│ │ │ ├── enquiries-detail
│ │ │ │ ├── enquiries-detail.component.scss
│ │ │ │ └── enquiries-detail.component.spec.ts
│ │ │ ├── enquiries-list-item
│ │ │ │ └── enquiries-list-item.component.spec.ts
│ │ │ ├── enquiries-new-form
│ │ │ │ ├── enquiries-new-form.component.spec.ts
│ │ │ │ └── enquiries-new-form.component.scss
│ │ │ └── enquiries.page.spec.ts
│ │ ├── settings
│ │ │ ├── settings.page.scss
│ │ │ ├── settings-theme
│ │ │ │ ├── settings-theme.component.scss
│ │ │ │ ├── settings-theme.component.spec.ts
│ │ │ │ ├── settings-theme.component.html
│ │ │ │ └── settings-theme.component.ts
│ │ │ ├── settings.page.ts
│ │ │ ├── settings-routing.module.ts
│ │ │ ├── settings.page.html
│ │ │ ├── settings-coord-default
│ │ │ │ ├── settings-coord-default.component.scss
│ │ │ │ └── settings-coord-default.component.spec.ts
│ │ │ ├── settings.page.spec.ts
│ │ │ └── settings.module.ts
│ │ ├── map
│ │ │ ├── map-leaflet
│ │ │ │ ├── map-leaflet.component.html
│ │ │ │ ├── map-leaflet.component.scss
│ │ │ │ └── map-leaflet.component.spec.ts
│ │ │ ├── map-search-field
│ │ │ │ ├── map-search-field.component.html
│ │ │ │ ├── map-search-field.component.spec.ts
│ │ │ │ └── map-search-field.component.scss
│ │ │ ├── map-routing.module.ts
│ │ │ ├── map.service.spec.ts
│ │ │ ├── map-markers-legend
│ │ │ │ ├── map-markers-legend.component.html
│ │ │ │ ├── map-markers-legend.component.scss
│ │ │ │ └── map-markers-legend.component.spec.ts
│ │ │ ├── map-popup
│ │ │ │ ├── map-popup.component.scss
│ │ │ │ ├── map-popup.component.html
│ │ │ │ ├── map-popup.component.ts
│ │ │ │ └── map-popup.component.spec.ts
│ │ │ ├── map.page.scss
│ │ │ ├── map.page.spec.ts
│ │ │ ├── map.page.ts
│ │ │ └── map.module.ts
│ │ ├── user
│ │ │ ├── user.page.scss
│ │ │ ├── user.page.ts
│ │ │ ├── notifications
│ │ │ │ ├── notifications.component.scss
│ │ │ │ ├── notifications.component.spec.ts
│ │ │ │ └── notifications.component.html
│ │ │ ├── auth.guard.ts
│ │ │ ├── auth-guest.guard.ts
│ │ │ ├── user.service.spec.ts
│ │ │ ├── auth.guard.spec.ts
│ │ │ ├── auth-guest.guard.spec.ts
│ │ │ ├── user.page.spec.ts
│ │ │ ├── change-password
│ │ │ │ ├── change-password.component.scss
│ │ │ │ └── change-password.component.spec.ts
│ │ │ ├── profile
│ │ │ │ ├── profile.component.spec.ts
│ │ │ │ └── profile.component.ts
│ │ │ ├── user.page.html
│ │ │ ├── user.module.ts
│ │ │ ├── signin
│ │ │ │ ├── signin.component.spec.ts
│ │ │ │ └── signin.component.scss
│ │ │ └── register
│ │ │ │ └── register.component.scss
│ │ ├── mortgage-calc
│ │ │ ├── mortgage-pie-chart
│ │ │ │ ├── mortgage-pie-chart.component.html
│ │ │ │ ├── mortgage-pie-chart.component.scss
│ │ │ │ └── mortgage-pie-chart.component.spec.ts
│ │ │ ├── mortgage-calc.page.ts
│ │ │ ├── mortgage-calc-routing.module.ts
│ │ │ ├── mortgage-line-chart
│ │ │ │ ├── mortgage-line-chart.component.html
│ │ │ │ ├── mortgage-line-chart.component.spec.ts
│ │ │ │ └── mortgage-line-chart.component.scss
│ │ │ ├── mortgage-calc.page.scss
│ │ │ ├── mortgage-calc.page.spec.ts
│ │ │ ├── mortgage-core-calc
│ │ │ │ └── mortgage-core-calc.component.spec.ts
│ │ │ └── mortgage-calc.module.ts
│ │ ├── about
│ │ │ ├── about.page.ts
│ │ │ ├── about-routing.module.ts
│ │ │ ├── about.module.ts
│ │ │ └── about.page.spec.ts
│ │ ├── properties
│ │ │ ├── properties.page.scss
│ │ │ ├── properties.service.spec.ts
│ │ │ ├── properties-routing.module.ts
│ │ │ ├── properties-edit-modal
│ │ │ │ ├── properties-edit.component.scss
│ │ │ │ └── properties-edit.component.spec.ts
│ │ │ ├── properties-coordinates-modal
│ │ │ │ ├── properties-coordinates.component.scss
│ │ │ │ ├── properties-coordinates.component.ts
│ │ │ │ └── properties-coordinates.component.spec.ts
│ │ │ ├── properties-list
│ │ │ │ ├── properties-list.component.spec.ts
│ │ │ │ └── properties-list.component.scss
│ │ │ ├── properties-gallery
│ │ │ │ ├── properties-gallery.component.spec.ts
│ │ │ │ ├── properties-gallery.component.html
│ │ │ │ └── properties-gallery.component.ts
│ │ │ ├── properties-uploads-modal
│ │ │ │ ├── properties-uploads.component.spec.ts
│ │ │ │ └── properties-current-images
│ │ │ │ │ ├── properties-current-images.component.spec.ts
│ │ │ │ │ └── properties-current-images.component.html
│ │ │ ├── properties-new-modal
│ │ │ │ ├── properties-new.component.spec.ts
│ │ │ │ └── properties-new.component.scss
│ │ │ ├── properties-card
│ │ │ │ ├── properties-card.component.spec.ts
│ │ │ │ ├── properties-card.component.scss
│ │ │ │ ├── properties-card.component.ts
│ │ │ │ └── properties-card.component.html
│ │ │ ├── properties.page.spec.ts
│ │ │ └── properties-detail
│ │ │ │ └── properties-detail.component.spec.ts
│ │ ├── folder
│ │ │ ├── folder-routing.module.ts
│ │ │ ├── folder.page.scss
│ │ │ ├── folder.page.ts
│ │ │ ├── folder.module.ts
│ │ │ ├── folder.page.html
│ │ │ └── folder.page.spec.ts
│ │ └── app.module.ts
│ ├── assets
│ │ ├── icon
│ │ │ └── favicon.png
│ │ ├── images
│ │ │ ├── logo.png
│ │ │ ├── avatar.png
│ │ │ ├── leaf-green.png
│ │ │ ├── no-image.jpeg
│ │ │ ├── leaf-shadow.png
│ │ │ └── map
│ │ │ │ ├── default-marker.svg
│ │ │ │ ├── marker-shadow.svg
│ │ │ │ └── marker-industrial.svg
│ │ └── shapes.svg
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── zone-flags.ts
│ ├── main.ts
│ ├── test.ts
│ └── index.html
├── resources
│ ├── icon.png
│ ├── splash.png
│ ├── icon-background.png
│ ├── icon-foreground.png.png
│ └── android
│ │ ├── icon-background.png
│ │ ├── icon-foreground.png
│ │ ├── icon
│ │ ├── hdpi-background.png
│ │ ├── hdpi-foreground.png
│ │ ├── ldpi-background.png
│ │ ├── ldpi-foreground.png
│ │ ├── mdpi-background.png
│ │ ├── mdpi-foreground.png
│ │ ├── xhdpi-background.png
│ │ ├── xhdpi-foreground.png
│ │ ├── xxhdpi-background.png
│ │ ├── xxhdpi-foreground.png
│ │ ├── drawable-hdpi-icon.png
│ │ ├── drawable-ldpi-icon.png
│ │ ├── drawable-mdpi-icon.png
│ │ ├── drawable-xhdpi-icon.png
│ │ ├── drawable-xxhdpi-icon.png
│ │ ├── xxxhdpi-background.png
│ │ ├── xxxhdpi-foreground.png
│ │ └── drawable-xxxhdpi-icon.png
│ │ └── splash
│ │ ├── drawable-land-hdpi-screen.png
│ │ ├── drawable-land-ldpi-screen.png
│ │ ├── drawable-land-mdpi-screen.png
│ │ ├── drawable-port-hdpi-screen.png
│ │ ├── drawable-port-ldpi-screen.png
│ │ ├── drawable-port-mdpi-screen.png
│ │ ├── drawable-land-xhdpi-screen.png
│ │ ├── drawable-land-xxhdpi-screen.png
│ │ ├── drawable-land-xxxhdpi-screen.png
│ │ ├── drawable-port-xhdpi-screen.png
│ │ ├── drawable-port-xxhdpi-screen.png
│ │ └── drawable-port-xxxhdpi-screen.png
├── ionic.config.json
├── capacitor.config.json
├── e2e
│ ├── tsconfig.json
│ ├── src
│ │ ├── app.po.ts
│ │ └── app.e2e-spec.ts
│ └── protractor.conf.js
├── .editorconfig
├── tsconfig.app.json
├── tsconfig.spec.json
├── .gitignore
├── .browserslistrc
└── tsconfig.json
└── backend-fastify
├── .gitignore
├── uploads
└── placeholder.jpeg
├── src
├── routes
│ ├── auth
│ │ ├── options
│ │ │ ├── index.js
│ │ │ ├── signin.js
│ │ │ ├── register.js
│ │ │ └── schema.js
│ │ └── index.js
│ ├── users
│ │ ├── options
│ │ │ ├── index.js
│ │ │ ├── schema.js
│ │ │ ├── get-user.js
│ │ │ └── get-users.js
│ │ └── index.js
│ ├── enquiries
│ │ ├── options
│ │ │ ├── index.js
│ │ │ ├── get-enquiry.js
│ │ │ ├── create-enquiry.js
│ │ │ ├── get-enquiries.js
│ │ │ ├── update-enquiry.js
│ │ │ ├── delete-enquiry.js
│ │ │ └── schema.js
│ │ └── index.js
│ ├── properties
│ │ ├── options
│ │ │ ├── get-properties.js
│ │ │ ├── get-property.js
│ │ │ ├── index.js
│ │ │ ├── update-property.js
│ │ │ ├── delete-property.js
│ │ │ ├── create-property.js
│ │ │ └── schema.js
│ │ └── index.js
│ └── index.js
├── utils
│ ├── requests.js
│ ├── users.js
│ └── schema
│ │ └── response.js
├── enums
│ ├── enquiries.js
│ └── properties.js
├── controllers
│ ├── users
│ │ ├── index.js
│ │ ├── get-users.js
│ │ ├── get-user.js
│ │ └── get-me.js
│ ├── auth
│ │ ├── index.js
│ │ ├── register.js
│ │ └── signin.js
│ ├── properties
│ │ ├── get-properties.js
│ │ ├── get-property.js
│ │ ├── index.js
│ │ ├── delete-property.js
│ │ └── create-property.js
│ └── enquiries
│ │ ├── index.js
│ │ ├── get-enquiry.js
│ │ └── get-enquiries.js
├── cors.js
├── static.js
├── swagger.js
├── models
│ ├── user.js
│ ├── property.js
│ └── enquiry.js
└── database-seeder
│ └── dummy-data
│ └── users.js
├── .env.example
├── package.json
└── README.md
/frontend/android/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build/*
2 | !/build/.npmkeep
3 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/enquiry-badge/enquiry-badge.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/property-badge/property-badge.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-reply-modal/enquiries-reply-modal.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/android/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/backend-fastify/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /uploads/*
3 | !/uploads/placeholder.jpeg
4 | .env
5 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/interface/map.ts:
--------------------------------------------------------------------------------
1 | export interface Coord {
2 | lat: number;
3 | lng: number;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings.page.scss:
--------------------------------------------------------------------------------
1 | .settings-container {
2 | max-width: 1600px;
3 | margin: 0 auto;
4 | }
5 |
--------------------------------------------------------------------------------
/frontend/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/icon.png
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/action-popup/action-popup.component.scss:
--------------------------------------------------------------------------------
1 | .heading-popup {
2 | font-weight: bold;
3 | }
4 |
--------------------------------------------------------------------------------
/frontend/resources/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/splash.png
--------------------------------------------------------------------------------
/frontend/src/app/map/map-leaflet/map-leaflet.component.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/frontend/src/assets/icon/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/src/assets/icon/favicon.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/src/assets/images/logo.png
--------------------------------------------------------------------------------
/frontend/resources/icon-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/icon-background.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/src/assets/images/avatar.png
--------------------------------------------------------------------------------
/backend-fastify/uploads/placeholder.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/backend-fastify/uploads/placeholder.jpeg
--------------------------------------------------------------------------------
/frontend/src/assets/images/leaf-green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/src/assets/images/leaf-green.png
--------------------------------------------------------------------------------
/frontend/src/assets/images/no-image.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/src/assets/images/no-image.jpeg
--------------------------------------------------------------------------------
/frontend/resources/icon-foreground.png.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/icon-foreground.png.png
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/enquiry-badge/enquiry-badge.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{ topicLabel() }}
3 |
4 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/property-badge/property-badge.component.html:
--------------------------------------------------------------------------------
1 |
2 | {{ typeLabel() }}
3 |
4 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/leaf-shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/src/assets/images/leaf-shadow.png
--------------------------------------------------------------------------------
/frontend/ionic.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "real-estate-management",
3 | "integrations": {
4 | "capacitor": {}
5 | },
6 | "type": "angular"
7 | }
8 |
--------------------------------------------------------------------------------
/frontend/resources/android/icon-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon-background.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon-foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon-foreground.png
--------------------------------------------------------------------------------
/frontend/src/app/shared/enums/notification.ts:
--------------------------------------------------------------------------------
1 | export enum NotificationType {
2 | enquiry = 'enquiry',
3 | property = 'property',
4 | app = 'app',
5 | }
6 |
--------------------------------------------------------------------------------
/frontend/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/frontend/resources/android/icon/hdpi-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/hdpi-background.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/hdpi-foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/hdpi-foreground.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/ldpi-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/ldpi-background.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/ldpi-foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/ldpi-foreground.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/mdpi-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/mdpi-background.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/mdpi-foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/mdpi-foreground.png
--------------------------------------------------------------------------------
/frontend/src/app/map/map-leaflet/map-leaflet.component.scss:
--------------------------------------------------------------------------------
1 | .map-container {
2 | display: block;
3 | height: 100%;
4 | }
5 |
6 | #mapId {
7 | height: 100%;
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable/splash.png
--------------------------------------------------------------------------------
/frontend/capacitor.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "io.ionic.starter",
3 | "appName": "real-estate-management",
4 | "webDir": "www",
5 | "bundledWebRuntime": false
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/resources/android/icon/xhdpi-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/xhdpi-background.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/xhdpi-foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/xhdpi-foreground.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/xxhdpi-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/xxhdpi-background.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/xxhdpi-foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/xxhdpi-foreground.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/drawable-hdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/drawable-hdpi-icon.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/drawable-ldpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/drawable-ldpi-icon.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/drawable-mdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/drawable-mdpi-icon.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/drawable-xhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/drawable-xhdpi-icon.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/drawable-xxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/drawable-xxhdpi-icon.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/xxxhdpi-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/xxxhdpi-background.png
--------------------------------------------------------------------------------
/frontend/resources/android/icon/xxxhdpi-foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/xxxhdpi-foreground.png
--------------------------------------------------------------------------------
/frontend/src/app/shared/enums/enquiry.ts:
--------------------------------------------------------------------------------
1 | export enum EnquiryTopic {
2 | schedule = 'schedule',
3 | payment = 'payment',
4 | sales = 'sales',
5 | info = 'information'
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/resources/android/icon/drawable-xxxhdpi-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/icon/drawable-xxxhdpi-icon.png
--------------------------------------------------------------------------------
/backend-fastify/src/routes/auth/options/index.js:
--------------------------------------------------------------------------------
1 | import { registerOpts } from "./register.js";
2 | import { signInOpts } from "./signin.js";
3 |
4 | export { registerOpts, signInOpts };
5 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/frontend/src/app/shared/interface/google.ts:
--------------------------------------------------------------------------------
1 | export interface GoogleAuthResponse {
2 | clientId: string;
3 | client_id: string;
4 | credential: string;
5 | select_by?: string;
6 | }
7 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/users/options/index.js:
--------------------------------------------------------------------------------
1 | import { getUsersOpts } from "./get-users.js";
2 | import { getUserOpts } from "./get-user.js";
3 |
4 | export { getUsersOpts, getUserOpts };
5 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-land-hdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-land-hdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-land-mdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-land-mdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-port-hdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-port-hdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-port-mdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-port-mdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-land-hdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-land-hdpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-land-ldpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-land-ldpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-land-mdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-land-mdpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-port-hdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-port-hdpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-port-ldpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-port-ldpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-port-mdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-port-mdpi-screen.png
--------------------------------------------------------------------------------
/backend-fastify/src/utils/requests.js:
--------------------------------------------------------------------------------
1 | export const authBearerToken = function (request) {
2 | const authorization = request.headers.authorization;
3 | return authorization.split(" ")[1];
4 | };
5 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/assets/capacitor.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "appId": "io.ionic.starter",
3 | "appName": "real-estate-management",
4 | "webDir": "www",
5 | "bundledWebRuntime": false
6 | }
7 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-land-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-land-xhdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-land-xxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-land-xxhdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-land-xxxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-land-xxxhdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-port-xhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-port-xhdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-port-xxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-port-xxhdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/drawable-port-xxxhdpi/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/drawable-port-xxxhdpi/splash.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-land-xhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-land-xhdpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-land-xxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-land-xxhdpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-land-xxxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-land-xxxhdpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-port-xhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-port-xhdpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-port-xxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-port-xxhdpi-screen.png
--------------------------------------------------------------------------------
/frontend/resources/android/splash/drawable-port-xxxhdpi-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/resources/android/splash/drawable-port-xxxhdpi-screen.png
--------------------------------------------------------------------------------
/frontend/src/app/shared/enums/property.ts:
--------------------------------------------------------------------------------
1 | export enum PropertyType {
2 | residential = 'residential',
3 | commercial = 'commercial',
4 | industrial = 'industrial',
5 | land = 'land'
6 | }
7 |
--------------------------------------------------------------------------------
/backend-fastify/src/utils/users.js:
--------------------------------------------------------------------------------
1 | import { fastify } from "../index.js";
2 |
3 | export const userIdToken = function (token) {
4 | const { id } = fastify.jwt.decode(token);
5 | return id;
6 | };
7 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/java/io/ionic/starter/MainActivity.java:
--------------------------------------------------------------------------------
1 | package io.ionic.starter;
2 |
3 | import com.getcapacitor.BridgeActivity;
4 |
5 | public class MainActivity extends BridgeActivity {}
6 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/backend-fastify/src/enums/enquiries.js:
--------------------------------------------------------------------------------
1 | export const EnquiryTopic = {
2 | schedule: "schedule",
3 | payment: "payment",
4 | sales: "sales",
5 | info: "information",
6 | };
7 | Object.freeze(EnquiryTopic);
8 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spider-yamet/real-estate-management/main/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/frontend/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | api: {
4 | server: 'http://localhost:8000/',
5 | mapKey: '',
6 | googleAuthClientId: ''
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/users/index.js:
--------------------------------------------------------------------------------
1 | import { getUsers } from "./get-users.js";
2 | import { getUser } from "./get-user.js";
3 | import { getMe } from './get-me.js';
4 |
5 | export { getUsers, getUser, getMe };
6 |
--------------------------------------------------------------------------------
/frontend/android/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/interface/notification.ts:
--------------------------------------------------------------------------------
1 | export interface Notification {
2 | id: string;
3 | title: string;
4 | type: string;
5 | date: Date;
6 | content?: {
7 | id: string;
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/backend-fastify/src/enums/properties.js:
--------------------------------------------------------------------------------
1 | export const PropertyType = {
2 | residential: "residential",
3 | commercial: "commercial",
4 | industrial: "industrial",
5 | land: "land",
6 | };
7 | Object.freeze(PropertyType);
8 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/auth/index.js:
--------------------------------------------------------------------------------
1 | import { register } from "./register.js";
2 | import { signIn } from "./signin.js";
3 | import { googleAuth } from './google-auth.js';
4 |
5 | export { register, signIn, googleAuth };
6 |
--------------------------------------------------------------------------------
/backend-fastify/.env.example:
--------------------------------------------------------------------------------
1 | PORT=8000
2 | LOGGER=true
3 | SALT=12
4 | SECRET_KEY='secret'
5 | DB_CONNECT=mongodb://localhost:27017/database-name
6 | # OPTIONAL for GOOGLE SIGNIN
7 | GOOGLE_AUTH_CLIENT_ID='[something].apps.googleusercontent.com'
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/users/get-users.js:
--------------------------------------------------------------------------------
1 | import { User } from "../../models/user.js";
2 |
3 | export const getUsers = async function (_, res) {
4 | const users = await User.find();
5 | res.status(200).send({ data: users });
6 | };
7 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/xml/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/frontend/src/zone-flags.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Prevents Angular change detection from
3 | * running with certain Web Component callbacks
4 | */
5 | // eslint-disable-next-line no-underscore-dangle
6 | (window as any).__Zone_disable_customElements = true;
7 |
--------------------------------------------------------------------------------
/frontend/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':capacitor-cordova-android-plugins'
3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
4 |
5 | apply from: 'capacitor.settings.gradle'
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-list/enquiries-list.component.scss:
--------------------------------------------------------------------------------
1 | .labels-card {
2 | --background: var(--ion-color-primary);
3 | --color: var(--ion-color-light);
4 | }
5 |
6 | .label-text {
7 | font-size: 19px;
8 | font-weight: bold;
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/src/app/user/user.page.scss:
--------------------------------------------------------------------------------
1 | ion-tab-bar {
2 | --border: 1px solid
3 | var(
4 | --ion-item-border-color,
5 | var(--ion-border-color, var(--ion-color-step-250, rgba(0, 0, 0, 0.13)))
6 | );
7 | box-shadow: 2px 0px 2px rgba(0, 0, 0, 0.12);
8 | }
9 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/auth/options/signin.js:
--------------------------------------------------------------------------------
1 | import { authProperties } from "./schema.js";
2 |
3 | export const signInOpts = (handler) => ({
4 | schema: {
5 | response: {
6 | 200: authProperties,
7 | },
8 | },
9 | handler: handler,
10 | });
11 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/xml/file_paths.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/frontend/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/properties/get-properties.js:
--------------------------------------------------------------------------------
1 | import { Property } from "../../models/property.js";
2 |
3 | export const getProperties = async function (_, res) {
4 | const properties = await Property.find();
5 | res.status(200).send({ data: properties });
6 | };
7 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/auth/options/register.js:
--------------------------------------------------------------------------------
1 | import { authProperties } from "./schema.js";
2 |
3 | export const registerOpts = (handler) => ({
4 | schema: {
5 | response: {
6 | 201: authProperties,
7 | },
8 | },
9 | handler: handler,
10 | });
11 |
--------------------------------------------------------------------------------
/frontend/e2e/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "module": "commonjs",
6 | "target": "es2018",
7 | "types": [
8 | "jasmine",
9 | "node"
10 | ]
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-pie-chart/mortgage-pie-chart.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/alert-card/alert-card.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ content }}
5 |
6 |
--------------------------------------------------------------------------------
/frontend/e2e/src/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo(destination) {
5 | return browser.get(destination);
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.deepCss('app-root ion-content')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings-theme/settings-theme.component.scss:
--------------------------------------------------------------------------------
1 | .heading {
2 | font-size: 24px;
3 | line-height: 150%;
4 | margin-top: 50px 0 0 0;
5 | font-weight: bold;
6 | // background-color: var(--ion-color-light-shade);
7 | }
8 | ion-item {
9 | --background: var(--ion-color-light);
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/interface/user.ts:
--------------------------------------------------------------------------------
1 | export interface User {
2 | id?: string;
3 | user_id: string;
4 | email: string;
5 | fullName: string;
6 | created?: Date;
7 | password?: string;
8 | enquiries?: string[];
9 | properties?: string[];
10 | accessToken?: string;
11 | aboutMe?: string;
12 | location?: string;
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/src/app/user/user.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-user',
5 | templateUrl: './user.page.html',
6 | styleUrls: ['./user.page.scss'],
7 | })
8 | export class UserPage implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/backend-fastify/src/cors.js:
--------------------------------------------------------------------------------
1 | import FastifyCors from "@fastify/cors";
2 |
3 | export const setFastifyCors = function (fastify) {
4 | fastify.register(FastifyCors, {
5 | // put your options here
6 | origin: [
7 | "http://localhost:9000",
8 | "http://localhost:8100",
9 | "http://localhost:4200",
10 | ],
11 | });
12 | };
13 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-about',
5 | templateUrl: './about.page.html',
6 | styleUrls: ['./about.page.scss'],
7 | })
8 | export class AboutPage implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/contact-form/contact-form.component.scss:
--------------------------------------------------------------------------------
1 | ion-card {
2 | ion-card-title {
3 | font-size: 32px;
4 | font-weight: bold;
5 | }
6 | ion-item {
7 | --background: var(--ion-color-light);
8 | margin: 0 0 8px;
9 | }
10 | }
11 |
12 | .alert-errors {
13 | width: 100%;
14 | margin: 6px 0 0 0;
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/modal-search/modal-search.component.scss:
--------------------------------------------------------------------------------
1 | ion-content {
2 | --background: var(--ion-color-light);
3 | }
4 | ion-searchbar {
5 | --box-shadow: none;
6 | border-top: 1px solid rgba(12, 12, 12, 0.096);
7 | }
8 | ion-toolbar {
9 | --background: var(--ion-color-primary);
10 | --color: var(--ion-color-dark);
11 | }
12 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/directives/custom-validators.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { CustomValidatorsDirective } from './custom-validators.directive';
2 |
3 | describe('CustomValidatorsDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new CustomValidatorsDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/auth/options/schema.js:
--------------------------------------------------------------------------------
1 | export const authProperties = {
2 | type: "object",
3 | properties: {
4 | id: { type: "string" },
5 | user_id: { type: "string" },
6 | email: { type: "string" },
7 | fullName: { type: "string" },
8 | accessToken: { type: "string" },
9 | },
10 | };
11 | Object.freeze(authProperties);
12 |
--------------------------------------------------------------------------------
/frontend/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/footer/footer.component.scss:
--------------------------------------------------------------------------------
1 | .footer-content {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: center;
6 | .title {
7 | color: var(--ion-color-dark);
8 | font-size: 14px;
9 | }
10 | .source {
11 | a {
12 | font-size: 12px;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-settings',
5 | templateUrl: './settings.page.html',
6 | styleUrls: ['./settings.page.scss'],
7 | })
8 | export class SettingsPage implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/div-horizontal-slide/div-horizontal-slide.component.scss:
--------------------------------------------------------------------------------
1 | .scrolling-wrapper-flexbox {
2 | display: flex;
3 | flex-wrap: nowrap;
4 | overflow-x: auto;
5 | -ms-overflow-style: none; /* IE and Edge */
6 | scrollbar-width: none; /* Firefox */
7 | }
8 | .scrolling-wrapper-flexbox ::-webkit-scrollbar {
9 | display: none;
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | real-estate-management
4 | real-estate-management
5 | io.ionic.starter
6 | io.ionic.starter
7 |
8 |
--------------------------------------------------------------------------------
/backend-fastify/src/static.js:
--------------------------------------------------------------------------------
1 | import FastifyStatic from "@fastify/static";
2 | import path from "path";
3 |
4 | export const setFastifyStatic = function (fastify) {
5 | // We serve uploaded files
6 | const __dirname = path.resolve(path.dirname(""));
7 | fastify.register(FastifyStatic, {
8 | root: path.join(__dirname, "uploads"),
9 | prefix: "/uploads",
10 | });
11 | };
12 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/footer/footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-footer',
5 | templateUrl: './footer.component.html',
6 | styleUrls: ['./footer.component.scss'],
7 | })
8 | export class FooterComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {}
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-calc.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-mortgage-calc',
5 | templateUrl: './mortgage-calc.page.html',
6 | styleUrls: ['./mortgage-calc.page.scss'],
7 | })
8 | export class MortgageCalcPage implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/div-horizontal-slide/div-horizontal-slide.component.html:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/interface/api-response.ts:
--------------------------------------------------------------------------------
1 | import { HttpHeaders } from '@angular/common/http';
2 |
3 | export interface ApiResponse {
4 | status: number | string;
5 | message: string;
6 |
7 | headers?: HttpHeaders;
8 | name?: string;
9 | data?: any;
10 | ok?: boolean;
11 | statusText?: string;
12 | error?: { status: number; message: string };
13 | url?: string;
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-search-field/map-search-field.component.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties.page.scss:
--------------------------------------------------------------------------------
1 | ion-content {
2 | --background: var(--ion-color-light-tint);
3 | --height: 100%;
4 | margin: 0;
5 | padding: 0;
6 | }
7 | .properties-container {
8 | height: 100%;
9 | max-width: 1600px;
10 | margin: 0 auto;
11 | }
12 | ion-searchbar {
13 | --box-shadow: none;
14 | }
15 | .heading {
16 | font-size: 24px;
17 | font-weight: bold;
18 | }
19 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/properties/get-property.js:
--------------------------------------------------------------------------------
1 | import { Property } from "../../models/property.js";
2 |
3 | export const getProperty = async function (req, res) {
4 | const { id } = req.params;
5 | try {
6 | const property = await Property.findOne({ property_id: id });
7 | res.status(200).send({ data: property });
8 | } catch (error) {
9 | res.status(404).send({});
10 | }
11 | };
12 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/auth/index.js:
--------------------------------------------------------------------------------
1 | import { registerOpts, signInOpts } from "./options/index.js";
2 | import { register, signIn, googleAuth } from "../../controllers/auth/index.js";
3 |
4 | export const authRoutes = function (fastify, opts, done) {
5 | fastify.post("/register", registerOpts(register));
6 | fastify.post("/signin", signInOpts(signIn));
7 | fastify.post("/google", signInOpts(googleAuth))
8 | done();
9 | };
10 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/users/options/schema.js:
--------------------------------------------------------------------------------
1 | // multiple users
2 | export const usersProperties = {
3 | user_id: { type: "string" },
4 | email: { type: "string" },
5 | fullName: { type: "string" },
6 | };
7 | Object.freeze(usersProperties);
8 |
9 | // single user
10 | export const userProperties = {
11 | ...usersProperties,
12 | properties: {
13 | type: "array"
14 | }
15 | };
16 | Object.freeze(userProperties);
17 |
--------------------------------------------------------------------------------
/frontend/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.log(err));
13 |
--------------------------------------------------------------------------------
/frontend/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": [
7 | "jasmine"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/users/index.js:
--------------------------------------------------------------------------------
1 | import { getUsersOpts, getUserOpts } from "./options/index.js";
2 | import { getUsers, getUser, getMe } from "../../controllers/users/index.js";
3 |
4 | export const usersRoutes = function (fastify, opts, done) {
5 | fastify.get("/", getUsersOpts(getUsers, fastify));
6 | fastify.get("/:id", getUserOpts(getUser, fastify));
7 | fastify.get("/me", getUserOpts(getMe, fastify));
8 | done();
9 | };
10 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { MapPage } from './map.page';
5 |
6 | const routes: Routes = [
7 | {
8 | path: '',
9 | component: MapPage
10 | }
11 | ];
12 |
13 | @NgModule({
14 | imports: [RouterModule.forChild(routes)],
15 | exports: [RouterModule],
16 | })
17 | export class MapPageRoutingModule {}
18 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { MapService } from './map.service';
4 |
5 | describe('MapService', () => {
6 | let service: MapService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(MapService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/enquiries/index.js:
--------------------------------------------------------------------------------
1 | import { getEnquiries } from "./get-enquiries.js";
2 | import { getEnquiry } from "./get-enquiry.js";
3 | import { createEnquiry } from "./create-enquiry.js";
4 | import { deleteEnquiry } from "./delete-enquiry.js";
5 | import { updateEnquiry } from "./update-enquiry.js";
6 |
7 | export {
8 | getEnquiries,
9 | getEnquiry,
10 | createEnquiry,
11 | deleteEnquiry,
12 | updateEnquiry,
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/e2e/src/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('new App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 | describe('default screen', () => {
10 | beforeEach(() => {
11 | page.navigateTo('/Inbox');
12 | });
13 | it('should say Inbox', () => {
14 | expect(page.getParagraphText()).toContain('Inbox');
15 | });
16 | });
17 | });
18 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-pie-chart/mortgage-pie-chart.component.scss:
--------------------------------------------------------------------------------
1 | ion-card {
2 | --background: var(--ion-color-light-tint);
3 | box-shadow: none;
4 | }
5 |
6 | .chart-container {
7 | width: 100%;
8 | height: 100%;
9 | }
10 | #myChart {
11 | height: 100%;
12 | width: 100%;
13 | }
14 |
15 | @media (max-width: 600px) {
16 | ion-card,
17 | ion-card-content {
18 | margin: 0 0 16px 0;
19 | padding: 0;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { AboutPage } from './about.page';
5 |
6 | const routes: Routes = [
7 | {
8 | path: '',
9 | component: AboutPage
10 | }
11 | ];
12 |
13 | @NgModule({
14 | imports: [RouterModule.forChild(routes)],
15 | exports: [RouterModule],
16 | })
17 | export class AboutPageRoutingModule { }
18 |
--------------------------------------------------------------------------------
/frontend/src/app/folder/folder-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { FolderPage } from './folder.page';
5 |
6 | const routes: Routes = [
7 | {
8 | path: '',
9 | component: FolderPage
10 | }
11 | ];
12 |
13 | @NgModule({
14 | imports: [RouterModule.forChild(routes)],
15 | exports: [RouterModule],
16 | })
17 | export class FolderPageRoutingModule {}
18 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { SettingsPage } from './settings.page';
5 |
6 | const routes: Routes = [
7 | {
8 | path: '',
9 | component: SettingsPage
10 | }
11 | ];
12 |
13 | @NgModule({
14 | imports: [RouterModule.forChild(routes)],
15 | exports: [RouterModule],
16 | })
17 | export class SettingsPageRoutingModule {}
18 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { EnquiriesService } from './enquiries.service';
4 |
5 | describe('EnquiriesService', () => {
6 | let service: EnquiriesService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(EnquiriesService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-markers-legend/map-markers-legend.component.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 | {{ marker.label }}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/enquiries/options/index.js:
--------------------------------------------------------------------------------
1 | import { getEnquiriesOpts } from "./get-enquiries.js";
2 | import { getEnquiryOpts } from "./get-enquiry.js";
3 | import { createEnquiryOpts } from "./create-enquiry.js";
4 | import { deleteEnquiryOpts } from "./delete-enquiry.js";
5 | import { updateEnquiryOpts } from "./update-enquiry.js";
6 |
7 | export {
8 | getEnquiriesOpts,
9 | getEnquiryOpts,
10 | createEnquiryOpts,
11 | deleteEnquiryOpts,
12 | updateEnquiryOpts,
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { PropertiesService } from './properties.service';
4 |
5 | describe('PropertiesService', () => {
6 | let service: PropertiesService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(PropertiesService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/alert-card/alert-card.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-alert-card',
5 | templateUrl: './alert-card.component.html',
6 | styleUrls: ['./alert-card.component.scss'],
7 | })
8 | export class AlertCardComponent implements OnInit {
9 | @Input() color = 'danger';
10 | @Input() content = 'Alert Something is wrong';
11 | constructor() { }
12 |
13 | ngOnInit() { }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/users/get-user.js:
--------------------------------------------------------------------------------
1 | import { User } from "../../models/user.js";
2 |
3 | export const getUser = async function (req, res) {
4 | const { id } = req.params;
5 | try {
6 | const user = await User.findOne({ user_id: id });
7 | if (!user) {
8 | return res.status(404).send({ message: "Error: Can't find User." });
9 | }
10 | res.send({ data: user });
11 | } catch (error) {
12 | res.send(error);
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-calc-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { MortgageCalcPage } from './mortgage-calc.page';
5 |
6 | const routes: Routes = [
7 | {
8 | path: '',
9 | component: MortgageCalcPage
10 | }
11 | ];
12 |
13 | @NgModule({
14 | imports: [RouterModule.forChild(routes)],
15 | exports: [RouterModule],
16 | })
17 | export class MortgageCalcPageRoutingModule {}
18 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/enquiries/options/get-enquiry.js:
--------------------------------------------------------------------------------
1 | import { enquiryProperties } from "./schema.js";
2 | import { responseSuccess, responseError } from '../../../utils/schema/response.js';
3 |
4 | export const getEnquiryOpts = (handler) => ({
5 | schema: {
6 | response: {
7 | 200: responseSuccess({ data: enquiryProperties }),
8 | 400: responseError(),
9 | 404: responseError({ status: 404, message: "Error: Can't find Enquiry." })
10 | },
11 | },
12 | handler,
13 | });
14 |
--------------------------------------------------------------------------------
/frontend/android/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/footer/footer.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/interface/property.ts:
--------------------------------------------------------------------------------
1 | import { Coord } from './map';
2 |
3 | export interface Property {
4 | property_id: string;
5 | name: string;
6 | address: string;
7 | description?: string;
8 | type: string;
9 | position: Coord;
10 | price: number;
11 | enquiries?: string[];
12 | features?: string[];
13 | images?: string[];
14 | currency?: string;
15 | contactNumber?: string;
16 | contactEmail?: string;
17 | createdAt?: Date;
18 | updatedAt?: Date;
19 | user_id: string;
20 | }
21 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/enquiries/get-enquiry.js:
--------------------------------------------------------------------------------
1 | import { Enquiry } from "../../models/enquiry.js";
2 |
3 | export const getEnquiry = async function (req, res) {
4 | const { id } = req.params;
5 | try {
6 | const enquiry = await Enquiry.findOne({ enquiry_id: id });
7 | if (!enquiry) {
8 | res.status(404).send({ message: "Can't find Enquiry." });
9 | return;
10 | }
11 | res.status(200).send({ data: enquiry });
12 | } catch (error) {
13 | res.status(400).send(error);
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/properties/options/get-properties.js:
--------------------------------------------------------------------------------
1 | import { propertyProperties } from "./schema.js";
2 | import { responseSuccess, responseError } from "../../../utils/schema/response.js";
3 |
4 | export const getPropertiesOpts = (handler) => ({
5 | schema: {
6 | response: {
7 | 200: responseSuccess({
8 | data: {
9 | type: "array",
10 | items: propertyProperties,
11 | }
12 | }),
13 | 400: responseError(),
14 | },
15 | },
16 | handler: handler,
17 | });
18 |
--------------------------------------------------------------------------------
/frontend/src/app/folder/folder.page.scss:
--------------------------------------------------------------------------------
1 | ion-menu-button {
2 | color: var(--ion-color-primary);
3 | }
4 |
5 | #container {
6 | text-align: center;
7 | position: absolute;
8 | left: 0;
9 | right: 0;
10 | top: 50%;
11 | transform: translateY(-50%);
12 | }
13 |
14 | #container strong {
15 | font-size: 20px;
16 | line-height: 26px;
17 | }
18 |
19 | #container p {
20 | font-size: 16px;
21 | line-height: 22px;
22 | color: #8c8c8c;
23 | margin: 0;
24 | }
25 |
26 | #container a {
27 | text-decoration: none;
28 | }
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/users/get-me.js:
--------------------------------------------------------------------------------
1 | import { User } from "../../models/user.js";
2 | import { authBearerToken } from "../../utils/requests.js";
3 | import { userIdToken } from "../../utils/users.js";
4 |
5 | export const getMe = async function (req, res) {
6 | const token = authBearerToken(req);
7 | const user_id = userIdToken(token);
8 | try {
9 | const user = await User.findOne({ user_id });
10 | res.send({ data: user });
11 | } catch (error) {
12 | res.send(error);
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/assets/capacitor.plugins.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "pkg": "@capacitor/app",
4 | "classpath": "com.capacitorjs.plugins.app.AppPlugin"
5 | },
6 | {
7 | "pkg": "@capacitor/haptics",
8 | "classpath": "com.capacitorjs.plugins.haptics.HapticsPlugin"
9 | },
10 | {
11 | "pkg": "@capacitor/keyboard",
12 | "classpath": "com.capacitorjs.plugins.keyboard.KeyboardPlugin"
13 | },
14 | {
15 | "pkg": "@capacitor/status-bar",
16 | "classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin"
17 | }
18 | ]
19 |
--------------------------------------------------------------------------------
/frontend/src/app/folder/folder.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 |
4 | @Component({
5 | selector: 'app-folder',
6 | templateUrl: './folder.page.html',
7 | styleUrls: ['./folder.page.scss'],
8 | })
9 | export class FolderPage implements OnInit {
10 | public folder: string;
11 |
12 | constructor(private activatedRoute: ActivatedRoute) { }
13 |
14 | ngOnInit() {
15 | this.folder = this.activatedRoute.snapshot.paramMap.get('id');
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.getcapacitor.myapp;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import org.junit.Test;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 |
14 | @Test
15 | public void addition_isCorrect() throws Exception {
16 | assertEquals(4, 2 + 2);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings.page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Settings Page
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/properties/options/get-property.js:
--------------------------------------------------------------------------------
1 | import { propertyProperties } from "./schema.js";
2 | import { responseSuccess, responseError } from "../../../utils/schema/response.js";
3 |
4 | export const getPropertyOpts = (handler) => ({
5 | schema: {
6 | response: {
7 | 200: responseSuccess({ data: propertyProperties }),
8 | 400: responseError(),
9 | 404: responseError({
10 | status: 404,
11 | message: "Error: Property not found!",
12 | }),
13 | },
14 | },
15 | handler: handler,
16 | });
17 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | /.angular/cache
2 | # Specifies intentionally untracked files to ignore when using Git
3 | # http://git-scm.com/docs/gitignore
4 |
5 | *~
6 | *.sw[mnpcod]
7 | .tmp
8 | *.tmp
9 | *.tmp.*
10 | *.sublime-project
11 | *.sublime-workspace
12 | .DS_Store
13 | Thumbs.db
14 | UserInterfaceState.xcuserstate
15 | $RECYCLE.BIN/
16 |
17 | *.log
18 | log.txt
19 | npm-debug.log*
20 |
21 | /.idea
22 | /.ionic
23 | /.sass-cache
24 | /.sourcemaps
25 | /.versions
26 | /.vscode
27 | /coverage
28 | /dist
29 | /node_modules
30 | /platforms
31 | /plugins
32 | /www
33 |
--------------------------------------------------------------------------------
/frontend/src/app/user/notifications/notifications.component.scss:
--------------------------------------------------------------------------------
1 | ion-content {
2 | --background: var(--ion-color-light);
3 | }
4 | ion-card {
5 | box-shadow: none;
6 | }
7 | .notification-container {
8 | min-height: 100%;
9 | max-width: 1600px;
10 | margin: 0 auto;
11 | }
12 |
13 | ion-item {
14 | --inner-padding-bottom: 16px;
15 | --inner-padding-top: 16px;
16 | .item-right-side {
17 | display: flex;
18 | justify-content: space-around;
19 | align-items: center;
20 | ion-badge {
21 | margin: 0 8px 0 0;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/enquiries/options/create-enquiry.js:
--------------------------------------------------------------------------------
1 | import { enquiryProperties } from "./schema.js";
2 | import { responseSuccess, responseError } from '../../../utils/schema/response.js';
3 |
4 | export const createEnquiryOpts = (fastify, handler) => ({
5 | preValidation: [fastify.authenticate],
6 | schema: {
7 | response: {
8 | 201: responseSuccess({
9 | status: 201,
10 | message: 'Enquiry created!',
11 | data: enquiryProperties
12 | }),
13 | 400: responseError(),
14 | },
15 | },
16 | handler,
17 | });
18 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/enquiries/options/get-enquiries.js:
--------------------------------------------------------------------------------
1 | import { enquiryProperties } from "./schema.js";
2 | import { responseSuccess, responseError } from '../../../utils/schema/response.js';
3 |
4 | export const getEnquiriesOpts = (fastify, handler) => ({
5 | preValidation: [fastify.authenticate],
6 | schema: {
7 | response: {
8 | 200: responseSuccess({
9 | data: {
10 | type: "array",
11 | items: enquiryProperties,
12 | }
13 | }),
14 | 400: responseError()
15 | },
16 | },
17 | handler,
18 | });
19 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/enquiries/options/update-enquiry.js:
--------------------------------------------------------------------------------
1 | import { enquiryProperties } from "./schema.js";
2 | import { responseError, responseSuccess } from '../../../utils/schema/response.js';
3 |
4 | export const updateEnquiryOpts = (fastify, handler) => ({
5 | preValidation: [fastify.authenticate],
6 | schema: {
7 | response: {
8 | 201: responseSuccess({
9 | message: "Enquiry updated!",
10 | data: enquiryProperties
11 | }),
12 | 400: responseError(),
13 | 404: responseError()
14 | },
15 | },
16 | handler,
17 | });
18 |
--------------------------------------------------------------------------------
/backend-fastify/src/swagger.js:
--------------------------------------------------------------------------------
1 | import FastifySwagger from "@fastify/swagger";
2 |
3 | export const setFastifySwagger = function (fastify) {
4 | fastify.register(FastifySwagger, {
5 | exposeRoute: true,
6 | routePrefix: "/docs",
7 | swagger: {
8 | info: {
9 | title: "API Documentation",
10 | description: "Fastify swagger API documentation.",
11 | version: "0.1.0",
12 | },
13 | externalDocs: {
14 | url: "https://swagger.io",
15 | description: "Find more info here",
16 | },
17 | },
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/services/storage/storage.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { Storage } from '@ionic/storage-angular';
3 |
4 | import { StorageService } from './storage.service';
5 |
6 | describe('StorageService', () => {
7 | let service: StorageService;
8 |
9 | beforeEach(() => {
10 | TestBed.configureTestingModule({
11 | providers: [ Storage ]
12 | });
13 | service = TestBed.inject(StorageService);
14 | });
15 |
16 | it('should be created', () => {
17 | expect(service).toBeTruthy();
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/frontend/android/variables.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | minSdkVersion = 22
3 | compileSdkVersion = 32
4 | targetSdkVersion = 32
5 | androidxActivityVersion = '1.4.0'
6 | androidxAppCompatVersion = '1.4.2'
7 | androidxCoordinatorLayoutVersion = '1.2.0'
8 | androidxCoreVersion = '1.8.0'
9 | androidxFragmentVersion = '1.4.1'
10 | junitVersion = '4.13.2'
11 | androidxJunitVersion = '1.1.3'
12 | androidxEspressoCoreVersion = '3.4.0'
13 | cordovaAndroidVersion = '10.1.1'
14 | coreSplashScreenVersion = '1.0.0-rc01'
15 | androidxWebkitVersion = '1.4.0'
16 | }
--------------------------------------------------------------------------------
/frontend/src/app/folder/folder.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { IonicModule } from '@ionic/angular';
6 |
7 | import { FolderPageRoutingModule } from './folder-routing.module';
8 |
9 | import { FolderPage } from './folder.page';
10 |
11 | @NgModule({
12 | imports: [
13 | CommonModule,
14 | FormsModule,
15 | IonicModule,
16 | FolderPageRoutingModule
17 | ],
18 | declarations: [FolderPage]
19 | })
20 | export class FolderPageModule {}
21 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/properties/index.js:
--------------------------------------------------------------------------------
1 | import { getProperties } from "./get-properties.js";
2 | import { getProperty } from "./get-property.js";
3 | import { createProperty } from "./create-property.js";
4 | import { deleteProperty } from "./delete-property.js";
5 | import { updateProperty } from "./update-property.js";
6 | import { addImagesProperty, deleteImagesProperty } from "./image-property.js";
7 |
8 | export {
9 | getProperties,
10 | getProperty,
11 | createProperty,
12 | deleteProperty,
13 | updateProperty,
14 | addImagesProperty,
15 | deleteImagesProperty
16 | };
17 |
--------------------------------------------------------------------------------
/frontend/src/app/user/auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { CanActivate, Router } from '@angular/router';
3 | import { UserService } from './user.service';
4 |
5 | @Injectable({
6 | providedIn: 'root'
7 | })
8 | export class AuthGuard implements CanActivate {
9 |
10 | constructor(private user: UserService, private router: Router) { }
11 | async canActivate() {
12 | const user = this.user.user;
13 | // if user is signed in
14 | if (!!user) {
15 | this.router.navigate(['/map']);
16 | return false;
17 | }
18 | return true;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/app/user/auth-guest.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { CanActivate, Router } from '@angular/router';
3 | import { UserService } from './user.service';
4 |
5 | @Injectable({
6 | providedIn: 'root'
7 | })
8 | export class AuthGuestGuard implements CanActivate {
9 | constructor(private user: UserService, private router: Router) { }
10 | async canActivate() {
11 | const user = this.user.user;
12 | // if user is guest in
13 | if (!user) {
14 | this.router.navigate(['/map']);
15 | return false;
16 | }
17 | return true;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-popup/map-popup.component.scss:
--------------------------------------------------------------------------------
1 | .popup-container {
2 | min-width: 200px;
3 | max-width: 300px;
4 | }
5 |
6 | .name {
7 | font-size: 17px;
8 | font-weight: bold;
9 | }
10 |
11 | .address {
12 | font-size: 12px;
13 | font-weight: 500;
14 | color: var(--ion-color-dark);
15 | padding: 8px;
16 | }
17 |
18 | ::ng-deep .leaflet-popup-content-wrapper,
19 | .leaflet-popup.tip {
20 | background: var(--ion-color-light-tint);
21 | color: var(--ion-color-dark);
22 | }
23 | ::ng-deep .leaflet-popup-tip-container .leaflet-popup-tip {
24 | background: var(--ion-color-light-tint);
25 | }
26 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/properties/options/index.js:
--------------------------------------------------------------------------------
1 | import { getPropertiesOpts } from "./get-properties.js";
2 | import { getPropertyOpts } from "./get-property.js";
3 | import { createPropertyOpts } from "./create-property.js";
4 | import { updatePropertyOpts } from "./update-property.js";
5 | import { deletePropertyOpts } from "./delete-property.js";
6 | import { uploadImagesOpts, deleteImagesOpts } from "./image-property.js";
7 |
8 | export {
9 | getPropertiesOpts,
10 | getPropertyOpts,
11 | createPropertyOpts,
12 | updatePropertyOpts,
13 | deletePropertyOpts,
14 | uploadImagesOpts,
15 | deleteImagesOpts,
16 | };
17 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-reply-modal/enquiries-reply-modal.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
18 |
19 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings-coord-default/settings-coord-default.component.scss:
--------------------------------------------------------------------------------
1 | .heading {
2 | font-size: 24px;
3 | line-height: 150%;
4 | margin-top: 50px 0 0 0;
5 | font-weight: bold;
6 | // background-color: var(--ion-color-light);
7 | }
8 |
9 | ion-item {
10 | --background: var(--ion-color-light);
11 | }
12 | ion-button {
13 | --padding-start: 20px;
14 | --padding-end: 20px;
15 | --padding-top: 16px;
16 | --padding-bottom: 16px;
17 | &.reset {
18 | --background: transparent;
19 | --background-hover: transparent;
20 | --box-shadow: none;
21 | --color: var(--ion-color-dark);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import { usersRoutes } from "./users/index.js";
2 | import { authRoutes } from "./auth/index.js";
3 | import { propertiesRoutes } from "./properties/index.js";
4 | import { enquiriesRoutes } from "./enquiries/index.js";
5 |
6 | export const setFastifyRoutes = function (fastify) {
7 | fastify.get("/", (_, res) => {
8 | res.send(true);
9 | });
10 | fastify.register(usersRoutes, { prefix: "/users" });
11 | fastify.register(authRoutes, { prefix: "/auth" });
12 | fastify.register(propertiesRoutes, { prefix: "/properties" });
13 | fastify.register(enquiriesRoutes, { prefix: "/enquiries" });
14 | };
15 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/map/default-marker.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-line-chart/mortgage-line-chart.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 | Re-Calculate
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { EnquiriesDetailComponent } from './enquiries-detail/enquiries-detail.component';
4 |
5 | import { EnquiriesPage } from './enquiries.page';
6 |
7 | const routes: Routes = [
8 | {
9 | path: '',
10 | component: EnquiriesPage
11 | },
12 | {
13 | path: ':id',
14 | component: EnquiriesDetailComponent
15 | }
16 | ];
17 |
18 | @NgModule({
19 | imports: [RouterModule.forChild(routes)],
20 | exports: [RouterModule],
21 | })
22 | export class EnquiriesPageRoutingModule { }
23 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/enquiries/options/delete-enquiry.js:
--------------------------------------------------------------------------------
1 | import { enquiryProperties } from "./schema.js";
2 | import { responseSuccess, responseError } from '../../../utils/schema/response.js';
3 |
4 | export const deleteEnquiryOpts = (fastify, handler) => ({
5 | preValidation: [fastify.authenticate],
6 | schema: {
7 | response: {
8 | 200: responseSuccess({
9 | message: "Enquiry deleted!",
10 | data: enquiryProperties
11 | }),
12 | 400: responseError(),
13 | 404: responseError({
14 | status: 404,
15 | message: "Can't find Enquiry."
16 | })
17 | },
18 | },
19 | handler: handler,
20 | });
21 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/users/options/get-user.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Schema for single user request
3 | */
4 | import { userProperties } from "./schema.js";
5 | import { responseSuccess, responseError } from "../../../utils/schema/response.js";
6 |
7 | export const getUserOpts = (handler, fastify) => ({
8 | preValidation: [fastify.authenticate],
9 | schema: {
10 | response: {
11 | 200: responseSuccess({
12 | data: {
13 | type: "object",
14 | properties: userProperties
15 | }
16 | }),
17 | 400: responseError(),
18 | 404: responseError({ status: 404 }),
19 | },
20 | },
21 | handler: handler,
22 | });
23 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { IonicModule } from '@ionic/angular';
6 |
7 | import { AboutPageRoutingModule } from './about-routing.module';
8 |
9 | import { AboutPage } from './about.page';
10 | import { SharedModule } from '../shared/shared.module';
11 |
12 | @NgModule({
13 | imports: [
14 | CommonModule,
15 | FormsModule,
16 | IonicModule,
17 | AboutPageRoutingModule,
18 | SharedModule
19 | ],
20 | declarations: [AboutPage]
21 | })
22 | export class AboutPageModule { }
23 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { PropertiesDetailComponent } from './properties-detail/properties-detail.component';
4 |
5 | import { PropertiesPage } from './properties.page';
6 |
7 | const routes: Routes = [
8 | {
9 | path: '',
10 | component: PropertiesPage
11 | },
12 | {
13 | path: ':id',
14 | component: PropertiesDetailComponent
15 | }
16 | ];
17 |
18 | @NgModule({
19 | imports: [RouterModule.forChild(routes)],
20 | exports: [RouterModule],
21 | })
22 | export class PropertiesPageRoutingModule { }
23 |
--------------------------------------------------------------------------------
/frontend/src/app/user/user.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { HttpClientTestingModule } from '@angular/common/http/testing';
3 | import { UserService } from './user.service';
4 | import { Storage } from '@ionic/storage-angular';
5 |
6 | describe('UserService', () => {
7 | let service: UserService;
8 |
9 | beforeEach(() => {
10 | TestBed.configureTestingModule({
11 | imports: [HttpClientTestingModule],
12 | providers: [UserService, Storage]
13 | });
14 | service = TestBed.inject(UserService);
15 | });
16 |
17 | it('should be created', () => {
18 | expect(service).toBeTruthy();
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/backend-fastify/src/models/user.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const userSchema = new mongoose.Schema(
4 | {
5 | user_id: {
6 | type: String,
7 | required: true,
8 | },
9 | fullName: {
10 | type: String,
11 | minlength: 4,
12 | required: true,
13 | },
14 | email: {
15 | type: String,
16 | required: true,
17 | match: /.+\@.+\..+/,
18 | unique: true,
19 | },
20 | password: {
21 | type: String,
22 | },
23 | properties: {
24 | type: Array,
25 | }
26 | },
27 | {
28 | timestamps: true,
29 | }
30 | );
31 |
32 | export const User = mongoose.model("User", userSchema);
33 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-popup/map-popup.component.html:
--------------------------------------------------------------------------------
1 |
24 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/map/marker-shadow.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/frontend/android/app/capacitor.build.gradle:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
2 |
3 | android {
4 | compileOptions {
5 | sourceCompatibility JavaVersion.VERSION_11
6 | targetCompatibility JavaVersion.VERSION_11
7 | }
8 | }
9 |
10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
11 | dependencies {
12 | implementation project(':capacitor-app')
13 | implementation project(':capacitor-haptics')
14 | implementation project(':capacitor-keyboard')
15 | implementation project(':capacitor-status-bar')
16 |
17 | }
18 |
19 |
20 | if (hasProperty('postBuildExtras')) {
21 | postBuildExtras()
22 | }
23 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/users/options/get-users.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Schema for multiple users request
3 | */
4 | import { usersProperties } from "./schema.js";
5 | import { responseSuccess, responseError } from "../../../utils/schema/response.js";
6 |
7 | export const getUsersOpts = (handler, fastify) => ({
8 | preValidation: [fastify.authenticate],
9 | schema: {
10 | response: {
11 | 200: responseSuccess({
12 | data: {
13 | type: "array",
14 | items: {
15 | type: "object",
16 | properties: usersProperties,
17 | }
18 | }
19 | }),
20 | 400: responseError(),
21 | },
22 | },
23 | handler: handler,
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-edit-modal/properties-edit.component.scss:
--------------------------------------------------------------------------------
1 | ion-content {
2 | padding-bottom: 100px;
3 | }
4 | .divider {
5 | background: var(--ion-color-success);
6 | padding: 2px 0;
7 | width: 100px;
8 | margin: 16px 0;
9 | border-radius: 2px;
10 | }
11 | .note-helper {
12 | color: var(--ion-color-dark-shade);
13 | font-size: 13px;
14 | font-weight: 300;
15 | }
16 |
17 | .coord-heading {
18 | padding: 8px;
19 | display: flex;
20 | align-items: center;
21 | ion-text {
22 | text-transform: capitalize;
23 | font-weight: 600;
24 | margin-right: 8px;
25 | }
26 | }
27 | .coord-input {
28 | display: flex;
29 | ion-item {
30 | width: 100%;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/app/user/auth.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpClientTestingModule } from '@angular/common/http/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { RouterTestingModule } from '@angular/router/testing';
4 | import { Storage } from '@ionic/storage-angular';
5 |
6 | import { AuthGuard } from './auth.guard';
7 |
8 | describe('AuthGuard', () => {
9 | let guard: AuthGuard;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [HttpClientTestingModule, RouterTestingModule],
14 | providers: [Storage]
15 | });
16 | guard = TestBed.inject(AuthGuard);
17 | });
18 |
19 | it('should be created', () => {
20 | expect(guard).toBeTruthy();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/enquiries/get-enquiries.js:
--------------------------------------------------------------------------------
1 | import { Enquiry } from "../../models/enquiry.js";
2 | import { authBearerToken } from "../../utils/requests.js";
3 | import { userIdToken } from "../../utils/users.js";
4 |
5 | export const getEnquiries = async function (req, res) {
6 | const token = authBearerToken(req);
7 | const user_id = userIdToken(token);
8 |
9 | try {
10 | const list = await Enquiry.find({
11 | $or: [
12 | { 'users.from.user_id': user_id, 'users.from.keep': true },
13 | { 'users.to.user_id': user_id, 'users.to.keep': true },
14 | ]
15 | });
16 | res.status(200).send({ data: list });
17 | } catch (error) {
18 | res.status(400).send(error);
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map.page.scss:
--------------------------------------------------------------------------------
1 | ion-grid {
2 | height: 100%;
3 | min-height: 100%;
4 | }
5 | ion-row.h-100 {
6 | height: 100%;
7 | min-height: 100%;
8 | }
9 |
10 | .map-section {
11 | background-color: var(--ion-color-light-shade);
12 | height: 100%;
13 | position: relative;
14 | z-index: 1;
15 | }
16 |
17 | .horizontal-slides-container {
18 | margin-bottom: 60px;
19 | width: 100%;
20 | }
21 |
22 | .extra-section {
23 | background-color: var(--ion-color-light);
24 | overflow: hidden;
25 | overflow-y: scroll;
26 | height: 100%;
27 | width: 100%;
28 | .properties-list {
29 | max-width: 500px;
30 | }
31 | }
32 |
33 | @media (max-width: 992px) {
34 | .map-section {
35 | height: 70%;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/frontend/src/app/folder/folder.page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ folder }}
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ folder }}
14 |
15 |
16 |
17 |
21 |
22 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { MapPage } from './map.page';
5 |
6 | describe('MapPage', () => {
7 | let component: MapPage;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ MapPage ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(MapPage);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/user/auth-guest.guard.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpClientTestingModule } from '@angular/common/http/testing';
2 | import { TestBed } from '@angular/core/testing';
3 | import { RouterTestingModule } from '@angular/router/testing';
4 | import { Storage } from '@ionic/storage-angular';
5 |
6 | import { AuthGuestGuard } from './auth-guest.guard';
7 |
8 | describe('AuthGuestGuard', () => {
9 | let guard: AuthGuestGuard;
10 |
11 | beforeEach(() => {
12 | TestBed.configureTestingModule({
13 | imports: [HttpClientTestingModule, RouterTestingModule],
14 | providers: [Storage]
15 | });
16 | guard = TestBed.inject(AuthGuestGuard);
17 | });
18 |
19 | it('should be created', () => {
20 | expect(guard).toBeTruthy();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/frontend/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
18 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "sourceMap": true,
8 | "declaration": false,
9 | "downlevelIteration": true,
10 | "experimentalDecorators": true,
11 | "moduleResolution": "node",
12 | "importHelpers": true,
13 | "target": "es2015",
14 | "module": "es2020",
15 | "lib": ["es2018", "dom"]
16 | },
17 | "angularCompilerOptions": {
18 | "enableI18nLegacyMessageIdFormat": false,
19 | "strictInjectionParameters": true,
20 | "strictInputAccessModifiers": true,
21 | "strictTemplates": true,
22 | "allowSyntheticDefaultImports": true
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/frontend/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | mavenCentral()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:7.2.1'
11 | classpath 'com.google.gms:google-services:4.3.13'
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | apply from: "variables.gradle"
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | mavenCentral()
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/enquiries/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | createEnquiryOpts,
3 | deleteEnquiryOpts,
4 | getEnquiriesOpts,
5 | getEnquiryOpts,
6 | updateEnquiryOpts,
7 | } from "./options/index.js";
8 | import {
9 | createEnquiry,
10 | deleteEnquiry,
11 | getEnquiries,
12 | getEnquiry,
13 | updateEnquiry,
14 | } from "../../controllers/enquiries/index.js";
15 |
16 | export const enquiriesRoutes = function (fastify, opts, done) {
17 | fastify.get("/", getEnquiriesOpts(fastify, getEnquiries));
18 | fastify.get("/:id", getEnquiryOpts(getEnquiry));
19 | fastify.post("/", createEnquiryOpts(fastify, createEnquiry));
20 | fastify.patch("/:id", updateEnquiryOpts(fastify, updateEnquiry));
21 | fastify.delete("/:id", deleteEnquiryOpts(fastify, deleteEnquiry));
22 | done();
23 | };
24 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/properties/options/update-property.js:
--------------------------------------------------------------------------------
1 | import { propertyProperties } from "./schema.js";
2 | import { responseSuccess, responseError } from "../../../utils/schema/response.js";
3 |
4 | export const updatePropertyOpts = (fastify, handler) => ({
5 | preValidation: [fastify.authenticate],
6 | schema: {
7 | response: {
8 | 201: responseSuccess({
9 | status: 201,
10 | data: propertyProperties
11 | }),
12 | 400: responseError(),
13 | 401: responseError({
14 | status: 401,
15 | message: "No Authorization was found in request.headers"
16 | }),
17 | 404: responseError({
18 | status: 404,
19 | message: "Error: Property not found!",
20 | }),
21 | },
22 | },
23 | handler: handler,
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/android/capacitor.settings.gradle:
--------------------------------------------------------------------------------
1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
2 | include ':capacitor-android'
3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
4 |
5 | include ':capacitor-app'
6 | project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/android')
7 |
8 | include ':capacitor-haptics'
9 | project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/haptics/android')
10 |
11 | include ':capacitor-keyboard'
12 | project(':capacitor-keyboard').projectDir = new File('../node_modules/@capacitor/keyboard/android')
13 |
14 | include ':capacitor-status-bar'
15 | project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
16 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-calc.page.scss:
--------------------------------------------------------------------------------
1 | ion-content {
2 | --background: var(--ion-color-light-tint);
3 | --height: 100%;
4 | margin: 0;
5 | }
6 | .mortgage-calc-container {
7 | min-height: 100%;
8 | max-width: 1600px;
9 | margin: 0 auto;
10 | }
11 |
12 | .note-container {
13 | background: var(--ion-color-light-shade);
14 | border-radius: 6px;
15 | padding: 16px;
16 | display: flex;
17 | color: var(--ion-color-danger);
18 | .icon-container {
19 | height: 100%;
20 | padding-top: 2px;
21 | }
22 | ion-icon {
23 | font-size: 18px;
24 | margin-right: 6px;
25 | }
26 | }
27 |
28 | .extra-spacer {
29 | height: 200px;
30 | width: 100%;
31 | }
32 |
33 | @media (max-width: 600px) {
34 | ion-col {
35 | margin: 0;
36 | padding: 4px;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { SettingsPage } from './settings.page';
5 |
6 | describe('SettingsPage', () => {
7 | let component: SettingsPage;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ SettingsPage ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(SettingsPage);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/alert-card/alert-card.component.scss:
--------------------------------------------------------------------------------
1 | .alert {
2 | padding: 6px 16px;
3 | color: var(--ion-color-dark);
4 | border-radius: 8px;
5 | border: 1px solid;
6 | display: flex;
7 | div {
8 | display: flex;
9 | align-items: flex-start;
10 | margin-right: 6px;
11 | ion-icon {
12 | font-size: 20px;
13 | }
14 | }
15 | }
16 |
17 | .danger {
18 | border-color: var(--ion-color-danger);
19 | background: #ed576b5d;
20 | color: var(--ion-color-danger-shade);
21 | }
22 | .warning {
23 | border-color: var(--ion-color-warning);
24 | background: #e0ae0852;
25 | color: var(--ion-color-warning-shade);
26 | }
27 | .success {
28 | border-color: var(--ion-color-success);
29 | background: #28ba6246;
30 | color: var(--ion-color-success-shade);
31 | }
32 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/properties/options/delete-property.js:
--------------------------------------------------------------------------------
1 | import { propertyProperties } from "./schema.js";
2 | import { responseSuccess, responseError } from "../../../utils/schema/response.js";
3 |
4 | export const deletePropertyOpts = (fastify, handler) => ({
5 | preValidation: [fastify.authenticate],
6 | schema: {
7 | response: {
8 | 200: responseSuccess({
9 | message: "Property deleted!",
10 | data: propertyProperties
11 | }),
12 | 400: responseError(),
13 | 401: responseError({
14 | status: 401,
15 | message: "No Authorization was found in request.headers"
16 | }),
17 | 404: responseError({
18 | status: 404,
19 | message: "Error: Property not found!",
20 | }),
21 | },
22 | },
23 | handler: handler,
24 | });
25 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/properties/options/create-property.js:
--------------------------------------------------------------------------------
1 | import { propertyProperties } from "./schema.js";
2 | import { responseSuccess, responseError } from "../../../utils/schema/response.js";
3 |
4 | export const createPropertyOpts = (fastify, handler) => ({
5 | preValidation: [fastify.authenticate],
6 | schema: {
7 | response: {
8 | 201: responseSuccess({
9 | status: 201,
10 | message: "Property created!",
11 | data: propertyProperties
12 | }),
13 | 400: responseError({
14 | status: 400,
15 | message: "Error: Something went wrong, please try again later."
16 | }),
17 | 401: responseError({
18 | status: 401,
19 | message: "No Authorization was found in request.headers",
20 | }),
21 | },
22 | },
23 | handler: handler,
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/footer/footer.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { FooterComponent } from './footer.component';
5 |
6 | describe('FooterComponent', () => {
7 | let component: FooterComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ FooterComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(FooterComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-markers-legend/map-markers-legend.component.scss:
--------------------------------------------------------------------------------
1 | .map-marker-info {
2 | z-index: 400;
3 | position: absolute;
4 | right: 0;
5 | bottom: 24px;
6 | background-color: transparent;
7 | padding: 4px;
8 | ion-item {
9 | --background: rgba(255, 255, 255, 0.185);
10 | --inner-padding-top: 0;
11 | --inner-padding-bottom: 0;
12 | --padding-top: 0;
13 | --padding-bottom: 0;
14 | --padding-start: 4px;
15 | --padding-end: 0;
16 | --color: var(--ion-color-dark);
17 | -webkit-text-stroke: 1px var(--ion-color-dark);
18 | font-size: 13px;
19 | line-height: 15px;
20 | img {
21 | height: 30px;
22 | width: 30px;
23 | }
24 | ion-checkbox {
25 | margin-right: 6px;
26 | }
27 | }
28 | }
29 | @media (max-width: 992px) {
30 | .text {
31 | display: none;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-coordinates-modal/properties-coordinates.component.scss:
--------------------------------------------------------------------------------
1 | ion-fab {
2 | margin-top: 90px;
3 | }
4 | .notice-container {
5 | width: 200px;
6 | padding: 8px 16px;
7 | border-radius: 8px;
8 | margin-top: 16px;
9 | color: var(--ion-color-dark);
10 | background: var(--ion-color-primary);
11 |
12 | display: flex;
13 | align-items: center;
14 | ion-icon {
15 | font-size: 24px;
16 | margin-left: 8px;
17 | }
18 | }
19 |
20 | .page-container {
21 | background-color: var(--ion-color-light-tint);
22 | height: 100%;
23 | }
24 | .btn-contianer {
25 | z-index: 400;
26 | position: absolute;
27 | bottom: 0;
28 | width: 100%;
29 | display: flex;
30 | justify-content: center;
31 | ion-button {
32 | --padding-end: 24px;
33 | --padding-start: 24px;
34 | margin: 0 0 32px 0;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/frontend/src/app/folder/folder.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 | import { RouterModule } from '@angular/router';
4 | import { FolderPage } from './folder.page';
5 |
6 | describe('FolderPage', () => {
7 | let component: FolderPage;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ FolderPage ],
13 | imports: [IonicModule.forRoot(), RouterModule.forRoot([])]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(FolderPage);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/user/user.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { UserPage } from './user.page';
6 |
7 | describe('UserPage', () => {
8 | let component: UserPage;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ UserPage ],
14 | imports: [IonicModule.forRoot(), RouterTestingModule]
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(UserPage);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false,
7 | api: {
8 | server: 'http://localhost:8000/',
9 | mapKey: '',
10 | googleAuthClientId: ''
11 | }
12 | };
13 |
14 | /*
15 | * For easier debugging in development mode, you can import the following file
16 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
17 | *
18 | * This import should be commented out in production mode because it will have a negative impact
19 | * on performance if an error is thrown.
20 | */
21 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
22 |
--------------------------------------------------------------------------------
/frontend/src/app/about/about.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { AboutPage } from './about.page';
6 |
7 | describe('AboutPage', () => {
8 | let component: AboutPage;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ AboutPage ],
14 | imports: [IonicModule.forRoot(), RouterTestingModule],
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(AboutPage);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/alert-card/alert-card.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { AlertCardComponent } from './alert-card.component';
5 |
6 | describe('AlertCardComponent', () => {
7 | let component: AlertCardComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ AlertCardComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(AlertCardComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map.page.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { PropertyType } from '../shared/enums/property';
3 |
4 | @Component({
5 | selector: 'app-map',
6 | templateUrl: './map.page.html',
7 | styleUrls: ['./map.page.scss'],
8 | })
9 | export class MapPage implements OnInit {
10 |
11 | public visibleType = [
12 | PropertyType.residential.toString(),
13 | PropertyType.commercial.toString(),
14 | PropertyType.industrial.toString(),
15 | PropertyType.land.toString()
16 | ];
17 |
18 | constructor() { }
19 |
20 | ngOnInit() {
21 | }
22 |
23 | setVisibleMarkerType(event: { type: string; isChecked: boolean }) {
24 | if (!event.isChecked) {
25 | this.visibleType = this.visibleType.filter(v => v !== event.type);
26 | } else {
27 | this.visibleType = [...this.visibleType, event.type];
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/modal-search/modal-search.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { ModalSearchComponent } from './modal-search.component';
5 |
6 | describe('ModalSearchComponent', () => {
7 | let component: ModalSearchComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ ModalSearchComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(ModalSearchComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/user/notifications/notifications.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { NotificationsComponent } from './notifications.component';
5 |
6 | describe('NotificationsComponent', () => {
7 | let component: NotificationsComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ NotificationsComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(NotificationsComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/backend-fastify/src/models/property.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const propertySchema = new mongoose.Schema(
4 | {
5 | property_id: { type: String, required: true },
6 | name: { type: String, minlength: 4, required: true },
7 | address: { type: String, required: true },
8 | description: { type: String, minlength: 10 },
9 | type: { type: String, required: true },
10 | position: { lat: Number, lng: Number },
11 | price: { type: Number },
12 | // enquiries: { type: Array },
13 | features: { type: Array },
14 | profileImage: { type: String },
15 | images: { type: Array },
16 | currency: { type: String },
17 | contactNumber: { type: String },
18 | contactEmail: { type: String },
19 | user_id: { type: String },
20 | },
21 | {
22 | timestamps: true,
23 | }
24 | );
25 | export const Property = mongoose.model("Property", propertySchema);
26 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-detail/enquiries-detail.component.scss:
--------------------------------------------------------------------------------
1 | ion-content {
2 | --background: var(--ion-color-light);
3 | }
4 | .view-property {
5 | font-size: 12px;
6 | font-weight: 400;
7 | margin-left: 12px;
8 | }
9 | .topic-subtitle {
10 | display: flex;
11 | gap: 0.5rem;
12 | align-items: center;
13 | }
14 | .property-title {
15 | display: flex;
16 | align-items: center;
17 | }
18 | .reply-to-btn {
19 | padding: 0;
20 | font-size: 16px;
21 | color: var(--ion-color-primary);
22 | background-color: transparent;
23 | max-width: 400px;
24 | }
25 |
26 | .details-container {
27 | height: 100%;
28 | max-width: 1600px;
29 | margin: 0 auto;
30 | height: 100%;
31 | .enquiry-content {
32 | background-color: var(--ion-color-light-tint);
33 | padding: 1rem;
34 | min-height: fit-content;
35 | }
36 | }
37 |
38 | ion-button {
39 | --box-shadow: none;
40 | }
41 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/enquiry-badge/enquiry-badge.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { EnquiryBadgeComponent } from './enquiry-badge.component';
5 |
6 | describe('EnquiryBadgeComponent', () => {
7 | let component: EnquiryBadgeComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ EnquiryBadgeComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(EnquiryBadgeComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-search-field/map-search-field.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { MapSearchFieldComponent } from './map-search-field.component';
5 |
6 | describe('MapSearchFieldComponent', () => {
7 | let component: MapSearchFieldComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ MapSearchFieldComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(MapSearchFieldComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-list/properties-list.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { PropertiesListComponent } from './properties-list.component';
5 |
6 | describe('PropertiesListComponent', () => {
7 | let component: PropertiesListComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ PropertiesListComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(PropertiesListComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/property-badge/property-badge.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { PropertyBadgeComponent } from './property-badge.component';
5 |
6 | describe('PropertyBadgeComponent', () => {
7 | let component: PropertyBadgeComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ PropertyBadgeComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(PropertyBadgeComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/properties/options/schema.js:
--------------------------------------------------------------------------------
1 | export const propertyProperties = {
2 | type: "object",
3 | properties: {
4 | property_id: { type: "string" },
5 | name: { type: "string" },
6 | address: { type: "string" },
7 | description: { type: "string" },
8 | type: { type: "string" },
9 | position: {
10 | type: "object",
11 | properties: { lat: { type: "number" }, lng: { type: "number" } },
12 | },
13 | price: { type: "number" },
14 | enquiries: { type: "array" },
15 | features: { type: "array" },
16 | profileImage: { type: "string" },
17 | images: { type: "array" },
18 | currency: { type: "string" },
19 | contactNumber: { type: "string" },
20 | contactEmail: { type: "string" },
21 | createdAt: { type: "string" },
22 | updatedAt: { type: "string" },
23 | user_id: { type: "string" },
24 | },
25 | };
26 | Object.freeze(propertyProperties);
--------------------------------------------------------------------------------
/frontend/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: {
11 | context(path: string, deep?: boolean, filter?: RegExp): {
12 | keys(): string[];
13 | (id: string): T;
14 | };
15 | };
16 |
17 | // First, initialize the Angular testing environment.
18 | getTestBed().initTestEnvironment(
19 | BrowserDynamicTestingModule,
20 | platformBrowserDynamicTesting(), {
21 | teardown: { destroyAfterEach: false }
22 | }
23 | );
24 | // Then we find all the tests.
25 | const context = require.context('./', true, /\.spec\.ts$/);
26 | // And load the modules.
27 | context.keys().map(context);
28 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-popup/map-popup.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { PropertiesService } from 'src/app/properties/properties.service';
4 | import { Property } from 'src/app/shared/interface/property';
5 |
6 | @Component({
7 | selector: 'app-map-popup',
8 | templateUrl: './map-popup.component.html',
9 | styleUrls: ['./map-popup.component.scss'],
10 | })
11 | export class MapPopupComponent implements OnInit {
12 | @Input() property: Property;
13 | constructor(
14 | public changeDetector: ChangeDetectorRef,
15 | private router: Router,
16 | private propertiesService: PropertiesService
17 | ) {}
18 |
19 | ngOnInit() {}
20 |
21 | viewMore() {
22 | this.propertiesService.property = this.property;
23 | this.router.navigate(['/properties', this.property.property_id]);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-calc.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { MortgageCalcPage } from './mortgage-calc.page';
6 |
7 | describe('MortgageCalcPage', () => {
8 | let component: MortgageCalcPage;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ MortgageCalcPage ],
14 | imports: [IonicModule.forRoot(), ReactiveFormsModule]
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(MortgageCalcPage);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-markers-legend/map-markers-legend.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { MapMarkersLegendComponent } from './map-markers-legend.component';
5 |
6 | describe('MapMarkersLegendComponent', () => {
7 | let component: MapMarkersLegendComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ MapMarkersLegendComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(MapMarkersLegendComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-reply-modal/enquiries-reply-modal.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 | import { ModalController } from '@ionic/angular';
3 | import { Property } from 'src/app/shared/interface/property';
4 |
5 | @Component({
6 | selector: 'app-enquiries-reply-modal',
7 | templateUrl: './enquiries-reply-modal.component.html',
8 | styleUrls: ['./enquiries-reply-modal.component.scss'],
9 | })
10 | export class EnquiriesReplyModalComponent implements OnInit {
11 | @Input() title = 'Create Enquiry';
12 | @Input() property: Partial;
13 | @Input() replyTo?: {
14 | enquiry_id: string;
15 | title: string;
16 | topic: string;
17 | };
18 | @Input() userTo: string;
19 |
20 | constructor(
21 | private modalCtrl: ModalController,
22 | ) { }
23 |
24 | ngOnInit() { }
25 |
26 | public dismissModal() {
27 | this.modalCtrl.dismiss();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-coordinates-modal/properties-coordinates.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 | import { ModalController } from '@ionic/angular';
3 | import { Coord } from 'src/app/shared/interface/map';
4 |
5 | @Component({
6 | selector: 'app-properties-coordinates',
7 | templateUrl: './properties-coordinates.component.html',
8 | styleUrls: ['./properties-coordinates.component.scss'],
9 | })
10 | export class PropertiesCoordinatesComponent implements OnInit {
11 | @Input() title = 'Set Property Marker';
12 | public coord: Coord;
13 |
14 | constructor(private modalCtrl: ModalController) { }
15 |
16 | ngOnInit() { }
17 |
18 | public setCoord(event: Coord) {
19 | this.coord = event;
20 | }
21 |
22 | public confirmed() {
23 | this.modalCtrl.dismiss(this.coord);
24 | }
25 |
26 | public dismissModal() {
27 | this.modalCtrl.dismiss();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-gallery/properties-gallery.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { PropertiesGalleryComponent } from './properties-gallery.component';
5 |
6 | describe('PropertiesGalleryComponent', () => {
7 | let component: PropertiesGalleryComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ PropertiesGalleryComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(PropertiesGalleryComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { IonicModule } from '@ionic/angular';
6 |
7 | import { SettingsPageRoutingModule } from './settings-routing.module';
8 |
9 | import { SettingsPage } from './settings.page';
10 | import { SettingsThemeComponent } from './settings-theme/settings-theme.component';
11 | import { SettingsCoordDefaultComponent } from './settings-coord-default/settings-coord-default.component';
12 | import { SharedModule } from '../shared/shared.module';
13 |
14 | @NgModule({
15 | imports: [
16 | CommonModule,
17 | FormsModule,
18 | IonicModule,
19 | SettingsPageRoutingModule,
20 | SharedModule
21 | ],
22 | declarations: [SettingsPage, SettingsThemeComponent, SettingsCoordDefaultComponent]
23 | })
24 |
25 | export class SettingsPageModule { }
26 |
--------------------------------------------------------------------------------
/frontend/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
17 |
18 |
19 |
22 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-list-item/enquiries-list-item.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { EnquiriesListItemComponent } from './enquiries-list-item.component';
5 |
6 | describe('EnquiriesListItemComponent', () => {
7 | let component: EnquiriesListItemComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ EnquiriesListItemComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(EnquiriesListItemComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-uploads-modal/properties-uploads.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { PropertiesUploadsComponent } from './properties-uploads.component';
5 |
6 | describe('PropertiesUploadsComponent', () => {
7 | let component: PropertiesUploadsComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ PropertiesUploadsComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(PropertiesUploadsComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Real-Estate-Management
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/frontend/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.getcapacitor.myapp;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | import android.content.Context;
6 | import androidx.test.ext.junit.runners.AndroidJUnit4;
7 | import androidx.test.platform.app.InstrumentationRegistry;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * @see Testing documentation
15 | */
16 | @RunWith(AndroidJUnit4.class)
17 | public class ExampleInstrumentedTest {
18 |
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
23 |
24 | assertEquals("com.getcapacitor.app", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-reply-modal/enquiries-reply-modal.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { EnquiriesReplyModalComponent } from './enquiries-reply-modal.component';
5 |
6 | describe('EnquiriesReplyModalComponent', () => {
7 | let component: EnquiriesReplyModalComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [EnquiriesReplyModalComponent],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(EnquiriesReplyModalComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/div-horizontal-slide/div-horizontal-slide.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { DivHorizontalSlideComponent } from './div-horizontal-slide.component';
5 |
6 | describe('DivHorizontalSlideComponent', () => {
7 | let component: DivHorizontalSlideComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ DivHorizontalSlideComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(DivHorizontalSlideComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-list/properties-list.component.scss:
--------------------------------------------------------------------------------
1 | .card-slider {
2 | flex: 0 0 auto;
3 | max-width: 250px;
4 | height: 100%;
5 | -webkit-touch-callout: none; /* iOS Safari */
6 | -webkit-user-select: none; /* Safari */
7 | -khtml-user-select: none; /* Konqueror HTML */
8 | -moz-user-select: none; /* Old versions of Firefox */
9 | -ms-user-select: none; /* Internet Explorer/Edge */
10 | user-select: none; /* Non-pr */
11 | .property-card {
12 | padding: 0.3rem 0.5rem;
13 | }
14 | }
15 |
16 | .card-view-all {
17 | ion-card {
18 | background: var(--ion-color-light-shade);
19 | ion-card-content {
20 | display: flex;
21 | align-items: center;
22 | justify-content: center;
23 | min-height: 506px;
24 | }
25 | }
26 | }
27 |
28 | .view-all {
29 | --background: var(--ion-color-dark);
30 | span {
31 | color: var(--ion-color-light);
32 | text-transform: capitalize;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/interface/enquiry.ts:
--------------------------------------------------------------------------------
1 | interface EnquiryProperty {
2 | property_id: string;
3 | name: string;
4 | }
5 |
6 | export interface Enquiry {
7 | enquiry_id: string;
8 | content: string;
9 | email: string;
10 | title: string;
11 | topic: string;
12 | read: boolean;
13 | property: EnquiryProperty;
14 | replyTo?: {
15 | enquiry_id: string;
16 | title: string;
17 | topic: string;
18 | };
19 | users: {
20 | from: {
21 | user_id: string;
22 | keep: boolean;
23 | };
24 | to: {
25 | user_id: string;
26 | keep: boolean;
27 | };
28 | };
29 | createdAt?: string;
30 | updatedAt?: string;
31 |
32 | }
33 |
34 | export interface EnquiryCreate {
35 | content: string;
36 | email: string;
37 | title: string;
38 | topic: string;
39 | property: EnquiryProperty;
40 | replyTo?: {
41 | id: string;
42 | title: string;
43 | topic: string;
44 | };
45 | userTo: string;
46 | }
47 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/contact-form/contact-form.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { ContactFormComponent } from './contact-form.component';
6 |
7 | describe('ContactFormComponent', () => {
8 | let component: ContactFormComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ ContactFormComponent ],
14 | imports: [IonicModule.forRoot(), ReactiveFormsModule]
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(ContactFormComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/user/change-password/change-password.component.scss:
--------------------------------------------------------------------------------
1 | ion-content {
2 | --background: var(--ion-color-light);
3 | }
4 |
5 | ion-card {
6 | box-shadow: none;
7 | }
8 |
9 | .change-pass-container {
10 | max-width: 1200px;
11 | margin: 0 auto;
12 | width: 100%;
13 | min-height: 100%;
14 | display: flex;
15 | justify-content: center;
16 | align-items: center;
17 | }
18 |
19 | .logo-container {
20 | margin: 30px auto 0;
21 | max-width: 200px;
22 | text-align: center;
23 | }
24 |
25 | .change-pass-card {
26 | width: 100%;
27 | max-width: 450px;
28 | .title {
29 | font-size: 20px;
30 | font-weight: bold;
31 | color: var(--ion-color-dark);
32 | }
33 | ion-item {
34 | margin-top: 8px;
35 | --background: var(--ion-color-light);
36 | }
37 | }
38 | @media (max-width: 600px) {
39 | .change-pass-card {
40 | box-shadow: none;
41 | min-height: 100%;
42 | width: 100%;
43 | max-width: 400px;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-coordinates-modal/properties-coordinates.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { PropertiesCoordinatesComponent } from './properties-coordinates.component';
5 |
6 | describe('PropertiesCoordinatesComponent', () => {
7 | let component: PropertiesCoordinatesComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ PropertiesCoordinatesComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(PropertiesCoordinatesComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/auth/register.js:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid";
2 | import { fastify } from "../../index.js";
3 | import { User } from "../../models/user.js";
4 |
5 | export const register = async function (req, res) {
6 | const { fullName, email, password } = req.body;
7 | if (fullName && email && password) {
8 | try {
9 | const hashedPassword = await fastify.bcrypt.hash(password);
10 | const newUser = new User({
11 | user_id: uuidv4(),
12 | fullName,
13 | email: email.toLowerCase(),
14 | password: hashedPassword,
15 | });
16 | const { user_id } = await newUser.save();
17 | const accessToken = fastify.jwt.sign({ id: newUser.user_id });
18 | res
19 | .status(201)
20 | .send({ user_id, email: email.toLowerCase(), fullName, accessToken });
21 | } catch (error) {
22 | res.send(error);
23 | }
24 | }
25 | res.status(400).send({ message: "Error: form is invalid" });
26 | };
27 |
--------------------------------------------------------------------------------
/backend-fastify/src/database-seeder/dummy-data/users.js:
--------------------------------------------------------------------------------
1 | export const users = [
2 | {
3 | user_id: "u01",
4 | fullName: "test tester",
5 | email: "test@email.com",
6 | // password - password
7 | password: "$2a$12$aTl.102LhEH2A81kotESTuPN47ItWYuDQ6uU6OlUvXBodZqn0Ra8a",
8 | },
9 | {
10 | user_id: "u02",
11 | fullName: "john doe",
12 | email: "john_doe@email.com",
13 | // password - password
14 | password: "$2a$12$aTl.102LhEH2A81kotESTuPN47ItWYuDQ6uU6OlUvXBodZqn0Ra8a",
15 | },
16 | {
17 | user_id: "u03",
18 | fullName: "jane doe",
19 | email: "jane_doe@email.com",
20 | // password - password
21 | password: "$2a$12$aTl.102LhEH2A81kotESTuPN47ItWYuDQ6uU6OlUvXBodZqn0Ra8a",
22 | },
23 | {
24 | user_id: "u04",
25 | fullName: "bread doe",
26 | email: "bread_doe@email.com",
27 | // password - password
28 | password: "$2a$12$aTl.102LhEH2A81kotESTuPN47ItWYuDQ6uU6OlUvXBodZqn0Ra8a",
29 | properties: [],
30 | },
31 | ];
32 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-list/enquiries-list.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { EnquiriesListComponent } from './enquiries-list.component';
6 |
7 | describe('EnquiriesListComponent', () => {
8 | let component: EnquiriesListComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ EnquiriesListComponent ],
14 | imports: [IonicModule.forRoot(), RouterTestingModule]
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(EnquiriesListComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-new-modal/properties-new.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { PropertiesNewComponent } from './properties-new.component';
6 |
7 | describe('PropertiesNewComponent', () => {
8 | let component: PropertiesNewComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ PropertiesNewComponent ],
14 | imports: [IonicModule.forRoot(), ReactiveFormsModule],
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(PropertiesNewComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings-theme/settings-theme.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 | import { Storage } from '@ionic/storage-angular';
4 |
5 | import { SettingsThemeComponent } from './settings-theme.component';
6 |
7 | describe('SettingsThemeComponent', () => {
8 | let component: SettingsThemeComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ SettingsThemeComponent ],
14 | imports: [IonicModule.forRoot()],
15 | providers: [ Storage ]
16 | }).compileComponents();
17 |
18 | fixture = TestBed.createComponent(SettingsThemeComponent);
19 | component = fixture.componentInstance;
20 | fixture.detectChanges();
21 | }));
22 |
23 | it('should create', () => {
24 | expect(component).toBeTruthy();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-card/properties-card.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { PropertiesCardComponent } from './properties-card.component';
6 |
7 | describe('PropertiesCardComponent', () => {
8 | let component: PropertiesCardComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [PropertiesCardComponent],
14 | imports: [IonicModule.forRoot(), RouterTestingModule]
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(PropertiesCardComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-edit-modal/properties-edit.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { PropertiesEditComponent } from './properties-edit.component';
6 |
7 | describe('PropertiesEditComponent', () => {
8 | let component: PropertiesEditComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [PropertiesEditComponent],
14 | imports: [IonicModule.forRoot(), ReactiveFormsModule]
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(PropertiesEditComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/div-horizontal-slide/div-horizontal-slide.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-div-horizontal-slide',
5 | templateUrl: './div-horizontal-slide.component.html',
6 | styleUrls: ['./div-horizontal-slide.component.scss'],
7 | })
8 | export class DivHorizontalSlideComponent {
9 |
10 | private mouseDown = false;
11 | private startX: any;
12 | private scrollLeft: any;
13 |
14 | startDragging(e, flag, el) {
15 | this.mouseDown = true;
16 | this.startX = e.pageX - el.offsetLeft;
17 | this.scrollLeft = el.scrollLeft;
18 | }
19 | stopDragging(e, flag) {
20 | this.mouseDown = false;
21 | }
22 | moveEvent(e, el) {
23 | e.preventDefault();
24 | if (!this.mouseDown) {
25 | return;
26 | }
27 | // console.log(e);
28 | const x = e.pageX - el.offsetLeft;
29 | const scroll = x - this.startX;
30 | el.scrollLeft = this.scrollLeft - scroll;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-search-field/map-search-field.component.scss:
--------------------------------------------------------------------------------
1 | .search-container {
2 | padding: 20px;
3 | background: rgba(255, 255, 255, 0.185);
4 | z-index: 500;
5 | position: absolute;
6 | top: 8px;
7 | left: 4px;
8 | right: 4px;
9 | width: calc(100% - 8px);
10 | .icon-container {
11 | position: absolute;
12 | height: 40px;
13 | display: flex;
14 | align-items: center;
15 | justify-content: center;
16 | ion-icon {
17 | min-width: 40px;
18 | color: var(--ion-color-dark);
19 | font-size: 20px;
20 | }
21 | }
22 |
23 | input {
24 | color: var(--ion-color-dark);
25 | background: var(--ion-color-light);
26 | width: 100%;
27 | height: 40px;
28 | padding-left: 36px;
29 | border-radius: 8px;
30 | &::placeholder {
31 | font-size: 18px;
32 | font-weight: 400;
33 | }
34 | }
35 | }
36 | @media (max-width: 992px) {
37 | .search-container {
38 | opacity: 0.7;
39 | padding: 6px;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-new-form/enquiries-new-form.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { EnquiriesNewFormComponent } from './enquiries-new-form.component';
6 |
7 | describe('EnquiriesNewFormComponent', () => {
8 | let component: EnquiriesNewFormComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ EnquiriesNewFormComponent ],
14 | imports: [IonicModule.forRoot(), ReactiveFormsModule]
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(EnquiriesNewFormComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-pie-chart/mortgage-pie-chart.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 | import { Storage } from '@ionic/storage-angular';
4 | import { MortgagePieChartComponent } from './mortgage-pie-chart.component';
5 |
6 | describe('MortgagePieChartComponent', () => {
7 | let component: MortgagePieChartComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ MortgagePieChartComponent ],
13 | imports: [IonicModule.forRoot()],
14 | providers: [Storage]
15 | }).compileComponents();
16 |
17 | fixture = TestBed.createComponent(MortgagePieChartComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | }));
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-uploads-modal/properties-current-images/properties-current-images.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 |
4 | import { PropertiesCurrentImagesComponent } from './properties-current-images.component';
5 |
6 | describe('PropertiesCurrentImagesComponent', () => {
7 | let component: PropertiesCurrentImagesComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ PropertiesCurrentImagesComponent ],
13 | imports: [IonicModule.forRoot()]
14 | }).compileComponents();
15 |
16 | fixture = TestBed.createComponent(PropertiesCurrentImagesComponent);
17 | component = fixture.componentInstance;
18 | fixture.detectChanges();
19 | }));
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-card/properties-card.component.scss:
--------------------------------------------------------------------------------
1 | ion-card {
2 | position: relative;
3 | width: 100%;
4 | margin: 0;
5 | }
6 |
7 | .tag {
8 | position: absolute;
9 | padding: 8px 16px;
10 | font-size: 20px;
11 | font-weight: 900;
12 | color: var(--ion-color-primary-contrast);
13 | border-bottom-right-radius: 12px;
14 | background: var(--ion-color-success);
15 | }
16 |
17 | .name {
18 | overflow: hidden;
19 | width: 100%;
20 | font-size: 24px;
21 | height: 28px;
22 | display: -webkit-box;
23 | -webkit-line-clamp: 3;
24 | -webkit-box-orient: vertical;
25 | text-overflow: ellipsis;
26 | }
27 | .desc {
28 | overflow: hidden;
29 | width: 100%;
30 | min-height: 80px;
31 | display: -webkit-box;
32 | -webkit-line-clamp: 3;
33 | -webkit-box-orient: vertical;
34 | }
35 |
36 | .price {
37 | margin: 16px 0 0 0;
38 | font-size: 18px;
39 | font-weight: bold;
40 | color: var(--ion-color-dark);
41 | }
42 | .profile-image {
43 | height: 200px;
44 | width: 100%;
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-line-chart/mortgage-line-chart.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 | import { Storage } from '@ionic/storage-angular';
4 |
5 | import { MortgageLineChartComponent } from './mortgage-line-chart.component';
6 |
7 | describe('MortgageLineChartComponent', () => {
8 | let component: MortgageLineChartComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ MortgageLineChartComponent ],
14 | imports: [IonicModule.forRoot()],
15 | providers: [Storage]
16 | }).compileComponents();
17 |
18 | fixture = TestBed.createComponent(MortgageLineChartComponent);
19 | component = fixture.componentInstance;
20 | fixture.detectChanges();
21 | }));
22 |
23 | it('should create', () => {
24 | expect(component).toBeTruthy();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-card/properties-card.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { PropertiesService } from 'src/app/properties/properties.service';
4 | import { Property } from 'src/app/shared/interface/property';
5 | import { UserService } from 'src/app/user/user.service';
6 |
7 |
8 | @Component({
9 | selector: 'app-properties-card',
10 | templateUrl: './properties-card.component.html',
11 | styleUrls: ['./properties-card.component.scss'],
12 | })
13 | export class PropertiesCardComponent implements OnInit {
14 |
15 | @Input() property: Property;
16 | constructor(
17 | private propertiesService: PropertiesService,
18 | private router: Router,
19 | public userService: UserService
20 | ) { }
21 |
22 | ngOnInit() { }
23 |
24 | public selectProperty(property: Property) {
25 | this.propertiesService.property = property;
26 | this.router.navigate(['/properties', property.property_id]);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/src/app/user/profile/profile.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpClientTestingModule } from '@angular/common/http/testing';
2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3 | import { IonicModule } from '@ionic/angular';
4 | import { Storage } from '@ionic/storage-angular';
5 |
6 | import { ProfileComponent } from './profile.component';
7 |
8 | describe('ProfileComponent', () => {
9 | let component: ProfileComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(waitForAsync(() => {
13 | TestBed.configureTestingModule({
14 | declarations: [ ProfileComponent ],
15 | imports: [IonicModule.forRoot(), HttpClientTestingModule],
16 | providers: [Storage]
17 | }).compileComponents();
18 |
19 | fixture = TestBed.createComponent(ProfileComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | }));
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings-theme/settings-theme.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Change Application Theme -
7 |
8 |
9 |
10 |
11 |
17 | Theme Switcher
18 |
19 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
32 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-leaflet/map-leaflet.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { IonicModule } from '@ionic/angular';
4 | import { Storage } from '@ionic/storage-angular';
5 |
6 | import { MapLeafletComponent } from './map-leaflet.component';
7 |
8 | describe('MapLeafletComponent', () => {
9 | let component: MapLeafletComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(waitForAsync(() => {
13 | TestBed.configureTestingModule({
14 | declarations: [ MapLeafletComponent ],
15 | imports: [IonicModule.forRoot(), RouterTestingModule],
16 | providers: [Storage]
17 | }).compileComponents();
18 |
19 | fixture = TestBed.createComponent(MapLeafletComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | }));
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings-theme/settings-theme.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, AfterViewInit } from '@angular/core';
2 | import { Platform } from '@ionic/angular';
3 | import { StorageService } from 'src/app/shared/services/storage/storage.service';
4 |
5 | @Component({
6 | selector: 'app-settings-theme',
7 | templateUrl: './settings-theme.component.html',
8 | styleUrls: ['./settings-theme.component.scss']
9 | })
10 | export class SettingsThemeComponent implements AfterViewInit {
11 |
12 | public darkTheme = false;
13 | constructor(private platform: Platform, private storage: StorageService) { }
14 |
15 | async ngAfterViewInit() {
16 | await this.storage.init();
17 | this.darkTheme = await this.storage.getDartTheme();
18 | }
19 |
20 | switchDarkMode(event) {
21 | if (event.detail.checked) {
22 | document.body.classList.add('dark');
23 | this.storage.setDarkTheme(true);
24 | } else {
25 | document.body.classList.remove('dark');
26 | this.storage.setDarkTheme(false);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/src/app/settings/settings-coord-default/settings-coord-default.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { IonicModule } from '@ionic/angular';
3 | import { Storage } from '@ionic/storage-angular';
4 |
5 | import { SettingsCoordDefaultComponent } from './settings-coord-default.component';
6 |
7 | describe('SettingsCoordDefaultComponent', () => {
8 | let component: SettingsCoordDefaultComponent;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(waitForAsync(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [ SettingsCoordDefaultComponent ],
14 | imports: [IonicModule.forRoot()],
15 | providers: [Storage]
16 | }).compileComponents();
17 |
18 | fixture = TestBed.createComponent(SettingsCoordDefaultComponent);
19 | component = fixture.componentInstance;
20 | fixture.detectChanges();
21 | }));
22 |
23 | it('should create', () => {
24 | expect(component).toBeTruthy();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/frontend/src/app/user/user.page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Account Page
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Profile
20 |
21 |
22 |
23 |
24 | Change Password
25 |
26 |
27 |
28 |
29 | Notifications
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-line-chart/mortgage-line-chart.component.scss:
--------------------------------------------------------------------------------
1 | ion-card {
2 | min-height: 100%;
3 | box-shadow: none;
4 | padding: 18px 16px 24px 16px;
5 | ion-card-content {
6 | position: relative;
7 | padding: 0;
8 |
9 | .line-chart {
10 | scroll-margin: 130px;
11 | &.blur {
12 | /* Add the blur effect */
13 | filter: blur(4px);
14 | -webkit-filter: blur(4px);
15 | }
16 | }
17 | .overlay-cover {
18 | background-color: rgba(0, 0, 0, 0.027);
19 | height: 100%;
20 | width: 100%;
21 | position: absolute;
22 | top: 0;
23 | left: 0;
24 |
25 | display: flex;
26 | justify-content: center;
27 | align-items: center;
28 | ion-button {
29 | --padding-start: 30px;
30 | --padding-end: 16px;
31 | --padding-top: 12px;
32 | --padding-bottom: 12px;
33 | }
34 | }
35 | }
36 | }
37 |
38 | @media (max-width: 600px) {
39 | ion-card {
40 | margin: 24px 0 0 0;
41 | padding: 8px 0 8px 0;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | // Protractor configuration file, see link for more information
3 | // https://github.com/angular/protractor/blob/master/lib/config.ts
4 |
5 | const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
6 |
7 | /**
8 | * @type { import("protractor").Config }
9 | */
10 | exports.config = {
11 | allScriptsTimeout: 11000,
12 | specs: [
13 | './src/**/*.e2e-spec.ts'
14 | ],
15 | capabilities: {
16 | browserName: 'chrome'
17 | },
18 | directConnect: true,
19 | SELENIUM_PROMISE_MANAGER: false,
20 | baseUrl: 'http://localhost:4200/',
21 | framework: 'jasmine',
22 | jasmineNodeOpts: {
23 | showColors: true,
24 | defaultTimeoutInterval: 30000,
25 | print: function() {}
26 | },
27 | onPrepare() {
28 | require('ts-node').register({
29 | project: require('path').join(__dirname, './tsconfig.json')
30 | });
31 | jasmine.getEnv().addReporter(new SpecReporter({
32 | spec: {
33 | displayStacktrace: StacktraceOption.PRETTY
34 | }
35 | }));
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/properties/delete-property.js:
--------------------------------------------------------------------------------
1 | import { Property } from "../../models/property.js";
2 | import { User } from "../../models/user.js";
3 | import { authBearerToken } from "../../utils/requests.js";
4 | import { userIdToken } from "../../utils/users.js";
5 | import { unlinkImages } from "./image-property.js";
6 |
7 | export const deleteProperty = async function (req, res) {
8 | const { id } = req.params;
9 | try {
10 | const token = authBearerToken(req);
11 | const user_id = userIdToken(token);
12 | const property = await Property.findOneAndDelete({ property_id: id, user_id });
13 | if (!property) {
14 | res.status(404).send({});
15 | }
16 | if (property.images?.length) {
17 | unlinkImages(property.images);
18 | }
19 | const user = await User.findOne({ user_id });
20 | user.properties = user.properties.filter(i => i !== property.property_id);
21 | user.save();
22 |
23 | res.status(200).send({ data: { ...property.toObject() } });
24 | return;
25 | } catch (error) {
26 | res.send(error);
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/properties/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | getPropertiesOpts,
3 | getPropertyOpts,
4 | createPropertyOpts,
5 | updatePropertyOpts,
6 | deletePropertyOpts,
7 | uploadImagesOpts,
8 | deleteImagesOpts,
9 | } from "./options/index.js";
10 | import {
11 | getProperties,
12 | getProperty,
13 | createProperty,
14 | updateProperty,
15 | deleteProperty,
16 | addImagesProperty,
17 | deleteImagesProperty,
18 | } from "../../controllers/properties/index.js";
19 |
20 | export const propertiesRoutes = function (fastify, opts, done) {
21 | fastify.get("/", getPropertiesOpts(getProperties));
22 | fastify.get("/:id", getPropertyOpts(getProperty));
23 | fastify.post("/", createPropertyOpts(fastify, createProperty));
24 | fastify.patch("/:id", updatePropertyOpts(fastify, updateProperty));
25 | fastify.delete("/:id", deletePropertyOpts(fastify, deleteProperty));
26 | fastify.post("/upload/images/:id", uploadImagesOpts(fastify, addImagesProperty));
27 | fastify.delete("/upload/images/:id", deleteImagesOpts(fastify, deleteImagesProperty));
28 | done();
29 | };
30 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-list/enquiries-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Message
8 |
9 |
10 |
11 | Topic
12 |
13 |
14 |
15 | Email Address
16 |
17 |
18 |
19 | Date
20 |
21 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpClientTestingModule } from '@angular/common/http/testing';
2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3 | import { RouterTestingModule } from '@angular/router/testing';
4 | import { IonicModule } from '@ionic/angular';
5 | import { Storage } from '@ionic/storage-angular';
6 |
7 | import { EnquiriesPage } from './enquiries.page';
8 |
9 | describe('EnquiriesPage', () => {
10 | let component: EnquiriesPage;
11 | let fixture: ComponentFixture;
12 |
13 | beforeEach(waitForAsync(() => {
14 | TestBed.configureTestingModule({
15 | declarations: [ EnquiriesPage ],
16 | imports: [IonicModule.forRoot(), HttpClientTestingModule, RouterTestingModule],
17 | providers: [Storage]
18 | }).compileComponents();
19 |
20 | fixture = TestBed.createComponent(EnquiriesPage);
21 | component = fixture.componentInstance;
22 | fixture.detectChanges();
23 | }));
24 |
25 | it('should create', () => {
26 | expect(component).toBeTruthy();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/backend-fastify/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "real-estate-management-backend",
3 | "version": "1.0.0",
4 | "description": "The server side for real-estate-management app",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "start": "node src/index.js",
10 | "dev": "nodemon src/index.js",
11 | "db:seeder": "node src/database-seeder/index.js"
12 | },
13 | "keywords": [
14 | "fastify",
15 | "real-estate-management"
16 | ],
17 | "author": "eevan7a9",
18 | "license": "ISC",
19 | "dependencies": {
20 | "@fastify/cors": "^7.0.0",
21 | "@fastify/jwt": "^5.0.1",
22 | "@fastify/multipart": "^6.0.0",
23 | "@fastify/static": "^5.0.2",
24 | "@fastify/swagger": "^6.1.0",
25 | "@types/mongoose": "^5.11.97",
26 | "dotenv": "^10.0.0",
27 | "fastify": "^3.29.4",
28 | "fastify-bcrypt": "^1.0.0",
29 | "mongoose": "^6.0.12",
30 | "uuid": "^8.3.2"
31 | },
32 | "devDependencies": {
33 | "@types/busboy": "^0.3.1",
34 | "nodemon": "^2.0.12"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpClientTestingModule } from '@angular/common/http/testing';
2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3 | import { RouterTestingModule } from '@angular/router/testing';
4 | import { IonicModule } from '@ionic/angular';
5 | import { Storage } from '@ionic/storage-angular';
6 |
7 | import { PropertiesPage } from './properties.page';
8 |
9 | describe('PropertiesPage', () => {
10 | let component: PropertiesPage;
11 | let fixture: ComponentFixture;
12 |
13 | beforeEach(waitForAsync(() => {
14 | TestBed.configureTestingModule({
15 | declarations: [ PropertiesPage ],
16 | imports: [IonicModule.forRoot(), HttpClientTestingModule, RouterTestingModule],
17 | providers: [Storage]
18 | }).compileComponents();
19 |
20 | fixture = TestBed.createComponent(PropertiesPage);
21 | component = fixture.componentInstance;
22 | fixture.detectChanges();
23 | }));
24 |
25 | it('should create', () => {
26 | expect(component).toBeTruthy();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-new-form/enquiries-new-form.component.scss:
--------------------------------------------------------------------------------
1 | ion-item {
2 | --inner-padding-end: 0;
3 | --inner-padding-start: 0;
4 | --padding-end: 8px;
5 | --padding-start: 8px;
6 | --padding-top: 0;
7 | --padding-bottom: 8px;
8 | margin: 0 0 16px 0;
9 | ion-label {
10 | --color: #8794a4;
11 | }
12 | ion-input {
13 | --padding-start: 12px;
14 | padding: 16px;
15 | border: 1px solid rgb(196 196 196);
16 | box-sizing: border-box;
17 | border-radius: 4px;
18 | margin-top: 8px;
19 | }
20 | .ckeditor {
21 | width: 100%;
22 | margin-top: 1rem;
23 | }
24 | }
25 |
26 | :host ::ng-deep .ck-editor__editable_inline {
27 | min-height: 200px;
28 | }
29 |
30 | .alert-errors {
31 | width: 100%;
32 | margin: 6px 0 0 0;
33 | }
34 |
35 | body.dark :host ::ng-deep {
36 | // ckeditor5-angular - we change color when dark-mode is active
37 | .ck-content.ck-editor__editable {
38 | &.ck-blurred {
39 | background: var(--ion-color-step-50);
40 | }
41 | &.ck-focused {
42 | background: var(--ion-color-light);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map-popup/map-popup.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { IonicModule } from '@ionic/angular';
4 | import { PropertiesPage } from 'src/app/properties/properties.page';
5 |
6 | import { MapPopupComponent } from './map-popup.component';
7 |
8 | describe('MapPopupComponent', () => {
9 | let component: MapPopupComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(waitForAsync(() => {
13 | TestBed.configureTestingModule({
14 | declarations: [ MapPopupComponent ],
15 | imports: [IonicModule.forRoot(), RouterTestingModule.withRoutes([{
16 | component: PropertiesPage,
17 | path: 'properties'
18 | }])]
19 | }).compileComponents();
20 |
21 | fixture = TestBed.createComponent(MapPopupComponent);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | }));
25 |
26 | it('should create', () => {
27 | expect(component).toBeTruthy();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/frontend/src/app/map/map.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { IonicModule } from '@ionic/angular';
6 |
7 | import { MapPageRoutingModule } from './map-routing.module';
8 |
9 | import { MapPage } from './map.page';
10 | import { MapPopupComponent } from './map-popup/map-popup.component';
11 | import { SharedModule } from '../shared/shared.module';
12 | import { MapMarkersLegendComponent } from './map-markers-legend/map-markers-legend.component';
13 | import { ModalSearchComponent } from '../shared/components/modal-search/modal-search.component';
14 | import { PropertiesPageModule } from '../properties/properties.module';
15 |
16 | @NgModule({
17 | imports: [
18 | CommonModule,
19 | FormsModule,
20 | IonicModule,
21 | MapPageRoutingModule,
22 | SharedModule,
23 | PropertiesPageModule
24 | ],
25 | declarations: [
26 | MapPage,
27 | MapPopupComponent,
28 | MapMarkersLegendComponent,
29 | ModalSearchComponent
30 | ],
31 | })
32 | export class MapPageModule { }
33 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/properties/create-property.js:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid";
2 | import { Property } from "../../models/property.js";
3 | import { User } from "../../models/user.js";
4 | import { authBearerToken } from "../../utils/requests.js";
5 | import { userIdToken } from "../../utils/users.js";
6 |
7 | export const createProperty = async function (req, res) {
8 | const { name, address, type, position } = req.body;
9 | if (!name || !address || !type || !position) {
10 | res.status(400).send({ message: "Error: Required fields are missing." });
11 | return;
12 | }
13 | const token = authBearerToken(req);
14 | const user_id = userIdToken(token);
15 |
16 | try {
17 | const newProperty = new Property({
18 | property_id: uuidv4(),
19 | user_id,
20 | ...req.body,
21 | });
22 |
23 | const user = await User.findOne({ user_id });
24 | user.properties.push(newProperty.property_id);
25 |
26 | await newProperty.save();
27 | await user.save();
28 |
29 | res.status(201).send({ data: newProperty });
30 | } catch (error) {
31 | res.send(error);
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/frontend/src/app/user/change-password/change-password.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { IonicModule } from '@ionic/angular';
4 | import { CustomValidatorsDirective } from 'src/app/shared/directives/custom-validators.directive';
5 |
6 | import { ChangePasswordComponent } from './change-password.component';
7 |
8 | describe('ChangePasswordComponent', () => {
9 | let component: ChangePasswordComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(waitForAsync(() => {
13 | TestBed.configureTestingModule({
14 | declarations: [ ChangePasswordComponent ],
15 | imports: [IonicModule.forRoot(), ReactiveFormsModule],
16 | providers: [CustomValidatorsDirective]
17 | }).compileComponents();
18 |
19 | fixture = TestBed.createComponent(ChangePasswordComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | }));
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/frontend/src/assets/images/map/marker-industrial.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/frontend/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { RouteReuseStrategy } from '@angular/router';
4 | import { MarkdownModule } from 'ngx-markdown';
5 |
6 | import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
7 | import { IonicStorageModule } from '@ionic/storage-angular';
8 |
9 | import { AppComponent } from './app.component';
10 | import { AppRoutingModule } from './app-routing.module';
11 | import { StorageService } from './shared/services/storage/storage.service';
12 | import { SharedModule } from './shared/shared.module';
13 | import { HttpClientModule } from '@angular/common/http';
14 |
15 | @NgModule({
16 | declarations: [AppComponent],
17 | imports: [
18 | BrowserModule,
19 | IonicModule.forRoot(),
20 | AppRoutingModule,
21 | IonicStorageModule.forRoot(),
22 | SharedModule,
23 | HttpClientModule,
24 | MarkdownModule.forRoot()
25 | ],
26 | providers: [
27 | { provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
28 | StorageService
29 | ],
30 | bootstrap: [AppComponent]
31 | })
32 | export class AppModule { }
33 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-core-calc/mortgage-core-calc.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { ReactiveFormsModule } from '@angular/forms';
3 | import { IonicModule } from '@ionic/angular';
4 | import { CustomValidatorsDirective } from 'src/app/shared/directives/custom-validators.directive';
5 |
6 | import { MortgageCoreCalcComponent } from './mortgage-core-calc.component';
7 |
8 | describe('MortgageCoreCalcComponent', () => {
9 | let component: MortgageCoreCalcComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(waitForAsync(() => {
13 | TestBed.configureTestingModule({
14 | declarations: [ MortgageCoreCalcComponent ],
15 | imports: [IonicModule.forRoot(), ReactiveFormsModule],
16 | providers: [CustomValidatorsDirective]
17 | }).compileComponents();
18 |
19 | fixture = TestBed.createComponent(MortgageCoreCalcComponent);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | }));
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/action-popup/action-popup.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpClientTestingModule } from '@angular/common/http/testing';
2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3 | import { RouterTestingModule } from '@angular/router/testing';
4 | import { IonicModule } from '@ionic/angular';
5 | import { Storage } from '@ionic/storage-angular';
6 |
7 | import { ActionPopupComponent } from './action-popup.component';
8 |
9 | describe('ActionPopupComponent', () => {
10 | let component: ActionPopupComponent;
11 | let fixture: ComponentFixture;
12 |
13 | beforeEach(waitForAsync(() => {
14 | TestBed.configureTestingModule({
15 | declarations: [ ActionPopupComponent ],
16 | imports: [IonicModule.forRoot(), HttpClientTestingModule, RouterTestingModule],
17 | providers: [Storage]
18 | }).compileComponents();
19 |
20 | fixture = TestBed.createComponent(ActionPopupComponent);
21 | component = fixture.componentInstance;
22 | fixture.detectChanges();
23 | }));
24 |
25 | it('should create', () => {
26 | expect(component).toBeTruthy();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/frontend/src/assets/shapes.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-detail/properties-detail.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { IonicModule } from '@ionic/angular';
4 | import { PropertiesPage } from '../properties.page';
5 |
6 | import { PropertiesDetailComponent } from './properties-detail.component';
7 |
8 | describe('PropertiesDetailComponent', () => {
9 | let component: PropertiesDetailComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(waitForAsync(() => {
13 | TestBed.configureTestingModule({
14 | declarations: [ PropertiesDetailComponent ],
15 | imports: [IonicModule.forRoot(), RouterTestingModule.withRoutes([{
16 | component: PropertiesPage,
17 | path: 'properties'
18 | }])]
19 | }).compileComponents();
20 |
21 | fixture = TestBed.createComponent(PropertiesDetailComponent);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | }));
25 |
26 | it('should create', () => {
27 | expect(component).toBeTruthy();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-new-modal/properties-new.component.scss:
--------------------------------------------------------------------------------
1 | .coord-heading {
2 | padding: 24px 0 16px 0;
3 | display: flex;
4 | align-items: center;
5 | ion-text {
6 | text-transform: capitalize;
7 | font-weight: 600;
8 | margin-right: 8px;
9 | }
10 | }
11 | .coord-input {
12 | display: flex;
13 | ion-item {
14 | width: 95%;
15 | }
16 | }
17 | ion-list-header {
18 | --color: #8794a4;
19 | }
20 | .notes {
21 | font-size: 14px;
22 | letter-spacing: 0.4;
23 | color: #8794a4;
24 | }
25 | ion-item {
26 | --inner-padding-end: 0;
27 | --inner-padding-start: 0;
28 | --padding-end: 8px;
29 | --padding-start: 8px;
30 | --padding-top: 8px;
31 | --padding-bottom: 8px;
32 | margin: 0 0 16px 0;
33 | ion-label {
34 | --color: #8794a4;
35 | }
36 | ion-input,
37 | ion-textarea {
38 | --padding-start: 12px;
39 | padding: 6px;
40 | border: 1px solid rgba(0, 56, 100, 0.2);
41 | box-sizing: border-box;
42 | border-radius: 4px;
43 | margin-top: 8px;
44 | &::placeholder {
45 | font-size: 15px;
46 | }
47 | }
48 | }
49 |
50 | .alert-errors {
51 | width: 100%;
52 | margin: 6px 0 0 0;
53 | }
54 |
--------------------------------------------------------------------------------
/frontend/src/app/user/notifications/notifications.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | My Notifications
9 |
10 |
11 |
12 |
13 |
18 | {{ item.title }}
19 |
20 | {{
21 | item.type
22 | }}
23 | {{ item.date | date }}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/frontend/src/app/user/user.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { IonicModule } from '@ionic/angular';
6 |
7 | import { UserPageRoutingModule } from './user-routing.module';
8 | import { SharedModule } from '../shared/shared.module';
9 | import { UserPage } from './user.page';
10 | import { SigninComponent } from './signin/signin.component';
11 | import { RegisterComponent } from './register/register.component';
12 | import { ChangePasswordComponent } from './change-password/change-password.component';
13 | import { ProfileComponent } from './profile/profile.component';
14 | import { NotificationsComponent } from './notifications/notifications.component';
15 |
16 | @NgModule({
17 | imports: [
18 | CommonModule,
19 | FormsModule,
20 | IonicModule,
21 | UserPageRoutingModule,
22 | SharedModule
23 | ],
24 | declarations: [
25 | UserPage,
26 | RegisterComponent,
27 | SigninComponent,
28 | ChangePasswordComponent,
29 | ProfileComponent,
30 | NotificationsComponent
31 | ]
32 | })
33 | export class UserPageModule { }
34 |
--------------------------------------------------------------------------------
/frontend/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | # AndroidX package structure to make it clearer which packages are bundled with the
20 | # Android operating system, and which are packaged with your app's APK
21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
22 | android.useAndroidX=true
23 | # Automatically convert third-party libraries to use AndroidX
24 | android.enableJetifier=true
25 |
--------------------------------------------------------------------------------
/frontend/src/app/user/signin/signin.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { HttpClientTestingModule } from '@angular/common/http/testing';
2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
3 | import { ReactiveFormsModule } from '@angular/forms';
4 | import { RouterTestingModule } from '@angular/router/testing';
5 | import { IonicModule } from '@ionic/angular';
6 | import { Storage } from '@ionic/storage-angular';
7 |
8 | import { SigninComponent } from './signin.component';
9 |
10 | describe('SigninComponent', () => {
11 | let component: SigninComponent;
12 | let fixture: ComponentFixture;
13 |
14 | beforeEach(waitForAsync(() => {
15 | TestBed.configureTestingModule({
16 | declarations: [ SigninComponent ],
17 | imports: [IonicModule.forRoot(), RouterTestingModule, ReactiveFormsModule, HttpClientTestingModule],
18 | providers: [Storage]
19 | }).compileComponents();
20 |
21 | fixture = TestBed.createComponent(SigninComponent);
22 | component = fixture.componentInstance;
23 | fixture.detectChanges();
24 | }));
25 |
26 | it('should create', () => {
27 | expect(component).toBeTruthy();
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/frontend/src/app/enquiries/enquiries-detail/enquiries-detail.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { RouterTestingModule } from '@angular/router/testing';
3 | import { IonicModule } from '@ionic/angular';
4 | import { EnquiriesPage } from '../enquiries.page';
5 |
6 | import { EnquiriesDetailComponent } from './enquiries-detail.component';
7 |
8 | describe('EnquiriesDetailComponent', () => {
9 | let component: EnquiriesDetailComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(waitForAsync(() => {
13 | TestBed.configureTestingModule({
14 | declarations: [ EnquiriesDetailComponent ],
15 | imports: [IonicModule.forRoot(), RouterTestingModule.withRoutes([
16 | {
17 | path: 'enquiries',
18 | component: EnquiriesPage
19 | }
20 | ])]
21 | }).compileComponents();
22 |
23 | fixture = TestBed.createComponent(EnquiriesDetailComponent);
24 | component = fixture.componentInstance;
25 | fixture.detectChanges();
26 | }));
27 |
28 | it('should create', () => {
29 | expect(component).toBeTruthy();
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/frontend/src/app/user/register/register.component.scss:
--------------------------------------------------------------------------------
1 | ion-content {
2 | --background: var(--ion-color-light);
3 | }
4 | .register-container {
5 | position: relative;
6 | max-width: 1200px;
7 | margin: 0 auto;
8 | width: 100%;
9 | min-height: calc(100% - 56px);
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .register-card {
16 | width: 100%;
17 | max-width: 400px;
18 | ion-item {
19 | margin-top: 8px;
20 | --background: var(--ion-color-light);
21 | }
22 | }
23 |
24 | .logo-container {
25 | margin: 30px auto 0;
26 | max-width: 200px;
27 | text-align: center;
28 | }
29 |
30 | .alert-errors {
31 | width: 100%;
32 | margin: 6px 0 0 0;
33 | }
34 |
35 | .ask-signin {
36 | width: 100%;
37 | margin-top: 36px;
38 | display: flex;
39 | justify-content: center;
40 | text-transform: uppercase;
41 | a {
42 | margin-left: 6px;
43 | }
44 | }
45 | @media (max-width: 600px) {
46 | ion-content {
47 | --background: var(--ion-color-dark-contrast);
48 | }
49 | .register-card {
50 | margin: 0;
51 | box-shadow: none;
52 | min-height: 100%;
53 | width: 100%;
54 | max-width: 400px;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/frontend/src/app/mortgage-calc/mortgage-calc.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { FormsModule } from '@angular/forms';
4 |
5 | import { IonicModule } from '@ionic/angular';
6 |
7 | import { MortgageCalcPageRoutingModule } from './mortgage-calc-routing.module';
8 |
9 | import { MortgageCalcPage } from './mortgage-calc.page';
10 | import { SharedModule } from '../shared/shared.module';
11 | import { MortgageCoreCalcComponent } from './mortgage-core-calc/mortgage-core-calc.component';
12 | import { MortgagePieChartComponent } from './mortgage-pie-chart/mortgage-pie-chart.component';
13 | import { MortgageLineChartComponent } from './mortgage-line-chart/mortgage-line-chart.component';
14 |
15 | @NgModule({
16 | imports: [
17 | CommonModule,
18 | FormsModule,
19 | IonicModule,
20 | MortgageCalcPageRoutingModule,
21 | SharedModule
22 | ],
23 | declarations: [
24 | MortgageCalcPage,
25 | MortgageCoreCalcComponent,
26 | MortgagePieChartComponent,
27 | MortgageLineChartComponent
28 | ],
29 | exports: [
30 | MortgageCoreCalcComponent
31 | ]
32 | })
33 | export class MortgageCalcPageModule { }
34 |
--------------------------------------------------------------------------------
/backend-fastify/src/models/enquiry.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const propertySchema = new mongoose.Schema({
4 | property_id: { type: String },
5 | name: { type: String },
6 | });
7 |
8 | const usersSchema = new mongoose.Schema({
9 | from: {
10 | user_id: { type: String },
11 | keep: { type: Boolean, default: true, required: true }
12 | },
13 | to: {
14 | user_id: { type: String },
15 | keep: { type: Boolean, default: true, required: true }
16 | }
17 | });
18 |
19 | const replyToSchema = new mongoose.Schema({
20 | enquiry_id: { type: String },
21 | title: { type: String },
22 | topic: { type: String },
23 | });
24 |
25 | const enquirySchema = new mongoose.Schema(
26 | {
27 | enquiry_id: { type: String, required: true },
28 | content: { type: String, minlength: 10 },
29 | email: { type: String },
30 | title: { type: String, required: true },
31 | topic: { type: String, required: true },
32 | read: { type: Boolean, default: false },
33 | property: propertySchema,
34 | replyTo: replyToSchema,
35 | users: usersSchema
36 | },
37 | {
38 | timestamps: true,
39 | }
40 | );
41 | export const Enquiry = mongoose.model("Enquiry", enquirySchema);
42 |
--------------------------------------------------------------------------------
/backend-fastify/src/controllers/auth/signin.js:
--------------------------------------------------------------------------------
1 | import { fastify } from "../../index.js";
2 | import { User } from "../../models/user.js";
3 |
4 | export const signIn = async function (req, res) {
5 | const { email, password } = req.body;
6 | try {
7 | const foundUser = await User.findOne({ email: email.toLowerCase() });
8 | if (!foundUser) {
9 | res.status(404).send({
10 | statusCode: 404,
11 | error: "Internal Server Error",
12 | message: "Error: We can't find a user with that e-mail address.",
13 | });
14 | }
15 | const validPassword = await fastify.bcrypt.compare(
16 | password,
17 | foundUser.password
18 | );
19 | if (!validPassword) {
20 | res
21 | .status(400)
22 | .send({ message: "Error: Invalid password.", validPassword });
23 | }
24 | const { user_id } = foundUser;
25 | const accessToken = fastify.jwt.sign({ id: user_id });
26 |
27 | res.status(200).send({
28 | id: foundUser.id,
29 | user_id: foundUser.user_id,
30 | fullName: foundUser.fullName,
31 | email: foundUser.email,
32 | accessToken,
33 | });
34 | } catch (error) {
35 | res.status(404).send({ message: "Error: Something went wrong." });
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-card/properties-card.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Owned
4 |
5 |
6 |
11 |
16 |
17 |
18 |
19 |
20 | {{ property?.name }}
21 |
22 |
23 |
24 | {{ property?.createdAt | date }}
25 |
26 |
27 |
28 |
29 |
30 | {{ property?.description }}
31 |
32 |
33 |
34 | {{ property?.price | currency: "PHP" }}
35 |
36 |
42 | View property
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/backend-fastify/src/routes/enquiries/options/schema.js:
--------------------------------------------------------------------------------
1 | const property = {
2 | type: "object",
3 | properties: {
4 | name: { type: "string" },
5 | property_id: { type: "string" }
6 | },
7 | };
8 | Object.freeze(property);
9 |
10 | const users = {
11 | type: "object",
12 | properties: {
13 | from: {
14 | user_id: { type: "string" },
15 | keep: { type: "boolean" }
16 | },
17 | to: {
18 | user_id: { type: "boolean" },
19 | keep: { type: "boolean" }
20 | }
21 | },
22 | };
23 | Object.freeze(users);
24 |
25 | const replyTo = {
26 | type: "object",
27 | properties: {
28 | enquiry_id: { type: "string" },
29 | title: { type: "string" },
30 | topic: { type: "string" },
31 | },
32 | };
33 | Object.freeze(replyTo);
34 |
35 | export const enquiryProperties = {
36 | type: "object",
37 | properties: {
38 | enquiry_id: { type: "string" },
39 | content: { type: "string" },
40 | email: { type: "string" },
41 | title: { type: "string" },
42 | topic: { type: "string" },
43 | read: { type: "boolean" },
44 | property,
45 | replyTo,
46 | users,
47 | createdAt: { type: "string" },
48 | updatedAt: { type: "string" },
49 | },
50 | };
51 | Object.freeze(enquiryProperties);
52 |
--------------------------------------------------------------------------------
/frontend/src/app/user/profile/profile.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { User } from 'src/app/shared/interface/user';
3 | import { UserService } from '../user.service';
4 |
5 | @Component({
6 | selector: 'app-profile',
7 | templateUrl: './profile.component.html',
8 | styleUrls: ['./profile.component.scss'],
9 | })
10 | export class ProfileComponent implements OnInit {
11 |
12 | public imgUrl: any = './assets/images/avatar.png';
13 | public user: User;
14 | constructor(private userService: UserService) {
15 | this.userService.user$.subscribe(data => this.user = data);
16 | }
17 |
18 | ngOnInit() {
19 |
20 | }
21 |
22 | public toggleUpload() {
23 | const input = document.getElementById('image-upload');
24 | input.click();
25 | }
26 |
27 | public onSelectFile(event) { // called each time file input changes
28 | if (event.target.files && event.target.files[0]) {
29 | const reader = new FileReader();
30 |
31 | reader.readAsDataURL(event.target.files[0]); // read file as data url
32 |
33 | reader.onload = (ev) => { // called once readAsDataURL is completed
34 | this.imgUrl = ev.target.result;
35 | console.log(this.imgUrl);
36 | };
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/backend-fastify/README.md:
--------------------------------------------------------------------------------
1 | # **Backend-Fastify (Part)**
2 | ## **1.1 navigate to `backend-fastify/` directory.**
3 | ```
4 | cd backend-fastify/
5 | ```
6 | ## **1.2 create `.env` file & add variables:**
7 | - copy `.env.example` & re-name it to `.env`
8 | - set your desired variable value
9 | ```
10 | PORT=8000
11 | LOGGER=true
12 | SALT=12
13 | SECRET_KEY='secret'
14 | DB_CONNECT=mongodb://localhost:27017/rem-db
15 | ```
16 | ## **2. then install dependencies & run dev**
17 |
18 | In terminal - command
19 | ```
20 | # navigate to backend-fastify
21 | $ cd backend-fastify
22 |
23 | # install dependencies
24 | $ npm install
25 |
26 | # start server
27 | $ npm start `or` $ npm run dev
28 |
29 | ```
30 |
31 | ## **2.1 Database seeder(optional)**
32 | - Make sure `.env` is configured & dependencies are installed
33 | - Will populate database with dummy data.
34 |
35 | ⚠️ This will delete existing records in the database document.
36 |
37 | ⚠️ Make a backup if needed
38 | ```
39 | $ npm run db:seeder
40 | ```
41 |
42 | dummy user:
43 | ```
44 | fullName: "test tester",
45 | email: "test@email.com",
46 | password: "password"
47 |
48 | You can use this to signin.
49 | ```
50 | ## Routes
51 | ```
52 | /docs/
53 | /users/
54 | /auth/
55 | /properties/
56 | /enquiries/
57 | ```
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-gallery/properties-gallery.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 | Edit
10 |
11 |
12 |
13 |
14 |
15 |
16 |
![image]()
17 |
18 |
19 |
20 |
21 |

26 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/frontend/src/app/user/signin/signin.component.scss:
--------------------------------------------------------------------------------
1 | ion-content {
2 | --background: var(--ion-color-light);
3 | }
4 | .signin-container {
5 | max-width: 1200px;
6 | margin: 0 auto;
7 | width: 100%;
8 | min-height: calc(100% - 56px);
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | }
13 |
14 | .signin-card {
15 | width: 100%;
16 | max-width: 400px;
17 | ion-item {
18 | margin-top: 8px;
19 | --background: var(--ion-color-light);
20 | }
21 | }
22 |
23 | .logo-container {
24 | margin: 30px auto 0;
25 | max-width: 200px;
26 | text-align: center;
27 | }
28 |
29 | .alert-errors {
30 | width: 100%;
31 | margin: 6px 0 0 0;
32 | }
33 |
34 | .ask-register {
35 | width: 100%;
36 | margin-top: 36px;
37 | display: flex;
38 | justify-content: center;
39 | text-transform: uppercase;
40 | a {
41 | margin-left: 6px;
42 | }
43 | }
44 |
45 | .social-container {
46 | width: 100%;
47 | display: flex;
48 | justify-content: center;
49 | }
50 | @media (max-width: 600px) {
51 | ion-content {
52 | --background: var(--ion-color-dark-contrast);
53 | }
54 | .signin-card {
55 | margin: 0;
56 | box-shadow: none;
57 | min-height: 100%;
58 | width: 100%;
59 | max-width: 400px;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/modal-search/modal-search.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ title }}
4 |
5 |
6 |
11 |
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 |
28 |
33 |
34 |
35 |
41 |
42 | {{ displayProperty ? item[displayProperty] : item }}
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-uploads-modal/properties-current-images/properties-current-images.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |

15 |
16 |
22 |
23 |
27 |
28 |
29 |
30 |
31 |
39 | Delete Selected
40 |
41 |
42 |
--------------------------------------------------------------------------------
/frontend/src/app/properties/properties-gallery/properties-gallery.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
2 | import { SwiperOptions } from 'swiper';
3 |
4 | @Component({
5 | selector: 'app-properties-gallery',
6 | templateUrl: './properties-gallery.component.html',
7 | styleUrls: ['./properties-gallery.component.scss'],
8 | encapsulation: ViewEncapsulation.None,
9 | })
10 | export class PropertiesGalleryComponent implements OnInit {
11 | @Input() images: string[] = [];
12 | @Input() showEdit = false;
13 | @Output() edit = new EventEmitter();
14 | public imagePresented = 'assets/images/no-image.jpeg';
15 | public slideOpts: SwiperOptions = {
16 | initialSlide: 0,
17 | speed: 400,
18 | spaceBetween: 15,
19 | freeMode: true,
20 | slidesPerView: 'auto',
21 | };
22 |
23 | constructor() { }
24 |
25 | ngOnInit() {
26 | if (this.images?.length) {
27 | this.imagePresented = this.images[0];
28 | }
29 | }
30 |
31 | public getImage(image: string) {
32 | image = image || 'assets/images/no-image.jpeg';
33 | return `url(${image})`;
34 | }
35 |
36 | public setSelected(image: string) {
37 | this.imagePresented = image || 'assets/images/no-image.jpeg';
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/backend-fastify/src/utils/schema/response.js:
--------------------------------------------------------------------------------
1 | const success = {
2 | status: 200,
3 | message: "Success!",
4 | data: {}
5 | }
6 | export const responseSuccess = (
7 | def = success
8 | ) => ({
9 | type: "object",
10 | properties: {
11 | status: {
12 | type: "number",
13 | default: def.status || success.status,
14 | },
15 | statusCode: {
16 | type: "number",
17 | default: def.status || success.status,
18 | },
19 | message: {
20 | type: "string",
21 | default: def.message || success.message,
22 | },
23 | data: def.data || success.data,
24 | },
25 | });
26 |
27 | const error = {
28 | status: 400,
29 | error: "error",
30 | message: "Something went wrong, please try again later.",
31 | }
32 | export const responseError = (
33 | def = error
34 | ) => ({
35 | type: "object",
36 | properties: {
37 | status: {
38 | type: "number",
39 | default: def.status || error.status,
40 | },
41 | statusCode: {
42 | type: "number",
43 | default: def.status || error.status,
44 | },
45 | error: {
46 | type: "string",
47 | default: def.error || error.error,
48 | },
49 | message: {
50 | type: "string",
51 | default: def.message || error.message,
52 | },
53 |
54 | },
55 | });
56 |
--------------------------------------------------------------------------------
/frontend/src/app/shared/components/enquiry-badge/enquiry-badge.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnInit } from '@angular/core';
2 | import { EnquiryTopic } from '../../enums/enquiry';
3 |
4 | @Component({
5 | selector: 'app-enquiry-badge',
6 | templateUrl: './enquiry-badge.component.html',
7 | styleUrls: ['./enquiry-badge.component.scss'],
8 | })
9 | export class EnquiryBadgeComponent implements OnInit {
10 |
11 | @Input() topic = 'residential';
12 | constructor() { }
13 |
14 | ngOnInit() { }
15 |
16 | topicColor() {
17 | switch (this.topic) {
18 | case EnquiryTopic.info:
19 | return 'secondary';
20 | case EnquiryTopic.sales:
21 | return 'warning';
22 | case EnquiryTopic.schedule:
23 | return 'danger';
24 | case EnquiryTopic.payment:
25 | return 'success';
26 | default:
27 | break;
28 | }
29 | }
30 |
31 | topicLabel() {
32 | switch (this.topic) {
33 | case EnquiryTopic.info:
34 | return 'Enquire Information';
35 | case EnquiryTopic.sales:
36 | return 'About Sales';
37 | case EnquiryTopic.schedule:
38 | return 'About Schedule';
39 | case EnquiryTopic.payment:
40 | return 'About Payment';
41 | default:
42 | break;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------