├── README.md ├── src ├── app │ ├── app.component.ts │ ├── app.html │ ├── app.module.ts │ ├── app.scss │ ├── main.dev.ts │ └── main.prod.ts ├── assets │ ├── icon │ │ └── favicon.ico │ └── manifest.json ├── declarations.d.ts ├── index.html ├── pages │ ├── crud │ │ ├── crud.html │ │ ├── crud.scss │ │ └── crud.ts │ ├── login │ │ ├── login.html │ │ ├── login.scss │ │ └── login.ts │ ├── signup │ │ ├── signup.html │ │ ├── signup.scss │ │ └── signup.ts │ └── tabs │ │ ├── tabs.html │ │ └── tabs.ts ├── providers │ └── backandService.ts ├── service-worker.js └── theme │ ├── global.scss │ └── variables.scss └── www └── manifest.json /README.md: -------------------------------------------------------------------------------- 1 | # Backand Ionic 2 Starter 2 | 3 | Compatible with Ionic 2.0.0-rc.0 4 | 5 | ## Running the app 6 | 7 | 1. Create an Ionic app: 8 | 9 | ionic start myApp https://github.com/backand/backand-ionic2-starter --v2 10 | 11 | cd myApp 12 | 13 | 2. Install Cordova Plugins 14 | 15 | ionic plugin add cordova-plugin-inappbrowser 16 | 17 | 3. Set details of your app in `src/app/app.component.ts`: 18 | 19 | backandService.setAppName('your app name'); 20 | 21 | backandService.setSignUpToken('your signup token'); 22 | 23 | backandService.setAnonymousToken('your anonymousToken token'); 24 | 25 | 4. Install dependencies: 26 | 27 | npm install socket.io-client --save 28 | 29 | npm install @types/node --save-dev --save-exact 30 | 31 | npm install @types/socket.io-client --save-dev --save-exact 32 | 33 | 5. Run the app 34 | 35 | ionic serve 36 | 37 | ## CRUD 38 | 39 | To fetch, create, and filter rows, from an object, say `stuff`, modify 40 | the object used in these functions: 41 | 42 | getItems 43 | filterItems 44 | postItem 45 | 46 | replacing `todo` with the name of your object, `stuff` 47 | 48 | ## Social Signup 49 | 50 | The app opens a dialog supplied by the social network. 51 | 52 | ## In App 53 | 54 | ### Facebook 55 | 56 | Use the Facebook Connect plugin to obtain access to the native FB application on iOS and Android. 57 | 58 | Install it with: 59 | 60 | ionic plugin add cordova-plugin-facebook4 --save --variable APP_ID="" --variable APP_NAME="" 61 | 62 | Use `BackandService` function `inappSocial` 63 | 64 | ## Socket Service 65 | 66 | 1. To subscribe to event `items_updated` from server side via sockets, in your component do, as in `src/app/pages/crud/crud.ts`: 67 | 68 | ```javascript 69 | this.backandService.on('items_updated') 70 | .subscribe( 71 | data => { 72 | 73 | }, 74 | err => { 75 | console.log(err); 76 | }, 77 | () => console.log('received update from socket') 78 | ); 79 | ``` 80 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Platform } from 'ionic-angular'; 3 | import { StatusBar } from 'ionic-native'; 4 | 5 | import { TabsPage } from '../pages/tabs/tabs'; 6 | 7 | import {BackandService} from '../providers/backandService' 8 | 9 | @Component({ 10 | template: `` 11 | }) 12 | export class MyApp { 13 | rootPage = TabsPage; 14 | 15 | constructor(platform: Platform, private backandService:BackandService) { 16 | platform.ready().then(() => { 17 | // Okay, so the platform is ready and our plugins are available. 18 | // Here you can do any higher level native things you might need. 19 | StatusBar.styleDefault(); 20 | backandService.setIsMobile(platform.is('mobile')); 21 | backandService.setAppName('your app name'); 22 | backandService.setSignUpToken('your signup token'); 23 | backandService.setAnonymousToken('your anonymousToken token'); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pages 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicApp, IonicModule } from 'ionic-angular'; 3 | import { MyApp } from './app.component'; 4 | import { LoginPage } from '../pages/login/login'; 5 | import { SignupPage } from '../pages/signup/signup'; 6 | import { CrudPage } from '../pages/crud/crud'; 7 | import { TabsPage } from '../pages/tabs/tabs'; 8 | import { BackandService } from '../providers/backandService'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | MyApp, 13 | LoginPage, 14 | SignupPage, 15 | CrudPage, 16 | TabsPage 17 | ], 18 | imports: [ 19 | IonicModule.forRoot(MyApp) 20 | ], 21 | bootstrap: [IonicApp], 22 | entryComponents: [ 23 | MyApp, 24 | LoginPage, 25 | SignupPage, 26 | CrudPage, 27 | TabsPage 28 | ], 29 | providers: [BackandService] 30 | }) 31 | export class AppModule {} 32 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | 4 | // App Global Sass 5 | // -------------------------------------------------- 6 | // Put style rules here that you want to apply globally. These 7 | // styles are for the entire app and not just one component. 8 | // Additionally, this file can be also used as an entry point 9 | // to import other Sass files to be included in the output CSS. 10 | // 11 | // Shared Sass variables, which can be used to adjust Ionic's 12 | // default Sass variables, belong in "theme/variables.scss". 13 | // 14 | // To declare rules for a specific mode, create a child rule 15 | // for the .md, .ios, or .wp mode classes. The mode class is 16 | // automatically applied to the element in the app. 17 | -------------------------------------------------------------------------------- /src/app/main.dev.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); 6 | -------------------------------------------------------------------------------- /src/app/main.prod.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowser } from '@angular/platform-browser'; 2 | import { enableProdMode } from '@angular/core'; 3 | 4 | import { AppModuleNgFactory } from './app.module.ngfactory'; 5 | 6 | enableProdMode(); 7 | platformBrowser().bootstrapModuleFactory(AppModuleNgFactory); 8 | -------------------------------------------------------------------------------- /src/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/backand/backand-ionic2-starter/448d42966847425de0f253bc83778a0004f4a561/src/assets/icon/favicon.ico -------------------------------------------------------------------------------- /src/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ionic", 3 | "short_name": "Ionic", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "icons": [{ 7 | "src": "assets/imgs/logo.png", 8 | "sizes": "512x512", 9 | "type": "image/png" 10 | }], 11 | "background_color": "#4e8ef7", 12 | "theme_color": "#4e8ef7" 13 | } -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Declaration files are how the Typescript compiler knows about the type information(or shape) of an object. 3 | They're what make intellisense work and make Typescript know all about your code. 4 | 5 | A wildcard module is declared below to allow third party libraries to be used in an app even if they don't 6 | provide their own type declarations. 7 | 8 | To learn more about using third party libraries in an Ionic app, check out the docs here: 9 | http://ionicframework.com/docs/v2/resources/third-party-libs/ 10 | 11 | For more info on type definition files, check out the Typescript docs here: 12 | https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html 13 | */ 14 | declare module '*'; -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Ionic App 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/pages/crud/crud.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CRUD 5 | 6 | 7 | 8 | 9 |

