community: learn: add angular-graphql tutorial (#2624)

This commit is contained in:
Apoorv Vardhan 2019-08-10 15:47:51 +05:30 committed by Shahidh K Muhammed
parent 18822ded9a
commit 9e18f0d297
171 changed files with 31164 additions and 8 deletions

View File

@ -0,0 +1,50 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: angular-apollo
name: angular-apollo
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: angular-apollo
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 100%
template:
metadata:
labels:
app: angular-apollo
spec:
containers:
- image: hasura/base-git-image:0.7
imagePullPolicy: IfNotPresent
name: angular-apollo
ports:
- containerPort: 8080
protocol: TCP
readinessProbe:
httpGet:
path: /graphql/angular-apollo/introduction
port: 8080
---
apiVersion: v1
kind: Service
metadata:
labels:
app: angular-apollo
name: angular-apollo
namespace: default
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: angular-apollo
type: ClusterIP

View File

@ -37,10 +37,18 @@ data:
/graphql/reason-react-apollo/boilerplate.zip https://graphql-engine-cdn.hasura.io/learn-hasura/boilerplates/reason-react-apollo/boilerplate.zip
}
redir 301 {
/graphql/angular-apollo/boilerplate.zip https://graphql-engine-cdn.hasura.io/learn-hasura/boilerplates/angular-apollo/boilerplate.zip
}
redir 301 {
/graphql/flutter-graphql/boilerplate.zip https://graphql-engine-cdn.hasura.io/learn-hasura/boilerplates/flutter-graphql/boilerplate.zip
}
redir 301 {
/graphql/angular-apollo/ /graphql/angular-apollo/introduction
}
redir 301 {
/graphql/flutter-graphql/ /graphql/flutter-graphql/introduction
}
@ -129,6 +137,8 @@ data:
proxy /graphql/reason-react-apollo reason-react-apollo
proxy /graphql/angular-apollo angular-apollo
proxy /graphql/flutter-graphql flutter-graphql
proxy /graphql hasura/v1/graphql {

View File

@ -69,6 +69,11 @@ spec:
name: reason-react-apollo
path: ./community/learn/graphql-tutorials/tutorials/reason-react-apollo/tutorial-site
name: reason-react-apollo
- containers:
- dockerfile: ./community/learn/graphql-tutorials/tutorials/angular-apollo/tutorial-site/Dockerfile
name: angular-apollo
path: ./community/learn/graphql-tutorials/tutorials/angular-apollo/tutorial-site
name: angular-apollo
- containers:
- dockerfile: ./community/learn/graphql-tutorials/tutorials/flutter-graphql/tutorial-site/Dockerfile
name: flutter-graphql

View File

@ -8,7 +8,7 @@ import GithubLink from '../../src/GithubLink.js'
In this section, we will learn more about GraphQL Queries and integrate with the android app UI.
### Create `.graphql` files with your queries or mutations
### Create .graphql files with your queries or mutations
Apollo android generates code from queries and mutations contained in `.graphql` files in your target. Let's define a graphql query to fetch the required data. We define all our queries and mutations in `api.graphql` file.

View File

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events.json
speed-measure-plugin.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,120 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"app-boilerplate": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/app-boilerplate",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "app-boilerplate:build"
},
"configurations": {
"production": {
"browserTarget": "app-boilerplate:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "app-boilerplate:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "app-boilerplate:serve"
},
"configurations": {
"production": {
"devServerTarget": "app-boilerplate:serve:production"
}
}
}
}
}},
"defaultProject": "app-boilerplate"
}

View File

@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -0,0 +1,32 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to app-boilerplate!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css('app-root h1')).getText() as Promise<string>;
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/app-boilerplate'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
{
"name": "app-boilerplate",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --port 3000",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~8.1.2",
"@angular/common": "~8.1.2",
"@angular/compiler": "~8.1.2",
"@angular/core": "~8.1.2",
"@angular/forms": "~8.1.2",
"@angular/platform-browser": "~8.1.2",
"@angular/platform-browser-dynamic": "~8.1.2",
"@angular/router": "~8.1.2",
"auth0-js": "^9.10.0",
"rxjs": "~6.4.0",
"tslib": "^1.9.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.801.2",
"@angular/cli": "~8.1.2",
"@angular/compiler-cli": "~8.1.2",
"@angular/language-service": "~8.1.2",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.4.3"
}
}

View File

@ -0,0 +1,14 @@
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'App',
templateUrl: './app.component.html',
})
export class App {
@Output('logout') logout: EventEmitter<any> = new EventEmitter();
logoutHandler(item: any) {
this.logout.emit(item);
}
}

View File

@ -0,0 +1,6 @@
<Callback *ngIf="isAuthenticated == 'loading'"></Callback>
<Login *ngIf="!isAuthenticated" (loginHandler)="login($event)"></Login>
<App *ngIf="isAuthenticated === true" (logout)="logout()"></App>
<router-outlet></router-outlet>

View File

@ -0,0 +1,125 @@
import { Component, OnInit, Input } from '@angular/core';
import auth0 from 'auth0-js';
import {AUTH_CONFIG} from './auth0-variables';
@Component({
selector: 'Auth0Wrapper',
templateUrl: './Auth0Wrapper.template.html',
})
export class Auth0Wrapper implements OnInit {
auth0 = new auth0.WebAuth({
domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.callbackUrl,
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
responseType: 'token id_token',
scope: 'openid profile'
});
isAuthenticated:any = false;// This can be true, false, 'loading'
idToken = null;
accessToken;
expiresAt;
@Input('location') location: any;
constructor() {
}
login() {
this.auth0.authorize();
}
handleAuthentication = () => {
this.isAuthenticated= 'loading';
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
} else if (err) {
this.logout();
console.error(err);
alert(`Error: ${err.error} - ${err.errorDescription}`);
}
});
};
getAccessToken() {
return this.accessToken;
}
getIdToken() {
return this.idToken;
}
setSession(authResult) {
// Set isLoggedIn flag in localStorage
localStorage.setItem('isLoggedIn', 'true');
// Set the time that the access token will expire at
let expiresAt = (authResult.expiresIn * 1000) + new Date().getTime();
this.accessToken = authResult.accessToken;
this.idToken = authResult.idToken;
this.expiresAt = expiresAt;
// navigate to the home route
this.isAuthenticated = true;
this.idToken = authResult.idToken;
}
renewSession() {
this.isAuthenticated = 'loading';
this.auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
} else if (err) {
this.logout();
console.log(err);
alert(`Could not get a new token (${err.error}: ${err.error_description}).`);
}
});
}
logout() {
// Remove tokens and expiry time
this.accessToken = null;
this.idToken = null;
this.expiresAt = 0;
// Remove isLoggedIn flag from localStorage
localStorage.removeItem('isLoggedIn');
this.auth0.logout({
return_to: window.location.origin
});
// navigate to the home route
this.isAuthenticated = false;
this.idToken = null;
}
isExpired () {
// Check whether the current time is past the
// access token's expiry time
let expiresAt = this.expiresAt;
return new Date().getTime() > expiresAt;
}
ngOnInit() {
// If this is a callback URL then do the right things
const location = window.location;
if (location && location.pathname.startsWith('/callback') && /access_token|id_token|error/.test(location.hash)) {
this.handleAuthentication();
return;
}
// On first load, check if we are already logged in and get the idTokens and things
if(localStorage.getItem('isLoggedIn') === 'true') {
this.renewSession();
return;
}
}
}

