├── 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 |
2 |
3 |
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 |
2 |
3 | 4 |
5 | 11 |
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 | marker 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 | 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 |
12 | 13 | 14 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 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 |
18 | {{ folder }} 19 |

Explore UI Components

20 |
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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 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 | image 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 |
Current Images:
3 | 4 | 5 | 6 |
11 | image 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 | --------------------------------------------------------------------------------