feat(pages): implement starter page with themes

This commit is contained in:
Alex 2020-03-25 13:52:20 +03:00 committed by Sergey Andrievskiy
parent 00f0e4a2ba
commit fb6e04b80a
16 changed files with 343 additions and 184 deletions

View file

@ -55,6 +55,8 @@ import { SecurityCamerasService } from './mock/security-cameras.service';
import { RippleService } from './utils/ripple.service'; import { RippleService } from './utils/ripple.service';
import { MockDataModule } from './mock/mock-data.module'; import { MockDataModule } from './mock/mock-data.module';
import { AbService } from './utils/ab.service'; import { AbService } from './utils/ab.service';
import {CurrentThemeService} from './utils/theme.service';
import {ThemeGuard} from './guard/theme.guard';
const socialLinks = [ const socialLinks = [
{ {
@ -97,6 +99,10 @@ const DATA_SERVICES = [
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useExisting: RippleService}, {provide: MAT_RIPPLE_GLOBAL_OPTIONS, useExisting: RippleService},
]; ];
const GUARDS = [
ThemeGuard,
];
export class NbSimpleRoleProvider extends NbRoleProvider { export class NbSimpleRoleProvider extends NbRoleProvider {
getRole() { getRole() {
// here you could provide any role based on any auth flow // here you could provide any role based on any auth flow
@ -107,6 +113,7 @@ export class NbSimpleRoleProvider extends NbRoleProvider {
export const NB_CORE_PROVIDERS = [ export const NB_CORE_PROVIDERS = [
...MockDataModule.forRoot().providers, ...MockDataModule.forRoot().providers,
...DATA_SERVICES, ...DATA_SERVICES,
...GUARDS,
...NbAuthModule.forRoot({ ...NbAuthModule.forRoot({
strategies: [ strategies: [
@ -148,6 +155,7 @@ export const NB_CORE_PROVIDERS = [
SeoService, SeoService,
StateService, StateService,
AbService, AbService,
CurrentThemeService,
]; ];
@NgModule({ @NgModule({

View file

@ -0,0 +1,28 @@
import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import {CurrentThemeService} from '../utils/theme.service';
import {NbDateService} from '@nebular/theme';
@Injectable()
export class ThemeGuard implements CanActivate {
constructor(private router: Router,
private currentThemeService: CurrentThemeService,
private dateService: NbDateService<number>) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean> | Promise<boolean> | boolean {
return this.currentThemeService.currentTheme$.pipe(
map(theme => {
const currentThemeExpiration = JSON.parse(theme).expires_in;
const currentDate = new Date().getTime();
if (!theme || currentDate > currentThemeExpiration) {
this.router.navigate(['themes']);
}
return true;
}));
}
}

View file

@ -0,0 +1,16 @@
import {Injectable, OnDestroy} from '@angular/core';
import {Observable} from 'rxjs';
import {takeWhile} from 'rxjs/operators';
@Injectable()
export class CurrentThemeService implements OnDestroy {
alive = true;
readonly currentTheme$: Observable<any> = new Observable(subscriber => {
subscriber.next(localStorage.theme);
}).pipe(takeWhile(() => this.alive));
ngOnDestroy(): void {
this.alive = false;
}
}

View file

@ -8,11 +8,12 @@ import {
NbRequestPasswordComponent, NbRequestPasswordComponent,
NbResetPasswordComponent, NbResetPasswordComponent,
} from '@nebular/auth'; } from '@nebular/auth';
import {StarterScreenComponent} from './themes-screen/starter-screen.component'; import { ThemeGuard } from './@core/guard/theme.guard';
export const routes: Routes = [ export const routes: Routes = [
{ {
path: 'pages', path: 'pages',
canActivate: [ThemeGuard],
loadChildren: () => import('./pages/pages.module') loadChildren: () => import('./pages/pages.module')
.then(m => m.PagesModule), .then(m => m.PagesModule),
}, },
@ -24,6 +25,7 @@ export const routes: Routes = [
{ {
path: 'auth', path: 'auth',
component: NbAuthComponent, component: NbAuthComponent,
canActivate: [ThemeGuard],
children: [ children: [
{ {
path: '', path: '',
@ -51,8 +53,8 @@ export const routes: Routes = [
}, },
], ],
}, },
{ path: '', redirectTo: 'pages', pathMatch: 'full' }, { path: '', redirectTo: 'themes', pathMatch: 'full' },
{ path: '**', redirectTo: 'pages' }, { path: '**', redirectTo: 'themes' },
]; ];
const config: ExtraOptions = { const config: ExtraOptions = {

View file

@ -1,65 +1,49 @@
<nb-layout windowMode> <nb-layout windowMode>
<nb-layout-header fixed> <nb-layout-header fixed>
<!-- <div class="header-container">--> <div class="header-container">
<!-- <div class="logo-container">--> <div class="logo-container">
<!-- <a class="logo" href="#">ngx-<span>admin</span></a>--> <p class="logo">ngx-<span>admin</span></p>
<!-- </div>--> </div>
<!-- </div>--> </div>
<!-- <div class="header-container">--> <div class="header-container">
<!-- <nb-actions size="small">--> <nb-actions size="small">
<!-- <nb-action class="control-item github-stars">--> <nb-action class="control-item github-stars">
<!-- <span class="subtitle text">Support us: </span>--> <span class="subtitle text">Support us: </span>
<!-- <iframe src="https://ghbtns.com/github-btn.html?user=akveo&repo=ngx-admin&type=star&count=true"--> <iframe src="https://ghbtns.com/github-btn.html?user=akveo&repo=ngx-admin&type=star&count=true"
<!-- frameborder="0"--> frameborder="0"
<!-- scrolling="0"--> scrolling="0"
<!-- width="170px"--> width="170px"
<!-- height="20px">--> height="20px">
<!-- </iframe>--> </iframe>
<!-- </nb-action>--> </nb-action>
<!-- <nb-action class="control-item downloads-count">--> <nb-action class="control-item downloads-count">
<!-- <nb-icon icon="download"></nb-icon>--> <nb-icon icon="download"></nb-icon>
<!-- <span class="subtitle number">470.000</span>--> <span class="subtitle number">470.000</span>
<!-- </nb-action>--> </nb-action>
<!-- <nb-action class="control-item contact-us">--> <nb-action class="control-item contact-us" (click)="trackEmailClick()">
<!-- &lt;!&ndash; (click)="trackEmailClick()"&ndash;&gt;--> <a nbButton ghost href="mailto:contact@akveo.com">
<!-- <a nbButton ghost href="mailto:contact@akveo.com" >--> <nb-icon icon="email-outline" pack="eva"></nb-icon>
<!-- <nb-icon icon="email-outline" pack="eva"></nb-icon>--> <span>contact@akveo.com</span>
<!-- <span>contact@akveo.com</span>--> </a>
<!-- </a>--> </nb-action>
<!-- </nb-action>--> </nb-actions>
<!-- </nb-actions>--> </div>
<!-- </div>-->
</nb-layout-header> </nb-layout-header>
<nb-layout-column class="small"> <nb-layout-column>
<nb-card> <h4>Choose theme</h4>
<nb-card-header>Light</nb-card-header>
<nb-card-body>
<img src="assets/image/corporate-theme.png"
class="swiper-lazy"
alt="Corporate Theme" />
</nb-card-body> <ng-container *ngFor="let theme of themes">
</nb-card> <nb-card (click)="navigate(theme.value)">
<nb-card> <nb-card-header>{{theme.name}}</nb-card-header>
<nb-card-header>Light</nb-card-header> <nb-card-body>
</nb-card> <img src="../../assets/images/{{theme.value}}-theme.png"
<nb-card> class="theme-preview"
<nb-card-header>Light</nb-card-header> alt="{{theme.name}} Theme"/>
</nb-card> </nb-card-body>
</nb-layout-column> </nb-card>
</ng-container>
<nb-layout-column>
<nb-card>
<nb-card-header>Light</nb-card-header>
</nb-card>
<nb-card>
<nb-card-header>Light</nb-card-header>
</nb-card>
<nb-card>
<nb-card-header>Light</nb-card-header>
</nb-card>
</nb-layout-column> </nb-layout-column>
<nb-layout-footer fixed> <nb-layout-footer fixed>

View file

@ -2,126 +2,176 @@
@import '~@nebular/theme/styles/global/breakpoints'; @import '~@nebular/theme/styles/global/breakpoints';
@import '../@theme/styles/themes'; @import '../@theme/styles/themes';
//@include nb-install-component() { @include nb-install-component() {
// display: flex; img {
// justify-content: space-between; width: 100%;
// width: 100%; object-fit: contain;
// }
// .logo-container {
// display: flex; h4 {
// align-items: center; text-align: center;
// width: calc(#{nb-theme(sidebar-width)} - #{nb-theme(header-padding)}); width: 100%;
// } height: 35px;
// margin-bottom: 36px;
// nb-action { }
// height: auto;
// display: flex; nb-layout-column {
// align-content: center; display: flex;
// } flex-wrap: wrap;
// justify-content: space-between;
// nb-user { }
// cursor: pointer;
// } nb-card {
// overflow: hidden;
// .subtitle { cursor: pointer;
// font-family: nb-theme(text-subtitle-font-family); width: 32%;
// font-size: nb-theme(text-subtitle-font-size); }
// font-weight: nb-theme(text-subtitle-font-weight);
// line-height: nb-theme(text-subtitle-line-height); nb-card-body {
// } padding: 0;
// height: auto;
// .downloads-count .number { }
// @include nb-ltr(margin-left, 0.5rem);
// @include nb-rtl(margin-right, 0.5rem); nb-layout-header {
// } display: flex;
// justify-content: space-between;
// ::ng-deep nb-search button { width: 100%;
// padding: 0!important;
// } ::ng-deep nav {
// width: 100%;
// .contact-us { justify-content: space-between;
// padding: 0; }
//
// nb-icon { .logo-container {
// font-size: 1.25rem; display: flex;
// } align-items: center;
// } width: calc(#{nb-theme(sidebar-width)} - #{nb-theme(header-padding)});
// }
// .header-container {
// display: flex; nb-action {
// align-items: center; height: auto;
// width: auto; display: flex;
// align-content: center;
// .sidebar-toggle { }
// @include nb-ltr(padding-right, 1.25rem);
// @include nb-rtl(padding-left, 1.25rem); .subtitle {
// text-decoration: none; font-family: nb-theme(text-subtitle-font-family);
// color: nb-theme(text-hint-color); font-size: nb-theme(text-subtitle-font-size);
// nb-icon { font-weight: nb-theme(text-subtitle-font-weight);
// font-size: 1.75rem; line-height: nb-theme(text-subtitle-line-height);
// } }
// }
// .downloads-count .number {
// .logo { @include nb-ltr(margin-left, 0.5rem);
// padding: 0 1.25rem; @include nb-rtl(margin-right, 0.5rem);
// font-size: 1.75rem; }
// @include nb-ltr(border-left, 1px solid nb-theme(divider-color));
// @include nb-rtl(border-right, 1px solid nb-theme(divider-color)); ::ng-deep nb-search button {
// white-space: nowrap; padding: 0 !important;
// text-decoration: none; }
// }
// } .contact-us {
// padding: 0;
// .github-stars {
// width: 245px; nb-icon {
// display: flex; font-size: 1.25rem;
// }
// iframe { }
// width: 100px;
// @include nb-ltr(margin-left, 1rem); .header-container {
// @include nb-rtl(margin-right, 1rem); display: flex;
// } align-items: center;
// } width: auto;
//
// @include media-breakpoint-down(xl) { .logo {
// .control-item.search, padding: 0 1.25rem;
// .control-item.notifications, font-size: 1.75rem;
// .control-item.email, margin-bottom: 0.5rem;
// .control-item.github-stars .text { @include nb-rtl(border-right, 1px solid nb-theme(divider-color));
// display: none; white-space: nowrap;
// } text-decoration: none;
// }
// .control-item.github-stars { }
// width: auto;
// .github-stars {
// iframe { width: 245px;
// margin: 0; display: flex;
// }
// } iframe {
// } width: 100px;
// @include nb-ltr(margin-left, 1rem);
// @include media-breakpoint-down(lg) { @include nb-rtl(margin-right, 1rem);
// .downloads-count { }
// display: none; }
// }
// } @include media-breakpoint-down(xl) {
// .control-item.github-stars .text {
// @include media-breakpoint-down(md) { display: none;
// .theme-select { }
// display: none;
// } .control-item.github-stars {
// } width: auto;
//
// @include media-breakpoint-down(sm) { iframe {
// .contact-us { margin: 0;
// display: none; }
// } }
// } }
//
// @include media-breakpoint-down(is) { @include media-breakpoint-down(lg) {
// .github-stars, .downloads-count {
// .user-action { display: none;
// display: none; }
// } }
// }
//} @include media-breakpoint-down(md) {
}
@include media-breakpoint-down(sm) {
.contact-us {
display: none;
}
}
@include media-breakpoint-down(is) {
.github-stars{
display: none;
}
}
}
@include media-breakpoint-down(xl) {
h4 {
margin: 0;
}
}
@include media-breakpoint-down(lg) {
h4 {
margin-bottom: 36px;
}
nb-card-header {
padding: 12px 20px;
}
nb-card {
width: 48%;
}
}
@include media-breakpoint-down(md) {
}
@include media-breakpoint-down(sm) {
nb-card-header {
padding: 10px 20px;
}
}
@include media-breakpoint-down(is) {
nb-card {
width: 100%;
}
}
}

View file

@ -1,8 +1,77 @@
import {Component} from '@angular/core'; import {Component, enableProdMode, OnDestroy} from '@angular/core';
import {NbMediaBreakpoint, NbThemeService} from '@nebular/theme';
import {ActivatedRoute, Router} from '@angular/router';
import {AnalyticsService} from '../@core/utils';
import { environment } from '../../environments/environment';
@Component({ @Component({
selector: 'ngx-starter', selector: 'ngx-starter',
templateUrl: './starter.component.html', templateUrl: './starter.component.html',
styleUrls: ['./starter.component.scss'], styleUrls: ['./starter.component.scss'],
}) })
export class NgxStarterComponent {} export class NgxStarterComponent implements OnDestroy {
breakpoint: NbMediaBreakpoint;
breakpoints: any;
private alive = true;
themes = [
{
value: 'material-light',
name: 'Material Light',
},
{
value: 'dark',
name: 'Dark',
},
{
value: 'default',
name: 'Light',
},
{
value: 'material-dark',
name: 'Material Dark',
},
{
value: 'corporate',
name: 'Corporate',
},
{
value: 'cosmic',
name: 'Cosmic',
},
];
constructor(private router: Router,
private route: ActivatedRoute,
protected themeService: NbThemeService,
private analytics: AnalyticsService,
) {}
navigate(themeName: string) {
const currentTheme = {
themeName: themeName,
expires_in: this.calculateExpiration(environment.currentThemeLife),
};
localStorage.setItem('theme', JSON.stringify(currentTheme));
this.themeService.changeTheme(themeName);
this.router.navigate(['/pages/dashboard'], {queryParams: {theme: themeName}});
}
trackEmailClick() {
this.analytics.trackEvent('clickContactEmail', 'click');
}
ngOnDestroy() {
this.alive = false;
}
calculateExpiration(iat: number): number {
const currentDate = new Date().getTime();
const timestamp = iat || Math.floor(Date.now() / 1000);
return Math.floor(timestamp + currentDate);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 KiB

After

Width:  |  Height:  |  Size: 328 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 KiB

After

Width:  |  Height:  |  Size: 355 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 958 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 658 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Before After
Before After

View file

@ -5,4 +5,5 @@
*/ */
export const environment = { export const environment = {
production: true, production: true,
currentThemeLife: 604800000, // 1 week in milliseconds
}; };

View file

@ -10,4 +10,5 @@
export const environment = { export const environment = {
production: false, production: false,
currentThemeLife: 604800000, // 1 week in milliseconds
}; };