View File

@ -0,0 +1,3 @@
<div class="callback">
<img src="../../assets/loading.svg" alt="loading" />
</div>

View File

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'Callback',
templateUrl: './Callback.template.html',
})
export class Callback {
}

View File

@ -0,0 +1,19 @@
<div class="overlay">
<div class="overlay-content">
<div class="overlay-heading">
Welcome to the GraphQL tutorial app
</div>
<div class="overlay-message">
Please login to continue
</div>
<div class="overlay-action">
<Button
id="qsLoginBtn"
type="button"
class="btn-margin loginBtn btn btn-primary "
(click)="loginHandlerWrapper()">
Log In
</Button>
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'Login',
templateUrl: './Login.template.html',
})
export class Login {
@Output('loginHandler') loginHandler: EventEmitter<any> = new EventEmitter();
loginHandlerWrapper(item: any) {
this.loginHandler.emit(item);
}
}

View File

@ -0,0 +1,8 @@
<Button
id="qsLogoutBtn"
type="button"
class="btn-margin logoutBtn btn btn-primary "
(click)="logoutHandlerWrapper()"
>
Log Out
</Button>

View File

@ -0,0 +1,14 @@
import { Component, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'LogoutBtn',
templateUrl: './Logout.template.html',
})
export class LogoutBtn {
@Output('logoutHandler') logoutHandler: EventEmitter<any> = new EventEmitter();
logoutHandlerWrapper(item: any) {
this.logoutHandler.emit(item);
}
}

View File

@ -0,0 +1,6 @@
export const AUTH_CONFIG = {
domain: 'graphql-tutorials.auth0.com',
clientId: 'P38qnFo1lFAQJrzkun--wEzqljVNGcWW',
callbackUrl: 'http://localhost:3000/callback'
};

View File

@ -0,0 +1,14 @@
<nav class="m-bottom-0 navbar navbar-default">
<div class="container-fluid">
<div class="navHeader navbar-header">
<span class="navBrand navbar-brand">GraphQL Tutorial App</span>
<ul class="nav navbar-nav navbar-right">
<li role="presentation" class="">
<a role="button" href="#">
<LogoutBtn (logoutHandler)="logoutHandlerWrapper()"></LogoutBtn>
</a>
</li>
</ul>
</div>
</div>
</nav>

View File

@ -0,0 +1,15 @@
import { Component, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'Header',
templateUrl: './Header.template.html',
})
export class Header {
@Output('logoutHandler') logoutHandler: EventEmitter<any> = new EventEmitter();
logoutHandlerWrapper(item: any) {
this.logoutHandler.emit(item);
}
}

View File

@ -0,0 +1,8 @@
<div class="userInfo">
<div class="userImg">
<i class="far fa-user" ></i>
</div>
<div class="userName">
{{user.name}}
</div>
</div>

View File

@ -0,0 +1,10 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'OnlineUser',
templateUrl: './OnlineUser.template.html',
})
export class OnlineUser {
@Input('user') user: any;
}

View File

@ -0,0 +1,8 @@
<div class="onlineUsersWrapper">
<div class="sliderHeader">
Online users - {{onlineUsers.length}}
</div>
<ng-container *ngFor="let user of onlineUsers; let i = index" [attr.data-index]="i">
<OnlineUser [user]="user"></OnlineUser>
</ng-container>
</div>

View File

@ -0,0 +1,14 @@
import { Component } from '@angular/core';
@Component({
selector: 'OnlineUsersWrapper',
templateUrl: './OnlineUsersWrapper.template.html',
})
export class OnlineUsersWrapper {
onlineUsers = [
{ name: "someUser1" },
{ name: "someUser2" }
]
}

View File

@ -0,0 +1,11 @@
<li>
<div class="userInfoPublic" title={todo.user.name}>
@{{todo.user.name}}
</div>
<div [ngClass]="todo.is_completed ? 'labelContent completed' : 'labelContent'">
<div>
{{todo.title}}
</div>
</div>
</li>

View File

@ -0,0 +1,10 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'TaskItem',
templateUrl: './TaskItem.template.html',
})
export class TaskItem {
@Input('todo') todo: any;
}

View File

@ -0,0 +1,27 @@
<div class="footerList">
<span> {{itemCount}} item{{itemCount !== 1 ? "s" : ""}}</span>
<ul>
<li (click)="filterResultsHandler('all')">
<a [ngClass]="currentFilter === 'all' ? 'selected' : ''">
All
</a>
</li>
<li (click)="filterResultsHandler('active')">
<a [ngClass]= "currentFilter === 'active' ? 'selected' : ''">
Active
</a>
</li>
<li (click)="filterResultsHandler('completed')">
<a [ngClass]="currentFilter === 'completed' ? 'selected' : ''">
Completed
</a>
</li>
</ul>
<button (click)="clearCompletedFnWrapper()" class="clearComp">
Clear completed
</button>
</div>

View File

@ -0,0 +1,38 @@
import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
@Component({
selector: 'TodoFilters',
templateUrl: './TodoFilters.template.html',
})
export class TodoFilters implements OnChanges{
@Input('todos') todos: any;
@Input('currentFilter') currentFilter: any;
@Output('filterResultsFn') filterResultsFn: EventEmitter<any> = new EventEmitter();;
@Output('clearCompletedFn') clearCompletedFn: EventEmitter<any> = new EventEmitter();;
itemCount: any;
ngOnChanges() {
this.updateItemCount();
}
updateItemCount() {
this.itemCount = this.todos.length;
const activeTodos = this.todos.filter(todo => todo.is_completed !== true);
if (this.currentFilter === 'active') {
this.itemCount = activeTodos.length;
} else if (this.currentFilter === 'completed') {
this.itemCount = this.todos.length - activeTodos.length;
}
}
clearCompletedFnWrapper(item: any) {
this.clearCompletedFn.emit(item);
}
filterResultsHandler(filter) {
this.filterResultsFn.emit({ event:event, filter: filter });
};
}

