feat(pages): implement starter page with themes
|
|
@ -55,6 +55,8 @@ import { SecurityCamerasService } from './mock/security-cameras.service';
|
|||
import { RippleService } from './utils/ripple.service';
|
||||
import { MockDataModule } from './mock/mock-data.module';
|
||||
import { AbService } from './utils/ab.service';
|
||||
import {CurrentThemeService} from './utils/theme.service';
|
||||
import {ThemeGuard} from './guard/theme.guard';
|
||||
|
||||
const socialLinks = [
|
||||
{
|
||||
|
|
@ -97,6 +99,10 @@ const DATA_SERVICES = [
|
|||
{provide: MAT_RIPPLE_GLOBAL_OPTIONS, useExisting: RippleService},
|
||||
];
|
||||
|
||||
const GUARDS = [
|
||||
ThemeGuard,
|
||||
];
|
||||
|
||||
export class NbSimpleRoleProvider extends NbRoleProvider {
|
||||
getRole() {
|
||||
// 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 = [
|
||||
...MockDataModule.forRoot().providers,
|
||||
...DATA_SERVICES,
|
||||
...GUARDS,
|
||||
...NbAuthModule.forRoot({
|
||||
|
||||
strategies: [
|
||||
|
|
@ -148,6 +155,7 @@ export const NB_CORE_PROVIDERS = [
|
|||
SeoService,
|
||||
StateService,
|
||||
AbService,
|
||||
CurrentThemeService,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
|||
28
src/app/@core/guard/theme.guard.ts
Normal 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;
|
||||
}));
|
||||
}
|
||||
}
|
||||
16
src/app/@core/utils/theme.service.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,12 @@ import {
|
|||
NbRequestPasswordComponent,
|
||||
NbResetPasswordComponent,
|
||||
} from '@nebular/auth';
|
||||
import {StarterScreenComponent} from './themes-screen/starter-screen.component';
|
||||
import { ThemeGuard } from './@core/guard/theme.guard';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: 'pages',
|
||||
canActivate: [ThemeGuard],
|
||||
loadChildren: () => import('./pages/pages.module')
|
||||
.then(m => m.PagesModule),
|
||||
},
|
||||
|
|
@ -24,6 +25,7 @@ export const routes: Routes = [
|
|||
{
|
||||
path: 'auth',
|
||||
component: NbAuthComponent,
|
||||
canActivate: [ThemeGuard],
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
|
|
@ -51,8 +53,8 @@ export const routes: Routes = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{ path: '', redirectTo: 'pages', pathMatch: 'full' },
|
||||
{ path: '**', redirectTo: 'pages' },
|
||||
{ path: '', redirectTo: 'themes', pathMatch: 'full' },
|
||||
{ path: '**', redirectTo: 'themes' },
|
||||
];
|
||||
|
||||
const config: ExtraOptions = {
|
||||
|
|
|
|||
|
|
@ -1,65 +1,49 @@
|
|||
<nb-layout windowMode>
|
||||
<nb-layout-header fixed>
|
||||
<!-- <div class="header-container">-->
|
||||
<!-- <div class="logo-container">-->
|
||||
<!-- <a class="logo" href="#">ngx-<span>admin</span></a>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<div class="header-container">
|
||||
<div class="logo-container">
|
||||
<p class="logo">ngx-<span>admin</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="header-container">-->
|
||||
<!-- <nb-actions size="small">-->
|
||||
<!-- <nb-action class="control-item github-stars">-->
|
||||
<!-- <span class="subtitle text">Support us: </span>-->
|
||||
<!-- <iframe src="https://ghbtns.com/github-btn.html?user=akveo&repo=ngx-admin&type=star&count=true"-->
|
||||
<!-- frameborder="0"-->
|
||||
<!-- scrolling="0"-->
|
||||
<!-- width="170px"-->
|
||||
<!-- height="20px">-->
|
||||
<!-- </iframe>-->
|
||||
<!-- </nb-action>-->
|
||||
<!-- <nb-action class="control-item downloads-count">-->
|
||||
<!-- <nb-icon icon="download"></nb-icon>-->
|
||||
<!-- <span class="subtitle number">470.000</span>-->
|
||||
<!-- </nb-action>-->
|
||||
<!-- <nb-action class="control-item contact-us">-->
|
||||
<!-- <!– (click)="trackEmailClick()"–>-->
|
||||
<!-- <a nbButton ghost href="mailto:contact@akveo.com" >-->
|
||||
<!-- <nb-icon icon="email-outline" pack="eva"></nb-icon>-->
|
||||
<!-- <span>contact@akveo.com</span>-->
|
||||
<!-- </a>-->
|
||||
<!-- </nb-action>-->
|
||||
<!-- </nb-actions>-->
|
||||
<!-- </div>-->
|
||||
<div class="header-container">
|
||||
<nb-actions size="small">
|
||||
<nb-action class="control-item github-stars">
|
||||
<span class="subtitle text">Support us: </span>
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=akveo&repo=ngx-admin&type=star&count=true"
|
||||
frameborder="0"
|
||||
scrolling="0"
|
||||
width="170px"
|
||||
height="20px">
|
||||
</iframe>
|
||||
</nb-action>
|
||||
<nb-action class="control-item downloads-count">
|
||||
<nb-icon icon="download"></nb-icon>
|
||||
<span class="subtitle number">470.000</span>
|
||||
</nb-action>
|
||||
<nb-action class="control-item contact-us" (click)="trackEmailClick()">
|
||||
<a nbButton ghost href="mailto:contact@akveo.com">
|
||||
<nb-icon icon="email-outline" pack="eva"></nb-icon>
|
||||
<span>contact@akveo.com</span>
|
||||
</a>
|
||||
</nb-action>
|
||||
</nb-actions>
|
||||
</div>
|
||||
</nb-layout-header>
|
||||
|
||||
<nb-layout-column class="small">
|
||||
<nb-card>
|
||||
<nb-card-header>Light</nb-card-header>
|
||||
<nb-card-body>
|
||||
<img src="assets/image/corporate-theme.png"
|
||||
class="swiper-lazy"
|
||||
alt="Corporate Theme" />
|
||||
<nb-layout-column>
|
||||
<h4>Choose theme</h4>
|
||||
|
||||
</nb-card-body>
|
||||
</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-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>
|
||||
<ng-container *ngFor="let theme of themes">
|
||||
<nb-card (click)="navigate(theme.value)">
|
||||
<nb-card-header>{{theme.name}}</nb-card-header>
|
||||
<nb-card-body>
|
||||
<img src="../../assets/images/{{theme.value}}-theme.png"
|
||||
class="theme-preview"
|
||||
alt="{{theme.name}} Theme"/>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
</ng-container>
|
||||
</nb-layout-column>
|
||||
|
||||
<nb-layout-footer fixed>
|
||||
|
|
|
|||
|
|
@ -2,126 +2,176 @@
|
|||
@import '~@nebular/theme/styles/global/breakpoints';
|
||||
@import '../@theme/styles/themes';
|
||||
|
||||
//@include nb-install-component() {
|
||||
// display: flex;
|
||||
// justify-content: space-between;
|
||||
// width: 100%;
|
||||
//
|
||||
// .logo-container {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// width: calc(#{nb-theme(sidebar-width)} - #{nb-theme(header-padding)});
|
||||
// }
|
||||
//
|
||||
// nb-action {
|
||||
// height: auto;
|
||||
// display: flex;
|
||||
// align-content: center;
|
||||
// }
|
||||
//
|
||||
// nb-user {
|
||||
// cursor: pointer;
|
||||
// }
|
||||
//
|
||||
// .subtitle {
|
||||
// font-family: nb-theme(text-subtitle-font-family);
|
||||
// font-size: nb-theme(text-subtitle-font-size);
|
||||
// font-weight: nb-theme(text-subtitle-font-weight);
|
||||
// line-height: nb-theme(text-subtitle-line-height);
|
||||
// }
|
||||
//
|
||||
// .downloads-count .number {
|
||||
// @include nb-ltr(margin-left, 0.5rem);
|
||||
// @include nb-rtl(margin-right, 0.5rem);
|
||||
// }
|
||||
//
|
||||
// ::ng-deep nb-search button {
|
||||
// padding: 0!important;
|
||||
// }
|
||||
//
|
||||
// .contact-us {
|
||||
// padding: 0;
|
||||
//
|
||||
// nb-icon {
|
||||
// font-size: 1.25rem;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// .header-container {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// width: auto;
|
||||
//
|
||||
// .sidebar-toggle {
|
||||
// @include nb-ltr(padding-right, 1.25rem);
|
||||
// @include nb-rtl(padding-left, 1.25rem);
|
||||
// text-decoration: none;
|
||||
// color: nb-theme(text-hint-color);
|
||||
// nb-icon {
|
||||
// font-size: 1.75rem;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// .logo {
|
||||
// padding: 0 1.25rem;
|
||||
// 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));
|
||||
// white-space: nowrap;
|
||||
// text-decoration: none;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// .github-stars {
|
||||
// width: 245px;
|
||||
// display: flex;
|
||||
//
|
||||
// iframe {
|
||||
// width: 100px;
|
||||
// @include nb-ltr(margin-left, 1rem);
|
||||
// @include nb-rtl(margin-right, 1rem);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @include media-breakpoint-down(xl) {
|
||||
// .control-item.search,
|
||||
// .control-item.notifications,
|
||||
// .control-item.email,
|
||||
// .control-item.github-stars .text {
|
||||
// display: none;
|
||||
// }
|
||||
//
|
||||
// .control-item.github-stars {
|
||||
// width: auto;
|
||||
//
|
||||
// iframe {
|
||||
// margin: 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @include media-breakpoint-down(lg) {
|
||||
// .downloads-count {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @include media-breakpoint-down(md) {
|
||||
// .theme-select {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @include media-breakpoint-down(sm) {
|
||||
// .contact-us {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @include media-breakpoint-down(is) {
|
||||
// .github-stars,
|
||||
// .user-action {
|
||||
// display: none;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@include nb-install-component() {
|
||||
img {
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
h4 {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
nb-layout-column {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
nb-card {
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
width: 32%;
|
||||
}
|
||||
|
||||
nb-card-body {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
nb-layout-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
::ng-deep nav {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: calc(#{nb-theme(sidebar-width)} - #{nb-theme(header-padding)});
|
||||
}
|
||||
|
||||
nb-action {
|
||||
height: auto;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-family: nb-theme(text-subtitle-font-family);
|
||||
font-size: nb-theme(text-subtitle-font-size);
|
||||
font-weight: nb-theme(text-subtitle-font-weight);
|
||||
line-height: nb-theme(text-subtitle-line-height);
|
||||
}
|
||||
|
||||
.downloads-count .number {
|
||||
@include nb-ltr(margin-left, 0.5rem);
|
||||
@include nb-rtl(margin-right, 0.5rem);
|
||||
}
|
||||
|
||||
::ng-deep nb-search button {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.contact-us {
|
||||
padding: 0;
|
||||
|
||||
nb-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
|
||||
.logo {
|
||||
padding: 0 1.25rem;
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
@include nb-rtl(border-right, 1px solid nb-theme(divider-color));
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.github-stars {
|
||||
width: 245px;
|
||||
display: flex;
|
||||
|
||||
iframe {
|
||||
width: 100px;
|
||||
@include nb-ltr(margin-left, 1rem);
|
||||
@include nb-rtl(margin-right, 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(xl) {
|
||||
.control-item.github-stars .text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.control-item.github-stars {
|
||||
width: auto;
|
||||
|
||||
iframe {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(lg) {
|
||||
.downloads-count {
|
||||
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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
selector: 'ngx-starter',
|
||||
templateUrl: './starter.component.html',
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 403 KiB After Width: | Height: | Size: 328 KiB |
|
Before Width: | Height: | Size: 455 KiB After Width: | Height: | Size: 355 KiB |
|
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 958 KiB |
BIN
src/assets/images/default-theme.png
Normal file
|
After Width: | Height: | Size: 381 KiB |
|
Before Width: | Height: | Size: 486 KiB |
|
Before Width: | Height: | Size: 658 KiB After Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 1.9 MiB |
|
|
@ -5,4 +5,5 @@
|
|||
*/
|
||||
export const environment = {
|
||||
production: true,
|
||||
currentThemeLife: 604800000, // 1 week in milliseconds
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,4 +10,5 @@
|
|||
|
||||
export const environment = {
|
||||
production: false,
|
||||
currentThemeLife: 604800000, // 1 week in milliseconds
|
||||
};
|
||||
|
|
|
|||