CRUD

10 | 11 | 12 | 13 | 14 | ADD TODO: 15 | 16 | 17 | 18 | Name 19 | 20 | 21 | 22 | 23 | Description 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 |

{{ item.name }}

46 |

{{ item.description }}

47 |
48 |
49 | 50 |
51 | -------------------------------------------------------------------------------- /src/pages/crud/crud.scss: -------------------------------------------------------------------------------- 1 | .page3 { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/crud/crud.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {BackandService} from '../../providers/backandService' 3 | 4 | @Component({ 5 | templateUrl: 'crud.html', 6 | selector: 'page-crud' 7 | }) 8 | export class CrudPage { 9 | name:string = 'World'; 10 | description:string = 'Wonderful'; 11 | public items:any[] = []; 12 | searchQuery: string; 13 | 14 | constructor(public backandService:BackandService) { 15 | this.searchQuery = ''; 16 | 17 | this.backandService.on("items_updated") 18 | .subscribe( 19 | data => { 20 | console.log("items_updated", data); 21 | let a = data as any[]; 22 | let newItem = {}; 23 | a.forEach((kv)=> newItem[kv.Key] = kv.Value); 24 | this.items.unshift(newItem); 25 | }, 26 | err => { 27 | console.log(err); 28 | }, 29 | () => console.log('received update from socket') 30 | ); 31 | 32 | } 33 | 34 | public postItem() { 35 | 36 | this.backandService.create('todo', { name: this.name, description: this.description }).subscribe( 37 | data => { 38 | // add to beginning of array 39 | this.items.unshift({ id: null, name: this.name, description: this.description }); 40 | console.log(this.items); 41 | this.name = ''; 42 | this.description = ''; 43 | }, 44 | err => this.backandService.logError(err), 45 | () => console.log('OK') 46 | ); 47 | } 48 | 49 | public getItems() { 50 | this.backandService.getList('todo') 51 | .subscribe( 52 | data => { 53 | console.log(data); 54 | this.items = data; 55 | }, 56 | err => this.backandService.logError(err), 57 | () => console.log('OK') 58 | ); 59 | } 60 | 61 | public filterItems(searchbar) { 62 | // set q to the value of the searchbar 63 | var q = searchbar; 64 | 65 | // if the value is an empty string don't filter the items 66 | if (!q || q.trim() == '') { 67 | return; 68 | } 69 | else{ 70 | q = q.trim(); 71 | } 72 | 73 | let filter = 74 | [ 75 | { 76 | fieldName: 'name', 77 | operator: 'contains', 78 | value: q 79 | } 80 | ] 81 | ; 82 | 83 | 84 | this.backandService.getList('todo', null, null, filter) 85 | .subscribe( 86 | data => { 87 | console.log("subscribe", data); 88 | this.items = data; 89 | }, 90 | err => this.backandService.logError(err), 91 | () => console.log('OK') 92 | ); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/pages/login/login.html: -------------------------------------------------------------------------------- 1 | 2 | Login 3 | 4 | 5 | 6 |

Authentication

7 | 8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 | 16 | 17 | 18 | 19 | Username 20 | 21 | 22 | 23 | 24 | Password 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 |
35 | 36 |
37 | 38 | 39 |
40 |

Change Password

41 | 42 | 43 | Old Password 44 | 45 | 46 | 47 | New Password 48 | 49 | 50 | 51 | Confirm Password 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
61 | -------------------------------------------------------------------------------- /src/pages/login/login.scss: -------------------------------------------------------------------------------- 1 | .page1 { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/login/login.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | // import {bootstrap} from '@angular/platform-browser-dynamic'; 3 | import 'rxjs/Rx' 4 | import {BackandService} from '../../providers/backandService' 5 | 6 | @Component({ 7 | templateUrl: 'login.html', 8 | selector: 'page-login', 9 | }) 10 | export class LoginPage { 11 | 12 | username:string = 'test@angular2.com'; 13 | password:string = 'angular2'; 14 | auth_type:string = "N/A"; 15 | is_auth_error:boolean = false; 16 | auth_status:string = null; 17 | loggedInUser: string = ''; 18 | 19 | 20 | oldPassword: string = ''; 21 | newPassword: string = ''; 22 | confirmNewPassword: string = ''; 23 | 24 | 25 | constructor(public backandService:BackandService) { 26 | this.auth_type = backandService.getAuthType(); 27 | this.auth_status = backandService.getAuthStatus(); 28 | this.loggedInUser = backandService.getUsername(); 29 | } 30 | 31 | 32 | public getAuthTokenSimple() { 33 | 34 | this.auth_type = 'Token'; 35 | var $obs = this.backandService.signin(this.username, this.password); 36 | $obs.subscribe( 37 | data => { 38 | this.auth_status = 'OK'; 39 | this.is_auth_error = false; 40 | this.loggedInUser = this.username; 41 | this.username = ''; 42 | this.password = ''; 43 | }, 44 | err => { 45 | var errorMessage = this.backandService.extractErrorMessage(err); 46 | 47 | this.auth_status = `Error: ${errorMessage}`; 48 | this.is_auth_error = true; 49 | this.backandService.logError(err) 50 | }, 51 | () => console.log('Finish Auth')); 52 | } 53 | 54 | public useAnonymousAuth() { 55 | this.backandService.useAnonymousAuth(); 56 | this.auth_status = 'OK'; 57 | this.is_auth_error = false; 58 | this.auth_type = 'Anonymous'; 59 | this.loggedInUser = 'Anonymous'; 60 | } 61 | 62 | public signOut() { 63 | this.auth_status = null; 64 | this.backandService.signout(); 65 | } 66 | 67 | 68 | 69 | public changePassword() { 70 | if (this.newPassword != this.confirmNewPassword){ 71 | alert('Passwords should match'); 72 | return; 73 | } 74 | var $obs = this.backandService.changePassword(this.oldPassword, this.newPassword); 75 | $obs.subscribe( 76 | data => { 77 | alert('Password changed'); 78 | this.oldPassword = this.newPassword = this.confirmNewPassword = ''; 79 | }, 80 | err => { 81 | this.backandService.logError(err) 82 | }, 83 | () => console.log('Finish change password')); 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/pages/signup/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sign Up 5 | 6 | 7 | 8 | 9 | 10 | 11 |

Sign Up

12 | 13 | 14 | Username 15 | 16 | 17 | 18 | First Name 19 | 20 | 21 | 22 | Last Name 23 | 24 | 25 | 26 | Password 27 | 28 | 29 | 30 | Confirm Password 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 | 43 | 47 | 51 |
52 |

In App Social

53 |
54 | 58 |
59 |
60 | -------------------------------------------------------------------------------- /src/pages/signup/signup.scss: -------------------------------------------------------------------------------- 1 | .page2 { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/signup/signup.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import 'rxjs/Rx' 3 | import {BackandService} from '../../providers/backandService' 4 | 5 | @Component({ 6 | templateUrl: 'signup.html', 7 | selector: 'page-signup', 8 | }) 9 | export class SignupPage { 10 | 11 | email:string = ''; 12 | firstName:string = ''; 13 | lastName:string = ''; 14 | signUpPassword: string = ''; 15 | confirmPassword: string = ''; 16 | 17 | constructor(private backandService:BackandService) { 18 | 19 | 20 | } 21 | 22 | public signUp() { 23 | if (this.signUpPassword != this.confirmPassword){ 24 | alert('Passwords should match'); 25 | return; 26 | } 27 | var $obs = this.backandService.signup(this.email, this.signUpPassword, this.confirmPassword, this.firstName, this.lastName); 28 | $obs.subscribe( 29 | data => { 30 | alert('Sign up succeeded'); 31 | this.email = this.signUpPassword = this.confirmPassword = this.firstName = this.lastName = ''; 32 | }, 33 | err => { 34 | this.backandService.logError(err) 35 | }, 36 | () => console.log('Finish Auth')); 37 | } 38 | 39 | public socialSignin(provider) { 40 | var $obs = this.backandService.socialSignin(provider); 41 | $obs.subscribe( 42 | data => { 43 | console.log('Sign up succeeded with:' + provider); 44 | }, 45 | err => { 46 | this.backandService.logError(err) 47 | }, 48 | () => console.log('Finish Auth')); 49 | } 50 | 51 | public inAppSocial(provider) { 52 | var $obs = this.backandService.inAppSocial(provider); 53 | $obs.subscribe( 54 | data => { 55 | console.log('Sign up succeeded with:' + provider); 56 | }, 57 | err => { 58 | this.backandService.logError(err) 59 | }, 60 | () => console.log('Finish Auth')); 61 | } 62 | } -------------------------------------------------------------------------------- /src/pages/tabs/tabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/pages/tabs/tabs.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | import { LoginPage } from '../login/login'; 4 | import { SignupPage } from '../signup/signup'; 5 | import { CrudPage } from '../crud/crud'; 6 | 7 | @Component({ 8 | templateUrl: 'tabs.html' 9 | }) 10 | export class TabsPage { 11 | // this tells the tabs component which Pages 12 | // should be each tab's root Page 13 | tab1Root: any = LoginPage; 14 | tab2Root: any = SignupPage; 15 | tab3Root: any = CrudPage; 16 | 17 | constructor() { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/providers/backandService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by backand on 3/23/16. 3 | */ 4 | 5 | export enum EVENTS { 6 | SIGNIN, 7 | SIGNOUT, 8 | SIGNUP 9 | }; 10 | 11 | export const URLS = { 12 | signup: '/1/user/signup', 13 | token: '/token', 14 | requestResetPassword: '/1/user/requestResetPassword', 15 | resetPassword: '/1/user/resetPassword', 16 | changePassword: '/1/user/changePassword', 17 | socialLoginWithToken: '/1/user/PROVIDER/token', 18 | socketUrl: 'https://socket.backand.com', 19 | actionUrl: '/1/objects/action/' 20 | } 21 | 22 | export const ERRORS = { 23 | NO_EMAIL_SOCIAL_ERROR: 'NO_EMAIL_SOCIAL', 24 | NOT_SIGNEDIN_ERROR: 'The user is not signed up to', 25 | ALREADY_SIGNEDUP_ERROR: 'The user already signed up to' 26 | }; 27 | 28 | // get token or error message from url in social sign-in popup 29 | (function () { 30 | var dataRegex = /\?(data|error)=(.+)/; 31 | var dataMatch = dataRegex.exec(location.href); 32 | if (dataMatch && dataMatch[1] && dataMatch[2]) { 33 | var userData = {}; 34 | userData[dataMatch[1]] = JSON.parse(decodeURI(dataMatch[2].replace(/#.*/, ''))); 35 | window.opener.postMessage(JSON.stringify(userData), location.origin); 36 | } 37 | }()); 38 | 39 | import {Observable, BehaviorSubject, Subject} from 'rxjs'; 40 | import {Http, Headers, URLSearchParams} from '@angular/http' 41 | import {Injectable} from '@angular/core'; 42 | import {Facebook} from 'ionic-native'; 43 | import * as io from 'socket.io-client'; 44 | 45 | @Injectable() 46 | export class BackandService { 47 | 48 | private api_url: string = 'https://api.backand.com'; 49 | private socialProviders: any = { 50 | github: {name: 'github', label: 'Github', url: 'www.github.com', css: 'github', id: 1}, 51 | google: {name: 'google', label: 'Google', url: 'www.google.com', css: 'google-plus', id: 2}, 52 | facebook: {name: 'facebook', label: 'Facebook', url: 'www.facebook.com', css: 'facebook', id: 3}, 53 | twitter: {name: 'twitter', label: 'Twitter', url: 'www.twitter.com', css: 'twitter', id: 4} 54 | }; 55 | private dummyReturnAddress: string = 'http://www.backandkuku.com'; 56 | 57 | // configuration variables 58 | private app_name:string = 'your app name'; 59 | private signUpToken: string = 'your signup token'; 60 | private anonymousToken: string = 'your anonymousToken token'; 61 | private callSignupOnSingInSocialError: boolean = true; 62 | private isMobile: boolean = false; 63 | 64 | // authentication state 65 | private auth_status:string = ""; 66 | private auth_type:string; 67 | private is_auth_error:boolean = false; 68 | private auth_token:{ header_name : string, header_value: string}; 69 | private username: string; 70 | 71 | private socialAuthWindow: any; 72 | private statusLogin: Subject; 73 | private socket: SocketIOClient.Socket; 74 | 75 | constructor(public http:Http) { 76 | if (this.setAuthenticationState()){ 77 | this.loginSocket(this.auth_token.header_value, 78 | this.anonymousToken, this.app_name); 79 | } 80 | else{ 81 | this.auth_token = {header_name: '', header_value: ''}; 82 | } 83 | } 84 | 85 | // configuration of SDK 86 | public setIsMobile(isMobile: boolean) { 87 | this.isMobile = isMobile; 88 | } 89 | 90 | public setRunSignupAfterErrorInSigninSocial(signUpOnSignIn: boolean) { 91 | this.callSignupOnSingInSocialError = signUpOnSignIn; 92 | } 93 | 94 | public setAppName(appName: string) { 95 | this.app_name = appName; 96 | localStorage.setItem('app_name', appName); 97 | } 98 | 99 | public setAnonymousToken(anonymousToken) { 100 | this.anonymousToken = anonymousToken; 101 | localStorage.setItem('anonymousToken', anonymousToken); 102 | } 103 | 104 | public setSignUpToken(signUpToken) { 105 | this.signUpToken = signUpToken; 106 | localStorage.setItem('signUpToken', signUpToken); 107 | } 108 | 109 | 110 | // methods 111 | public signin(username, password): Observable { 112 | let creds = `username=${username}` + 113 | `&password=${password}` + 114 | `&appName=${this.app_name}` + 115 | `&grant_type=password`; 116 | let header = new Headers(); 117 | header.append('Content-Type', 'application/x-www-form-urlencoded'); 118 | let url = this.api_url + URLS.token; 119 | var $obs = this.http.post(url, creds, { 120 | headers: header 121 | }) 122 | .map(res => res.json()); 123 | 124 | $obs.subscribe( 125 | data => { 126 | this.setTokenHeader(data.access_token) 127 | localStorage.setItem('username', username); 128 | localStorage.setItem('user', JSON.stringify(data)); 129 | this.setAuthenticationState(); 130 | this.loginSocket(data, this.anonymousToken, this.app_name); 131 | }, 132 | err => { 133 | console.log(err); 134 | }, 135 | () => console.log('Finish Auth')); 136 | 137 | return $obs; 138 | 139 | } 140 | 141 | 142 | private signinWithToken(userData): Observable { 143 | let creds = `accessToken=${userData.access_token}` + 144 | `&appName=${this.app_name}` + 145 | `&grant_type=password`; 146 | let header = new Headers(); 147 | header.append('Content-Type', 'application/x-www-form-urlencoded'); 148 | let url = this.api_url + URLS.token; 149 | var $obs = this.http.post(url, creds, { 150 | headers: header 151 | }) 152 | .map(res => res.json()); 153 | 154 | 155 | $obs.subscribe( 156 | data => { 157 | this.setTokenHeader(data.access_token); 158 | localStorage.setItem('username', data.username); 159 | localStorage.setItem('user', data); 160 | this.loginSocket(data, this.anonymousToken, this.app_name); 161 | this.statusLogin.next(EVENTS.SIGNUP); 162 | }, 163 | err => { 164 | this.statusLogin.error(err); 165 | }, 166 | () => console.log('Finish Auth')); 167 | 168 | return $obs; 169 | 170 | } 171 | 172 | public signout() { 173 | this.auth_token = {header_name: '', header_value: ''}; 174 | localStorage.removeItem('auth_token'); 175 | localStorage.removeItem('username'); 176 | localStorage.removeItem('user'); 177 | this.setAuthenticationState(); 178 | } 179 | 180 | public signup(email, password, confirmPassword, firstName, lastName): Observable { 181 | let creds = { 182 | firstName: firstName, 183 | lastName: lastName, 184 | email: email, 185 | password: password, 186 | confirmPassword: confirmPassword 187 | }; 188 | let header = new Headers(); 189 | header.append('SignUpToken', this.signUpToken); 190 | var $obs = this.http.post(this.api_url + URLS.signup, creds, 191 | { 192 | headers: header 193 | } 194 | ); 195 | 196 | $obs.subscribe( 197 | data => { 198 | console.log(data); 199 | }, 200 | err => { 201 | console.log(err); 202 | }, 203 | () => console.log('Finish Sign Up')); 204 | 205 | return $obs; 206 | } 207 | 208 | public changePassword(oldPassword, newPassword): Observable { 209 | let creds = { 210 | oldPassword: oldPassword, 211 | newPassword: newPassword 212 | }; 213 | var $obs = this.http.post(this.api_url + URLS.changePassword, creds, 214 | { 215 | headers: this.authHeader 216 | } 217 | ).map(res => { 218 | console.log(res); 219 | }); 220 | 221 | 222 | $obs.subscribe( 223 | data => { 224 | console.log(data); 225 | }, 226 | err => { 227 | console.log(err); 228 | }, 229 | () => console.log('Finish Change Password')); 230 | 231 | return $obs; 232 | } 233 | 234 | public requestResetPassword(userName: string) { 235 | let header = new Headers(); 236 | header.append('Content-Type', 'application/x-www-form-urlencoded'); 237 | let data = { 238 | appName: this.app_name, 239 | username: userName 240 | }; 241 | return this.http.post(this.api_url + URLS.requestResetPassword, data, 242 | { 243 | headers: header 244 | } 245 | ); 246 | } 247 | 248 | public resetPassword(newPassword, resetToken) { 249 | let header = new Headers(); 250 | header.append('Content-Type', 'application/x-www-form-urlencoded'); 251 | let data = { 252 | newPassword: newPassword, 253 | resetToken: resetToken 254 | }; 255 | return this.http.post(this.api_url + URLS.resetPassword, data, 256 | { 257 | headers: header 258 | } 259 | ); 260 | } 261 | 262 | 263 | private socialSigninWithToken(provider, token): Observable { 264 | 265 | let url = this.api_url + URLS.socialLoginWithToken.replace('PROVIDER', provider) + 266 | "?accessToken=" + encodeURIComponent(token) + 267 | "&appName=" + encodeURI(this.app_name) + 268 | "&signupIfNotSignedIn=" + (this.callSignupOnSingInSocialError ? "true" : "false"); 269 | 270 | this.signout(); 271 | let headers = new Headers(); 272 | headers.append('Content-Type', 'application/json'); 273 | let $obs = this.http.get(url, 274 | { 275 | headers: headers 276 | } 277 | ) 278 | .map(res => res.json()); 279 | 280 | $obs.subscribe( 281 | data => { 282 | this.setTokenHeader(data.access_token); 283 | localStorage.setItem('user', data); 284 | this.loginSocket(data.access_token, this.anonymousToken, this.app_name); 285 | this.statusLogin.next(EVENTS.SIGNUP); 286 | }, 287 | err => { 288 | this.logError(err); 289 | this.statusLogin.error(err); 290 | }, 291 | () => { } 292 | ); 293 | 294 | return $obs; 295 | 296 | } 297 | 298 | public socialSignin(provider: string, spec: any = null, email: string = null) { 299 | return this.socialAuth(provider, false, spec, email); 300 | } 301 | 302 | public socialSignup(provider: string, spec: any = null, email: string = null) { 303 | return this.socialAuth(provider, true, spec, email); 304 | } 305 | 306 | private socialAuth(provider: string, isSignUp: boolean, spec: any = null, email: string = null) 307 | { 308 | if (!this.statusLogin){ 309 | this.statusLogin = new Subject(); 310 | } 311 | 312 | if (!this.socialProviders[provider]) { 313 | throw Error('Unknown Social Provider'); 314 | } 315 | 316 | if (this.isMobile) { 317 | let windowUrl = this.api_url + '/1/' 318 | + this.getSocialUrl(provider, isSignUp) 319 | + '&appname=' + this.app_name + (email ? ("&email=" + email) : '') 320 | + '&returnAddress=' + this.dummyReturnAddress 321 | + "&signupIfNotSignedIn=" + (this.callSignupOnSingInSocialError ? "true" : "false"); 322 | this.socialAuthWindow = window.open( 323 | windowUrl, 324 | spec || 'left=1, top=1, width=600, height=600'); 325 | 326 | let source = Observable.fromEvent(this.socialAuthWindow, 'loadstart') 327 | source.subscribe((e: any) => { 328 | 329 | if (e.url.indexOf(this.dummyReturnAddress) == 0) { // mean startWith 330 | 331 | var url = e.url; 332 | // handle case of misformatted json from server 333 | if (url && url.indexOf('#_=_') > -1){ 334 | url = url.slice(0, url.lastIndexOf('#_=_')); 335 | } 336 | 337 | url = decodeURI(url); 338 | let breakdown = this.parseQueryString(url); 339 | 340 | // error return from server 341 | if (breakdown.error) { 342 | if (!isSignUp && this.callSignupOnSingInSocialError && breakdown.error.message.indexOf(ERRORS.NOT_SIGNEDIN_ERROR) > -1) { // check is right error 343 | this.socialAuth(provider, true, spec); 344 | } 345 | if (breakdown.error.message.indexOf(ERRORS.ALREADY_SIGNEDUP_ERROR) > -1) { 346 | this.statusLogin.error(breakdown.error.message + ' (signing in with ' + breakdown.error.provider + ')'); 347 | } 348 | } 349 | else{ 350 | // login is OK 351 | let userData = breakdown.data; 352 | this.signinWithToken(userData); 353 | } 354 | 355 | try { 356 | this.socialAuthWindow.close(); 357 | } 358 | catch (err) { 359 | console.log(err); 360 | } 361 | } 362 | }); 363 | 364 | 365 | } 366 | else { 367 | 368 | let windowUrl = this.api_url + '/1/' 369 | + this.getSocialUrl(provider, isSignUp) 370 | + '&appname=' + this.app_name + (email ? ("&email=" + email) : '') 371 | + '&returnAddress=' 372 | + "&signupIfNotSignedIn=" + (this.callSignupOnSingInSocialError ? "true" : "false"); 373 | this.socialAuthWindow = window.open(windowUrl, 374 | 'id1', 375 | spec || 'left=1, top=1, width=600, height=600' 376 | ); 377 | 378 | let source = Observable.fromEvent(window, 'message'); 379 | var subscription = source.subscribe((e: any) => { 380 | 381 | // where does location come from ? 382 | if (e.target.location.origin !== location.origin) { 383 | return; 384 | } 385 | let data = e.data; 386 | 387 | // handle case of misformatted json from server 388 | if (data && data.indexOf('#_=_') > -1){ 389 | data = data.slice(0, data.lastIndexOf('#_=_')); 390 | } 391 | data = JSON.parse(data); 392 | let error = data.error; 393 | data = data.data; 394 | 395 | 396 | if (error) { 397 | if (this.callSignupOnSingInSocialError && error.message.indexOf(ERRORS.NOT_SIGNEDIN_ERROR) > -1) { // check is right error 398 | this.socialAuth(provider, true, spec); 399 | } 400 | if (isSignUp && error.message.indexOf(ERRORS.ALREADY_SIGNEDUP_ERROR) > -1) { 401 | this.statusLogin.error(error.message + ' (signing in with ' + error.provider + ')'); 402 | } 403 | } 404 | else{ 405 | // login is OK 406 | let userData = data; 407 | this.signinWithToken(userData); 408 | } 409 | e.target.removeEventListener('message'); 410 | this.socialAuthWindow.close(); 411 | this.socialAuthWindow = null; 412 | }); 413 | } 414 | return this.statusLogin; 415 | } 416 | 417 | public inAppSocial(provider: string) { 418 | if (this.isMobile){ 419 | let that: any = this; 420 | if (!this.statusLogin){ 421 | this.statusLogin = new Subject(); 422 | } 423 | let permissions: string[] = ['public_profile', 'email']; 424 | Facebook.login(permissions).then( 425 | function(data) { 426 | console.log(data); 427 | if (data.status.toLowerCase() == 'connected'){ 428 | let token: string = data.authResponse.accessToken; 429 | that.socialSigninWithToken(provider, token); 430 | } 431 | else{ 432 | that.statusLogin.error(data.status); 433 | } 434 | 435 | }, 436 | function(err) { 437 | console.log(err); 438 | that.statusLogin.error(err); 439 | } 440 | ); 441 | return this.statusLogin; 442 | } 443 | else{ 444 | this.socialAuth(provider, true); 445 | } 446 | } 447 | 448 | public create(object: string, item: any, deep: boolean = false, returnObject: boolean = false) { 449 | let data: string = JSON.stringify(item); 450 | let query: string = ''; 451 | if (returnObject){ 452 | query += 'returnObject=true'; 453 | } 454 | if (deep){ 455 | query += query ? '&deep = true' : 'deep=true'; 456 | } 457 | 458 | 459 | return this.http.post(this.api_url + '/1/objects/' + object + (query ? '?' + query : ''), data, 460 | { 461 | headers: this.authHeader 462 | }) 463 | .retry(3) 464 | .map(res => res.json()); 465 | } 466 | 467 | public update(object: string, id: string, item: any, deep: boolean = false, returnObject: boolean = false) { 468 | let data: string = JSON.stringify(item); 469 | let query: string = ''; 470 | if (returnObject){ 471 | query += 'returnObject=true'; 472 | } 473 | if (deep){ 474 | query += query ? '&deep = true' : 'deep=true'; 475 | } 476 | 477 | 478 | return this.http.put(this.api_url + '/1/objects/' + object + '/' + id + (query ? '?' + query : ''), data, 479 | { 480 | headers: this.authHeader 481 | }) 482 | .retry(3) 483 | .map(res => res.json()); 484 | } 485 | 486 | 487 | 488 | public getList(object: string, 489 | pageSize: number = null, 490 | pageNumber: number = null, 491 | filter: any = null, 492 | sort: any = null, 493 | deep: boolean = false, 494 | search: string = null, 495 | exclude: string[] = null, 496 | relatedObjects: boolean = false) { 497 | let query: string = ''; 498 | let queryParams : string[] = []; 499 | 500 | if (deep){ 501 | queryParams.push('deep=true'); 502 | } 503 | if (relatedObjects){ 504 | queryParams.push('relatedObjects=true'); 505 | } 506 | if (exclude){ 507 | queryParams.push(exclude.join(',')); 508 | } 509 | if (pageSize){ 510 | queryParams.push('pageSize=' + pageSize); 511 | } 512 | if (pageNumber){ 513 | queryParams.push('pageNumber=' + pageNumber); 514 | } 515 | if (filter){ 516 | queryParams.push('filter=' + encodeURI(JSON.stringify(filter))); 517 | } 518 | if (sort){ 519 | queryParams.push('sort=' + encodeURI(JSON.stringify(sort))); 520 | } 521 | if (search){ 522 | queryParams.push('search=' + search); 523 | } 524 | if (queryParams.length > 0){ 525 | query = '?' + queryParams.join('&'); 526 | } 527 | 528 | return this.http.get(this.api_url + '/1/objects/' + object + query, { 529 | headers: this.authHeader 530 | }) 531 | .retry(3) 532 | .map(res => res.json().data); 533 | } 534 | 535 | public getOne(object: string, id: string, deep: boolean = false, exclude: string[] = null, level: number = null) { 536 | let query: string = ''; 537 | let queryParams : string[] = []; 538 | 539 | if (deep){ 540 | queryParams.push('deep=true'); 541 | } 542 | if (exclude){ 543 | queryParams.push(exclude.join(',')); 544 | } 545 | if (level){ 546 | queryParams.push('level=' + level); 547 | } 548 | if (queryParams.length > 0){ 549 | query = '?' + queryParams.join(','); 550 | } 551 | 552 | return this.http.get(this.api_url + '/1/objects/' + object + '/' + id + query, { 553 | headers: this.authHeader 554 | }) 555 | .retry(3) 556 | .map(res => res.json()); 557 | } 558 | 559 | public delete(object: string, id: string) { 560 | let headers = this.authHeader; 561 | headers.append('Content-Type', 'application/json'); 562 | return this.http.delete( 563 | this.api_url + '/1/objects/' + object + '/' + id, 564 | { 565 | headers: headers 566 | } 567 | ); 568 | } 569 | 570 | public uploadFile(objectName: string, fileActionName: string, filename: string, filedata: string) { 571 | let headers = this.authHeader; 572 | headers.append('Content-Type', 'application/json'); 573 | let data = JSON.stringify({ 574 | 'filename': filename, 575 | 'filedata': filedata.substr(filedata.indexOf(',') + 1, filedata.length) //need to remove the file prefix type 576 | }); 577 | return this.http.post( 578 | this.api_url + URLS.actionUrl + objectName + '?name=' + fileActionName, 579 | data, 580 | { 581 | headers: headers 582 | } 583 | ); 584 | } 585 | 586 | public deleteFile(objectName: string, fileActionName: string, filename: string) { 587 | let headers = this.authHeader; 588 | headers.append('Content-Type', 'application/json'); 589 | return this.http.delete( 590 | this.api_url + URLS.actionUrl + objectName + '?name=' + fileActionName + "&filename=" + filename, 591 | { 592 | headers: headers 593 | } 594 | ); 595 | } 596 | 597 | public loginSocket(token, anonymousToken, appName) { 598 | 599 | 600 | this.socket = io.connect(URLS.socketUrl, {'forceNew':true }); 601 | 602 | this.socket.on('connect', () => { 603 | console.log('connected'); 604 | this.socket.emit("login", token, anonymousToken, appName); 605 | }); 606 | 607 | this.socket.on('disconnect', () => { 608 | console.log('disconnect'); 609 | }); 610 | 611 | this.socket.on('reconnecting', () => { 612 | console.log('reconnecting'); 613 | }); 614 | 615 | this.socket.on('error', (error: string) => { 616 | console.log('error: ${error}'); 617 | }); 618 | 619 | } 620 | 621 | public logoutSocket() { 622 | if (this.socket){ 623 | this.socket.close(); 624 | } 625 | } 626 | 627 | public on(eventName: string) { 628 | let socketStream = Observable.fromEvent(this.socket, eventName); 629 | return socketStream; 630 | } 631 | 632 | public getAuthType():string { 633 | return this.auth_type; 634 | } 635 | 636 | public getAuthStatus():string { 637 | return this.auth_status; 638 | } 639 | 640 | // user details 641 | public getUsername():string { 642 | return this.username; 643 | } 644 | 645 | public getUserDetails(force: boolean) { 646 | if (force){ 647 | let $obs = this.http.get(this.api_url + '/api/account/profile', { 648 | headers: this.authHeader 649 | }) 650 | .retry(3) 651 | .map(res => res.json()); 652 | 653 | $obs.subscribe( 654 | data => { 655 | localStorage.setItem('user', JSON.stringify(data)); 656 | }, 657 | err => { 658 | console.log(err); 659 | }, 660 | () => console.log('Got User Details')); 661 | 662 | return $obs; 663 | } 664 | else{ 665 | let userDetails = localStorage.getItem('user'); 666 | let promise = Promise.resolve(userDetails ? JSON.parse(userDetails) : null); 667 | let $obs = Observable.fromPromise(promise); 668 | return $obs; 669 | } 670 | 671 | } 672 | 673 | public getUserRole(): string { 674 | let userDetails = localStorage.getItem('user'); 675 | if (userDetails){ 676 | return userDetails.role; 677 | } 678 | else{ 679 | return null; 680 | } 681 | } 682 | 683 | private setAuthenticationState(): boolean { 684 | let storedToken = localStorage.getItem('auth_token'); 685 | if (storedToken){ 686 | this.auth_token = JSON.parse(storedToken); 687 | this.auth_type = this.auth_token.header_name == 'Anonymous' ? 'Anonymous' : 'Token'; 688 | this.auth_status = 'OK'; 689 | if (this.auth_type == 'Token'){ 690 | this.username = localStorage.getItem('username'); 691 | } 692 | this.app_name = localStorage.getItem('app_name'); 693 | this.anonymousToken = localStorage.getItem('anonymousToken'); 694 | return true; 695 | } 696 | else{ 697 | this.auth_token = {header_name: '', header_value: ''}; 698 | return false; 699 | } 700 | } 701 | 702 | private getSocialUrl(providerName: string, isSignup: boolean) { 703 | let provider = this.socialProviders[providerName]; 704 | let action = isSignup ? 'up' : 'in'; 705 | return 'user/socialSign' + action + 706 | '?provider=' + provider.label + 707 | '&response_type=token&client_id=self&redirect_uri=' + provider.url + 708 | '&state='; 709 | } 710 | 711 | private parseQueryString(queryString): any { 712 | let query = queryString.substr(queryString.indexOf('/?') + 2); 713 | let breakdown = {}; 714 | let vars = query.split('&'); 715 | for (var i = 0; i < vars.length; i++) { 716 | var pair = vars[i].split('='); 717 | breakdown[pair[0]] = JSON.parse(pair[1]); 718 | } 719 | return breakdown; 720 | } 721 | 722 | public extractErrorMessage(err) { 723 | return JSON.parse(err._body).error_description; 724 | } 725 | 726 | public useAnonymousAuth() { 727 | this.setAnonymousHeader(); 728 | } 729 | 730 | private setTokenHeader(jwt) { 731 | if (jwt) { 732 | this.auth_token.header_name = "Authorization"; 733 | this.auth_token.header_value = "Bearer " + jwt; 734 | this.storeAuthToken(this.auth_token); 735 | } 736 | } 737 | 738 | private setAnonymousHeader() { 739 | this.auth_status = "OK"; 740 | this.auth_token.header_name = "AnonymousToken"; 741 | this.auth_token.header_value = this.anonymousToken; 742 | this.storeAuthToken(this.auth_token); 743 | localStorage.setItem('username', 'Anonymous'); 744 | } 745 | 746 | private storeAuthToken(token) { 747 | localStorage.setItem('auth_token', JSON.stringify(token)); 748 | } 749 | 750 | private getToken(res) { 751 | return res.json().access_token; 752 | } 753 | 754 | private get authHeader() { 755 | var authHeader = new Headers(); 756 | if (this.auth_token && this.auth_token.header_name && this.auth_token.header_value){ 757 | authHeader.append(this.auth_token.header_name, this.auth_token.header_value); 758 | } 759 | return authHeader; 760 | } 761 | 762 | public logError(err) { 763 | console.error('Error: ' + err); 764 | } 765 | } 766 | -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | // tick this to make the cache invalidate and update 2 | const CACHE_VERSION = 1; 3 | const CURRENT_CACHES = { 4 | 'read-through': 'read-through-cache-v' + CACHE_VERSION 5 | }; 6 | 7 | self.addEventListener('activate', (event) => { 8 | // Delete all caches that aren't named in CURRENT_CACHES. 9 | // While there is only one cache in this example, the same logic will handle the case where 10 | // there are multiple versioned caches. 11 | const expectedCacheNames = Object.keys(CURRENT_CACHES).map((key) => { 12 | return CURRENT_CACHES[key]; 13 | }); 14 | 15 | event.waitUntil( 16 | caches.keys().then((cacheNames) => { 17 | return Promise.all( 18 | cacheNames.map((cacheName) => { 19 | if (expectedCacheNames.indexOf(cacheName) === -1) { 20 | // If this cache name isn't present in the array of "expected" cache names, then delete it. 21 | console.log('Deleting out of date cache:', cacheName); 22 | return caches.delete(cacheName); 23 | } 24 | }) 25 | ); 26 | }) 27 | ); 28 | }); 29 | 30 | // This sample illustrates an aggressive approach to caching, in which every valid response is 31 | // cached and every request is first checked against the cache. 32 | // This may not be an appropriate approach if your web application makes requests for 33 | // arbitrary URLs as part of its normal operation (e.g. a RSS client or a news aggregator), 34 | // as the cache could end up containing large responses that might not end up ever being accessed. 35 | // Other approaches, like selectively caching based on response headers or only caching 36 | // responses served from a specific domain, might be more appropriate for those use cases. 37 | self.addEventListener('fetch', (event) => { 38 | 39 | event.respondWith( 40 | caches.open(CURRENT_CACHES['read-through']).then((cache) => { 41 | return cache.match(event.request).then((response) => { 42 | if (response) { 43 | // If there is an entry in the cache for event.request, then response will be defined 44 | // and we can just return it. 45 | 46 | return response; 47 | } 48 | 49 | // Otherwise, if there is no entry in the cache for event.request, response will be 50 | // undefined, and we need to fetch() the resource. 51 | console.log(' No response for %s found in cache. ' + 52 | 'About to fetch from network...', event.request.url); 53 | 54 | // We call .clone() on the request since we might use it in the call to cache.put() later on. 55 | // Both fetch() and cache.put() "consume" the request, so we need to make a copy. 56 | // (see https://fetch.spec.whatwg.org/#dom-request-clone) 57 | return fetch(event.request.clone()).then((response) => { 58 | 59 | // Optional: add in extra conditions here, e.g. response.type == 'basic' to only cache 60 | // responses from the same domain. See https://fetch.spec.whatwg.org/#concept-response-type 61 | if (response.status < 400 && response.type === 'basic') { 62 | // We need to call .clone() on the response object to save a copy of it to the cache. 63 | // (https://fetch.spec.whatwg.org/#dom-request-clone) 64 | cache.put(event.request, response.clone()); 65 | } 66 | 67 | // Return the original response object, which will be used to fulfill the resource request. 68 | return response; 69 | }); 70 | }).catch((error) => { 71 | // This catch() will handle exceptions that arise from the match() or fetch() operations. 72 | // Note that a HTTP error response (e.g. 404) will NOT trigger an exception. 73 | // It will return a normal response object that has the appropriate error code set. 74 | console.error(' Read-through caching failed:', error); 75 | 76 | throw error; 77 | }); 78 | }) 79 | ); 80 | }); -------------------------------------------------------------------------------- /src/theme/global.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/v2/theming/ 2 | 3 | 4 | // Global CSS 5 | // -------------------------------------------------- 6 | // Put CSS rules here that you want to apply globally. 7 | // 8 | // To declare rules for a specific mode, create a child rule 9 | // for the .md, .ios, or .wp mode classes. The mode class is 10 | // automatically applied to the element in the app. 11 | // 12 | // App Shared Sass variables belong in app.variables.scss. 13 | -------------------------------------------------------------------------------- /src/theme/variables.scss: -------------------------------------------------------------------------------- 1 | // Ionic Variables and Theming. For more info, please see: 2 | // http://ionicframework.com/docs/v2/theming/ 3 | @import "ionic.globals"; 4 | 5 | 6 | // Shared Variables 7 | // -------------------------------------------------- 8 | // To customize the look and feel of this app, you can override 9 | // the Sass variables found in Ionic's source scss files. 10 | // To view all the possible Ionic variables, see: 11 | // http://ionicframework.com/docs/v2/theming/overriding-ionic-variables/ 12 | 13 | $text-color: #000; 14 | $background-color: #fff; 15 | 16 | 17 | // Named Color Variables 18 | // -------------------------------------------------- 19 | // Named colors makes it easy to reuse colors on various components. 20 | // It's highly recommended to change the default colors 21 | // to match your app's branding. Ionic uses a Sass map of 22 | // colors so you can add, rename and remove colors as needed. 23 | // The "primary" color is the only required color in the map. 24 | 25 | $colors: ( 26 | primary: #387ef5, 27 | secondary: #32db64, 28 | danger: #f53d3d, 29 | light: #f4f4f4, 30 | dark: #222, 31 | favorite: #69BB7B 32 | ); 33 | 34 | 35 | // App Theme 36 | // -------------------------------------------------- 37 | // Ionic apps can have different themes applied, which can 38 | // then be future customized. This import comes last 39 | // so that the above variables are used and Ionic's 40 | // default are overridden. 41 | 42 | @import "ionic.theme.default"; 43 | 44 | 45 | // Ionicons 46 | // -------------------------------------------------- 47 | // The premium icon font for Ionic. For more info, please see: 48 | // http://ionicframework.com/docs/v2/ionicons/ 49 | 50 | $ionicons-font-path: "../assets/fonts"; 51 | @import "ionicons"; 52 | -------------------------------------------------------------------------------- /www/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "My Ionic App", 3 | "short_name": "My Ionic App", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "icons": [{ 7 | "src": "icon.png", 8 | "sizes": "512x512", 9 | "type": "image/png" 10 | }] 11 | } --------------------------------------------------------------------------------