View File

@ -0,0 +1,7 @@
<form class="formInput" (submit)="addTodo($event)">
<input
class="input"
placeholder="What needs to be done?"
/>
<i class="inputMarker fa fa-angle-right"></i>
</form>

View File

@ -0,0 +1,14 @@
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'TodoInput',
templateUrl: './TodoInput.template.html'
})
export class TodoInput {
@Input('isPublic') isPublic: any = false;
addTodo(e) {
e.preventDefault();
}
}

View File

@ -0,0 +1,24 @@
<li>
<div class="view">
<div class="round">
<input
[checked]="todo.is_completed"
type="checkbox"
id={{todo.id}}
disabled
(change)="toggleTodo()"
/>
<label htmlFor={{todo.id}}> </label>
</div>
</div>
<div [ngClass]="todo.is_completed ? 'labelContent completed' : 'labelContent'">
<div>
{{todo.title}}
</div>
</div>
<button class="closeBtn" (click)="removeTodo($event)">
x
</button>
</li>

View File

@ -0,0 +1,17 @@
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'TodoItem',
templateUrl: './TodoItem.template.html',
})
export class TodoItem {
@Input('todo') todo: any;
removeTodo = (e) => {
e.preventDefault();
e.stopPropagation();
};
toggleTodo = () => {};
}

View File

@ -0,0 +1,16 @@
<div class="todoListWrapper">
<ul>
<ng-container *ngFor="let todo of filteredTodos; let i = index" [attr.data-index]="i">
<TodoItem
[todo]="todo"
></TodoItem>
</ng-container>
</ul>
</div>
<TodoFilters
[todos]="filteredTodos"
[currentFilter]="filter"
(filterResultsFn)="filterResults($event)"
(clearCompletedFn)="clearCompleted()"
></TodoFilters>

View File

@ -0,0 +1,44 @@
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'TodoPrivateList',
templateUrl: './TodoPrivateList.template.html',
})
export class TodoPrivateList implements OnInit {
filter = "all";
clearInProgress= false;
todos= [
{
id: "1",
title: "This is private todo 1",
is_completed: true,
is_public: false
},
{
id: "2",
title: "This is private todo 2",
is_completed: false,
is_public: false
}
];
filteredTodos: any;
ngOnInit() {
this.filteredTodos = this.todos;
}
filterResults($event) {
this.filter = $event.filter;
this.filteredTodos = this.todos;
if (this.filter === "active") {
this.filteredTodos = this.todos.filter(todo => todo.is_completed !== true);
} else if (this.filter === "completed") {
this.filteredTodos = this.todos.filter(todo => todo.is_completed === true);
}
}
clearCompleted() {}
}

View File

@ -0,0 +1,5 @@
<div class="todoWrapper">
<div class="sectionHeader">Personal todos</div>
<TodoInput></TodoInput>
<TodoPrivateList></TodoPrivateList>
</div>

View File

@ -0,0 +1,9 @@
import { Component } from '@angular/core';
@Component({
selector: 'TodoPrivateWrapper',
templateUrl: './TodoPrivateWrapper.template.html',
})
export class TodoPrivateWrapper {
}

View File

@ -0,0 +1,17 @@
<div class="todoListWrapper">
<div *ngIf="newTodosCount" class="loadMoreSection" (click)="loadNew()">
New tasks have arrived! ({{newTodosCount}})
</div>
<ul>
<ng-container *ngFor="let todo of todos; let i = index" [attr.data-index]="i">
<TaskItem
[todo]="todo"
></TaskItem>
</ng-container>
</ul>
<div class="loadMoreSection" (click)="loadOlder()">
{{olderTodosAvailable ? 'Load older tasks' : 'No more public tasks!'}}
</div>
</div>

View File

@ -0,0 +1,48 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'TodoPublicList',
templateUrl: './TodoPublicList.template.html',
})
export class TodoPublicList {
olderTodosAvailable= true;
newTodosCount= 1;
todos= [
{
id: "1",
title: "This is public todo 1",
user: {
name: "someUser1"
}
},
{
id: "2",
title: "This is public todo 2",
is_completed: false,
is_public: true,
user: {
name: "someUser2"
}
},
{
id: "3",
title: "This is public todo 3",
user: {
name: "someUser3"
}
},
{
id: "4",
title: "This is public todo 4",
user: {
name: "someUser4"
}
}
]
loadNew() {}
loadOlder() {}
}

View File

@ -0,0 +1,6 @@
<div class="todoWrapper">
<div class="sectionHeader">Public feed (realtime)</div>
<TodoInput isPublic ></TodoInput>
<TodoPublicList></TodoPublicList>
</div>

View File

@ -0,0 +1,9 @@
import { Component } from '@angular/core';
@Component({
selector: 'TodoPublicWrapper',
templateUrl: './TodoPublicWrapper.template.html',
})
export class TodoPublicWrapper {
}

View File

@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -0,0 +1,18 @@
<div>
<Header (logoutHandler)="logoutHandler()"></Header>
<div class="container-fluid p-left-right-0">
<div class="col-xs-12 col-md-9 p-left-right-0">
<div class="col-xs-12 col-md-6 sliderMenu p-30">
<TodoPrivateWrapper></TodoPrivateWrapper>
</div>
<div class="col-xs-12 col-md-6 sliderMenu p-30 bg-gray border-right">
<TodoPublicWrapper ></TodoPublicWrapper>
</div>
</div>
<div class="col-xs-12 col-md-3 p-left-right-0">
<div class="col-xs-12 col-md-12 sliderMenu p-30 bg-gray">
<OnlineUsersWrapper ></OnlineUsersWrapper>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,35 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { App } from './App';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'app-boilerplate'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app-boilerplate');
});
it('should render title in a h1 tag', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app-boilerplate!');
});
});

View File

