@@ -1364,6 +1364,16 @@ | |||
"tslib": "^1.10.0" | |||
} | |||
}, | |||
"@ionic/storage": { | |||
"version": "2.2.0", | |||
"resolved": "https://registry.npmjs.org/@ionic/storage/-/storage-2.2.0.tgz", | |||
"integrity": "sha512-2pszrzmI+fAar2Rx0WmJDVpc15D1k5tvLkB49NLYWJ2pOMaO/3/vp7mg/mEbg3rdsPE9FRbYI6vdKjQ2pP1EWA==", | |||
"requires": { | |||
"localforage": "1.7.1", | |||
"localforage-cordovasqlitedriver": "1.7.0", | |||
"tslib": "^1.7.1" | |||
} | |||
}, | |||
"@ngtools/webpack": { | |||
"version": "8.1.3", | |||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-8.1.3.tgz", | |||
@@ -5056,8 +5066,7 @@ | |||
"immediate": { | |||
"version": "3.0.6", | |||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", | |||
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", | |||
"dev": true | |||
"integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" | |||
}, | |||
"import-cwd": { | |||
"version": "2.1.0", | |||
@@ -6691,6 +6700,32 @@ | |||
"json5": "^1.0.1" | |||
} | |||
}, | |||
"localforage": { | |||
"version": "1.7.1", | |||
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.1.tgz", | |||
"integrity": "sha1-5JJ+BCMCuGTbMPMhHxO1xvDell0=", | |||
"requires": { | |||
"lie": "3.1.1" | |||
}, | |||
"dependencies": { | |||
"lie": { | |||
"version": "3.1.1", | |||
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", | |||
"integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", | |||
"requires": { | |||
"immediate": "~3.0.5" | |||
} | |||
} | |||
} | |||
}, | |||
"localforage-cordovasqlitedriver": { | |||
"version": "1.7.0", | |||
"resolved": "https://registry.npmjs.org/localforage-cordovasqlitedriver/-/localforage-cordovasqlitedriver-1.7.0.tgz", | |||
"integrity": "sha1-i5OVd1nuaI06WNW6fAR39sy1ODg=", | |||
"requires": { | |||
"localforage": ">=1.5.0" | |||
} | |||
}, | |||
"locate-path": { | |||
"version": "3.0.0", | |||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", | |||
@@ -26,6 +26,7 @@ | |||
"@ionic-native/splash-screen": "^5.0.0", | |||
"@ionic-native/status-bar": "^5.0.0", | |||
"@ionic/angular": "^4.7.1", | |||
"@ionic/storage": "^2.2.0", | |||
"cordova-android": "8.0.0", | |||
"core-js": "^2.5.4", | |||
"rxjs": "~6.5.1", | |||
@@ -6,6 +6,7 @@ const routes: Routes = [ | |||
{ path: 'onboarding', loadChildren: './onboarding/onboarding.module#OnboardingPageModule' }, | |||
{ path: 'login', loadChildren: './login/login.module#LoginPageModule' }, | |||
{ path: 'malls', loadChildren: './malls/malls.module#MallsPageModule' }, | |||
{ path: 'mall-details', loadChildren: './mall-details/mall-details.module#MallDetailsPageModule' }, | |||
]; | |||
@NgModule({ | |||
@@ -1,3 +1,32 @@ | |||
<ion-app> | |||
<ion-router-outlet></ion-router-outlet> | |||
<div class="overlay" [ngClass]="{ 'active' : show_menu }"></div> | |||
<ion-router-outlet></ion-router-outlet> | |||
<!-- Humburger Menu Starts from here --> | |||
<div class="menu-icon-holder" (click)="show_menu = true" [ngClass]="{'inactive' : show_menu }"> | |||
<ion-icon name="menu"></ion-icon> | |||
</div> | |||
<div class="close-icon-holder" [ngClass]="{'active' : show_menu }" (click)="show_menu = false"> | |||
<ion-icon name="close"></ion-icon> | |||
</div> | |||
<div class="menu-items-holder" [ngClass]="{'active' : show_menu }"> | |||
<button> | |||
<ion-icon src="assets/custom/001-house.svg"></ion-icon> | |||
</button> | |||
<button> | |||
<ion-icon src="assets/custom/002-bookmark.svg"></ion-icon> | |||
</button> | |||
<button> | |||
<ion-icon src="assets/custom/003-shop.svg"></ion-icon> | |||
</button> | |||
<button> | |||
<ion-icon src="assets/custom/004-map.svg"></ion-icon> | |||
</button> | |||
<button> | |||
<ion-icon src="assets/custom/005-boss.svg"></ion-icon> | |||
</button> | |||
</div> | |||
</ion-app> |
@@ -5,23 +5,25 @@ import { SplashScreen } from '@ionic-native/splash-screen/ngx'; | |||
import { StatusBar } from '@ionic-native/status-bar/ngx'; | |||
@Component({ | |||
selector: 'app-root', | |||
templateUrl: 'app.component.html', | |||
styleUrls: ['app.component.scss'] | |||
selector: 'app-root', | |||
templateUrl: 'app.component.html', | |||
styleUrls: ['app.component.scss'] | |||
}) | |||
export class AppComponent { | |||
constructor( | |||
private platform: Platform, | |||
private splashScreen: SplashScreen, | |||
private statusBar: StatusBar | |||
) { | |||
this.initializeApp(); | |||
} | |||
show_menu = false; | |||
constructor( | |||
private platform: Platform, | |||
private splashScreen: SplashScreen, | |||
private statusBar: StatusBar | |||
) { | |||
this.initializeApp(); | |||
} | |||
initializeApp() { | |||
this.platform.ready().then(() => { | |||
this.statusBar.styleDefault(); | |||
this.splashScreen.hide(); | |||
}); | |||
} | |||
initializeApp() { | |||
this.platform.ready().then(() => { | |||
this.statusBar.styleDefault(); | |||
this.splashScreen.hide(); | |||
}); | |||
} | |||
} |
@@ -6,6 +6,9 @@ import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; | |||
import { SplashScreen } from '@ionic-native/splash-screen/ngx'; | |||
import { StatusBar } from '@ionic-native/status-bar/ngx'; | |||
// Modules import for Modals | |||
// Services import | |||
import { MallService } from './services/mall.service'; | |||
import { AppComponent } from './app.component'; | |||
@@ -16,7 +19,11 @@ import { environment } from '../environments/environment'; | |||
@NgModule({ | |||
declarations: [AppComponent], | |||
entryComponents: [], | |||
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule, ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })], | |||
imports: [ | |||
BrowserModule, | |||
IonicModule.forRoot(), | |||
AppRoutingModule, | |||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })], | |||
providers: [ | |||
StatusBar, | |||
SplashScreen, | |||
@@ -0,0 +1,26 @@ | |||
import { NgModule } from '@angular/core'; | |||
import { CommonModule } from '@angular/common'; | |||
import { FormsModule } from '@angular/forms'; | |||
import { Routes, RouterModule } from '@angular/router'; | |||
import { IonicModule } from '@ionic/angular'; | |||
import { MallDetailsPage } from './mall-details.page'; | |||
const routes: Routes = [ | |||
{ | |||
path: '', | |||
component: MallDetailsPage | |||
} | |||
]; | |||
@NgModule({ | |||
imports: [ | |||
CommonModule, | |||
FormsModule, | |||
IonicModule, | |||
RouterModule.forChild(routes) | |||
], | |||
declarations: [MallDetailsPage] | |||
}) | |||
export class MallDetailsPageModule {} |
@@ -0,0 +1,35 @@ | |||
<ion-content *ngIf="mall_details"> | |||
<div class="upfold-holder"> | |||
<img src="{{ mall_details.image_url }}"> | |||
<div class="icons-holder"> | |||
<div> | |||
<button> <ion-icon name="arrow-back"></ion-icon> </button> | |||
</div> | |||
<div> | |||
<button [ngClass]="{'active' : mall_details.is_bookmarked }"> <ion-icon name="bookmark"></ion-icon> </button> | |||
<button> <ion-icon name="share"></ion-icon> </button> | |||
</div> | |||
</div> | |||
<div class="icons-holder navigate-button"> | |||
<button> <ion-icon name="navigate"></ion-icon> </button> | |||
</div> | |||
</div> | |||
<div class="card-holder"> | |||
<div class="card"> | |||
<h3> {{ mall_details.name }} </h3> | |||
<div class="stats-holder"> | |||
<div class="stat"> <ion-icon name="star"></ion-icon> {{ mall_details.rating }} <span>(2000)</span> </div> | |||
<div class="stat"> <ion-icon name="pin"></ion-icon> {{ mall_details.distance }} km</div> | |||
</div> | |||
<p> {{ mall_details.description }} </p> | |||
</div> | |||
</div> | |||
<div class="tabs-holder" [ngClass]="{ 'right' : selected_tab === 'shopping' }"> | |||
<button class="tab" [ngClass]="{ 'active' : selected_tab === 'food' }" | |||
(click)="selected_tab = 'food'"> FOOD OFFERS </button> | |||
<button class="tab" [ngClass]="{ 'active' : selected_tab === 'shopping' }" | |||
(click)="selected_tab = 'shopping'"> SHOPPING OFFERS </button> | |||
</div> | |||
</ion-content> |
@@ -0,0 +1,137 @@ | |||
.upfold-holder { | |||
position: relative; | |||
height: 45vh; | |||
width: 100%; | |||
padding-top: 30px; | |||
margin-bottom: -25px; | |||
.icons-holder { | |||
position: relative; | |||
display: flex; | |||
width: 90%; | |||
margin: 0 auto; | |||
justify-content: space-between; | |||
&.navigate-button { | |||
position: absolute; | |||
left: 86%; | |||
bottom: 30px; | |||
width: auto; | |||
} | |||
button { | |||
background-color: white; | |||
color: var(--brand-blue); | |||
font-size: 18px; | |||
padding: 5px; | |||
border-radius: 50%; | |||
height: 30px; | |||
width: 30px; | |||
display: block; | |||
margin-bottom: 10px; | |||
box-shadow: 0px 0px 5px var(--brand-grey); | |||
&.active { | |||
background-color: var(--brand-blue); | |||
color: white; | |||
} | |||
} | |||
} | |||
img { | |||
position: absolute; | |||
left: 0; | |||
top: 0; | |||
height: 100%; | |||
width: 100%; | |||
object-fit: cover; | |||
filter: brightness(80%); | |||
} | |||
} | |||
.card-holder { | |||
background-color: transparent; | |||
position: relative; | |||
.card { | |||
box-shadow: 0px 0px 5px var(--brand-grey); | |||
width: 90%; | |||
margin: 0 auto; | |||
border-radius: 10px; | |||
overflow: hidden; | |||
padding: 20px; | |||
background-color: white; | |||
h3 { | |||
color: var(--brand-dark-grey); | |||
font-size: 20px; | |||
letter-spacing: 0.5px; | |||
font-weight: 600; | |||
} | |||
.stats-holder { | |||
display: flex; | |||
justify-content: space-between; | |||
color: var(--brand-blue); | |||
font-size: 10px; | |||
font-weight: bold; | |||
margin: 15px 0; | |||
span { | |||
color: var(--brand-grey); | |||
} | |||
} | |||
p { | |||
margin: 0 auto; | |||
font-size: 11px; | |||
line-height: 1.5; | |||
color: var(--brand-grey); | |||
} | |||
} | |||
} | |||
.tabs-holder { | |||
display: flex; | |||
width: 90%; | |||
margin: 20px auto; | |||
overflow: hidden; | |||
border-radius: 30px; | |||
box-shadow: 0px 0px 5px var(--brand-grey); | |||
position: relative; | |||
&::before { | |||
content: ''; | |||
position: absolute; | |||
left: 0; | |||
top: 0; | |||
height: 100%; | |||
width: 50%; | |||
background-color: var(--brand-blue); | |||
border-radius: 30px; | |||
transform: translateX(0%); | |||
transition: transform 0.3s; | |||
} | |||
&.right { | |||
&::before { | |||
transform: translateX(100%); | |||
} | |||
} | |||
button { | |||
position: relative; | |||
background-color: transparent; | |||
color: var(--brand-blue); | |||
font-size: 12px; | |||
letter-spacing: 0.5px; | |||
height: 40px; | |||
width: 50%; | |||
font-weight: 500; | |||
transition: color 0.3s; | |||
&.active { | |||
color: white; | |||
} | |||
} | |||
} |
@@ -0,0 +1,27 @@ | |||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; | |||
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; | |||
import { MallDetailsPage } from './mall-details.page'; | |||
describe('MallDetailsPage', () => { | |||
let component: MallDetailsPage; | |||
let fixture: ComponentFixture<MallDetailsPage>; | |||
beforeEach(async(() => { | |||
TestBed.configureTestingModule({ | |||
declarations: [ MallDetailsPage ], | |||
schemas: [CUSTOM_ELEMENTS_SCHEMA], | |||
}) | |||
.compileComponents(); | |||
})); | |||
beforeEach(() => { | |||
fixture = TestBed.createComponent(MallDetailsPage); | |||
component = fixture.componentInstance; | |||
fixture.detectChanges(); | |||
}); | |||
it('should create', () => { | |||
expect(component).toBeTruthy(); | |||
}); | |||
}); |
@@ -0,0 +1,28 @@ | |||
import { Component, OnInit } from '@angular/core'; | |||
import { ActivatedRoute, Router } from '@angular/router'; | |||
import { Mall, MallService } from '../services/mall.service'; | |||
@Component({ | |||
selector: 'app-mall-details', | |||
templateUrl: './mall-details.page.html', | |||
styleUrls: ['./mall-details.page.scss'], | |||
}) | |||
export class MallDetailsPage implements OnInit { | |||
mall_details: Mall; | |||
selected_tab: string = 'food'; | |||
constructor( | |||
private mallService: MallService, | |||
private route: ActivatedRoute, | |||
private router: Router | |||
) { } | |||
ngOnInit() { | |||
let mall_id = this.route.snapshot.paramMap.get('mall_id'); | |||
this.mallService.getMallByID(mall_id).then((data: Mall) => { | |||
this.mall_details = data; | |||
}); | |||
} | |||
} |
@@ -1,7 +1,5 @@ | |||
<ion-content> | |||
<div class="overlay" [ngClass]="{ 'active' : show_menu }"></div> | |||
<section class="header-bar"> | |||
<h2> Find malls near you </h2> | |||
<button> <ion-icon src="assets/custom/search.svg"></ion-icon> </button> | |||
@@ -22,14 +20,14 @@ | |||
</div> | |||
<ion-list lines="none" class="malls-list"> | |||
<ion-item *ngFor="let mall of malls"> | |||
<ion-item *ngFor="let mall of malls" (click)="showMallDetails(mall)"> | |||
<img src="{{ mall.image_url }}" slot="start"> | |||
<ion-label> | |||
<h3> {{ mall.name }} <ion-icon name="bookmark" [ngClass]="{'active' : mall.is_bookmarked }"></ion-icon> </h3> | |||
<p class="description"> {{ mall.description }} </p> | |||
<div class="offers-holder"> | |||
<div class="offer" *ngFor="let offer of mall.offers_collection"> | |||
{{ offer.name }}: <strong> {{ offer.offer.length }} </strong> | |||
<div class="offer" *ngFor="let offer of mall.offer_collection"> | |||
{{ offer.name }}: <strong> {{ offer.offers.length }} </strong> | |||
</div> | |||
</div> | |||
<div class="utilities-holder"> | |||
@@ -72,14 +70,14 @@ | |||
</section> | |||
<ion-list lines="none" class="malls-list"> | |||
<ion-item *ngFor="let mall of malls"> | |||
<ion-item *ngFor="let mall of malls" (click)="showMallDetails(mall)"> | |||
<img src="{{ mall.image_url }}" slot="start"> | |||
<ion-label> | |||
<h3> {{ mall.name }} <ion-icon name="bookmark" [ngClass]="{'active' : mall.is_bookmarked }"></ion-icon> </h3> | |||
<p class="description"> {{ mall.description }} </p> | |||
<div class="offers-holder"> | |||
<div class="offer" *ngFor="let offer of mall.offers_collection"> | |||
{{ offer.name }}: <strong> {{ offer.offer.length }} </strong> | |||
<div class="offer" *ngFor="let offer of mall.offer_collection"> | |||
{{ offer.name }}: <strong> {{ offer.offers.length }} </strong> | |||
</div> | |||
</div> | |||
<div class="utilities-holder"> | |||
@@ -119,14 +117,14 @@ | |||
</div> | |||
<ion-list lines="none" class="malls-list"> | |||
<ion-item *ngFor="let mall of malls"> | |||
<ion-item *ngFor="let mall of malls" (click)="showMallDetails(mall)"> | |||
<img src="{{ mall.image_url }}" slot="start"> | |||
<ion-label> | |||
<h3> {{ mall.name }} <ion-icon name="bookmark" [ngClass]="{'active' : mall.is_bookmarked }"></ion-icon> </h3> | |||
<p class="description"> {{ mall.description }} </p> | |||
<div class="offers-holder"> | |||
<div class="offer" *ngFor="let offer of mall.offers_collection"> | |||
{{ offer.name }}: <strong> {{ offer.offer.length }} </strong> | |||
<div class="offer" *ngFor="let offer of mall.offer_collection"> | |||
{{ offer.name }}: <strong> {{ offer.offers.length }} </strong> | |||
</div> | |||
</div> | |||
<div class="utilities-holder"> | |||
@@ -153,23 +151,4 @@ | |||
</ion-item> | |||
</ion-list> | |||
<!-- Humburger Menu Starts from here --> | |||
<div class="menu-icon-holder" (click)="show_menu = true" [ngClass]="{'inactive' : show_menu }"> | |||
<ion-icon name="menu"></ion-icon> | |||
</div> | |||
<div class="close-icon-holder" [ngClass]="{'active' : show_menu }" | |||
(click)="show_menu = false"> | |||
<ion-icon name="close"></ion-icon> | |||
</div> | |||
<div class="menu-items-holder" [ngClass]="{'active' : show_menu }"> | |||
<button> <ion-icon src="assets/custom/001-house.svg"></ion-icon> </button> | |||
<button> <ion-icon src="assets/custom/002-bookmark.svg"></ion-icon> </button> | |||
<button> <ion-icon src="assets/custom/003-shop.svg"></ion-icon> </button> | |||
<button> <ion-icon src="assets/custom/004-map.svg"></ion-icon> </button> | |||
<button> <ion-icon src="assets/custom/005-boss.svg"></ion-icon> </button> | |||
</div> | |||
</ion-content> |
@@ -116,6 +116,10 @@ | |||
ion-icon { | |||
color: var(--brand-grey); | |||
font-size: 16px; | |||
&.active { | |||
color: var(--brand-blue); | |||
} | |||
} | |||
} | |||
@@ -219,131 +223,6 @@ | |||
} | |||
} | |||
.menu-icon-holder { | |||
background-color: #005cbe; | |||
color: white; | |||
position: fixed; | |||
bottom: -60px; | |||
box-shadow: 0px 0px 5px var(--brand-blue); | |||
width: 100px; | |||
height: 100px; | |||
text-align: center; | |||
left: calc((100% - 100px)/2); | |||
border-radius: 50%; | |||
padding-top: 7px; | |||
z-index: 1; | |||
transition: transform 0.5s; | |||
transform: translateY(0); | |||
opacity: 0.8; | |||
&.inactive { | |||
transform: translateY(100px); | |||
} | |||
ion-icon { | |||
font-size: 30px; | |||
} | |||
} | |||
.close-icon-holder { | |||
background-color: white; | |||
width: 70%; | |||
position: fixed; | |||
left: calc((100% - 70%) / 2); | |||
height: 100px; | |||
border-radius: 50%; | |||
color: var(--brand-blue); | |||
text-align: center; | |||
z-index: 2; | |||
bottom: -60px; | |||
padding-top: 10px; | |||
transition: transform 0.3s; | |||
transform: scale(0); | |||
transition-delay: 0.7s; | |||
&.active { | |||
transform: scale(1); | |||
} | |||
ion-icon { | |||
font-size: 25px; | |||
} | |||
} | |||
.menu-items-holder { | |||
background-image: url('../../assets/custom/background-2.svg'); | |||
background-size: cover; | |||
background-repeat: no-repeat; | |||
background-position: left top; | |||
border-radius: 50%; | |||
position: fixed; | |||
left: calc((100% - 150%) / 2); | |||
bottom: -150px; | |||
z-index: 1; | |||
height: 300px; | |||
width: 150%; | |||
padding: 20px 30%; | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: flex-start; | |||
transition: transform 0.5s; | |||
transform: scale(0); | |||
transition-delay: 0.3s; | |||
&.active { | |||
transform: scale(1); | |||
} | |||
button { | |||
background-color: white; | |||
height: 50px; | |||
width: 50px; | |||
border-radius: 50%; | |||
font-size: 20px; | |||
position: relative; | |||
&:first-child { | |||
transform: translateY(40px); | |||
} | |||
&:nth-child(2) { | |||
transform: translateY(20px); | |||
} | |||
&:nth-child(3) { | |||
transform: translateY(10px); | |||
} | |||
&:last-child { | |||
transform: translateY(40px); | |||
} | |||
&:nth-child(4) { | |||
transform: translateY(20px); | |||
} | |||
} | |||
} | |||
.overlay { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
height: 100vh; | |||
width: 100vw; | |||
pointer-events: none; | |||
background-color: black; | |||
opacity: 0; | |||
z-index: 1; | |||
transition: opacity 0.5s; | |||
&.active { | |||
pointer-events: all; | |||
opacity: 0.5; | |||
} | |||
} | |||
.food-types-holder{ | |||
display: flex; | |||
width: 100%; | |||
@@ -1,5 +1,7 @@ | |||
import { Component, OnInit } from '@angular/core'; | |||
import { MallService } from '../services/mall.service'; | |||
import { MallService, Mall } from '../services/mall.service'; | |||
import { MallDetailsPage } from '../mall-details/mall-details.page'; | |||
import { Router } from '@angular/router'; | |||
@Component({ | |||
selector: 'app-malls', | |||
@@ -7,19 +9,25 @@ import { MallService } from '../services/mall.service'; | |||
styleUrls: ['./malls.page.scss'], | |||
}) | |||
export class MallsPage implements OnInit { | |||
selected_tab = 'you'; | |||
show_menu = false; | |||
malls = []; | |||
selected_tab: string = 'you'; | |||
malls: Array<Mall>; | |||
constructor(private mallService: MallService) { } | |||
constructor( | |||
private mallService: MallService, | |||
private router: Router | |||
) { } | |||
ngOnInit() { | |||
} | |||
ionViewDidEnter() { | |||
this.mallService.getAllMalls().then((data: Array<object>) => { | |||
this.mallService.getAllMalls().then((data: Array<Mall>) => { | |||
this.malls = data; | |||
}); | |||
} | |||
showMallDetails(mall) { | |||
this.router.navigate(['/mall-details', { mall_id: mall.id }]); | |||
} | |||
} |
@@ -0,0 +1,2 @@ | |||
export class Mall { | |||
} |
@@ -0,0 +1,30 @@ | |||
export class Offer { | |||
name: string; | |||
description: string; | |||
coupon_code: string; | |||
constructor(initializationObject: any) { | |||
// Check if object has the required fields | |||
if (!initializationObject.hasOwnProperty('name')) { | |||
throw new Error('The offer needs a name'); | |||
} | |||
if (!initializationObject.hasOwnProperty('description')) { | |||
throw new Error('The offer needs a description'); | |||
} | |||
if (!initializationObject.hasOwnProperty('coupon_code')) { | |||
throw new Error('The offer needs a coupon_code'); | |||
} | |||
this.name = initializationObject.name; | |||
this.description = initializationObject.description; | |||
this.coupon_code = initializationObject.coupon_code; | |||
} | |||
} | |||
export type OfferCollection = { | |||
name: string, | |||
offers: Array<Offer>, | |||
}; |
@@ -1,79 +1,119 @@ | |||
import { Injectable } from '@angular/core'; | |||
import { Offer, OfferCollection } from '../models/offer'; | |||
type CoOrdinates = { | |||
latitude: number, | |||
longitude: number, | |||
}; | |||
type Outlet = { | |||
id: string, | |||
image_url?: string, | |||
name: string, | |||
type: string, | |||
offers: Array<Offer>, | |||
is_bookmarked: boolean, | |||
rating: number, | |||
}; | |||
export type Mall = { | |||
id: string, | |||
name: string, | |||
is_bookmarked: boolean, | |||
is_archived: boolean, | |||
image_url?: string, | |||
description: string, | |||
offer_collection: Array<OfferCollection>, | |||
outlets: Array<Outlet>, | |||
rating: number, | |||
distance: number, | |||
location: CoOrdinates, | |||
}; | |||
@Injectable({ | |||
providedIn: 'root' | |||
}) | |||
export class MallService { | |||
malls = [{ | |||
id: '00001', | |||
name: 'Gopalan Mall', | |||
is_bookmarked: false, | |||
image_url: 'https://images.jdmagicbox.com/comp/chennai/s2/044pxx44.xx44.171122071536.w6s2/catalogue/v-r-mall-anna-nagar-chennai-malls-jwyyh0m5kq-t.jpg', | |||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', | |||
offers_collection: [{ | |||
name: 'Food', | |||
offer: [{ | |||
name: 'McDonalds Offer', | |||
description: 'Get 25% offer on you first meal', | |||
cupon_code: 'MCD25F' | |||
}] | |||
}, { | |||
name: 'Shopping', | |||
offer: [] | |||
}], | |||
rating: 4.3, | |||
distance: 2, | |||
location: { | |||
latitude: 12.903903292, | |||
longitude: 34.940349039 | |||
} | |||
}, | |||
{ | |||
id: '00002', | |||
name: 'Gopalan Mall 2', | |||
is_bookmarked: false, | |||
image_url: 'https://images.jdmagicbox.com/comp/chennai/s2/044pxx44.xx44.171122071536.w6s2/catalogue/v-r-mall-anna-nagar-chennai-malls-jwyyh0m5kq-t.jpg', | |||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', | |||
offers_collection: [{ | |||
name: 'Food', | |||
offer: [] | |||
}, { | |||
name: 'Shopping', | |||
offer: [] | |||
}], | |||
rating: 4.3, | |||
distance: 2, | |||
location: { | |||
latitude: 12.903903292, | |||
longitude: 34.940349039 | |||
} | |||
}, | |||
{ | |||
id: '00003', | |||
name: 'Gopalan Mall 3', | |||
is_bookmarked: false, | |||
image_url: 'https://images.jdmagicbox.com/comp/chennai/s2/044pxx44.xx44.171122071536.w6s2/catalogue/v-r-mall-anna-nagar-chennai-malls-jwyyh0m5kq-t.jpg', | |||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', | |||
offers_collection: [{ | |||
name: 'Food', | |||
offer: [] | |||
}, { | |||
name: 'Shopping', | |||
offer: [] | |||
}], | |||
rating: 4.3, | |||
distance: 2, | |||
location: { | |||
latitude: 12.903903292, | |||
longitude: 34.940349039 | |||
} | |||
}]; | |||
private malls: Array<Mall>; | |||
constructor() { } | |||
constructor() { | |||
this.malls = [{ | |||
id: '00001', | |||
name: 'Gopalan Mall', | |||
is_bookmarked: true, | |||
is_archived: false, | |||
image_url: 'https://www.gopalanmall.com/images/mall-arcade-01.jpg', | |||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', | |||
outlets: [], | |||
offer_collection: [{ | |||
name: 'Food', | |||
offers: [{ | |||
name: 'McDonalds Offer', | |||
description: 'Get 25% offer on you first meal', | |||
coupon_code: 'MCD25F', | |||
}] | |||
}, { | |||
name: 'Shopping', | |||
offers: [] | |||
}], | |||
rating: 4.3, | |||
distance: 2, | |||
location: { | |||
latitude: 12.903903292, | |||
longitude: 34.940349039 | |||
} | |||
}, | |||
{ | |||
id: '00002', | |||
name: 'Gopalan Mall 2', | |||
is_bookmarked: false, | |||
is_archived: false, | |||
image_url: 'https://www.gopalanmall.com/images/mall-arcade-01.jpg', | |||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', | |||
outlets: [], | |||
offer_collection: [{ | |||
name: 'Food', | |||
offers: [] | |||
}, { | |||
name: 'Shopping', | |||
offers: [] | |||
}], | |||
rating: 4.3, | |||
distance: 2, | |||
location: { | |||
latitude: 12.903903292, | |||
longitude: 34.940349039 | |||
} | |||
}, | |||
{ | |||
id: '00003', | |||
name: 'Gopalan Mall 3', | |||
is_bookmarked: false, | |||
is_archived: false, | |||
image_url: 'https://www.gopalanmall.com/images/mall-arcade-01.jpg', | |||
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', | |||
outlets: [], | |||
offer_collection: [{ | |||
name: 'Food', | |||
offers: [] | |||
}, { | |||
name: 'Shopping', | |||
offers: [] | |||
}], | |||
rating: 4.3, | |||
distance: 2, | |||
location: { | |||
latitude: 12.903903292, | |||
longitude: 34.940349039 | |||
} | |||
}]; | |||
} | |||
public async getAllMalls() { | |||
return this.malls; | |||
} | |||
getAllMalls() { | |||
return new Promise((resolve, reject) => { | |||
resolve(this.malls); | |||
}); | |||
public async getMallByID(id: string) { | |||
return this.malls.find((mall) => mall.id === id); | |||
} | |||
} |
@@ -0,0 +1,8 @@ | |||
export interface Storable { | |||
pk?: number; | |||
id: string; | |||
archived: boolean; | |||
update: (object: object) => void; | |||
convertToPlainObject: () => object; | |||
} |
@@ -0,0 +1,90 @@ | |||
import { Storage } from '@ionic/storage'; | |||
import { Storable } from './storable'; | |||
export class StorageWrapper<T extends Storable> { | |||
entries: Array<T> = []; | |||
activeEntries: Array<T> = []; | |||
storageKey: string; | |||
lastMigration = 1; | |||
createdIds: Set<string> = new Set(); | |||
updatedIds: Set<string> = new Set(); | |||
deletedIds: Set<string> = new Set(); | |||
constructor(storageKey: string, private storageType: any, private storage: Storage, private seedData?: T[]) { | |||
this.storageKey = storageKey; | |||
} | |||
private async deserialize_entries() { | |||
const parsedEntries = await this.storage.get(this.storageKey); | |||
const journalEntries = parsedEntries.map(entry => new this.storageType(entry)); | |||
return journalEntries; | |||
} | |||
private async serialize_entries() { | |||
const plainObjectEntries = this.entries.map(entry => entry.convertToPlainObject()); | |||
return this.storage.set(this.storageKey, plainObjectEntries); | |||
} | |||
async getEntries(force = false) { | |||
if (this.entries.length === 0 || force) { | |||
const entriesInStorage = await this.storage.get(this.storageKey); | |||
if (!entriesInStorage) { | |||
this.entries = this.seedData; | |||
await this.serialize_entries(); | |||
} else { | |||
this.entries = await this.deserialize_entries(); | |||
} | |||
this.activeEntries = this.entries.filter(entry => !entry.archived); | |||
} | |||
return this.activeEntries; | |||
} | |||
async addEntry(newEntry: T) { | |||
// Make sure the old entries have been fetched before adding new ones | |||
await this.getEntries(); | |||
this.entries.push(newEntry); | |||
this.activeEntries.push(newEntry); | |||
this.createdIds.add(newEntry.id); | |||
this.serialize_entries(); | |||
} | |||
async getEntryById(id: string) { | |||
const journalEntries = await this.getEntries(); | |||
return journalEntries.find(entry => entry.id === id); | |||
} | |||
async updateEntry(updateObject: Storable) { | |||
const entryToUpdate = this.entries.find(entry => entry.id === updateObject.id); | |||
entryToUpdate.update(updateObject); | |||
const newActiveEntries = this.entries.filter(entry => !entry.archived); | |||
this.activeEntries.splice(0, Infinity, ...newActiveEntries); | |||
this.updatedIds.add(updateObject.id); | |||
this.serialize_entries(); | |||
} | |||
async softDeleteEntry(deletedId: string) { | |||
this.entries.find(entry => entry.id === deletedId).archived = true; | |||
const newActiveEntries = this.entries.filter(entry => !entry.archived); | |||
this.activeEntries.splice(0, Infinity, ...newActiveEntries); | |||
this.updatedIds.add(deletedId); | |||
this.serialize_entries(); | |||
} | |||
async hardDeleteEntry(deletedId: string) { | |||
const deletedEntryIndex = this.entries.findIndex(entry => entry.id === deletedId); | |||
this.entries.splice(deletedEntryIndex, 1); | |||
const deletedActiveEntryIndex = this.activeEntries.findIndex(entry => entry.id === deletedId); | |||
this.activeEntries.splice(deletedActiveEntryIndex, Infinity); | |||
this.deletedIds.add(deletedId); | |||
this.serialize_entries(); | |||
} | |||
} |
@@ -27,7 +27,7 @@ | |||
@import url('https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'); | |||
@import url('https://fonts.googleapis.com/css?family=M+PLUS+Rounded+1c:400,500&display=swap'); | |||
@import url('https://fonts.googleapis.com/css?family=M+PLUS+Rounded+1c:400&display=swap'); | |||
ion-button, button, a, p, div, input { | |||
font-family: 'Roboto', sans-serif; | |||
@@ -41,4 +41,130 @@ figure { | |||
header, h1, h2, h3, h4, h5 { | |||
font-family: 'M PLUS Rounded 1c', sans-serif; | |||
margin: 0; | |||
} | |||
// Hamburger menu | |||
.menu-icon-holder { | |||
background-color: #005cbe; | |||
color: white; | |||
position: fixed; | |||
bottom: -60px; | |||
box-shadow: 0px 0px 5px var(--brand-blue); | |||
width: 100px; | |||
height: 100px; | |||
text-align: center; | |||
left: calc((100% - 100px)/2); | |||
border-radius: 50%; | |||
padding-top: 7px; | |||
z-index: 1; | |||
transition: transform 0.5s; | |||
transform: translateY(0); | |||
opacity: 0.7; | |||
&.inactive { | |||
transform: translateY(100px); | |||
} | |||
ion-icon { | |||
font-size: 30px; | |||
} | |||
} | |||
.close-icon-holder { | |||
background-color: white; | |||
width: 70%; | |||
position: fixed; | |||
left: calc((100% - 70%) / 2); | |||
height: 100px; | |||
border-radius: 50%; | |||
color: var(--brand-blue); | |||
text-align: center; | |||
z-index: 2; | |||
bottom: -60px; | |||
padding-top: 10px; | |||
transition: transform 0.3s; | |||
transform: scale(0); | |||
transition-delay: 0.7s; | |||
&.active { | |||
transform: scale(1); | |||
} | |||
ion-icon { | |||
font-size: 25px; | |||
} | |||
} | |||
.menu-items-holder { | |||
background-image: url('assets/custom/background-2.svg'); | |||
background-size: cover; | |||
background-repeat: no-repeat; | |||
background-position: left top; | |||
border-radius: 50%; | |||
position: fixed; | |||
left: calc((100% - 150%) / 2); | |||
bottom: -150px; | |||
z-index: 1; | |||
height: 300px; | |||
width: 150%; | |||
padding: 20px 30%; | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: flex-start; | |||
transition: transform 0.5s; | |||
transform: scale(0); | |||
transition-delay: 0.3s; | |||
&.active { | |||
transform: scale(1); | |||
} | |||
button { | |||
background-color: white; | |||
height: 50px; | |||
width: 50px; | |||
border-radius: 50%; | |||
font-size: 20px; | |||
position: relative; | |||
&:first-child { | |||
transform: translateY(40px); | |||
} | |||
&:nth-child(2) { | |||
transform: translateY(20px); | |||
} | |||
&:nth-child(3) { | |||
transform: translateY(10px); | |||
} | |||
&:last-child { | |||
transform: translateY(40px); | |||
} | |||
&:nth-child(4) { | |||
transform: translateY(20px); | |||
} | |||
} | |||
} | |||
.overlay { | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
height: 100vh; | |||
width: 100vw; | |||
pointer-events: none; | |||
background-color: black; | |||
opacity: 0; | |||
z-index: 1; | |||
transition: opacity 0.5s; | |||
&.active { | |||
pointer-events: all; | |||
opacity: 0.5; | |||
} | |||
} |
@@ -75,7 +75,7 @@ | |||
--ion-color-light-shade: #d7d8da; | |||
--ion-color-light-tint: #f5f6f9; | |||
--brand-blue: #8173e6; | |||
--brand-blue: #1881e5; | |||
--background-blue: #f5f7fa; | |||
--brand-grey: #9a9a9a; | |||
--brand-black: #1b1d1e; | |||