Final state from MSSE692

This commit is contained in:
Kerry Johnson 2021-03-13 08:45:04 -07:00
parent 5f18537bd5
commit 43c7198f22
41 changed files with 816 additions and 218 deletions

36
package-lock.json generated
View file

@ -49,6 +49,7 @@
"rxjs-compat": "6.3.0",
"socicon": "3.0.5",
"style-loader": "^1.1.3",
"thingbook-api": "^1.0.14",
"tinymce": "4.5.7",
"tslib": "^2.0.0",
"typeface-exo": "0.0.22",
@ -649,6 +650,7 @@
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-11.0.4.tgz",
"integrity": "sha512-suhAhsZEv+lLwm8dc524cMvO7gHPi+z2+4tueNS+zDiIObdZc4fs+KoOlnRMdYwba++X/V8mHXuDEQetl3GFcw==",
"dependencies": {
"parse5": "^5.0.0",
"tslib": "^2.0.0"
},
"optionalDependencies": {
@ -911,6 +913,7 @@
"dependencies": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
@ -4780,6 +4783,7 @@
"dependencies": {
"anymatch": "^1.3.0",
"async-each": "^1.0.0",
"fsevents": "^1.0.0",
"glob-parent": "^2.0.0",
"inherits": "^2.0.1",
"is-binary-path": "^1.0.0",
@ -10966,7 +10970,8 @@
"dependencies": {
"async": "^1.4.0",
"optimist": "^0.6.1",
"source-map": "^0.4.4"
"source-map": "^0.4.4",
"uglify-js": "^2.6"
},
"bin": {
"handlebars": "bin/handlebars"
@ -13695,6 +13700,7 @@
"dependencies": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
@ -14220,8 +14226,11 @@
"dependencies": {
"errno": "^0.1.1",
"graceful-fs": "^4.1.2",
"image-size": "~0.5.0",
"make-dir": "^2.1.0",
"mime": "^1.4.1",
"native-request": "^1.0.5",
"source-map": "~0.6.0",
"tslib": "^1.10.0"
},
"bin": {
@ -21141,6 +21150,9 @@
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz",
"integrity": "sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw==",
"dev": true,
"dependencies": {
"fsevents": "~2.1.2"
},
"bin": {
"rollup": "dist/bin/rollup"
},
@ -21493,6 +21505,7 @@
"dependencies": {
"anymatch": "~3.1.1",
"braces": "~3.0.2",
"fsevents": "~2.3.1",
"glob-parent": "~5.1.0",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
@ -22832,8 +22845,12 @@
"dependencies": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"getpass": "^0.1.1"
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"tweetnacl": "~0.14.0"
},
"engines": {
"node": ">=0.10.0"
@ -24353,6 +24370,11 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"node_modules/thingbook-api": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/thingbook-api/-/thingbook-api-1.0.14.tgz",
"integrity": "sha512-nX72Sa2SM13Hg1W8PTbJ+IIvoynCNDwTcTEXNScQLrw16b6QcgCRaVJ69W4tzxb0sFffzDN7+xcPoewl//syxg=="
},
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@ -25412,8 +25434,10 @@
"integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==",
"dev": true,
"dependencies": {
"chokidar": "^3.4.1",
"graceful-fs": "^4.1.2",
"neo-async": "^2.5.0"
"neo-async": "^2.5.0",
"watchpack-chokidar2": "^2.0.1"
},
"optionalDependencies": {
"chokidar": "^3.4.1",
@ -26366,6 +26390,7 @@
"anymatch": "^2.0.0",
"async-each": "^1.0.1",
"braces": "^2.3.2",
"fsevents": "^1.2.7",
"glob-parent": "^3.1.0",
"inherits": "^2.0.3",
"is-binary-path": "^1.0.0",
@ -48529,6 +48554,11 @@
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
"dev": true
},
"thingbook-api": {
"version": "1.0.14",
"resolved": "https://registry.npmjs.org/thingbook-api/-/thingbook-api-1.0.14.tgz",
"integrity": "sha512-nX72Sa2SM13Hg1W8PTbJ+IIvoynCNDwTcTEXNScQLrw16b6QcgCRaVJ69W4tzxb0sFffzDN7+xcPoewl//syxg=="
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",

View file

@ -4,15 +4,15 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/akveo/ngx-admin.git"
"url": "git+https://github.com/kerry-t-johnson/thingbook-ui"
},
"bugs": {
"url": "https://github.com/akveo/ngx-admin/issues"
"url": "https://github.com/kerry-t-johnson/thingbook-ui/issues"
},
"scripts": {
"ng": "ng",
"conventional-changelog": "conventional-changelog",
"start": "ng serve",
"start": "ng serve --host 0.0.0.0",
"build": "ng build",
"build:prod": "npm run build -- --prod --aot",
"test": "ng test",
@ -70,6 +70,7 @@
"rxjs-compat": "6.3.0",
"socicon": "3.0.5",
"style-loader": "^1.1.3",
"thingbook-api": "^1.0.14",
"tinymce": "4.5.7",
"tslib": "^2.0.0",
"typeface-exo": "0.0.22",

View file

@ -12,7 +12,6 @@ import {
SeoService,
StateService,
} from './utils';
import { UserData } from './data/users';
import { ElectricityData } from './data/electricity';
import { SmartTableData } from './data/smart-table';
import { UserActivityData } from './data/user-activity';
@ -32,7 +31,6 @@ import { StatsProgressBarData } from './data/stats-progress-bar';
import { VisitorsAnalyticsData } from './data/visitors-analytics';
import { SecurityCamerasData } from './data/security-cameras';
import { UserService } from './mock/users.service';
import { ElectricityService } from './mock/electricity.service';
import { SmartTableService } from './mock/smart-table.service';
import { UserActivityService } from './mock/user-activity.service';
@ -72,7 +70,6 @@ const socialLinks = [
];
const DATA_SERVICES = [
{ provide: UserData, useClass: UserService },
{ provide: ElectricityData, useClass: ElectricityService },
{ provide: SmartTableData, useClass: SmartTableService },
{ provide: UserActivityData, useClass: UserActivityService },

View file

@ -1,21 +0,0 @@
import { Observable } from 'rxjs';
export interface User {
name: string;
picture: string;
}
export interface Contacts {
user: User;
type: string;
}
export interface RecentUsers extends Contacts {
time: number;
}
export abstract class UserData {
abstract getUsers(): Observable<User[]>;
abstract getContacts(): Observable<Contacts[]>;
abstract getRecentUsers(): Observable<RecentUsers[]>;
}

View file

@ -1,7 +1,6 @@
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UserService } from './users.service';
import { ElectricityService } from './electricity.service';
import { SmartTableService } from './smart-table.service';
import { UserActivityService } from './user-activity.service';
@ -23,7 +22,6 @@ import { VisitorsAnalyticsService } from './visitors-analytics.service';
import { SecurityCamerasService } from './security-cameras.service';
const SERVICES = [
UserService,
ElectricityService,
SmartTableService,
UserActivityService,

View file

@ -1,53 +0,0 @@
import { of as observableOf, Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import { Contacts, RecentUsers, UserData } from '../data/users';
@Injectable()
export class UserService extends UserData {
private time: Date = new Date;
private users = {
nick: { name: 'Nick Jones', picture: 'assets/images/nick.png' },
eva: { name: 'Eva Moor', picture: 'assets/images/eva.png' },
jack: { name: 'Jack Williams', picture: 'assets/images/jack.png' },
lee: { name: 'Lee Wong', picture: 'assets/images/lee.png' },
alan: { name: 'Alan Thompson', picture: 'assets/images/alan.png' },
kate: { name: 'Kate Martinez', picture: 'assets/images/kate.png' },
};
private types = {
mobile: 'mobile',
home: 'home',
work: 'work',
};
private contacts: Contacts[] = [
{ user: this.users.nick, type: this.types.mobile },
{ user: this.users.eva, type: this.types.home },
{ user: this.users.jack, type: this.types.mobile },
{ user: this.users.lee, type: this.types.mobile },
{ user: this.users.alan, type: this.types.home },
{ user: this.users.kate, type: this.types.work },
];
private recentUsers: RecentUsers[] = [
{ user: this.users.alan, type: this.types.home, time: this.time.setHours(21, 12)},
{ user: this.users.eva, type: this.types.home, time: this.time.setHours(17, 45)},
{ user: this.users.nick, type: this.types.mobile, time: this.time.setHours(5, 29)},
{ user: this.users.lee, type: this.types.mobile, time: this.time.setHours(11, 24)},
{ user: this.users.jack, type: this.types.mobile, time: this.time.setHours(10, 45)},
{ user: this.users.kate, type: this.types.work, time: this.time.setHours(9, 42)},
{ user: this.users.kate, type: this.types.work, time: this.time.setHours(9, 31)},
{ user: this.users.jack, type: this.types.mobile, time: this.time.setHours(8, 0)},
];
getUsers(): Observable<any> {
return observableOf(this.users);
}
getContacts(): Observable<Contacts[]> {
return observableOf(this.contacts);
}
getRecentUsers(): Observable<RecentUsers[]> {
return observableOf(this.recentUsers);
}
}

View file

@ -3,7 +3,7 @@
<a (click)="toggleSidebar()" href="#" class="sidebar-toggle">
<nb-icon icon="menu-2-outline"></nb-icon>
</a>
<a class="logo" href="#" (click)="navigateHome()">ngx-<span>admin</span></a>
<a class="logo" href="#" (click)="navigateHome()">Thing<span>Book</span></a>
</div>
<nb-select [selected]="currentTheme" (selectedChange)="changeTheme($event)" status="primary">
<nb-option *ngFor="let theme of themes" [value]="theme.value"> {{ theme.name }}</nb-option>
@ -18,12 +18,10 @@
</nb-action>
<nb-action class="control-item" icon="email-outline"></nb-action>
<nb-action class="control-item" icon="bell-outline"></nb-action>
<nb-action class="user-action" *nbIsGranted="['view', 'user']" >
<nb-user [nbContextMenu]="userMenu"
[onlyPicture]="userPictureOnly"
[name]="user?.name"
[picture]="user?.picture">
<nb-action class="user-action" *nbIsGranted="['view', 'user']">
<nb-user [nbContextMenu]="userMenu" [onlyPicture]="userPictureOnly" [name]="displayName"
[picture]="user?.picture">
</nb-user>
</nb-action>
</nb-actions>
</div>
</div>

View file

@ -1,10 +1,10 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { NbMediaBreakpointsService, NbMenuService, NbSidebarService, NbThemeService } from '@nebular/theme';
import { UserData } from '../../../@core/data/users';
import { LayoutService } from '../../../@core/utils';
import { map, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { UserService, User } from '../../../modules/user/user.service';
@Component({
selector: 'ngx-header',
@ -15,45 +15,40 @@ export class HeaderComponent implements OnInit, OnDestroy {
private destroy$: Subject<void> = new Subject<void>();
userPictureOnly: boolean = false;
user: any;
user: User;
displayName: string;
themes = [
{
value: 'default',
name: 'Light',
},
{
value: 'dark',
name: 'Dark',
},
{
value: 'cosmic',
name: 'Cosmic',
},
{
value: 'corporate',
name: 'Corporate',
},
{ value: 'default', name: 'Light', },
{ value: 'dark', name: 'Dark', },
{ value: 'cosmic', name: 'Cosmic', },
{ value: 'corporate', name: 'Corporate', },
];
currentTheme = 'default';
userMenu = [ { title: 'Profile' }, { title: 'Log out' } ];
userMenu = [
{ title: 'Profile', icon: 'person-outline', link: 'pages/user/profile', },
{ title: 'Log out' }
];
constructor(private sidebarService: NbSidebarService,
private menuService: NbMenuService,
private themeService: NbThemeService,
private userService: UserData,
private layoutService: LayoutService,
private breakpointService: NbMediaBreakpointsService) {
private menuService: NbMenuService,
private themeService: NbThemeService,
private userService: UserService,
private layoutService: LayoutService,
private breakpointService: NbMediaBreakpointsService) {
}
ngOnInit() {
this.currentTheme = this.themeService.currentTheme;
this.userService.getUsers()
this.userService.onUserStatus()
.pipe(takeUntil(this.destroy$))
.subscribe((users: any) => this.user = users.nick);
.subscribe((user: any) => {
this.user = user;
this.displayName = user?.first ?? user?.email;
});
const { xl } = this.breakpointService.getBreakpointsMap();
this.themeService.onMediaQueryChange()

View file

@ -6,7 +6,7 @@
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { CoreModule } from './@core/core.module';
import { ThemeModule } from './@theme/theme.module';
import { AppComponent } from './app.component';
@ -15,14 +15,19 @@ import {
NbChatModule,
NbDatepickerModule,
NbDialogModule,
NbIconModule,
NbMenuModule,
NbSidebarModule,
NbToastrModule,
NbWindowModule,
} from '@nebular/theme';
import { UserModule } from './modules/user/user.module';
@NgModule({
declarations: [AppComponent],
declarations: [
AppComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
@ -32,6 +37,7 @@ import {
NbMenuModule.forRoot(),
NbDatepickerModule.forRoot(),
NbDialogModule.forRoot(),
NbIconModule,
NbWindowModule.forRoot(),
NbToastrModule.forRoot(),
NbChatModule.forRoot({
@ -39,8 +45,11 @@ import {
}),
CoreModule.forRoot(),
ThemeModule.forRoot(),
UserModule.forRoot(),
],
bootstrap: [AppComponent],
providers: []
})
export class AppModule {
}

View file

@ -0,0 +1,34 @@
<nb-card>
<nb-card-header>
<span class="col-lg-10">{{agreement?.name}}</span>
<span class="col-lg-1">
<nb-icon icon="activity" [status]='agreement?.state == "ACTIVE" ? "success" : "danger" '>
</nb-icon>
</span>
</nb-card-header>
<nb-card-body>
<div>
<span>{{agreement?.producer.name}}<nb-icon icon="arrowhead-right"></nb-icon>
{{agreement?.consumer.name}}</span>
</div>
<div>
<app-date-calendar [date]="agreement?.commenceDate"></app-date-calendar>
<app-date-calendar [date]="agreement?.expirationDate"></app-date-calendar>
</div>
<div>
<nb-accordion>
<nb-accordion-item *ngFor="let fragment of agreement.template.template.fragments">
<nb-accordion-item-header>
{{ fragment.name }}
<span style="justify-content: flex-end;">
{{ fragment.type | titlecase}}
</span>
</nb-accordion-item-header>
<nb-accordion-item-body>
{{fragment.text}}
</nb-accordion-item-body>
</nb-accordion-item>
</nb-accordion>
</div>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,21 @@
import { Component, Input, OnInit } from "@angular/core";
import { OrganizationDataSharingAgreement } from "thingbook-api/lib";
import { OrganizationService } from "../../organization.service";
@Component({
selector: 'org-data-sharing-agreement',
templateUrl: './data-sharing-agreement.component.html',
styleUrls: ['./data-sharing-agreement.component.scss']
})
export class DataSharingAgreementComponent implements OnInit {
@Input() agreement: OrganizationDataSharingAgreement;
constructor() {
}
ngOnInit() {
}
}

View file

@ -0,0 +1,19 @@
<h3>{{org?.name}}</h3>
<nb-tabset fullWidth="true">
<nb-tab tabTitle="Data Sharing Agreements ({{ agreements?.length }})" class="container">
<ng-container *ngIf="agreements?.length > 0; else loading">
<nb-list class="col-lg-12">
<nb-list-item *ngFor="let a of agreements; odd as odd; even as even;"
[ngClass]="{row: true, odd: odd, even: even}">
<org-data-sharing-agreement [agreement]="a"></org-data-sharing-agreement>
</nb-list-item>
</nb-list>
</ng-container>
<ng-template #loading>
<div>Loading...</div>
</ng-template>
</nb-tab>
<nb-tab tabTitle="Data Sharing Templates">
<span>TBD</span>
</nb-tab>
</nb-tabset>

View file

@ -0,0 +1,38 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, ParamMap } from "@angular/router";
import { switchMap } from "rxjs/operators";
import { Organization, OrganizationDataSharingAgreement } from "thingbook-api/lib";
import { OrganizationService } from "../../organization.service";
@Component({
selector: 'org-detail',
templateUrl: './detail-page.component.html',
styleUrls: ['./detail-page.component.scss'],
})
export class OrganizationDetailPageComponent implements OnInit {
org: Organization;
agreements: OrganizationDataSharingAgreement[] = [];
constructor(private route: ActivatedRoute, private orgSvc: OrganizationService) {
}
ngOnInit() {
this.route.paramMap.pipe(
switchMap((params: ParamMap) => {
return this.orgSvc.getOrganization(params.get('id'));
})
).subscribe(org => this.org = org);
this.route.paramMap.pipe(
switchMap((params: ParamMap) => {
return this.orgSvc.getOrganizationAgreements(params.get('id'));
})
).subscribe(agreements => {
this.agreements = agreements;
console.log(agreements);
});
}
}

View file

@ -0,0 +1,17 @@
<div class="lists row">
<div class="col-md-12 col-lg-12 col-xxxl-12">
<nb-card class="list-card">
<nb-card-header>Organizations</nb-card-header>
<nb-card-body>
<nb-list>
<nb-list-item *ngFor="let o of orgs">
<span class="col-sm-6">{{o.name}}</span>
<span class="col-sm-3">{{o.domainName}}</span>
<app-verification-icon [value]=o?.verification?.verified></app-verification-icon>
<a [routerLink]="['/pages/org', o._id]"><span class="col-sm-2">Details...</span></a>
</nb-list-item>
</nb-list>
</nb-card-body>
</nb-card>
</div>
</div>

View file

@ -0,0 +1,13 @@
@import '../../../../@theme/styles/themes';
@include nb-install-component() {
.list-card {
nb-card-header {
border-bottom: none;
}
nb-card-body {
padding: 0;
}
}
}

View file

@ -0,0 +1,25 @@
import { Component, OnInit } from "@angular/core";
import { Organization } from "thingbook-api/lib";
import { OrganizationService } from "../../organization.service";
@Component({
selector: 'organization-list',
templateUrl: './list-page.component.html',
styleUrls: ['./list-page.component.scss']
})
export class OrganizationListPageComponent implements OnInit {
orgs: Organization[];
constructor(private orgSvc: OrganizationService) {
}
ngOnInit() {
this.orgSvc.getOrganizations()
.subscribe(orgs => {
this.orgs = orgs;
console.log(this.orgs);
});
}
}

View file

@ -0,0 +1,34 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { OrganizationListPageComponent } from './components/list-page/list-page.component';
import { NbAccordionModule, NbCardModule, NbIconModule, NbListModule, NbTabsetModule } from '@nebular/theme';
import { ThemeModule } from '../../@theme/theme.module';
import { OrganizationDetailPageComponent } from './components/detail-page/detail-page.component';
import { DataSharingAgreementComponent } from './components/data-sharing-agreement/data-sharing-agreement.component';
import { WidgetsModule } from '../../widgets/widgets.module';
@NgModule({
declarations: [
OrganizationListPageComponent,
OrganizationDetailPageComponent,
DataSharingAgreementComponent,
],
imports: [
CommonModule,
ThemeModule,
NbAccordionModule,
NbCardModule,
NbIconModule,
NbListModule,
NbTabsetModule,
RouterModule.forChild([
{ path: 'list', component: OrganizationListPageComponent },
{ path: ':id', component: OrganizationDetailPageComponent }
]),
WidgetsModule,
]
})
export class OrganizationModule { }

View file

@ -0,0 +1,51 @@
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, of } from "rxjs";
import { catchError, tap } from "rxjs/operators";
import { Organization, OrganizationDataSharingAgreement } from 'thingbook-api';
@Injectable({
providedIn: 'root'
})
export class OrganizationService {
constructor(private http: HttpClient) {
}
getOrganization(id): Observable<Organization> {
return this.http.get<Organization>(`http://localhost:3000/api/v1/organization/${id}`)
.pipe(
catchError(this.handleError<Organization>('getOrganization', null))
);
}
getOrganizationAgreements(id): Observable<OrganizationDataSharingAgreement[]> {
return this.http.get<OrganizationDataSharingAgreement[]>(`http://localhost:3000/api/v1/organization/${id}/agreement`)
.pipe(
catchError(this.handleError<OrganizationDataSharingAgreement[]>('getOrganizationAgreements', []))
);
}
getOrganizations(): Observable<Organization[]> {
return this.http.get<Organization[]>('http://localhost:3000/api/v1/organization')
.pipe(
catchError(this.handleError<Organization[]>('getOrganizations', []))
);
}
private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
// TODO: send the error to remote logging infrastructure
console.error(error); // log to console instead
// TODO: better job of transforming error for user consumption
console.log(`${operation} failed: ${error.message}`);
// Let the app keep running by returning an empty result.
return of(result as T);
};
}
}

View file

@ -0,0 +1,17 @@
<nb-card>
<nb-card-header>{{user?.email}}</nb-card-header>
<nb-card-body>
<form>
<div class="form-group">
<label for="inputFirst" class="label">First name</label>
<input nbInput fullWidth id="inputFirst" ngModel [(ngModel)]="first"
placeholder="First name">
</div>
<div class="form-group">
<label for="inputLast" class="label">Last name</label>
<input type="text" nbInput fullWidth id="inputLast" placeholder="Last name">
</div>
<button type="submit" nbButton hero status="primary">Update</button>
</form>
</nb-card-body>
</nb-card>

View file

@ -0,0 +1,15 @@
nb-checkbox {
margin-bottom: 1rem;
}
.form-inline [fullWidth] {
flex: 1;
}
.form-inline > * {
margin: 0 1.5rem 1.5rem 0;
}
nb-card.inline-form-card nb-card-body {
padding-bottom: 0;
}

View file

@ -0,0 +1,38 @@
import { Component, OnDestroy, OnInit } from "@angular/core";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { User, UserService } from "../user.service";
@Component({
selector: 'user-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss']
})
export class UserProfileComponent implements OnInit, OnDestroy {
private destroy$: Subject<void> = new Subject<void>();
public user: User;
public first: string = "Blah";
public last: string;
constructor(private userService: UserService) {
}
ngOnInit() {
this.userService.onUserStatus()
.pipe(takeUntil(this.destroy$))
.subscribe((user: any) => {
this.user = user;
this.user.first = 'Blargle';
this.first = user?.first || 'Foo!';
this.last = user?.last || 'Bar!';
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}

View file

@ -0,0 +1,67 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NbPasswordAuthStrategy, NbAuthModule } from '@nebular/auth';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { LoginInterceptor } from '../../user.interceptors';
import { RouterModule } from '@angular/router';
import { UserProfileComponent } from './pages/profile.component';
import { ThemeModule } from '../../@theme/theme.module';
import { NbCardModule, NbCheckboxComponent, NbCheckboxModule, NbInputModule, NbMenuModule } from '@nebular/theme';
import { FormsModule } from '@angular/forms';
@NgModule({
declarations: [
UserProfileComponent,
],
imports: [
CommonModule,
ThemeModule,
NbMenuModule,
NbCardModule,
NbCheckboxModule,
NbInputModule,
FormsModule,
RouterModule.forChild([
{ path: 'profile', component: UserProfileComponent }
]),
],
exports: [
UserProfileComponent,
RouterModule,
],
})
export class UserModule {
static forRoot(): ModuleWithProviders<UserModule>[] {
return [{
ngModule: UserModule,
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: LoginInterceptor, multi: true }
],
},
NbAuthModule.forRoot({
strategies: [
NbPasswordAuthStrategy.setup({
name: 'email',
baseEndpoint: 'http://localhost:3000/api/v1/user',
login: {
endpoint: '/login',
requireValidToken: false,
},
register: {
endpoint: '/register',
requireValidToken: false,
},
logout: {
method: 'get',
endpoint: '/logout',
requireValidToken: false,
}
}),
],
forms: {},
}),
];
}
}

View file

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UserServiceImpl } from './user.service.impl';
describe('UserService', () => {
let service: UserServiceImpl;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UserServiceImpl);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View file

@ -0,0 +1,27 @@
import { Injectable } from '@angular/core';
import { Observable, of as ObservableOf } from 'rxjs';
import { User } from 'thingbook-api';
import { LoginInterceptor } from '../../user.interceptors';
export { User };
@Injectable({
providedIn: 'root'
})
export class UserService {
private user: User = undefined;
constructor() {
LoginInterceptor.s.subscribe({
next: (v) => {
this.user = v;
}
});
}
public onUserStatus(): Observable<User> {
return ObservableOf(this.user);
}
}

View file

@ -1,23 +0,0 @@
<nb-card size="giant">
<nb-tabset fullWidth>
<nb-tab tabTitle="Contacts">
<nb-list>
<nb-list-item class="contact" *ngFor="let c of contacts">
<nb-user [picture]="c.user.picture" [name]="c.user.name" [title]="c.type" size="large"></nb-user>
<nb-icon icon="phone-outline" pack="eva"></nb-icon>
</nb-list-item>
</nb-list>
</nb-tab>
<nb-tab tabTitle="Recent">
<nb-list>
<nb-list-item class="contact" *ngFor="let c of recent">
<nb-user [picture]="c.user.picture" [name]="c.user.name" [title]="c.type" size="large"></nb-user>
<span class="caption">{{ c.time | date: 'shortTime' }}</span>
</nb-list-item>
</nb-list>
</nb-tab>
</nb-tabset>
</nb-card>

View file

@ -1,34 +0,0 @@
@import '../../../@theme/styles/themes';
@include nb-install-component() {
nb-card {
overflow: hidden;
}
nb-tabset {
display: flex;
flex-direction: column;
::ng-deep ul {
// make same size as card header
padding-bottom: 1px;
::ng-deep .tab-link {
padding: 1.25rem 2rem;
}
}
}
nb-tab {
padding: 0;
}
.contact {
display: flex;
align-items: center;
justify-content: space-between;
&:first-child {
border-top: none;
}
}
}

View file

@ -1,34 +0,0 @@
import { Component, OnDestroy } from '@angular/core';
import { takeWhile } from 'rxjs/operators';
import { forkJoin } from 'rxjs';
import { Contacts, RecentUsers, UserData } from '../../../@core/data/users';
@Component({
selector: 'ngx-contacts',
styleUrls: ['./contacts.component.scss'],
templateUrl: './contacts.component.html',
})
export class ContactsComponent implements OnDestroy {
private alive = true;
contacts: any[];
recent: any[];
constructor(private userService: UserData) {
forkJoin(
this.userService.getContacts(),
this.userService.getRecentUsers(),
)
.pipe(takeWhile(() => this.alive))
.subscribe(([contacts, recent]: [Contacts[], RecentUsers[]]) => {
this.contacts = contacts;
this.recent = recent;
});
}
ngOnDestroy() {
this.alive = false;
}
}

View file

@ -21,10 +21,6 @@
<ngx-rooms></ngx-rooms>
</div>
<div class="col-xxxl-3 col-xxl-4 col-lg-7 col-md-6">
<ngx-contacts></ngx-contacts>
</div>
<div class="col-xxxl-3 col-xxl-4 col-lg-5 col-md-6">
<ngx-solar [chartValue]="solarValue"></ngx-solar>
@ -39,4 +35,4 @@
<div class="col-xxxl-6 col-xxl-12 col-md-7">
<ngx-security-cameras></ngx-security-cameras>
</div>
</div>
</div>

View file

@ -15,7 +15,6 @@ import { NgxEchartsModule } from 'ngx-echarts';
import { ThemeModule } from '../../@theme/theme.module';
import { DashboardComponent } from './dashboard.component';
import { StatusCardComponent } from './status-card/status-card.component';
import { ContactsComponent } from './contacts/contacts.component';
import { RoomsComponent } from './rooms/rooms.component';
import { RoomSelectorComponent } from './rooms/room-selector/room-selector.component';
import { TemperatureComponent } from './temperature/temperature.component';
@ -51,7 +50,6 @@ import { FormsModule } from '@angular/forms';
DashboardComponent,
StatusCardComponent,
TemperatureDraggerComponent,
ContactsComponent,
RoomSelectorComponent,
TemperatureComponent,
RoomsComponent,

View file

@ -1,6 +1,11 @@
import { NbMenuItem } from '@nebular/theme';
export const MENU_ITEMS: NbMenuItem[] = [
{
title: 'Organizations',
icon: 'keypad-outline',
link: '/pages/org/list',
},
{
title: 'E-commerce',
icon: 'shopping-cart-outline',

View file

@ -14,6 +14,16 @@ const routes: Routes = [{
path: 'dashboard',
component: ECommerceComponent,
},
{
path: 'user',
loadChildren: () => import('../modules/user/user.module')
.then(m => m.UserModule)
},
{
path: 'org',
loadChildren: () => import('../modules/organization/organization.module')
.then(m => m.OrganizationModule)
},
{
path: 'iot-dashboard',
component: DashboardComponent,

View file

@ -0,0 +1,22 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { BehaviorSubject, Observable, Subject } from "rxjs";
import { filter, tap } from "rxjs/operators";
import * as api from 'thingbook-api';
@Injectable()
export class LoginInterceptor implements HttpInterceptor {
public static s: BehaviorSubject<api.User> = new BehaviorSubject<api.User>(undefined);
intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(httpRequest).pipe(
tap((event: HttpResponse<any>) => {
if (event instanceof HttpResponse && event.url.endsWith('/user/login')) {
LoginInterceptor.s.next(<api.User>event.body);
}
})
);
}
}

View file

@ -0,0 +1,6 @@
<time datetime="datestr" class="date-as-calendar inline-flex {{size}}">
<span class="weekday">{{weekday}}</span>
<span class="day">{{day}}</span>
<span class="month">{{month}}</span>
<span class="year">{{year}}</span>
</time>

View file

@ -0,0 +1,175 @@
/****************************************/
/* Styling rules, such as font and colors */
.date-as-calendar {
font-variant: normal;
font-style: normal;
font-weight: normal;
font-family: "Helvetica", "Arial", sans-serif;
/* It seems vertical-align: baseline does not work correctly with display: inline-flex. */
vertical-align: top;
/* margin: 1ex; */
color: black;
background: white;
background : linear-gradient(to bottom right, #FFF 0%, #EEE 100%);
border: 1px solid #888;
border-radius: 3px;
overflow: hidden;
box-shadow: 2px 2px 2px -2px black;
}
.date-as-calendar .weekday,
.date-as-calendar .day,
.date-as-calendar .month,
.date-as-calendar .year {
text-align: center;
line-height: 1.0;
}
.date-as-calendar .month {
font-family: "Oswald", sans-serif;
text-transform: uppercase;
background: #B11;
background : linear-gradient(to bottom right, #D66 0%, #A00 100%);
color: white;
}
/****************************************/
/* Layout rules using position: absolute and pixels. */
.position-pixels.date-as-calendar {
display: inline-block;
position: relative;
width: 64px;
height: 64px;
}
.position-pixels.date-as-calendar .weekday,
.position-pixels.date-as-calendar .day,
.position-pixels.date-as-calendar .month,
.position-pixels.date-as-calendar .year {
display: block;
position: absolute;
left: 0;
right: 0;
width: 100%;
height: 1em;
}
.position-pixels.date-as-calendar .month {
top: 0px;
font-size: 12px;
padding: 2px 0;
}
.position-pixels.date-as-calendar .weekday {
top: 16px;
font-size: 10px;
}
.position-pixels.date-as-calendar .day {
top: 26px;
font-size: 24px;
}
.position-pixels.date-as-calendar .year {
top: 50px;
font-size: 14px;
}
/****************************************/
/* Layout rules using position: absolute and relative dimensions using em. */
.position-em.date-as-calendar {
display: inline-block;
position: relative;
width: 4em;
height: 4em;
}
.position-em.date-as-calendar .weekday,
.position-em.date-as-calendar .day,
.position-em.date-as-calendar .month,
.position-em.date-as-calendar .year {
display: block;
position: absolute;
left: 0;
right: 0;
width: 100%;
height: 1em;
}
.position-em.date-as-calendar .month {
top: 0px;
font-size: 0.75em;
padding: 0.1em 0;
}
.position-em.date-as-calendar .weekday {
top: 1.6em;
font-size: 0.6125em;
}
.position-em.date-as-calendar .day {
top: 1.1em;
font-size: 1.5em
}
.position-em.date-as-calendar .year {
bottom: 0px;
font-size: 0.87750em;
}
/****************************************/
/* Layout rules using display: inline-flex and relative dimensions using em. */
.inline-flex.date-as-calendar {
display: inline-flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: space-between;
width: 4em;
height: 4em;
}
.inline-flex.date-as-calendar .weekday,
.inline-flex.date-as-calendar .day,
.inline-flex.date-as-calendar .month,
.inline-flex.date-as-calendar .year {
display: block;
flex: 1 1 auto;
}
.inline-flex.date-as-calendar .month {
order: 1;
font-size: 0.75em;
padding: 0.1em 0;
}
.inline-flex.date-as-calendar .weekday {
order: 2;
font-size: 0.6125em;
}
.inline-flex.date-as-calendar .day {
order: 3;
font-size: 1.5em;
}
.inline-flex.date-as-calendar .year {
order: 4;
font-size: 0.87750em;
}
/****************************************/
/* Multiple sizes. */
.date-as-calendar.size0_5x {
font-size: 8px;
}
.date-as-calendar.size0_75x {
font-size: 12px;
}
.date-as-calendar.size1x {
font-size: 16px;
}
.date-as-calendar.size1_25x {
font-size: 20px;
}
.date-as-calendar.size1_5x {
font-size: 24px;
}
.date-as-calendar.size1_75x {
font-size: 28px;
}
.date-as-calendar.size2x {
font-size: 32px;
}
.date-as-calendar.size3x {
font-size: 48px;
}

View file

@ -0,0 +1,58 @@
import { getLocaleDateTimeFormat } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
@Component({
selector: 'app-date-calendar',
templateUrl: './date-calendar.component.html',
styleUrls: ['./date-calendar.component.scss']
})
export class DateCalendarComponent implements OnInit {
@Input() date: Date | string;
@Input() size: string = "size1x";
datestr: string;
weekday: string;
day: string;
month: string;
year: string;
constructor() {
}
ngOnInit() {
if (typeof this.date === 'string') {
this.date = new Date(this.date);
}
this.datestr = this.date.toISOString();
const dateParts: any = this.extract(this.date);
this.weekday = dateParts.weekday;
this.day = dateParts.day;
this.month = dateParts.month;
this.year = dateParts.year;
}
private extract(date: Date): object {
const formatter: Intl.DateTimeFormat = new Intl.DateTimeFormat(navigator.language, {
weekday: 'long',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: true,
timeZone: 'UTC'
});
const parts: Intl.DateTimeFormatPart[] = formatter.formatToParts(date);
let result = {}
for (let p of parts) {
result[p.type] = p.value;
}
return result;
}
}

View file

@ -0,0 +1,2 @@
<nb-icon [icon]='value ? "shield-outline" : "shield-off-outline"' [status]='value ? "success" : "danger"'>
</nb-icon>

View file

@ -0,0 +1,12 @@
import { Component, Input } from "@angular/core";
@Component({
selector: 'app-verification-icon',
templateUrl: './verification-icon.component.html',
styleUrls: ['./verification-icon.component.scss']
})
export class VerificationIconComponent {
@Input() value: boolean;
}

View file

@ -0,0 +1,19 @@
import { NgModule } from "@angular/core";
import { NbIconModule } from "@nebular/theme";
import { DateCalendarComponent } from "./date/date-calendar.component";
import { VerificationIconComponent } from "./verification/verification-icon.component";
@NgModule({
declarations: [
VerificationIconComponent,
DateCalendarComponent,
],
exports: [
VerificationIconComponent,
DateCalendarComponent,
],
imports: [
NbIconModule,
]
})
export class WidgetsModule { }