@ -0,0 +1,48 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { App } from './App';
import { Auth0Wrapper } from './Auth/Auth0Wrapper';
import { Callback } from './Auth/Callback';
import { Login } from './Auth/Login';
import { LogoutBtn } from './Auth/LogoutBtn';
import { OnlineUser } from './OnlineUsers/OnlineUser';
import { OnlineUsersWrapper } from './OnlineUsers/OnlineUsersWrapper';
import { TaskItem } from './Todo/TaskItem';
import { TodoFilters } from './Todo/TodoFilters';
import { TodoInput } from './Todo/TodoInput';
import { TodoItem } from './Todo/TodoItem';
import { TodoPrivateList } from './Todo/TodoPrivateList';
import { TodoPrivateWrapper } from './Todo/TodoPrivateWrapper';
import { TodoPublicList } from './Todo/TodoPublicList';
import { TodoPublicWrapper } from './Todo/TodoPublicWrapper';
import { Header} from './Header';
@NgModule({
declarations: [
Auth0Wrapper,
Callback,
Login,
LogoutBtn,
OnlineUser,
OnlineUsersWrapper,
TaskItem,
TodoFilters,
TodoInput,
TodoItem,
TodoPrivateList,
TodoPrivateWrapper,
TodoPublicList,
TodoPublicWrapper,
App,
Header
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [Auth0Wrapper]
})
export class AppModule { }

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><svg width='120px' height='120px' xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" class="uil-ring"><rect x="0" y="0" width="100" height="100" fill="none" class="bk"></rect><defs><filter id="uil-ring-shadow" x="-100%" y="-100%" width="300%" height="300%"><feOffset result="offOut" in="SourceGraphic" dx="0" dy="0"></feOffset><feGaussianBlur result="blurOut" in="offOut" stdDeviation="0"></feGaussianBlur><feBlend in="SourceGraphic" in2="blurOut" mode="normal"></feBlend></filter></defs><path d="M10,50c0,0,0,0.5,0.1,1.4c0,0.5,0.1,1,0.2,1.7c0,0.3,0.1,0.7,0.1,1.1c0.1,0.4,0.1,0.8,0.2,1.2c0.2,0.8,0.3,1.8,0.5,2.8 c0.3,1,0.6,2.1,0.9,3.2c0.3,1.1,0.9,2.3,1.4,3.5c0.5,1.2,1.2,2.4,1.8,3.7c0.3,0.6,0.8,1.2,1.2,1.9c0.4,0.6,0.8,1.3,1.3,1.9 c1,1.2,1.9,2.6,3.1,3.7c2.2,2.5,5,4.7,7.9,6.7c3,2,6.5,3.4,10.1,4.6c3.6,1.1,7.5,1.5,11.2,1.6c4-0.1,7.7-0.6,11.3-1.6 c3.6-1.2,7-2.6,10-4.6c3-2,5.8-4.2,7.9-6.7c1.2-1.2,2.1-2.5,3.1-3.7c0.5-0.6,0.9-1.3,1.3-1.9c0.4-0.6,0.8-1.3,1.2-1.9 c0.6-1.3,1.3-2.5,1.8-3.7c0.5-1.2,1-2.4,1.4-3.5c0.3-1.1,0.6-2.2,0.9-3.2c0.2-1,0.4-1.9,0.5-2.8c0.1-0.4,0.1-0.8,0.2-1.2 c0-0.4,0.1-0.7,0.1-1.1c0.1-0.7,0.1-1.2,0.2-1.7C90,50.5,90,50,90,50s0,0.5,0,1.4c0,0.5,0,1,0,1.7c0,0.3,0,0.7,0,1.1 c0,0.4-0.1,0.8-0.1,1.2c-0.1,0.9-0.2,1.8-0.4,2.8c-0.2,1-0.5,2.1-0.7,3.3c-0.3,1.2-0.8,2.4-1.2,3.7c-0.2,0.7-0.5,1.3-0.8,1.9 c-0.3,0.7-0.6,1.3-0.9,2c-0.3,0.7-0.7,1.3-1.1,2c-0.4,0.7-0.7,1.4-1.2,2c-1,1.3-1.9,2.7-3.1,4c-2.2,2.7-5,5-8.1,7.1 c-0.8,0.5-1.6,1-2.4,1.5c-0.8,0.5-1.7,0.9-2.6,1.3L66,87.7l-1.4,0.5c-0.9,0.3-1.8,0.7-2.8,1c-3.8,1.1-7.9,1.7-11.8,1.8L47,90.8 c-1,0-2-0.2-3-0.3l-1.5-0.2l-0.7-0.1L41.1,90c-1-0.3-1.9-0.5-2.9-0.7c-0.9-0.3-1.9-0.7-2.8-1L34,87.7l-1.3-0.6 c-0.9-0.4-1.8-0.8-2.6-1.3c-0.8-0.5-1.6-1-2.4-1.5c-3.1-2.1-5.9-4.5-8.1-7.1c-1.2-1.2-2.1-2.7-3.1-4c-0.5-0.6-0.8-1.4-1.2-2 c-0.4-0.7-0.8-1.3-1.1-2c-0.3-0.7-0.6-1.3-0.9-2c-0.3-0.7-0.6-1.3-0.8-1.9c-0.4-1.3-0.9-2.5-1.2-3.7c-0.3-1.2-0.5-2.3-0.7-3.3 c-0.2-1-0.3-2-0.4-2.8c-0.1-0.4-0.1-0.8-0.1-1.2c0-0.4,0-0.7,0-1.1c0-0.7,0-1.2,0-1.7C10,50.5,10,50,10,50z" fill="#337ab7" filter="url(#uil-ring-shadow)"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" repeatCount="indefinite" dur="1s"></animateTransform></path></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,3 @@
export const environment = {
production: true
};

View File

@ -0,0 +1,16 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 26 26" enable-background="new 0 0 26 26" width="512px" height="512px">
<path d="m.3,14c-0.2-0.2-0.3-0.5-0.3-0.7s0.1-0.5 0.3-0.7l1.4-1.4c0.4-0.4 1-0.4 1.4,0l.1,.1 5.5,5.9c0.2,0.2 0.5,0.2 0.7,0l13.4-13.9h0.1v-8.88178e-16c0.4-0.4 1-0.4 1.4,0l1.4,1.4c0.4,0.4 0.4,1 0,1.4l0,0-16,16.6c-0.2,0.2-0.4,0.3-0.7,0.3-0.3,0-0.5-0.1-0.7-0.3l-7.8-8.4-.2-.3z" fill="#91DC5A"/>
</svg>

After

Width:  |  Height:  |  Size: 523 B

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>GraphQL Angular Tutorial</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href="https://afeld.github.io/emoji-css/emoji.css" rel="stylesheet">
<link href="https://use.fontawesome.com/releases/v5.0.7/css/all.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body>
<Auth0Wrapper></Auth0Wrapper>
</body>
</html>

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@ -0,0 +1,63 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -0,0 +1,414 @@
/* You can add global styles to this file, and also import other style files */
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,700");
body {
background-color: #f7f7f7;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: sans-serif;
font-size: 14px;
}
.wd-95 {
width: 95%;
margin: 0 auto;
}
.p-left-0 {
padding-left: 0;
}
.p-left-right-0 {
padding-left: 0;
padding-right: 0;
}
.p-30 {
padding: 30px;
}
.p-top-bottom-30 {
padding-top: 30px;
padding-bottom: 30px;
}
.border-right {
border-right: 1px solid #ccc;
}
.bg-gray {
background-color: #efeded;
}
.display-block {
display: block;
}
.m-bottom-0 {
margin-bottom: 0;
}
.m-bottom-10 {
margin-bottom: 10px;
}
.m-bottom-20 {
margin-bottom: 20px;
}
.navHeader {
width: 100%;
}
.navHeader button {
padding: 3.75px 7.5px;
font-size: 14px;
}
.breakWord {
word-wrap: break-word;
}
.navBrand {
font-size: 14px;
}
.todoWrapper {
position: relative;
width: 100%;
}
.sectionHeader {
font-size: 18px;
font-weight: 400;
padding-bottom: 20px;
}
.formInput input {
height: 60px;
padding: 16px 16px 16px 60px;
border: none;
background-color: #fff;
width: 100%;
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
opacity: 0.6;
font-size: 14px;
}
.formInput {
position: relative;
display: flex;
align-items: center;
}
.inputMarker {
position: absolute;
left: 15px;
font-size: 25px;
opacity: 0.6;
}
.todoListWrapper {
border-top: 1px solid #e6e6e6;
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
}
.todoListWrapper ul {
-webkit-padding-start: 0px;
-moz-padding-start: 0px;
margin-bottom: 0;
}
.todoListWrapper ul li {
list-style-type: none;
min-height: 60px;
max-height: 60px;
/* overflow: auto; */
display: flex;
font-size: 14px;
align-items: center;
border-bottom: 1px solid #ededed;
background-color: #fff;
position: relative;
}
.closeBtn {
background-color: transparent;
border: none;
color: #cc9a9a;
position: absolute;
right: 15px;
padding: 0;
font-size: 25px;
}
.closeBtn i {
font-size: 20px;
}
.closeBtn:hover {
color: #af5b5e;
}
.closeBtn:focus {
outline: none;
}
.labelContent {
padding-left: 1px;
color: #777;
width: calc(100% - 128px);
max-height: 60px;
overflow: auto;
display: flex;
align-items: center;
}
.labelContent.completed {
text-decoration: line-through;
}
.view {
width: 28px;
height: 28px;
margin: 0 15px;
}
.footerList {
height: 60px;
background-color: #fff;
border-bottom: 1px solid #ededed;
display: flex;
align-items: center;
padding: 0 15px;
margin-bottom: 30px;
}
.footerList:before {
content: "";
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.footerList ul {
-webkit-padding-start: 0px;
-moz-padding-start: 0px;
margin-bottom: 0;
padding-left: 20px;
z-index: 1;
}
.footerList ul li {
list-style-type: none;
display: inline-block;
}
.footerList ul li a {
padding: 3px 7px;
border: 1px solid transparent;
border-radius: 3px;
color: #777;
cursor: pointer;
}
.footerList ul li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clearComp {
background-color: transparent;
border: none;
color: #777;
position: absolute;
right: 15px;
padding: 0;
}
.clearComp:focus {
outline: none;
}
.sliderMenu {
height: calc(100vh - 50px);
overflow-y: auto;
}
.sliderHeader {
padding-bottom: 20px;
text-transform: uppercase;
font-weight: 600;
}
.userInfo {
padding: 10px 0 10px 15px;
display: flex;
align-items: center;
}
.userInfo:hover {
background-color: #e0e0e0;
}
.userImg {
text-align: center;
padding-right: 10px;
}
.userImg i {
font-size: 20px;
}
.userImg img {
width: 30px;
}
/*Custom Checkbox */
.round {
position: relative;
}
.round label {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 50%;
cursor: pointer;
height: 28px;
left: 0;
position: absolute;
top: 0;
width: 28px;
min-width: 28px;
}
.round label:after {
border: 2px solid #66bb6a;
border-top: none;
border-right: none;
content: "";
height: 6px;
left: 7px;
opacity: 0;
position: absolute;
top: 8px;
transform: rotate(-45deg);
width: 12px;
}
.round input[type="checkbox"] {
visibility: hidden;
}
.round input[type="checkbox"]:checked + label {
background-color: #fff;
border-color: #66bb6a;
}
.round input[type="checkbox"]:checked + label:after {
opacity: 1;
}
.loadMoreSection {
list-style-type: none;
height: 30px;
display: -ms-flexbox;
display: flex;
font-size: 14px;
-ms-flex-align: center;
align-items: center;
border-bottom: 1px solid #ededed;
background-color: #e6ecf0;
position: relative;
justify-content: center;
cursor: pointer;
}
.userInfoPublic {
background-color: #dfe3e6;
display: flex;
align-items: center;
justify-content: center;
margin-left: 10px;
font-size: 12px;
font-weight: bold;
padding: 5px 8px;
margin-right: 10px;
}
.overlay {
position: fixed;
display: block;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ddd;
z-index: 2;
}
.overlay .overlay-content {
position: absolute;
top: 40%;
left: 50%;
font-size: 18px;
text-align: center;
transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
}
.overlay .overlay-heading {
font-size: 24px;
font-weight: 500;
margin-bottom: 20px;
color: #444;
}
.overlay .overlay-message {
color: #5f5f5f;
}
.overlay .overlay-action {
margin-top: 10px;
}
@media (max-width: 991px) {
.sliderMenu {
height: auto;
}
.labelContent {
display: block;
}
}
.callback {
position: "absolute";
display: "flex";
justify-content: "center";
height: "100vh";
width: "100vw";
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: "white"
}

View File

@ -0,0 +1,399 @@
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,700");
body {
background-color: #f7f7f7;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: sans-serif;
font-size: 14px;
}
.wd-95 {
width: 95%;
margin: 0 auto;
}
.p-left-0 {
padding-left: 0;
}
.p-left-right-0 {
padding-left: 0;
padding-right: 0;
}
.p-30 {
padding: 30px;
}
.p-top-bottom-30 {
padding-top: 30px;
padding-bottom: 30px;
}
.border-right {
border-right: 1px solid #ccc;
}
.bg-gray {
background-color: #efeded;
}
.display-block {
display: block;
}
.m-bottom-0 {
margin-bottom: 0;
}
.m-bottom-10 {
margin-bottom: 10px;
}
.m-bottom-20 {
margin-bottom: 20px;
}
.navHeader {
width: 100%;
}
.navHeader button {
padding: 3.75px 7.5px;
font-size: 14px;
}
.breakWord {
word-wrap: break-word;
}
.navBrand {
font-size: 14px;
}
.todoWrapper {
position: relative;
width: 100%;
}
.sectionHeader {
font-size: 18px;
font-weight: 400;
padding-bottom: 20px;
}
.formInput input {
height: 60px;
padding: 16px 16px 16px 60px;
border: none;
background-color: #fff;
width: 100%;
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
opacity: 0.6;
font-size: 14px;
}
.formInput {
position: relative;
display: flex;
align-items: center;
}
.inputMarker {
position: absolute;
left: 15px;
font-size: 25px;
opacity: 0.6;
}
.todoListWrapper {
border-top: 1px solid #e6e6e6;
-webkit-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
}
.todoListWrapper ul {
-webkit-padding-start: 0px;
-moz-padding-start: 0px;
margin-bottom: 0;
}
.todoListWrapper ul li {
list-style-type: none;
min-height: 60px;
max-height: 60px;
/* overflow: auto; */
display: flex;
font-size: 14px;
align-items: center;
border-bottom: 1px solid #ededed;
background-color: #fff;
position: relative;
}
.closeBtn {
background-color: transparent;
border: none;
color: #cc9a9a;
position: absolute;
right: 15px;
padding: 0;
font-size: 25px;
}
.closeBtn i {
font-size: 20px;
}
.closeBtn:hover {
color: #af5b5e;
}
.closeBtn:focus {
outline: none;
}
.labelContent {
padding-left: 1px;
color: #777;
width: calc(100% - 128px);
max-height: 60px;
overflow: auto;
display: flex;
align-items: center;
}
.labelContent.completed {
text-decoration: line-through;
}
.view {
width: 28px;
height: 28px;
margin: 0 15px;
}
.footerList {
height: 60px;
background-color: #fff;
border-bottom: 1px solid #ededed;
display: flex;
align-items: center;
padding: 0 15px;
margin-bottom: 30px;
}
.footerList:before {
content: "";
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.footerList ul {
-webkit-padding-start: 0px;
-moz-padding-start: 0px;
margin-bottom: 0;
padding-left: 20px;
z-index: 1;
}
.footerList ul li {
list-style-type: none;
display: inline-block;
}
.footerList ul li a {
padding: 3px 7px;
border: 1px solid transparent;
border-radius: 3px;
color: #777;
cursor: pointer;
}
.footerList ul li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clearComp {
background-color: transparent;
border: none;
color: #777;
position: absolute;
right: 15px;
padding: 0;
}
.clearComp:focus {
outline: none;
}
.sliderMenu {
height: calc(100vh - 50px);
overflow-y: auto;
}
.sliderHeader {
padding-bottom: 20px;
text-transform: uppercase;
font-weight: 600;
}
.userInfo {
padding: 10px 0 10px 15px;
display: flex;
align-items: center;
}
.userInfo:hover {
background-color: #e0e0e0;
}
.userImg {
text-align: center;
padding-right: 10px;
}
.userImg i {
font-size: 20px;
}
.userImg img {
width: 30px;
}
/*Custom Checkbox */
.round {
position: relative;
}
.round label {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 50%;
cursor: pointer;
height: 28px;
left: 0;
position: absolute;
top: 0;
width: 28px;
min-width: 28px;
}
.round label:after {
border: 2px solid #66bb6a;
border-top: none;
border-right: none;
content: "";
height: 6px;
left: 7px;
opacity: 0;
position: absolute;
top: 8px;
transform: rotate(-45deg);
width: 12px;
}
.round input[type="checkbox"] {
visibility: hidden;
}
.round input[type="checkbox"]:checked + label {
background-color: #fff;
border-color: #66bb6a;
}
.round input[type="checkbox"]:checked + label:after {
opacity: 1;
}
.loadMoreSection {
list-style-type: none;
height: 30px;
display: -ms-flexbox;
display: flex;
font-size: 14px;
-ms-flex-align: center;
align-items: center;
border-bottom: 1px solid #ededed;
background-color: #e6ecf0;
position: relative;
justify-content: center;
cursor: pointer;
}
.userInfoPublic {
background-color: #dfe3e6;
display: flex;
align-items: center;
justify-content: center;
margin-left: 10px;
font-size: 12px;
font-weight: bold;
padding: 5px 8px;
margin-right: 10px;
}
.overlay {
position: fixed;
display: block;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ddd;
z-index: 2;
}
.overlay .overlay-content {
position: absolute;
top: 40%;
left: 50%;
font-size: 18px;
text-align: center;
transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
}
.overlay .overlay-heading {
font-size: 24px;
font-weight: 500;
margin-bottom: 20px;
color: #444;
}
.overlay .overlay-message {
color: #5f5f5f;
}
.overlay .overlay-action {
margin-top: 10px;
}
@media (max-width: 991px) {
.sliderMenu {
height: auto;
}
.labelContent {
display: block;
}
}

View File

@ -0,0 +1,20 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/test.ts",
"src/**/*.spec.ts"
]
}

View File

@ -0,0 +1,26 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
}
}

