diff --git a/src/app/@core/core.module.ts b/src/app/@core/core.module.ts index 1ec33d03..ca8160e8 100644 --- a/src/app/@core/core.module.ts +++ b/src/app/@core/core.module.ts @@ -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({ diff --git a/src/app/@core/guard/theme.guard.ts b/src/app/@core/guard/theme.guard.ts new file mode 100644 index 00000000..016172bc --- /dev/null +++ b/src/app/@core/guard/theme.guard.ts @@ -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) {} + + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, + ): Observable | Promise | 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; + })); + } +} diff --git a/src/app/@core/utils/theme.service.ts b/src/app/@core/utils/theme.service.ts new file mode 100644 index 00000000..b52ef350 --- /dev/null +++ b/src/app/@core/utils/theme.service.ts @@ -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 = new Observable(subscriber => { + subscriber.next(localStorage.theme); + }).pipe(takeWhile(() => this.alive)); + + ngOnDestroy(): void { + this.alive = false; + } +} diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 93810b0d..00381cbe 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -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 = { diff --git a/src/app/themes-screen/starter.component.html b/src/app/themes-screen/starter.component.html index d3b78471..1dc27dcc 100644 --- a/src/app/themes-screen/starter.component.html +++ b/src/app/themes-screen/starter.component.html @@ -1,65 +1,49 @@ - - - - - +
+
+ +
+
- - - - - - - - - - - - - - - - - - - - - - - - +
+ + + Support us: + + + + + 470.000 + + + + + contact@akveo.com + + + +
- - - Light - - Corporate Theme + +

Choose theme

-
-
- - Light - - - Light - -
- - - - Light - - - Light - - - Light - + + + {{theme.name}} + + {{theme.name}} Theme + + + diff --git a/src/app/themes-screen/starter.component.scss b/src/app/themes-screen/starter.component.scss index 92930115..0e3781f0 100644 --- a/src/app/themes-screen/starter.component.scss +++ b/src/app/themes-screen/starter.component.scss @@ -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%; + } + } +} diff --git a/src/app/themes-screen/starter.component.ts b/src/app/themes-screen/starter.component.ts index d8c2280c..fae2c015 100644 --- a/src/app/themes-screen/starter.component.ts +++ b/src/app/themes-screen/starter.component.ts @@ -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); + } +} diff --git a/src/assets/images/corporate-theme.png b/src/assets/images/corporate-theme.png index 74fb2cf9..e64efab7 100644 Binary files a/src/assets/images/corporate-theme.png and b/src/assets/images/corporate-theme.png differ diff --git a/src/assets/images/cosmic-theme.png b/src/assets/images/cosmic-theme.png index 1d7607f9..aad37875 100644 Binary files a/src/assets/images/cosmic-theme.png and b/src/assets/images/cosmic-theme.png differ diff --git a/src/assets/images/dark-theme.png b/src/assets/images/dark-theme.png index 2af97aab..b59c5d9e 100644 Binary files a/src/assets/images/dark-theme.png and b/src/assets/images/dark-theme.png differ diff --git a/src/assets/images/default-theme.png b/src/assets/images/default-theme.png new file mode 100644 index 00000000..935d5202 Binary files /dev/null and b/src/assets/images/default-theme.png differ diff --git a/src/assets/images/light-theme.png b/src/assets/images/light-theme.png deleted file mode 100644 index 035fd468..00000000 Binary files a/src/assets/images/light-theme.png and /dev/null differ diff --git a/src/assets/images/material-dark-theme.png b/src/assets/images/material-dark-theme.png index a61e22ef..2324cd7d 100644 Binary files a/src/assets/images/material-dark-theme.png and b/src/assets/images/material-dark-theme.png differ diff --git a/src/assets/images/material-light-theme.png b/src/assets/images/material-light-theme.png index 9135ec3d..80497862 100644 Binary files a/src/assets/images/material-light-theme.png and b/src/assets/images/material-light-theme.png differ diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 3b862d32..98d92d4b 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -5,4 +5,5 @@ */ export const environment = { production: true, + currentThemeLife: 604800000, // 1 week in milliseconds }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index e42b3a0b..58ceffd8 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -10,4 +10,5 @@ export const environment = { production: false, + currentThemeLife: 604800000, // 1 week in milliseconds };