mirror of
https://github.com/fergalmoran/icebreaker-mobile.git
synced 2025-12-22 09:39:31 +00:00
Initial Commit
This commit is contained in:
46
app/app.ts
Normal file
46
app/app.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
//import 'es6-shim';
|
||||
import {Component} from '@angular/core';
|
||||
import {ionicBootstrap, Platform} from 'ionic-angular';
|
||||
import {StatusBar} from 'ionic-native';
|
||||
import {HomePage} from './pages/home/home';
|
||||
|
||||
|
||||
|
||||
import {
|
||||
FIREBASE_PROVIDERS, defaultFirebase,
|
||||
AngularFire, firebaseAuthConfig, AuthProviders,
|
||||
AuthMethods
|
||||
} from 'angularfire2';
|
||||
|
||||
@Component({
|
||||
template: '<ion-nav [root]="rootPage"></ion-nav>',
|
||||
providers: [
|
||||
FIREBASE_PROVIDERS,
|
||||
// Initialize Firebase app
|
||||
defaultFirebase({
|
||||
apiKey: "AIzaSyBltKr00N9-KPzkGYut2tkZSk_TIiydZm8",
|
||||
authDomain: "icebreaker-f5f20.firebaseapp.com",
|
||||
databaseURL: "https://icebreaker-f5f20.firebaseio.com",
|
||||
storageBucket: "icebreaker-f5f20.appspot.com",
|
||||
}),
|
||||
firebaseAuthConfig({
|
||||
provider: AuthProviders.Password,
|
||||
method: AuthMethods.Password,
|
||||
remember: 'default',
|
||||
scope: ['email']
|
||||
})
|
||||
]
|
||||
})
|
||||
export class MyApp {
|
||||
rootPage: any = HomePage;
|
||||
|
||||
constructor(platform: Platform) {
|
||||
platform.ready().then(() => {
|
||||
// Okay, so the platform is ready and our plugins are available.
|
||||
// Here you can do any higher level native things you might need.
|
||||
StatusBar.styleDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ionicBootstrap(MyApp);
|
||||
18
app/lib/MomentDate.ts
Normal file
18
app/lib/MomentDate.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
import {Pipe} from '@angular/core';
|
||||
import * as moment from 'moment';
|
||||
|
||||
// Tell Angular2 we're creating a Pipe with TypeScript decorators
|
||||
@Pipe({
|
||||
name: 'MomentDate'
|
||||
})
|
||||
export class MomentDate {
|
||||
|
||||
// Transform is the new "return function(value, args)" in Angular 1.x
|
||||
transform(value, args?) {
|
||||
// see http://momentjs.com/docs/#/displaying/ for information
|
||||
// on formatting the date using moment
|
||||
return moment(value).format(args[0])
|
||||
}
|
||||
|
||||
}
|
||||
41
app/pages/home/home.html
Normal file
41
app/pages/home/home.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<ion-header>
|
||||
<ion-navbar>
|
||||
<ion-title>
|
||||
Ice Breaker
|
||||
</ion-title>
|
||||
<ion-buttons end>
|
||||
<button (click)="logoutClicked()">
|
||||
<ion-icon name="contact"></ion-icon>
|
||||
{{buttonTitle}}
|
||||
</button>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content class="home">
|
||||
<ion-item>
|
||||
<ion-row>
|
||||
<ion-col width-100>
|
||||
<button (click)="addNewItemClicked()">Add Item</button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-item>
|
||||
<ion-card *ngFor="let item of textItems | async">
|
||||
<ion-card-header>
|
||||
{{item.title}}
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
{{item.description}} - {{ item.timestamp | MomentDate:"LLL"}}
|
||||
</ion-card-content>
|
||||
<ion-col offset-80>
|
||||
<button (click)="deleteItemClicked(item)">Delete</button>
|
||||
</ion-col>
|
||||
</ion-card>
|
||||
<ion-card *ngFor="let user of usersWithMessages | async">
|
||||
<ion-card-header>
|
||||
{{user.displayName}}
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
{{ (user.messages | async) | json}}
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ion-content>
|
||||
3
app/pages/home/home.scss
Normal file
3
app/pages/home/home.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
.home {
|
||||
|
||||
}
|
||||
127
app/pages/home/home.ts
Normal file
127
app/pages/home/home.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {Modal, NavController, Page} from 'ionic-angular';
|
||||
import {Component, OnInit, Inject} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {LoginPage} from '../login/login'
|
||||
import {NewItemModal} from '../item/newItem';
|
||||
import {MomentDate} from '../../lib/MomentDate'
|
||||
import 'rxjs';
|
||||
|
||||
|
||||
import { AngularFire, AuthProviders, AuthMethods } from 'angularfire2';
|
||||
|
||||
@Page({
|
||||
templateUrl: 'build/pages/home/home.html',
|
||||
pipes: [MomentDate]
|
||||
})
|
||||
export class HomePage implements OnInit {
|
||||
textItems: Observable<any[]>;
|
||||
usersWithMessages: Observable<any[]>;
|
||||
authInfo: any
|
||||
displayName: any
|
||||
buttonTitle = "LOGIN"
|
||||
|
||||
constructor(
|
||||
public af: AngularFire,
|
||||
public navCtrl: NavController) {
|
||||
// dont do anything heavy here... do it in ngOnInit
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
// subscribe to the auth object to check for the login status
|
||||
// of the user, if logged in, save some user information and
|
||||
// execute the firebase query...
|
||||
// .. otherwise
|
||||
// show the login modal page
|
||||
this.af.auth.subscribe((data) => {
|
||||
console.log("in auth subscribe", data)
|
||||
|
||||
|
||||
if (data) {
|
||||
|
||||
this.af.auth.unsubscribe()
|
||||
|
||||
this.buttonTitle = "LOGOUT"
|
||||
|
||||
// if no user, then add it
|
||||
this.addOrUpdateUser(data)
|
||||
|
||||
|
||||
if (data.auth.providerData[0].providerId === "twitter.com") {
|
||||
this.authInfo = data.auth.providerData[0]
|
||||
this.displayName = data.auth.providerData[0].displayName
|
||||
} else if (data.github) {
|
||||
this.authInfo = data.github
|
||||
//this.authInfo.displayName = data.github.displayName
|
||||
} else {
|
||||
this.authInfo = data.auth || {}
|
||||
this.displayName = data.auth.providerData[0].email
|
||||
}
|
||||
this.textItems = this.af.database.list('/textItems')
|
||||
|
||||
//this.getMoreData()
|
||||
|
||||
} else {
|
||||
this.buttonTitle = "LOGIN"
|
||||
this.authInfo = null
|
||||
this.displayLoginModal()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
addOrUpdateUser(_authData) {
|
||||
const itemObservable = this.af.database.object('/users/' + _authData.uid);
|
||||
itemObservable.set({
|
||||
"provider": _authData.auth.providerData[0].providerId,
|
||||
"avatar": _authData.auth.photoURL || "MISSING",
|
||||
"displayName": _authData.auth.providerData[0].displayName || _authData.auth.email,
|
||||
})
|
||||
}
|
||||
getMoreData() {
|
||||
this.usersWithMessages = this.af.list('/users').map((_users) => {
|
||||
return _users.map((_user) => {
|
||||
_user.messages = this.af.object("/userObjects/public-messages/" + _user.$key)
|
||||
return _user
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* displays the login window
|
||||
*/
|
||||
displayLoginModal() {
|
||||
let loginPage = Modal.create(LoginPage);
|
||||
this.navCtrl.present(loginPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* adds a new item to firebase /textItems
|
||||
*
|
||||
* pass in the auth information to the modal to associate the user with the newly
|
||||
* created entry
|
||||
*/
|
||||
addNewItemClicked(_data) {
|
||||
let newItemPage = Modal.create(NewItemModal, { "user": this.authInfo });
|
||||
this.navCtrl.present(newItemPage);
|
||||
}
|
||||
|
||||
deleteItemClicked(_data) {
|
||||
this.af.database.object("/textItems/" + _data.$key).remove()
|
||||
.then(() => { alert("success") })
|
||||
.catch((_error) => { alert("Error") })
|
||||
}
|
||||
|
||||
/**
|
||||
* logs out the current user
|
||||
*/
|
||||
logoutClicked() {
|
||||
|
||||
if (this.authInfo && (this.authInfo.email || this.authInfo.providerId)) {
|
||||
this.af.auth.logout();
|
||||
this.authInfo = null
|
||||
this.displayLoginModal()
|
||||
} else {
|
||||
this.displayLoginModal()
|
||||
}
|
||||
}
|
||||
}
|
||||
26
app/pages/item/newItem.html
Normal file
26
app/pages/item/newItem.html
Normal file
@@ -0,0 +1,26 @@
|
||||
<ion-navbar *navbar hideBackButton>
|
||||
<ion-title>
|
||||
Angular Fire Test App - New Item
|
||||
</ion-title>
|
||||
</ion-navbar>
|
||||
<ion-content padding>
|
||||
|
||||
<form #modelData="ngForm" (ngSubmit)="addTheItem(modelData.value)">
|
||||
<ion-item>
|
||||
<ion-label>Title</ion-label>
|
||||
<ion-input required type="text" ngControl="title"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Description</ion-label>
|
||||
<ion-input required type="text" ngControl="description"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<div padding>
|
||||
<button block type="submit" [disabled]="!modelData.valid">Add New Item</button>
|
||||
<button block (click)="cancelItem($event)">Cancel</button>
|
||||
</div>
|
||||
<div padding>
|
||||
<p *ngIf="error" class="error">Error: {{ error.code }}</p>
|
||||
</div>
|
||||
</form>
|
||||
</ion-content>
|
||||
61
app/pages/item/newItem.ts
Normal file
61
app/pages/item/newItem.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import {Modal, NavController, Page, ViewController, NavParams} from 'ionic-angular';
|
||||
import {Component, OnInit, Inject} from '@angular/core';
|
||||
import { AngularFire } from 'angularfire2';
|
||||
|
||||
@Page({
|
||||
templateUrl: 'build/pages/item/newItem.html'
|
||||
})
|
||||
|
||||
|
||||
export class NewItemModal {
|
||||
|
||||
error: any
|
||||
|
||||
/**
|
||||
* even though this is a Modal, we can access the navParam data passed into the object
|
||||
* when the object is created. This is how we access the user auth data
|
||||
*/
|
||||
constructor(
|
||||
public af: AngularFire,
|
||||
public viewCtrl: ViewController,
|
||||
private _navParams: NavParams) {
|
||||
console.log("initialize NewItemModal")
|
||||
}
|
||||
|
||||
/**
|
||||
* this will dismiss the modal page
|
||||
*/
|
||||
dismiss() {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
|
||||
/**
|
||||
* exits the modal with no new item added
|
||||
*/
|
||||
cancelItem(_event) {
|
||||
_event.preventDefault();
|
||||
this.dismiss()
|
||||
}
|
||||
|
||||
/**
|
||||
* adds the item to the path /textItems
|
||||
*/
|
||||
addTheItem(_data) {
|
||||
var textItems = this.af.database.list('/textItems');
|
||||
textItems.push({
|
||||
"title": _data.title,
|
||||
"description": _data.description,
|
||||
// auth data from the navParam object...
|
||||
"user": this._navParams.get("user").email,
|
||||
"timestamp": (new Date()).getTime()
|
||||
}).then((_data) => {
|
||||
console.log(_data)
|
||||
alert("Item Successfully Added")
|
||||
this.dismiss()
|
||||
}).catch((_error) => {
|
||||
console.log(_error)
|
||||
alert("Error Adding Item")
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
29
app/pages/login/login.html
Normal file
29
app/pages/login/login.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<ion-navbar *navbar>
|
||||
<ion-title>
|
||||
Ice Breaker
|
||||
</ion-title>
|
||||
</ion-navbar>
|
||||
<ion-content padding>
|
||||
<h1>User Login</h1>
|
||||
<form #loginCreds="ngForm">
|
||||
<ion-item>
|
||||
<ion-label>Email</ion-label>
|
||||
<ion-input type="text" ngControl="email"></ion-input>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Password</ion-label>
|
||||
<ion-input type="password" ngControl="password"></ion-input>
|
||||
</ion-item>
|
||||
<div padding>
|
||||
<button block (click)="login(loginCreds.value, $event)">Login</button>
|
||||
<button block (click)="registerUser(loginCreds.value, $event)">Create Account</button>
|
||||
</div>
|
||||
<div padding>
|
||||
<button block (click)="registerUserWithFacebook(loginCreds.value, $event)">Facebook</button>
|
||||
<button block (click)="registerUserWithTwitter(loginCreds.value, $event)">Twitter</button>
|
||||
</div>
|
||||
<div padding>
|
||||
<p *ngIf="error" class="error">Error: {{ error.code }}</p>
|
||||
</div>
|
||||
</form>
|
||||
</ion-content>
|
||||
128
app/pages/login/login.ts
Normal file
128
app/pages/login/login.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import {Modal, NavController, Page, ViewController} from 'ionic-angular';
|
||||
import {Component, OnInit, Inject} from '@angular/core';
|
||||
import {AngularFire, AuthProviders, AuthMethods } from 'angularfire2';
|
||||
|
||||
@Page({
|
||||
templateUrl: 'build/pages/login/login.html'
|
||||
})
|
||||
export class LoginPage {
|
||||
|
||||
error: any
|
||||
|
||||
constructor(public af: AngularFire,
|
||||
public viewCtrl: ViewController) { }
|
||||
/**
|
||||
* this will dismiss the modal page
|
||||
*/
|
||||
dismiss() {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
/**
|
||||
* this create in the user using the form credentials.
|
||||
*
|
||||
* we are preventing the default behavor of submitting
|
||||
* the form
|
||||
*
|
||||
* @param _credentials {Object} the email and password from the form
|
||||
* @param _event {Object} the event information from the form submit
|
||||
*/
|
||||
registerUser(_credentials, _event) {
|
||||
_event.preventDefault();
|
||||
|
||||
|
||||
this.af.auth.createUser(_credentials)
|
||||
.then((user) => {
|
||||
console.log(`Create User Success:`, user);
|
||||
_credentials.created = true;
|
||||
|
||||
return this.login(_credentials, _event);
|
||||
})
|
||||
.catch(e => console.error(`Create User Failure:`, e));
|
||||
}
|
||||
|
||||
registerUserWithFacebook(_credentials, _event) {
|
||||
_event.preventDefault();
|
||||
|
||||
this.af.auth.login({
|
||||
provider: AuthProviders.Facebook,
|
||||
method: AuthMethods.Popup
|
||||
}).then((value) => {
|
||||
this.dismiss()
|
||||
}).catch((error) => {
|
||||
this.error = error
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
registerUserWithTwitter(_credentials, _event) {
|
||||
_event.preventDefault();
|
||||
|
||||
this.af.auth.login({
|
||||
provider: AuthProviders.Twitter,
|
||||
method: AuthMethods.Redirect
|
||||
}).then((authData) => {
|
||||
console.log(authData)
|
||||
|
||||
// already has user... need better info??
|
||||
if (!authData) {
|
||||
this.dismiss()
|
||||
}
|
||||
|
||||
|
||||
const itemObservable = this.af.database.object('/users/' + authData.uid);
|
||||
itemObservable.set({
|
||||
"provider": authData.auth.providerData[0].providerId,
|
||||
"avatar": authData.auth.photoURL || "MISSING",
|
||||
"displayName": authData.auth.providerData[0].displayName || authData.auth.email,
|
||||
})
|
||||
|
||||
}).then((value) => {
|
||||
this.dismiss()
|
||||
}).catch((error) => {
|
||||
this.error = error
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
/**
|
||||
* this logs in the user using the form credentials.
|
||||
*
|
||||
* if the user is a new user, then we need to create the user AFTER
|
||||
* we have successfully logged in
|
||||
*
|
||||
* @param _credentials {Object} the email and password from the form
|
||||
* @param _event {Object} the event information from the form submit
|
||||
*/
|
||||
login(credentials, _event) {
|
||||
_event.preventDefault();
|
||||
|
||||
// if this was called from the register user, the check if we
|
||||
// need to create the user object or not
|
||||
let addUser = credentials.created
|
||||
credentials.created = null;
|
||||
|
||||
// login usig the email/password auth provider
|
||||
this.af.auth.login(credentials, {
|
||||
provider: AuthProviders.Password,
|
||||
method: AuthMethods.Password
|
||||
}).then((authData) => {
|
||||
console.log(authData)
|
||||
|
||||
if (addUser) {
|
||||
const itemObservable = this.af.database.object('/users/' + authData.uid);
|
||||
itemObservable.set({
|
||||
"provider": authData.auth.providerData[0].providerId,
|
||||
"avatar": authData.auth.photoURL || "MISSING",
|
||||
"displayName": authData.auth.providerData[0].displayName || authData.auth.email,
|
||||
})
|
||||
} else {
|
||||
this.dismiss()
|
||||
}
|
||||
}).then((value) => {
|
||||
this.dismiss()
|
||||
}).catch((error) => {
|
||||
this.error = error
|
||||
console.log(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
10
app/theme/app.core.scss
Normal file
10
app/theme/app.core.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
// http://ionicframework.com/docs/v2/theming/
|
||||
|
||||
|
||||
// App Shared Imports
|
||||
// --------------------------------------------------
|
||||
// These are the imports which make up the design of this app.
|
||||
// By default each design mode includes these shared imports.
|
||||
// App Shared Sass variables belong in app.variables.scss.
|
||||
|
||||
@import '../pages/home/home';
|
||||
31
app/theme/app.ios.scss
Normal file
31
app/theme/app.ios.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
// http://ionicframework.com/docs/v2/theming/
|
||||
|
||||
|
||||
// App Shared Variables
|
||||
// --------------------------------------------------
|
||||
// Shared Sass variables go in the app.variables.scss file
|
||||
@import 'app.variables';
|
||||
|
||||
|
||||
// App iOS Variables
|
||||
// --------------------------------------------------
|
||||
// iOS only Sass variables can go here
|
||||
|
||||
|
||||
// Ionic iOS Sass
|
||||
// --------------------------------------------------
|
||||
// Custom App variables must be declared before importing Ionic.
|
||||
// Ionic will use its default values when a custom variable isn't provided.
|
||||
@import 'ionic.ios';
|
||||
|
||||
|
||||
// App Shared Sass
|
||||
// --------------------------------------------------
|
||||
// All Sass files that make up this app goes into the app.core.scss file.
|
||||
// For simpler CSS overrides, custom app CSS must come after Ionic's CSS.
|
||||
@import 'app.core';
|
||||
|
||||
|
||||
// App iOS Only Sass
|
||||
// --------------------------------------------------
|
||||
// CSS that should only apply to the iOS app
|
||||
31
app/theme/app.md.scss
Normal file
31
app/theme/app.md.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
// http://ionicframework.com/docs/v2/theming/
|
||||
|
||||
|
||||
// App Shared Variables
|
||||
// --------------------------------------------------
|
||||
// Shared Sass variables go in the app.variables.scss file
|
||||
@import 'app.variables';
|
||||
|
||||
|
||||
// App Material Design Variables
|
||||
// --------------------------------------------------
|
||||
// Material Design only Sass variables can go here
|
||||
|
||||
|
||||
// Ionic Material Design Sass
|
||||
// --------------------------------------------------
|
||||
// Custom App variables must be declared before importing Ionic.
|
||||
// Ionic will use its default values when a custom variable isn't provided.
|
||||
@import 'ionic.md';
|
||||
|
||||
|
||||
// App Shared Sass
|
||||
// --------------------------------------------------
|
||||
// All Sass files that make up this app goes into the app.core.scss file.
|
||||
// For simpler CSS overrides, custom app CSS must come after Ionic's CSS.
|
||||
@import 'app.core';
|
||||
|
||||
|
||||
// App Material Design Only Sass
|
||||
// --------------------------------------------------
|
||||
// CSS that should only apply to the Material Design app
|
||||
35
app/theme/app.variables.scss
Normal file
35
app/theme/app.variables.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
// http://ionicframework.com/docs/v2/theming/
|
||||
|
||||
// Ionic Shared Functions
|
||||
// --------------------------------------------------
|
||||
// Makes Ionic Sass functions available to your App
|
||||
|
||||
@import 'globals.core';
|
||||
|
||||
// App Shared Variables
|
||||
// --------------------------------------------------
|
||||
// To customize the look and feel of this app, you can override
|
||||
// the Sass variables found in Ionic's source scss files. Setting
|
||||
// variables before Ionic's Sass will use these variables rather than
|
||||
// Ionic's default Sass variable values. App Shared Sass imports belong
|
||||
// in the app.core.scss file and not this file. Sass variables specific
|
||||
// to the mode belong in either the app.ios.scss or app.md.scss files.
|
||||
|
||||
|
||||
// App Shared Color Variables
|
||||
// --------------------------------------------------
|
||||
// It's highly recommended to change the default colors
|
||||
// to match your app's branding. Ionic uses a Sass map of
|
||||
// colors so you can add, rename and remove colors as needed.
|
||||
// The "primary" color is the only required color in the map.
|
||||
// Both iOS and MD colors can be further customized if colors
|
||||
// are different per mode.
|
||||
|
||||
$colors: (
|
||||
primary: #387ef5,
|
||||
secondary: #32db64,
|
||||
danger: #f53d3d,
|
||||
light: #f4f4f4,
|
||||
dark: #222,
|
||||
favorite: #69BB7B
|
||||
);
|
||||
31
app/theme/app.wp.scss
Normal file
31
app/theme/app.wp.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
// http://ionicframework.com/docs/v2/theming/
|
||||
|
||||
|
||||
// App Shared Variables
|
||||
// --------------------------------------------------
|
||||
// Shared Sass variables go in the app.variables.scss file
|
||||
@import 'app.variables';
|
||||
|
||||
|
||||
// App Windows Variables
|
||||
// --------------------------------------------------
|
||||
// Windows only Sass variables can go here
|
||||
|
||||
|
||||
// Ionic Windows Sass
|
||||
// --------------------------------------------------
|
||||
// Custom App variables must be declared before importing Ionic.
|
||||
// Ionic will use its default values when a custom variable isn't provided.
|
||||
@import "ionic.wp";
|
||||
|
||||
|
||||
// App Shared Sass
|
||||
// --------------------------------------------------
|
||||
// All Sass files that make up this app goes into the app.core.scss file.
|
||||
// For simpler CSS overrides, custom app CSS must come after Ionic's CSS.
|
||||
@import 'app.core';
|
||||
|
||||
|
||||
// App Windows Only Sass
|
||||
// --------------------------------------------------
|
||||
// CSS that should only apply to the Windows app
|
||||
Reference in New Issue
Block a user