View File

@ -0,0 +1,18 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -0,0 +1,92 @@
{
"extends": "tslint:recommended",
"rules": {
"array-type": false,
"arrow-parens": false,
"deprecation": {
"severity": "warning"
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"import-blacklist": [
true,
"rxjs/Rx"
],
"interface-name": false,
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-consecutive-blank-lines": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-use-before-declare": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"object-literal-sort-keys": false,
"ordered-imports": false,
"quotemark": [
true,
"single"
],
"trailing-comma": false,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
},
"rulesDirectory": [
"codelyzer"
]
}

View File

@ -0,0 +1,13 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -0,0 +1,46 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events.json
speed-measure-plugin.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

View File

@ -0,0 +1,120 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"app-boilerplate": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/app-boilerplate",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "app-boilerplate:build"
},
"configurations": {
"production": {
"browserTarget": "app-boilerplate:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "app-boilerplate:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "app-boilerplate:serve"
},
"configurations": {
"production": {
"devServerTarget": "app-boilerplate:serve:production"
}
}
}
}
}},
"defaultProject": "app-boilerplate"
}

View File

@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -0,0 +1,32 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View File

@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to app-boilerplate!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

View File

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css('app-root h1')).getText() as Promise<string>;
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View File

@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/app-boilerplate'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
{
"name": "app-boilerplate",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --port 3000",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~8.1.2",
"@angular/common": "~8.1.2",
"@angular/compiler": "~8.1.2",
"@angular/core": "~8.1.2",
"@angular/forms": "~8.1.2",
"@angular/platform-browser": "~8.1.2",
"@angular/platform-browser-dynamic": "~8.1.2",
"@angular/router": "~8.1.2",
"apollo-angular": "^1.6.0",
"apollo-angular-link-http": "^1.8.0",
"apollo-cache-inmemory": "^1.6.2",
"apollo-client": "^2.6.3",
"apollo-link": "^1.2.12",
"apollo-link-ws": "^1.0.18",
"auth0-js": "^9.10.0",
"graphql": "^14.4.2",
"graphql-tag": "^2.10.1",
"rxjs": "~6.4.0",
"subscriptions-transport-ws": "^0.9.16",
"tslib": "^1.9.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.801.2",
"@angular/cli": "~8.1.2",
"@angular/compiler-cli": "~8.1.2",
"@angular/language-service": "~8.1.2",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.4.3"
}
}

View File

@ -0,0 +1,14 @@
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'App',
templateUrl: './app.component.html',
})
export class App {
@Output('logout') logout: EventEmitter<any> = new EventEmitter();
logoutHandler(item: any) {
this.logout.emit(item);
}
}

View File

@ -0,0 +1,6 @@
<Callback *ngIf="isAuthenticated === 'loading'"></Callback>
<Login *ngIf="!isAuthenticated" (loginHandler)="login($event)"></Login>
<App *ngIf="isAuthenticated === true" (logout)="logout()"></App>
<router-outlet></router-outlet>

View File

@ -0,0 +1,127 @@
import { Component, OnInit, Input } from '@angular/core';
import auth0 from 'auth0-js';
import {AUTH_CONFIG} from './auth0-variables';
;
@Component({
selector: 'Auth0Wrapper',
templateUrl: './Auth0Wrapper.template.html',
})
export class Auth0Wrapper implements OnInit {
auth0 = new auth0.WebAuth({
domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.callbackUrl,
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
responseType: 'token id_token',
scope: 'openid profile'
});
isAuthenticated:any = false;// This can be true, false, 'loading'
idToken = null;
accessToken;
expiresAt;
@Input('location') location: any;
constructor() {
}
login() {
this.auth0.authorize();
}
handleAuthentication = () => {
this.isAuthenticated= 'loading';
this.auth0.parseHash((err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
} else if (err) {
this.logout();
console.error(err);
alert(`Error: ${err.error} - ${err.errorDescription}`);
}
});
};
getAccessToken() {
return this.accessToken;
}
getIdToken() {
return this.idToken;
}
setSession(authResult) {
// Set isLoggedIn flag in localStorage
localStorage.setItem('isLoggedIn', 'true');
// Set the time that the access token will expire at
let expiresAt = (authResult.expiresIn * 1000) + new Date().getTime();
this.accessToken = authResult.accessToken;
this.idToken = authResult.idToken;
this.expiresAt = expiresAt;
localStorage.setItem('token', this.idToken);
// navigate to the home route
this.isAuthenticated = true;
this.idToken = authResult.idToken;
}
renewSession() {
this.isAuthenticated = 'loading';
this.auth0.checkSession({}, (err, authResult) => {
if (authResult && authResult.accessToken && authResult.idToken) {
this.setSession(authResult);
} else if (err) {
this.logout();
console.log(err);
alert(`Could not get a new token (${err.error}: ${err.error_description}).`);
}
});
}
logout() {
// Remove tokens and expiry time
this.accessToken = null;
this.idToken = null;
this.expiresAt = 0;
// Remove isLoggedIn flag from localStorage
localStorage.removeItem('isLoggedIn');
localStorage.removeItem('token');
this.auth0.logout({
return_to: window.location.origin
});
// navigate to the home route
this.isAuthenticated = false;
this.idToken = null;
}
isExpired () {
// Check whether the current time is past the
// access token's expiry time
let expiresAt = this.expiresAt;
return new Date().getTime() > expiresAt;
}
ngOnInit() {
// If this is a callback URL then do the right things
const location = window.location;
if (location && location.pathname.startsWith('/callback') && /access_token|id_token|error/.test(location.hash)) {
this.handleAuthentication();
return;
}
// On first load, check if we are already logged in and get the idTokens and things
if(localStorage.getItem('isLoggedIn') === 'true') {
this.renewSession();
return;
}
}
}

View File

@ -0,0 +1,3 @@
<div class="spinner">
<img src="../../assets/loading.svg" alt="loading" />
</div>

View File

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'Callback',
templateUrl: './Callback.template.html'
})
export class Callback {
}

View File

@ -0,0 +1,19 @@
<div class="overlay">
<div class="overlay-content">
<div class="overlay-heading">
Welcome to the GraphQL tutorial app
</div>
<div class="overlay-message">
Please login to continue
</div>
<div class="overlay-action">
<Button
id="qsLoginBtn"
type="button"
class="btn-margin loginBtn btn btn-primary "
(click)="loginHandlerWrapper()">
Log In
</Button>
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'Login',
templateUrl: './Login.template.html',
})
export class Login {
@Output('loginHandler') loginHandler: EventEmitter<any> = new EventEmitter();
loginHandlerWrapper(item: any) {
this.loginHandler.emit(item);
}
}

View File

@ -0,0 +1,8 @@
<Button
id="qsLogoutBtn"
type="button"
class="btn-margin logoutBtn btn btn-primary "
(click)="logoutHandlerWrapper()"
>
Log Out
</Button>

View File

@ -0,0 +1,14 @@
import { Component, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'LogoutBtn',
templateUrl: './Logout.template.html',
})
export class LogoutBtn {
@Output('logoutHandler') logoutHandler: EventEmitter<any> = new EventEmitter();
logoutHandlerWrapper(item: any) {
this.logoutHandler.emit(item);
}
}

View File

@ -0,0 +1,6 @@
export const AUTH_CONFIG = {
domain: 'graphql-tutorials.auth0.com',
clientId: 'P38qnFo1lFAQJrzkun--wEzqljVNGcWW',
callbackUrl: 'http://localhost:3000/callback'
};

View File

@ -0,0 +1,14 @@
<nav class="m-bottom-0 navbar navbar-default">
<div class="container-fluid">
<div class="navHeader navbar-header">
<span class="navBrand navbar-brand">GraphQL Tutorial App</span>
<ul class="nav navbar-nav navbar-right">
<li role="presentation" class="">
<a role="button" href="#">
<LogoutBtn (logoutHandler)="logoutHandlerWrapper()"></LogoutBtn>
</a>
</li>
</ul>
</div>
</div>
</nav>

View File

@ -0,0 +1,14 @@
import { Component, EventEmitter, Output } from '@angular/core';
@Component({
selector: 'Header',
templateUrl: './Header.template.html',
})
export class Header {
@Output('logoutHandler') logoutHandler: EventEmitter<any> = new EventEmitter();
logoutHandlerWrapper(item: any) {
this.logoutHandler.emit(item);
}
}

View File

@ -0,0 +1,8 @@
<div class="userInfo">
<div class="userImg">
<i class="far fa-user" ></i>
</div>
<div class="userName">
{{user.name}}
</div>
</div>

View File

@ -0,0 +1,10 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'OnlineUser',
templateUrl: './OnlineUser.template.html',
})
export class OnlineUser {
@Input('user') user: any;
}

View File

@ -0,0 +1,10 @@
<div class="onlineUsersWrapper" *ngIf="!loading">
<div class="sliderHeader">
Online users - {{onlineUsers.length}}
</div>
<ng-container *ngFor="let user of onlineUsers; let i = index" [attr.data-index]="i">
<OnlineUser [user]="user"></OnlineUser>
</ng-container>
</div>
<div *ngIf="loading">Loading...</div>

View File

@ -0,0 +1,71 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { Apollo } from 'apollo-angular';
import gql from 'graphql-tag';
const SUBSCRIBE_TO_ONLINE_USERS = gql`
subscription getOnlineUsers {
online_users(order_by: {user: {name: asc }}) {
id
user {
name
}
}
}`
@Component({
selector: 'OnlineUsersWrapper',
templateUrl: './OnlineUsersWrapper.template.html',
})
export class OnlineUsersWrapper implements OnInit, OnDestroy {
onlineUsers = []
onlineIndicator: any;
loading: boolean = true;
private querySubscription: Subscription;
constructor(private apollo: Apollo) {}
ngOnInit(){
this.onlineIndicator = setInterval(() => this.updateLastSeen(), 30000);
this.querySubscription = this.apollo.subscribe({
query: SUBSCRIBE_TO_ONLINE_USERS,
}).subscribe(({ data, loading }) => {
if(data) {
const users = data.online_users;
this.loading = loading;
this.onlineUsers = [];
users.forEach((u, index) => {
this.onlineUsers.push(u.user)
})
}
console.log('got data ', data);
},(error) => {
console.log('there was an error sending the query', error);
});
}
updateLastSeen() {
// Use the apollo client to run a mutation to update the last_seen value
const UPDATE_LASTSEEN_MUTATION=gql`
mutation updateLastSeen ($now: timestamptz!) {
update_users(where: {}, _set: {last_seen: $now}) {
affected_rows
}
}`;
this.apollo.mutate({
mutation: UPDATE_LASTSEEN_MUTATION,
variables: {now: (new Date()).toISOString()}
}).subscribe(({ data, loading }) => {
console.log('got data ', data);
},(error) => {
console.log('there was an error sending the query', error);
});
}
ngOnDestroy() {
this.querySubscription.unsubscribe();
}
}

View File

@ -0,0 +1,11 @@
<li>
<div class="userInfoPublic" title={todo.user.name}>
@{{todo.user.name}}
</div>
<div [ngClass]="todo.is_completed ? 'labelContent completed' : 'labelContent'">
<div>
{{todo.title}}
</div>
</div>
</li>

View File

@ -0,0 +1,10 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'TaskItem',
templateUrl: './TaskItem.template.html',
})
export class TaskItem {
@Input('todo') todo: any;
}

View File

@ -0,0 +1,27 @@
<div class="footerList">
<span> {{itemCount}} item{{itemCount !== 1 ? "s" : ""}}</span>
<ul>
<li (click)="filterResultsHandler('all')">
<a [ngClass]="currentFilter === 'all' ? 'selected' : ''">
All
</a>
</li>
<li (click)="filterResultsHandler('active')">
<a [ngClass]= "currentFilter === 'active' ? 'selected' : ''">
Active
</a>
</li>
<li (click)="filterResultsHandler('completed')">
<a [ngClass]="currentFilter === 'completed' ? 'selected' : ''">
Completed
</a>
</li>
</ul>
<button (click)="clearCompletedFnWrapper()" class="clearComp">
Clear completed
</button>
</div>

View File

@ -0,0 +1,41 @@
import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
@Component({
selector: 'TodoFilters',
templateUrl: './TodoFilters.template.html',
})
export class TodoFilters implements OnChanges{
@Input('todos') todos: any;
@Input('currentFilter') currentFilter: any;
@Output('filterResultsFn') filterResultsFn: EventEmitter<any> = new EventEmitter();;
@Output('clearCompletedFn') clearCompletedFn: EventEmitter<any> = new EventEmitter();;
itemCount: any;
ngOnChanges(){
this.updateItemCount();
}
updateItemCount() {
this.itemCount = this.todos.length;
const activeTodos = this.todos.filter(todo => todo.is_completed !== true);
if (this.currentFilter === 'active') {
this.itemCount = activeTodos.length;
} else if (this.currentFilter === 'completed') {
this.itemCount = this.todos.length - activeTodos.length;
}
}
clearCompletedFnWrapper(item: any) {
this.clearCompletedFn.emit(item);
}
filterResultsHandler(filter) {
this.filterResultsFn.emit({ event:event, filter: filter });
;
};
}

View File

@ -0,0 +1,10 @@
<form class="formInput" (submit)="addTodo($event)">
<input
class="input"
placeholder="What needs to be done?"
(disabled)= "loading"
[(ngModel)]="todoInput"
[ngModelOptions]="{standalone: true}"
/>
<i class="inputMarker fa fa-angle-right"></i>
</form>

View File

@ -0,0 +1,64 @@
import { Component, OnInit, Input } from '@angular/core';
import { Apollo } from 'apollo-angular';
import gql from 'graphql-tag';
import {GET_MY_TODOS} from './TodoPrivateList';
const ADD_TODO = gql `
mutation ($todo: String!, $isPublic: Boolean!) {
insert_todos(objects: {title: $todo, is_public: $isPublic}) {
affected_rows
returning {
id
title
created_at
is_completed
}
}
}
`;
@Component({
selector: 'TodoInput',
templateUrl: './TodoInput.template.html',
})
export class TodoInput {
@Input('isPublic') isPublic: any = false;
todoInput: any= '';
loading: boolean = true;
constructor(private apollo: Apollo) {}
addTodo(e) {
e.preventDefault();
this.apollo.mutate({
mutation: ADD_TODO,
variables: {
todo: this.todoInput,
isPublic: this.isPublic
},
update: (cache, {data}) => {
if(this.isPublic) return null;
const existingTodos : any = cache.readQuery({
query: GET_MY_TODOS
});
const newTodo = data.insert_todos.returning[0];
cache.writeQuery({
query: GET_MY_TODOS,
data: {todos: [newTodo, ...existingTodos.todos]}
});
},
}).subscribe(({ data, loading }) => {
this.loading = loading;
this.todoInput = '';
console.log('got data ', data);
},(error) => {
console.log('there was an error sending the query', error);
});
}
}

Some files were not shown because too many files have changed in this diff Show More