Browse Source

final commit

devBranch
Benjamin Arhen 3 years ago
parent
commit
54d3b97922
  1. 33902
      package-lock.json
  2. 8
      package.json
  3. 4
      src/app/app.component.ts
  4. 27
      src/app/app.module.ts
  5. 6
      src/app/app.resolvers.ts
  6. 4
      src/app/app.routing.ts
  7. 58
      src/app/core/auth/auth.service.ts
  8. 47
      src/app/core/user/user.service.ts
  9. 10
      src/app/layout/common/goldcoin/goldcoin.component.ts
  10. 164
      src/app/layout/common/notifications/notifications.component.html
  11. 126
      src/app/layout/common/notifications/notifications.component.ts
  12. 6
      src/app/layout/common/user/user.component.html
  13. 10
      src/app/layout/common/user/user.component.ts
  14. 2
      src/app/layout/layouts/vertical/compact/compact.component.html
  15. 5
      src/app/models/businessModel.ts
  16. 52
      src/app/models/dashSummary.ts
  17. 5
      src/app/models/generalModel.ts
  18. 2
      src/app/pages/admin/Coupons/Active/active-coupons.component.ts
  19. 1
      src/app/pages/admin/Coupons/DetailsDialog/details-dialog.component.ts
  20. 4
      src/app/pages/admin/Coupons/Inactive/inactive-coupons.component.html
  21. 2
      src/app/pages/admin/Coupons/Inactive/inactive-coupons.component.ts
  22. 2
      src/app/pages/admin/Coupons/coupons.component.html
  23. 297
      src/app/pages/admin/Dashboard/dashboard.component.html
  24. 21
      src/app/pages/admin/Dashboard/dashboard.component.scss
  25. 3468
      src/app/pages/admin/Dashboard/dashboard.component.ts
  26. 8
      src/app/pages/admin/Dashboard/dashboard.module.ts
  27. 37
      src/app/pages/admin/Dashboard/dashboard.service.ts
  28. 47
      src/app/pages/admin/Products/EditProduct/edit-product.component.html
  29. 63
      src/app/pages/admin/Products/EditProduct/edit-product.component.ts
  30. 29
      src/app/pages/admin/Products/products.service.ts
  31. 2
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.html
  32. 2
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.ts
  33. 3
      src/app/pages/admin/Profile/Information/information.component.html
  34. 2
      src/app/pages/admin/Profile/Information/information.component.ts
  35. 4
      src/app/pages/admin/Profile/QRCode/qrcode-dialog.component.html
  36. 0
      src/app/pages/admin/Profile/QRCode/qrcode-dialog.component.scss
  37. 25
      src/app/pages/admin/Profile/QRCode/qrcode-dialog.component.spec.ts
  38. 20
      src/app/pages/admin/Profile/QRCode/qrcode-dialog.component.ts
  39. 22
      src/app/pages/admin/Profile/profile.component.html
  40. 25
      src/app/pages/admin/Profile/profile.component.ts
  41. 4
      src/app/pages/admin/Profile/profile.module.ts
  42. 23
      src/app/pages/admin/Profile/profile.service.ts
  43. 2
      src/app/pages/admin/TempShop/temp-shop.component.ts
  44. 62
      src/app/pages/auth/confirmation-required/confirmation-required.component.html
  45. 18
      src/app/pages/auth/confirmation-required/confirmation-required.component.ts
  46. 22
      src/app/pages/auth/confirmation-required/confirmation-required.module.ts
  47. 9
      src/app/pages/auth/confirmation-required/confirmation-required.routing.ts
  48. 93
      src/app/pages/auth/forgot-password/forgot-password.component.html
  49. 105
      src/app/pages/auth/forgot-password/forgot-password.component.ts
  50. 32
      src/app/pages/auth/forgot-password/forgot-password.module.ts
  51. 9
      src/app/pages/auth/forgot-password/forgot-password.routing.ts
  52. 122
      src/app/pages/auth/reset-password/reset-password.component.html
  53. 111
      src/app/pages/auth/reset-password/reset-password.component.ts
  54. 32
      src/app/pages/auth/reset-password/reset-password.module.ts
  55. 9
      src/app/pages/auth/reset-password/reset-password.routing.ts
  56. 94
      src/app/pages/auth/sign-in/sign-in.component.css
  57. 122
      src/app/pages/auth/sign-in/sign-in.component.html
  58. 211
      src/app/pages/auth/sign-in/sign-in.component.ts
  59. 8
      src/app/pages/auth/sign-in/sign-in.module.ts
  60. 106
      src/app/pages/auth/sign-in/signOld.txt
  61. 136
      src/app/pages/auth/sign-up/sign-up.component.html
  62. 104
      src/app/pages/auth/sign-up/sign-up.component.ts
  63. 34
      src/app/pages/auth/sign-up/sign-up.module.ts
  64. 9
      src/app/pages/auth/sign-up/sign-up.routing.ts
  65. 25
      src/app/pages/auth/verification/verification-dialog.component.html
  66. 82
      src/app/pages/auth/verification/verification-dialog.component.scss
  67. 90
      src/app/pages/auth/verification/verification-dialog.component.ts
  68. 13
      src/app/pipes/percentage-pipe.ts
  69. 11
      src/app/window.service.ts
  70. BIN
      src/assets/icons/notransaction.png
  71. 12
      src/environments/environment.prod.ts
  72. 14
      src/environments/environment.ts

33902
package-lock.json

File diff suppressed because it is too large

8
package.json

@ -18,6 +18,7 @@
"@angular/common": "13.0.2",
"@angular/compiler": "13.0.2",
"@angular/core": "13.0.2",
"@angular/fire": "^7.3.0",
"@angular/forms": "13.0.2",
"@angular/material": "13.0.2",
"@angular/material-moment-adapter": "13.0.2",
@ -35,20 +36,25 @@
"@fullcalendar/rrule": "4.4.2",
"@fullcalendar/timegrid": "4.4.2",
"@ngneat/transloco": "^2.23.5",
"@types/grecaptcha": "^3.0.4",
"apexcharts": "3.28.1",
"chart.js": "^3.7.1",
"crypto-js": "3.3.0",
"dayjs": "^1.10.7",
"dayjs": "^1.11.0",
"highlight.js": "11.2.0",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.9.49",
"lodash-es": "4.17.21",
"moment": "2.29.1",
"ng-apexcharts": "1.5.12",
"ng-otp-input": "^1.8.5",
"ngx-markdown": "^12.1.0",
"ngx-mat-intl-tel-input": "^4.1.0",
"ngx-quill": "14.3.0",
"node.js": "^0.0.1-security",
"p-charts": "^2.0.1",
"perfect-scrollbar": "1.5.2",
"primeng": "^13.3.2",
"quill": "1.3.7",
"rrule": "2.6.8",
"rxjs": "6.6.7",

4
src/app/app.component.ts

@ -1,6 +1,8 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
import { interval } from 'rxjs';
import firebase from 'firebase/compat/app';
import { environment } from 'environments/environment';
@Component({
selector: 'app-root',
@ -14,6 +16,8 @@ export class AppComponent implements OnInit {
constructor(
private swUpdate: SwUpdate,
) {
firebase.initializeApp(environment.firebaseConfig);
}
ngOnInit(): void {
// check for platform update

27
src/app/app.module.ts

@ -14,12 +14,17 @@ import { appRoutes } from 'app/app.routing';
import { LocationStrategy, PathLocationStrategy } from '@angular/common';
import { tesoModule } from '@teso/teso.module';
import { NgxAwesomePopupModule, ConfirmBoxConfigModule } from '@costlydeveloper/ngx-awesome-popup';
import { ProductDescriptionShort } from './pipes/productDescriptionShort.pipe';
import { CouponWorthPipe } from './pipes/coupon-worth.pipe';
import { GoldTransactionComponent } from './pages/admin/GoldPurchase/gold-transaction.component';
import { ServiceWorkerModule } from '@angular/service-worker';
import { environment } from 'environments/environment.prod';
import { environment } from '../environments/environment';
import { TempShopModule } from './pages/admin/TempShop/temp-shop.module';
import { provideFirebaseApp, getApp, initializeApp } from '@angular/fire/app';
import { getFirestore, provideFirestore } from '@angular/fire/firestore';
import { provideAuth, getAuth } from '@angular/fire/auth';
import { NgOtpInputModule } from 'ng-otp-input';
import { VerificationDialogComponent } from './pages/auth/verification/verification-dialog.component';
import { AngularFireModule } from '@angular/fire/compat';
import { AngularFirestoreModule } from '@angular/fire/compat/firestore';
const routerConfig: ExtraOptions = {
preloadingStrategy: PreloadAllModules,
@ -30,12 +35,15 @@ const routerConfig: ExtraOptions = {
declarations: [
AppComponent,
GoldTransactionComponent,
VerificationDialogComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
RouterModule.forRoot(appRoutes, routerConfig),
NgOtpInputModule,
NgxAwesomePopupModule.forRoot(), // Essential, mandatory main module.
ConfirmBoxConfigModule.forRoot(),
// teso, tesoConfig & tesoMockAPI
@ -48,7 +56,12 @@ TempShopModule,
// Layout module of your application
LayoutModule,
provideFirebaseApp(() => initializeApp(environment.firebaseConfig)),
provideAuth(() => getAuth()),
provideFirestore(() => getFirestore()),
AngularFireModule.initializeApp(environment.firebaseConfig),
AngularFirestoreModule,
// provideFirestore(() => getFirestorre()),
// 3rd party modules that require global configuration via forRoot
MarkdownModule.forRoot({}),
ServiceWorkerModule.register('ngsw-worker.js', {
@ -60,11 +73,13 @@ TempShopModule,
],
providers: [
{ provide: LocationStrategy, useClass: PathLocationStrategy },
],
bootstrap: [
AppComponent
]
})
export class AppModule
{
export class AppModule {
}

6
src/app/app.resolvers.ts

@ -11,6 +11,7 @@ import { CouponsService } from './pages/admin/Coupons/coupons.service';
import { ProductsService } from './pages/admin/Products/products.service';
import { FollowersService } from './pages/admin/Followers/followers.service';
import { ProfileService } from './pages/admin/Profile/profile.service';
import { DashboardService } from './pages/admin/Dashboard/dashboard.service';
@Injectable({
providedIn: 'root'
@ -31,6 +32,7 @@ export class InitialDataResolver implements Resolve<any>
private _goldCoinService: GoldCoinService,
private _subscriberService:FollowersService,
private _profileService:ProfileService,
private _dashboardService:DashboardService,
) {
}
@ -57,7 +59,9 @@ export class InitialDataResolver implements Resolve<any>
this._productService.getCategories(),
this._productService.getData(),
this._subscriberService.getData(),
this._profileService.getCategories()
this._profileService.getCategories(),
this._dashboardService.getData(),
this._dashboardService.getDataGraph()
]);
}
}

4
src/app/app.routing.ts

@ -29,11 +29,7 @@ export const appRoutes: Route[] = [
layout: 'empty'
},
children: [
{path: 'confirmation-required', loadChildren: () => import('app/pages/auth/confirmation-required/confirmation-required.module').then(m => m.AuthConfirmationRequiredModule)},
{path: 'forgot-password', loadChildren: () => import('app/pages/auth/forgot-password/forgot-password.module').then(m => m.AuthForgotPasswordModule)},
{path: 'reset-password', loadChildren: () => import('app/pages/auth/reset-password/reset-password.module').then(m => m.AuthResetPasswordModule)},
{path: 'sign-in', loadChildren: () => import('app/pages/auth/sign-in/sign-in.module').then(m => m.AuthSignInModule)},
{path: 'sign-up', loadChildren: () => import('app/pages/auth/sign-up/sign-up.module').then(m => m.AuthSignUpModule)}
]
},

58
src/app/core/auth/auth.service.ts

@ -4,6 +4,7 @@ import { Observable, of, throwError } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
import { AuthUtils } from 'app/core/auth/auth.utils';
import { UserService } from 'app/core/user/user.service';
import { environment } from 'environments/environment';
@Injectable()
export class AuthService {
@ -76,10 +77,9 @@ export class AuthService {
return this._httpClient.post('api/auth/sign-in', credentials).pipe(
switchMap((response: any) => {
// Store the access token in the local storage
this.accessToken = response.accessToken;
this.relevantToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiItMTU5MDU0MDI0NCIsImp0aSI6IjRiYzI2MzdlLWY5OGEtNGZhOS04YmQ3LWNhOWYxYzQzYzAyNyIsImlzcyI6IlRFU08gQVVUSCBTRVJWRVIiLCJleHAiOjE2NDg4ODY1MDEsIm5iZiI6Ii0xNTkwNDgwMjQ0Iiwic3ViIjoiMVRFU0JVMDAwMDAwMDAiLCJidXNpbmVzc0lEIjoiMVRFU0JVMDAwMDAwMDAiLCJzdWJzY3JpcHRpb25QbGFuIjoiVFRTMDAxIiwiYnVzaW5lc3NOYW1lIjoiVGVzbyBHaGFuYSIsImF1ZCI6IlRFU08ifQ.mQg8CylBYrvA-L2570Rr-NOqIuuPeNhHAzorN6OW_Rs";
this.relevantToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiItMTk4NjA3NTYzIiwianRpIjoiODE5MTFjMzEtMzFjOC00MTVhLTk2Y2UtODg4ODBhZmE3Y2JkIiwiaXNzIjoiVEVTTyBBVVRIIFNFUlZFUiIsImV4cCI6MTY1MDI3ODQzNCwibmJmIjoiLTE5ODU0NzU2MyIsInN1YiI6IjFURVNCVTAwMDAwMDAwIiwiYnVzaW5lc3NJRCI6IjFURVNCVTAwMDAwMDAwIiwic3Vic2NyaXB0aW9uUGxhbiI6IlRUUzAwMSIsImJ1c2luZXNzTmFtZSI6IlRlc28gR2hhbmEiLCJhdWQiOiJURVNPIn0.x9Bj1F8DsLWStH30F44kff4W8Q8Wk6LrRmseBKLafs0";
// Set the authenticated flag to true
this._authenticated = true;
@ -141,19 +141,32 @@ export class AuthService {
*
* @param user
*/
signUp(user: { name: string; email: string; password: string; company: string }): Observable<any> {
return this._httpClient.post('api/auth/sign-up', user);
}
signUp(telephone: any, name: any, address: any,image:any): Observable<any> {
const formData = new FormData();
formData.set("telephone", telephone);
formData.set("businessName", name);
formData.set("digitalAddress", address);
if (image != null) {
formData.set("file", image.file);
}
return this._httpClient.post(environment.apiURL + 'businessregistration', formData).pipe(
switchMap((response: any) => {
// Store the access token in the local storage
this.accessToken = response.tokenTeso;
this.relevantToken = response.tokenTeso;
// Set the authenticated flag to true
this._authenticated = true;
// Store the user on the user service
this._userService.user = response.business;
// Return a new observable with the response
return of(response);
}));
/**
* Unlock session
*
* @param credentials
*/
unlockSession(credentials: { email: string; password: string }): Observable<any> {
return this._httpClient.post('api/auth/unlock-session', credentials);
}
/**
* Check the authentication status
*/
@ -178,4 +191,25 @@ export class AuthService {
// If the access token exists and it didn't expire, sign in using it
return this.signInUsingToken();
}
authenticate(phonenumber): Observable<any> {
const formData = new FormData();
formData.set("phoneNumber", phonenumber);
return this._httpClient.post(environment.apiURL + "businessauth", formData).pipe(
switchMap((response: any) => {
// Store the access token in the local storage
this.accessToken = response.tokenTeso;
this.relevantToken = response.tokenTeso;
// Set the authenticated flag to true
this._authenticated = true;
// Store the user on the user service
this._userService.user = response.business;
// Return a new observable with the response
return of(response);
})
);
}
}

47
src/app/core/user/user.service.ts

@ -5,19 +5,18 @@ import { map, tap } from 'rxjs/operators';
import { User } from 'app/core/user/user.types';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { environment } from 'environments/environment';
import { ProfileImage } from 'app/models/generalModel';
@Injectable({
providedIn: 'root'
})
export class UserService
{
export class UserService {
private _user: ReplaySubject<TesoBusinessDetail> = new ReplaySubject<TesoBusinessDetail>(1);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient)
{
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
@ -29,14 +28,12 @@ export class UserService
*
* @param value
*/
set user(value: TesoBusinessDetail)
{
set user(value: TesoBusinessDetail) {
// Store the value
this._user.next(value);
}
get user$(): Observable<TesoBusinessDetail>
{
get user$(): Observable<TesoBusinessDetail> {
return this._user.asObservable();
}
@ -47,8 +44,7 @@ export class UserService
/**
* Get the current logged in user data
*/
get(): Observable<TesoBusinessDetail>
{
get(): Observable<TesoBusinessDetail> {
return this._httpClient.get<TesoBusinessDetail>(environment.apiURL + 'business/getProfile').pipe(
tap((user) => {
this._user.next(user);
@ -61,12 +57,31 @@ export class UserService
*
* @param user
*/
update(user: TesoBusinessDetail): Observable<any>
{
update(user: TesoBusinessDetail, image: ProfileImage): Observable<any> {
const formData = new FormData();
formData.set("name", user.businessName);
formData.set("description", user.businessDescription);
formData.set("category", user.businessCategory);
formData.set("address", user.businessAddress);
formData.set("digitalAddress", user.businessDigitalAddress);
formData.set("email", user.businessEmail);
formData.set("date", user.dateOfEst.toString());
formData.set("contact", user.businessContact);
if (image != null) {
formData.set("file", image.file);
user.businessLogo = image.imageSRC;
user.handle = true;
}else{
user.handle = false;
user.businessLogo = "";
}
this._user.next(user);
return this._httpClient.patch<TesoBusinessDetail>('api/common/user', {user}).pipe(
map((response) => {
})
);
return this._httpClient.post(environment.apiURL + 'business/updateprofile', formData);
}
updateSub(user: TesoBusinessDetail) {
this._user.next(user);
}
}

10
src/app/layout/common/goldcoin/goldcoin.component.ts

@ -32,11 +32,11 @@ export class GoldCoinComponent implements OnInit, OnDestroy, AfterViewInit {
}
ngAfterViewInit(): void {
interval(1000)
// .pipe(takeWhile(() => !stop))
.subscribe(() => {
this._goldcoinsService.getAll();
});
// interval(1000)
// // .pipe(takeWhile(() => !stop))
// .subscribe(() => {
// this._goldcoinsService.getAll();
// });
}
// -----------------------------------------------------------------------------------------------------

164
src/app/layout/common/notifications/notifications.component.html

@ -1,11 +1,9 @@
<!-- Notifications toggle -->
<button
mat-icon-button
(click)="openPanel()"
#notificationsOrigin>
<button mat-icon-button (click)="openPanel()" #notificationsOrigin>
<ng-container *ngIf="unreadCount > 0">
<span class="absolute top-0 right-0 left-0 flex items-center justify-center h-3">
<span class="flex items-center justify-center flex-shrink-0 min-w-4 h-4 px-1 ml-4 mt-2.5 rounded-full bg-teal-600 text-indigo-50 text-xs font-medium">
<span
class="flex items-center justify-center flex-shrink-0 min-w-4 h-4 px-1 ml-4 mt-2.5 rounded-full bg-teal-600 text-indigo-50 text-xs font-medium">
{{unreadCount}}
</span>
</span>
@ -16,29 +14,22 @@
<!-- Notifications panel -->
<ng-template #notificationsPanel>
<div class="fixed inset-0 sm:static sm:inset-auto flex flex-col sm:min-w-90 sm:w-90 sm:rounded-2xl overflow-hidden shadow-lg">
<div
class="fixed inset-0 sm:static sm:inset-auto flex flex-col sm:min-w-90 sm:w-90 sm:rounded-2xl overflow-hidden shadow-lg">
<!-- Header -->
<div class="flex flex-shrink-0 items-center py-4 pr-4 pl-6 bg-primary text-on-primary">
<div class="flex flex-shrink-0 items-center py-4 pr-4 pl-6 bg-primary text-on-primary"
style="background-color: #0f172a !important;">
<div class="sm:hidden -ml-1 mr-3">
<button
mat-icon-button
(click)="closePanel()">
<mat-icon
class="icon-size-5 text-current"
[svgIcon]="'heroicons_solid:x'"></mat-icon>
<button mat-icon-button (click)="closePanel()">
<mat-icon class="icon-size-5 text-current" [svgIcon]="'heroicons_solid:x'"></mat-icon>
</button>
</div>
<div class="text-lg font-medium leading-10">Notifications</div>
<div class="ml-auto">
<button
mat-icon-button
[matTooltip]="'Mark all as read'"
[disabled]="unreadCount === 0"
<button mat-icon-button [matTooltip]="'Mark all as read'" [disabled]="unreadCount === 0"
(click)="markAllAsRead()">
<mat-icon
class="icon-size-5 text-current"
[svgIcon]="'heroicons_solid:mail-open'"></mat-icon>
<mat-icon class="icon-size-5 text-current" [svgIcon]="'heroicons_solid:mail-open'"></mat-icon>
</button>
</div>
</div>
@ -47,96 +38,110 @@
<div class="relative flex flex-col flex-auto sm:max-h-120 divide-y overflow-y-auto bg-card">
<!-- Notifications -->
<ng-container *ngFor="let notification of notifications; trackBy: trackByFn">
<div
class="flex group hover:bg-gray-50 dark:hover:bg-black dark:hover:bg-opacity-5"
<div class="flex group hover:bg-gray-50 dark:hover:bg-black dark:hover:bg-opacity-5"
[ngClass]="{'unread': !notification.read}">
<!-- Notification with a link -->
<ng-container *ngIf="notification.link">
<!-- Normal links -->
<ng-container *ngIf="!notification.useRouter">
<a
class="flex flex-auto py-5 pl-6 cursor-pointer"
[href]="notification.link">
<ng-container *ngTemplateOutlet="notificationContent"></ng-container>
</a>
</ng-container>
<!-- Router links -->
<ng-container *ngIf="notification.useRouter">
<a
class="flex flex-auto py-5 pl-6 cursor-pointer"
[routerLink]="notification.link">
<ng-container *ngTemplateOutlet="notificationContent"></ng-container>
</a>
<!-- Notification without a link -->
<ng-container *ngIf="notification.notificationType === 'subscription'">
<div class="flex flex-auto py-5 pl-6">
<ng-container *ngTemplateOutlet="notificationSubscription"></ng-container>
</div>
</ng-container>
<!-- Notification without a link -->
<ng-container *ngIf="notification.notificationType === 'couponRedemption'">
<div class="flex flex-auto py-5 pl-6">
<ng-container *ngTemplateOutlet="notificationRedemption"></ng-container>
</div>
</ng-container>
<!-- Notification without a link -->
<ng-container *ngIf="!notification.link">
<ng-container *ngIf="notification.notificationType === 'couponAcquisition'">
<div class="flex flex-auto py-5 pl-6">
<ng-container *ngTemplateOutlet="notificationContent"></ng-container>
<ng-container *ngTemplateOutlet="couponAcquisition"></ng-container>
</div>
</ng-container>
<!-- Actions -->
<div class="relative flex flex-col my-5 mr-6 ml-2">
<!-- Indicator -->
<button
class="w-6 h-6 min-h-6"
mat-icon-button
(click)="toggleRead(notification)"
<button class="w-6 h-6 min-h-6" mat-icon-button (click)="toggleRead(notification)"
[matTooltip]="notification.read ? 'Mark as unread' : 'Mark as read'">
<span
class="w-2 h-2 rounded-full"
[ngClass]="{'bg-gray-400 dark:bg-gray-500 sm:opacity-0 sm:group-hover:opacity-100': notification.read,
<span class="w-2 h-2 rounded-full" [ngClass]="{'bg-gray-400 dark:bg-gray-500 sm:opacity-0 sm:group-hover:opacity-100': notification.read,
'bg-primary': !notification.read}"></span>
</button>
<!-- Remove -->
<button
class="w-6 h-6 min-h-6 sm:opacity-0 sm:group-hover:opacity-100"
mat-icon-button
(click)="delete(notification)"
[matTooltip]="'Remove'">
<mat-icon
class="icon-size-4"
[svgIcon]="'heroicons_solid:x'"></mat-icon>
<button class="w-6 h-6 min-h-6 sm:opacity-0 sm:group-hover:opacity-100" mat-icon-button
(click)="delete(notification)" [matTooltip]="'Remove'" >
<mat-icon class="icon-size-4" [svgIcon]="'heroicons_solid:x'"></mat-icon>
</button>
</div>
</div>
<!-- Notification content template -->
<ng-template #notificationRedemption>
<!-- Icon -->
<ng-container>
<div
class="flex flex-shrink-0 items-center justify-center w-8 h-8 mr-4 rounded-full bg-gray-100 dark:bg-gray-700">
<mat-icon class="icon-size-5" [svgIcon]="'heroicons_outline:credit-card'">
</mat-icon>
</div>
</ng-container>
<!-- Title, description & time -->
<div class="flex flex-col flex-auto">
<ng-container>
<div class="font-semibold line-clamp-1">Coupon Redeemed</div>
</ng-container>
<ng-container *ngIf="notification.message">
<div class="line-clamp-2" [innerHTML]="notification.message"></div>
</ng-container>
<div class="mt-2 text-sm leading-none text-secondary">
{{notification.timestamp | date:'MMM dd, h:mm a'}}
</div>
</div>
</ng-template>
<!-- Notification content template -->
<ng-template #notificationContent>
<ng-template #couponAcquisition>
<!-- Icon -->
<ng-container *ngIf="notification.icon && !notification.image">
<div class="flex flex-shrink-0 items-center justify-center w-8 h-8 mr-4 rounded-full bg-gray-100 dark:bg-gray-700">
<mat-icon
class="icon-size-5"
[svgIcon]="notification.icon">
<ng-container>
<div
class="flex flex-shrink-0 items-center justify-center w-8 h-8 mr-4 rounded-full bg-gray-100 dark:bg-gray-700">
<mat-icon class="icon-size-5" [svgIcon]="'heroicons_outline:credit-card'">
</mat-icon>
</div>
</ng-container>
<!-- Title, description & time -->
<div class="flex flex-col flex-auto">
<ng-container>
<div class="font-semibold line-clamp-1">Coupon Claimed</div>
</ng-container>
<ng-container *ngIf="notification.message">
<div class="line-clamp-2" [innerHTML]="notification.message"></div>
</ng-container>
<div class="mt-2 text-sm leading-none text-secondary">
{{notification.timestamp | date:'MMM dd, h:mm a'}}
</div>
</div>
</ng-template>
<!-- Notification subscription template -->
<ng-template #notificationSubscription>
<!-- Image -->
<ng-container *ngIf="notification.image">
<img
class="flex-shrink-0 w-8 h-8 mr-4 rounded-full overflow-hidden object-cover object-center"
[src]="notification.image"
[alt]="'Notification image'">
<ng-container *ngIf="notification.initiatorThumbnail">
<img class="flex-shrink-0 w-8 h-8 mr-4 rounded-full overflow-hidden object-cover object-center"
[src]="imageLoader(notification.initiatorThumbnail)" [alt]="'Notification image'">
</ng-container>
<!-- Title, description & time -->
<div class="flex flex-col flex-auto">
<ng-container *ngIf="notification.title">
<div
class="font-semibold line-clamp-1"
[innerHTML]="notification.title"></div>
<div class="font-semibold line-clamp-1">New Subscriber</div>
</ng-container>
<ng-container *ngIf="notification.description">
<div
class="line-clamp-2"
[innerHTML]="notification.description"></div>
<ng-container>
<div class="line-clamp-2">{{notification.initiatorFirstname}}
{{notification.initiatorSurname}} started following you</div>
</ng-container>
<div class="mt-2 text-sm leading-none text-secondary">
{{notification.time | date:'MMM dd, h:mm a'}}
{{notification.timestamp | date:'MMM dd, h:mm a'}}
</div>
</div>
</ng-template>
@ -146,12 +151,11 @@
<ng-container *ngIf="!notifications || !notifications.length">
<div class="flex flex-col flex-auto items-center justify-center sm:justify-start py-12 px-8">
<div class="flex flex-0 items-center justify-center w-14 h-14 rounded-full bg-primary-100">
<mat-icon
class="text-primary-500-700"
[svgIcon]="'heroicons_outline:bell'"></mat-icon>
<mat-icon class="text-primary-500-700" [svgIcon]="'heroicons_outline:bell'"></mat-icon>
</div>
<div class="mt-5 text-2xl font-semibold tracking-tight">No notifications</div>
<div class="w-full max-w-60 mt-1 text-md text-center text-secondary">When you have notifications, they will appear here.</div>
<div class="w-full max-w-60 mt-1 text-md text-center text-secondary">When you have notifications,
they will appear here.</div>
</div>
</ng-container>

126
src/app/layout/common/notifications/notifications.component.ts

@ -2,10 +2,14 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { MatButton } from '@angular/material/button';
import { Subject } from 'rxjs';
import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Notification } from 'app/layout/common/notifications/notifications.types';
import { NotificationsService } from 'app/layout/common/notifications/notifications.service';
import { AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument } from '@angular/fire/compat/firestore';
import { environment } from 'environments/environment';
import jwt_decode from 'jwt-decode';
import { AuthService } from 'app/core/auth/auth.service';
@Component({
selector: 'notifications',
@ -14,12 +18,11 @@ import { NotificationsService } from 'app/layout/common/notifications/notificati
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'notifications'
})
export class NotificationsComponent implements OnInit, OnDestroy
{
export class NotificationsComponent implements OnInit, OnDestroy {
@ViewChild('notificationsOrigin') private _notificationsOrigin: MatButton;
@ViewChild('notificationsPanel') private _notificationsPanel: TemplateRef<any>;
notifications: Notification[];
notificationCollection: AngularFirestoreCollection<any>;
notifications: any[];
unreadCount: number = 0;
private _overlayRef: OverlayRef;
private _unsubscribeAll: Subject<any> = new Subject<any>();
@ -31,9 +34,13 @@ export class NotificationsComponent implements OnInit, OnDestroy
private _changeDetectorRef: ChangeDetectorRef,
private _notificationsService: NotificationsService,
private _overlay: Overlay,
private _viewContainerRef: ViewContainerRef
)
{
private _viewContainerRef: ViewContainerRef,
private db: AngularFirestore,
private _authService: AuthService
) {
const tokenInfo = this.getDecodedAccessToken(this._authService.relevantToken);
this.notificationCollection = db.collection("business_notifications").doc(tokenInfo.businessID).collection(tokenInfo.businessID);
}
// -----------------------------------------------------------------------------------------------------
@ -43,36 +50,34 @@ export class NotificationsComponent implements OnInit, OnDestroy
/**
* On init
*/
ngOnInit(): void
{
ngOnInit(): void {
// Subscribe to notification changes
this._notificationsService.notifications$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((notifications: Notification[]) => {
this.notificationCollection.valueChanges().subscribe((response) => {
// Load the notifications
this.notifications = notifications;
this.notifications = response;
// Calculate the unread count
this._calculateUnreadCount();
this.notificationCollection.get().subscribe((response) => {
response.docs.forEach((e) => {
e.ref.update({ "docRef": e.id });
});
});
// Mark for check
this._changeDetectorRef.markForCheck();
});
})
}
/**
* On destroy
*/
ngOnDestroy(): void
{
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
// Dispose the overlay
if ( this._overlayRef )
{
if (this._overlayRef) {
this._overlayRef.dispose();
}
}
@ -84,17 +89,14 @@ export class NotificationsComponent implements OnInit, OnDestroy
/**
* Open the notifications panel
*/
openPanel(): void
{
openPanel(): void {
// Return if the notifications panel or its origin is not defined
if ( !this._notificationsPanel || !this._notificationsOrigin )
{
if (!this._notificationsPanel || !this._notificationsOrigin) {
return;
}
// Create the overlay if it doesn't exist
if ( !this._overlayRef )
{
if (!this._overlayRef) {
this._createOverlay();
}
@ -105,39 +107,55 @@ export class NotificationsComponent implements OnInit, OnDestroy
/**
* Close the messages panel
*/
closePanel(): void
{
closePanel(): void {
this._overlayRef.detach();
}
/**
* Mark all notifications as read
*/
markAllAsRead(): void
{
markAllAsRead(): void {
// Mark all as read
this._notificationsService.markAllAsRead().subscribe();
this.notificationCollection.get().subscribe((response) => {
// Load the notifications
// this.notifications = response;
response.docs.forEach((e) => {
e.ref.update({ "read": true });
});
// // Calculate the unread count
this._calculateUnreadCount();
// Mark for check
this._changeDetectorRef.markForCheck();
})
}
/**
* Toggle read status of the given notification
*/
toggleRead(notification: Notification): void
{
// Toggle the read status
notification.read = !notification.read;
// Update the notification
this._notificationsService.update(notification.id, notification).subscribe();
toggleRead(notification): void {
this.notificationCollection.get().subscribe((response) => {
response.docs.forEach((e) => {
if (e.id == notification.docRef) {
e.ref.update({ "read": !notification.read });
}
})
});
}
/**
* Delete the given notification
*/
delete(notification: Notification): void
{
delete(notification): void {
// Delete the notification
this._notificationsService.delete(notification.id).subscribe();
this.notificationCollection.get().subscribe((response) => {
response.docs.forEach((e) => {
if (e.id == notification.docRef) {
e.ref.delete();
}
})
});
}
/**
@ -146,8 +164,7 @@ export class NotificationsComponent implements OnInit, OnDestroy
* @param index
* @param item
*/
trackByFn(index: number, item: any): any
{
trackByFn(index: number, item: any): any {
return item.id || index;
}
@ -158,8 +175,7 @@ export class NotificationsComponent implements OnInit, OnDestroy
/**
* Create the overlay
*/
private _createOverlay(): void
{
private _createOverlay(): void {
// Create the overlay
this._overlayRef = this._overlay.create({
hasBackdrop: true,
@ -208,15 +224,23 @@ export class NotificationsComponent implements OnInit, OnDestroy
*
* @private
*/
private _calculateUnreadCount(): void
{
private _calculateUnreadCount(): void {
let count = 0;
if ( this.notifications && this.notifications.length )
{
if (this.notifications && this.notifications.length) {
count = this.notifications.filter(notification => !notification.read).length;
}
this.unreadCount = count;
}
imageLoader(path: string): string {
return environment.apiURL + `followeruserdp/${path}`;
}
getDecodedAccessToken(token: string): any {
try {
return jwt_decode(token);
} catch (Error) {
return null;
}
}
}

6
src/app/layout/common/user/user.component.html

@ -6,7 +6,7 @@
<img
class="w-10 h-10 rounded-full"
*ngIf="showAvatar && user.businessLogo"
[src]="imageLoader(user.businessLogo)">
[src]="imageLoader(user.businessLogo,user.handle)">
<mat-icon
*ngIf="!showAvatar || !user.businessLogo"
[svgIcon]="'heroicons_outline:user-circle'"></mat-icon>
@ -28,10 +28,6 @@
<mat-icon [svgIcon]="'heroicons_outline:user-circle'"></mat-icon>
<span>Profile</span>
</button>
<button mat-menu-item (click)="changePassword()">
<mat-icon [svgIcon]="'heroicons_outline:lock-closed'"></mat-icon>
<span class="mt-1.5 text-md "> Password</span>
</button>
<mat-divider class="my-2"></mat-divider>
<button
mat-menu-item

10
src/app/layout/common/user/user.component.ts

@ -82,10 +82,10 @@ export class UserComponent implements OnInit, OnDestroy {
}
// Update the user
this._userService.update({
this._userService.updateSub({
...this.user,
}).subscribe();
});
}
/**
@ -97,9 +97,13 @@ export class UserComponent implements OnInit, OnDestroy {
profile(): void {
this._router.navigate(['/profile']);
}
imageLoader(path: string): string {
imageLoader(path: string, handle = false): string {
if (handle) {
return path;
} else {
return environment.apiURL + `shoplogo/${path}`;
}
}
changePassword() {
const dialogRef = this.dialog.open(SettingsComponent, {
hasBackdrop: true,

2
src/app/layout/layouts/vertical/compact/compact.component.html

@ -22,7 +22,7 @@
<div class="flex items-center pl-2 ml-auto space-x-0.5 sm:space-x-2">
<!-- <languages></languages> -->
<!-- <teso-fullscreen class="hidden md:block"></teso-fullscreen> -->
<goldcoins></goldcoins>
<!-- <goldcoins></goldcoins> -->
<!-- <messages></messages> -->
<notifications></notifications>
<!-- <button class="lg:hidden" mat-icon-button (click)="quickChat.toggle()">

5
src/app/models/businessModel.ts

@ -1,10 +1,12 @@
import { ProfileImage } from "./generalModel";
export interface Finance {
businessID: string;
gold: number;
}
export interface TesoBusinessDetail {
businessId?: string,
Handle?: string,
handle?: boolean,
businessName?: string,
businessEmail?: string,
businessTin?: string,
@ -17,6 +19,7 @@ export interface TesoBusinessDetail {
businessDigitalAddress?: string,
businessLatitude?: string,
businessLongitude?: string,
image?:ProfileImage,
}
export interface BusinessCategory {

52
src/app/models/dashSummary.ts

@ -0,0 +1,52 @@
export interface DashSummary {
summary:Summary;
views:ProductViews[];
recent:Transactions[];
visitorsCurrent:MonthVisits[];
visitorsPrevious:MonthVisits[];
redemptions:MonthRates[];
visits:MonthRates[];
sales:MonthRates[];
}
export interface Summary {
totalVisits: number;
couponRedeemed: number;
totalSales: number;
totalFreebie: number;
totalProximity: number;
totalDiscount: number;
visitLastMonth:number;
redemptionsLastMonth:number;
salesLastMonth:number;
}
export interface Transactions {
userGuid: String;
firstname: String;
surname: String;
timestamp: Date;
productName: String;
countId: number;
worth: number;
paid: number
}
export interface ProductViews {
userGuid: String;
firstname: String;
surname: String;
timesViewed: number;
pname: String;
productId: String;
picture: String;
}
export interface MonthVisits{
evisits:number;
inshop:number;
month:number;
year:number;
}
export interface MonthRates{
day:number;
rate:number;
}

5
src/app/models/generalModel.ts

@ -4,3 +4,8 @@ export interface ProductUpload {
imageSRC:string;
id?:string;
}
export interface ProfileImage {
file?: File;
imageSRC?:string;
id?:string;
}

2
src/app/pages/admin/Coupons/Active/active-coupons.component.ts

@ -67,7 +67,7 @@ export class ActiveCouponsComponent implements OnInit, AfterViewInit {
disableClose: true,
hasBackdrop: true,
data: { coupon: item },
width: "750px"
});
}
}

1
src/app/pages/admin/Coupons/DetailsDialog/details-dialog.component.ts

@ -60,6 +60,7 @@ export class DetailsDialogComponent implements OnInit {
this.products = d;
this.selectedProduct = this.products.find((p)=> p.productName == this.newCoupon.targetProduct);
});
console.log(this.newCoupon)
}

4
src/app/pages/admin/Coupons/Inactive/inactive-coupons.component.html

@ -93,7 +93,7 @@
</td>
</ng-container>
<!-- Amount -->
<ng-container matColumnDef="actions">
<!-- <ng-container matColumnDef="actions">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
Actions
</th>
@ -104,7 +104,7 @@
</div>
</td>
</ng-container>
</ng-container> -->
<!-- Footer -->
<ng-container matColumnDef="recentOrdersTableFooter">
<td class="py-6 px-0 border-0" mat-footer-cell *matFooterCellDef colspan="6">

2
src/app/pages/admin/Coupons/Inactive/inactive-coupons.component.ts

@ -12,7 +12,7 @@ import { MatTableDataSource } from '@angular/material/table';
})
export class InactiveCouponsComponent implements OnInit,AfterViewInit {
@Input() couponsDataSource: MatTableDataSource<any>;
couponsTableColumns: string[] = ['product', 'quantity', 'type', 'condition', 'range', 'claimed', 'status', 'expiration', 'actions'];
couponsTableColumns: string[] = ['product', 'quantity', 'type', 'condition', 'range', 'claimed', 'status', 'expiration'];//, 'actions'];
@ViewChild('couponsTable', { read: MatSort }) couponsTableMatSort: MatSort;
@ViewChild(MatPaginator) paginator: MatPaginator;
constructor() { }

2
src/app/pages/admin/Coupons/coupons.component.html

@ -8,6 +8,8 @@
<div class="text-3xl font-semibold tracking-tight leading-8">Coupons</div>
<div class="font-medium tracking-tight text-secondary">Here is a list of all your coupons on Teso ({{allcoupons.length}})
</div>
<div class="font-small tracking-tight text-secondary" *ngIf="allcoupons.length > 0">({{activecouponsData.data.length}}) active coupons, ({{inactivecouponsData.data.length}}) inactive coupons
</div>
</div>
<div class="flex flex-shrink-0 items-center mt-6 sm:mt-0 sm:ml-4">

297
src/app/pages/admin/Dashboard/dashboard.component.html

@ -6,14 +6,16 @@
<div class="flex items-center justify-between w-full">
<div>
<div class="text-3xl font-semibold tracking-tight leading-8">Analytics dashboard</div>
<div class="font-medium tracking-tight text-secondary">Monitor metrics, check statistics and review performance on Teso</div>
<div class="font-medium tracking-tight text-secondary">Monitor metrics, check statistics and review
performance on Teso</div>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 w-full mt-8">
<!-- Visitors overview -->
<div class="sm:col-span-2 lg:col-span-3 light flex flex-col flex-auto bg-card shadow rounded-2xl overflow-hidden">
<div
class="sm:col-span-2 lg:col-span-3 light flex flex-col flex-auto bg-card shadow rounded-2xl overflow-hidden">
<div class="flex items-center justify-between mt-10 ml-10 mr-6 sm:mr-10">
<div class="flex flex-col">
<div class="mr-4 text-2xl md:text-3xl font-semibold tracking-tight leading-7">Visitors Overview
@ -21,10 +23,13 @@
<div class="font-medium text-secondary">Number of unique visitors</div>
</div>
<div class="ml-2">
<mat-button-toggle-group class="hidden sm:inline-flex border-none space-x-1" value="this-year" #visitorsYearSelector="matButtonToggleGroup">
<mat-button-toggle class="px-1.5 rounded-full overflow-hidden border-none font-medium" value="last-year">Last Year
<mat-button-toggle-group class="hidden sm:inline-flex border-none space-x-1" value="this-year"
#visitorsYearSelector="matButtonToggleGroup">
<mat-button-toggle class="px-1.5 rounded-full overflow-hidden border-none font-medium"
value="last-year" (click)="toggle()">Last Year
</mat-button-toggle>
<mat-button-toggle class="px-1.5 rounded-full overflow-hidden border-none font-medium" value="this-year">This Year
<mat-button-toggle class="px-1.5 rounded-full overflow-hidden border-none font-medium"
value="this-year" (click)="toggle()">This Year
</mat-button-toggle>
</mat-button-toggle-group>
<div class="sm:hidden">
@ -32,111 +37,151 @@
<mat-icon [svgIcon]="'heroicons_outline:dots-vertical'"></mat-icon>
</button>
<mat-menu #visitorsMenu="matMenu">
<button mat-menu-item>This Year</button>
<button mat-menu-item>Last Year</button>
<button mat-menu-item (click)="toggle()">This Year</button>
<button mat-menu-item (click)="toggle()">Last Year</button>
</mat-menu>
</div>
</div>
</div>
<div class="flex flex-col flex-auto h-80">
<apx-chart class="flex-auto w-full h-full" [chart]="chartVisitors.chart" [colors]="chartVisitors.colors" [dataLabels]="chartVisitors.dataLabels" [fill]="chartVisitors.fill" [grid]="chartVisitors.grid" [series]="chartVisitors.series[visitorsYearSelector.value]"
[stroke]="chartVisitors.stroke" [tooltip]="chartVisitors.tooltip" [xaxis]="chartVisitors.xaxis" [yaxis]="chartVisitors.yaxis">
</apx-chart>
<div class="flex flex-col flex-auto" *ngIf="mainChartRendered">
<p-chart type="bar" [data]="accountBalanceOptions" [options]="basicOptions">
</p-chart>
</div>
<div class="h-90" style="display: block;justify-content: space-around;" *ngIf="!mainChartRendered">
<center>
<img src="assets/icons/notransaction.png" style="max-width: 400px;" />
<h6 style="color: gray;" class="whitespace-wrap">Sorry, you only get to see the nice graphs
after your very first transaction with a customer on Teso!!</h6>
</center>
</div>
<!-- Conversions -->
</div>
<!-- Redemptions -->
<div class="sm:col-span-2 lg:col-span-1 flex flex-col flex-auto bg-card shadow rounded-2xl overflow-hidden">
<div class="flex items-start justify-between m-6 mb-0">
<div class="text-lg font-medium tracking-tight leading-6 truncate">Coupon Redemptions</div>
<div class="ml-2">
<button class="h-6 min-h-6 px-2 rounded-full bg-hover" mat-button [matMenuTriggerFor]="conversionMenu">
<span class="font-medium text-sm text-secondary">30 days</span>
<button class="h-6 min-h-6 px-2 rounded-full bg-hover" mat-button>
<span class="font-medium text-sm text-secondary">This month</span>
</button>
<mat-menu #conversionMenu="matMenu">
<button mat-menu-item>30 days</button>
<button mat-menu-item>3 months</button>
<button mat-menu-item>9 months</button>
</mat-menu>
</div>
</div>
<div class="flex flex-col lg:flex-row lg:items-center mx-6 mt-3">
<div class="text-7xl font-bold tracking-tighter leading-tight">{{data.conversions.amount | number:'1.0-0'}}
<div class="text-7xl font-bold tracking-tighter leading-tight">{{data.summary.couponRedeemed |
number:'1.0-0'}}
</div>
<div class="flex lg:flex-col lg:ml-3">
<mat-icon class="icon-size-5 text-red-500" [svgIcon]="'heroicons_solid:trending-down'">
<mat-icon class="icon-size-5 text-red-500" [svgIcon]="'heroicons_solid:trending-down'"
*ngIf="redemptionRate < 0">
</mat-icon>
<div class="flex items-center ml-1 lg:ml-0 lg:mt-0.5 text-md leading-none whitespace-nowrap text-secondary">
<span class="font-medium text-red-500">2%</span>
<span class="ml-1">below target</span>
<mat-icon class="icon-size-5 text-green-500" [svgIcon]="'heroicons_solid:trending-up'"
*ngIf="redemptionRate > 0">
</mat-icon>
<mat-icon class="icon-size-5 text-gray-500" [svgIcon]="'heroicons_solid:minus'"
*ngIf="redemptionRate == 0">
</mat-icon>
<div
class="flex items-center ml-1 lg:ml-0 lg:mt-0.5 text-md leading-none whitespace-nowrap text-secondary">
<span class="font-medium text-red-500" *ngIf="redemptionRate < 0">{{redemptionRate | number
: '1.2-2'}}%</span>
<span class="font-medium text-green-500" *ngIf="redemptionRate > 0">{{redemptionRate |
number : '1.2-2'}}%</span>
<span class="ml-1 font-medium text-red-500" *ngIf="redemptionRate < 0">decrement</span>
<span class="ml-1 font-medium text-green-500" *ngIf="redemptionRate > 0">increment</span>
</div>
</div>
</div>
<div class="flex flex-col flex-auto h-20">
<apx-chart class="flex-auto w-full h-full" [chart]="chartConversions.chart" [colors]="chartConversions.colors" [series]="chartConversions.series" [stroke]="chartConversions.stroke" [tooltip]="chartConversions.tooltip" [xaxis]="chartConversions.xaxis"
[yaxis]="chartConversions.yaxis"></apx-chart>
<apx-chart class="flex-auto w-full h-full" [chart]="chartRedeemptions.chart"
[colors]="chartRedeemptions.colors" [series]="chartRedeemptions.series"
[stroke]="chartRedeemptions.stroke" [tooltip]="chartRedeemptions.tooltip"
[xaxis]="chartRedeemptions.xaxis" [yaxis]="chartRedeemptions.yaxis" *ngIf="redemptionsRendered">
</apx-chart>
</div>
</div>
<!-- Impressions -->
<!-- Visits -->
<div class="flex flex-col flex-auto bg-card shadow rounded-2xl overflow-hidden">
<div class="flex items-start justify-between m-6 mb-0">
<div class="text-lg font-medium tracking-tight leading-6 truncate">Total Sales</div>
<div class="text-lg font-medium tracking-tight leading-6 truncate">Visits</div>
<div class="ml-2">
<button class="h-6 min-h-6 px-2 rounded-full bg-hover" mat-button [matMenuTriggerFor]="impressionsMenu">
<span class="font-medium text-sm text-secondary">30 days</span>
<button class="h-6 min-h-6 px-2 rounded-full bg-hover" mat-button>
<span class="font-medium text-sm text-secondary">This month</span>
</button>
<mat-menu #impressionsMenu="matMenu">
<button mat-menu-item>30 days</button>
<button mat-menu-item>3 months</button>
<button mat-menu-item>9 months</button>
</mat-menu>
</div>
</div>
<div class="flex flex-col lg:flex-row lg:items-center mx-6 mt-3">
<div class="text-7xl font-bold tracking-tighter leading-tight">{{data.impressions.amount | number:'1.0-0'}}
<div class="text-7xl font-bold tracking-tighter leading-tight">{{data.summary.totalVisits |
number:'1.0-0'}}
</div>
<div class="flex lg:flex-col lg:ml-3">
<mat-icon class="icon-size-5 text-red-500" [svgIcon]="'heroicons_solid:trending-down'">
<mat-icon class="icon-size-5 text-red-500" [svgIcon]="'heroicons_solid:trending-down'"
*ngIf="visitsRate < 0">
</mat-icon>
<div class="flex items-center ml-1 lg:ml-0 lg:mt-0.5 text-md leading-none whitespace-nowrap text-secondary">
<span class="font-medium text-red-500">4%</span>
<span class="ml-1">below target</span>
<mat-icon class="icon-size-5 text-green-500" [svgIcon]="'heroicons_solid:trending-up'"
*ngIf="visitsRate > 0">
</mat-icon>
<mat-icon class="icon-size-5 text-gray-500" [svgIcon]="'heroicons_solid:minus'"
*ngIf="visitsRate == 0">
</mat-icon>
<div
class="flex items-center ml-1 lg:ml-0 lg:mt-0.5 text-md leading-none whitespace-nowrap text-secondary">
<span class="font-medium text-red-500" *ngIf="visitsRate < 0">{{visitsRate | number :
'1.2-2'}}%</span>
<span class="font-medium text-green-500" *ngIf="visitsRate > 0">{{visitsRate | number :
'1.2-2'}}%</span>
<span class="ml-1 font-medium text-red-500" *ngIf="visitsRate < 0">decrement</span>
<span class="ml-1 font-medium text-green-500" *ngIf="visitsRate > 0">increment</span>
</div>
</div>
</div>
<div class="flex flex-col flex-auto h-20">
<apx-chart class="flex-auto w-full h-full" [chart]="chartImpressions.chart" [colors]="chartImpressions.colors" [series]="chartImpressions.series" [stroke]="chartImpressions.stroke" [tooltip]="chartImpressions.tooltip" [xaxis]="chartImpressions.xaxis"
[yaxis]="chartImpressions.yaxis"></apx-chart>
<apx-chart class="flex-auto w-full h-full" [chart]="chartVisits.chart" [colors]="chartVisits.colors"
[series]="chartVisits.series" [stroke]="chartVisits.stroke" [tooltip]="chartVisits.tooltip"
[xaxis]="chartVisits.xaxis" [yaxis]="chartVisits.yaxis" *ngIf="visitsRendered"></apx-chart>
</div>
</div>
<!-- Visits -->
<!-- Sales -->
<div class="flex flex-col flex-auto bg-card shadow rounded-2xl overflow-hidden">
<div class="flex items-start justify-between m-6 mb-0">
<div class="text-lg font-medium tracking-tight leading-6 truncate">Visits</div>
<div class="text-lg font-medium tracking-tight leading-6 truncate">Total Sales</div>
<div class="ml-2">
<button class="h-6 min-h-6 px-2 rounded-full bg-hover" mat-button [matMenuTriggerFor]="impressionsMenu">
<span class="font-medium text-sm text-secondary">30 days</span>
<button class="h-6 min-h-6 px-2 rounded-full bg-hover" mat-button>
<span class="font-medium text-sm text-secondary">This month</span>
</button>
<mat-menu #impressionsMenu="matMenu">
<button mat-menu-item>30 days</button>
<button mat-menu-item>3 months</button>
<button mat-menu-item>9 months</button>
</mat-menu>
</div>
</div>
<div class="flex flex-col lg:flex-row lg:items-center mx-6 mt-3">
<div class="text-7xl font-bold tracking-tighter leading-tight">{{data.visits.amount | number:'1.0-0'}}
<div class="text-7xl font-bold tracking-tighter leading-tight">{{data.summary.totalSales |
number:'1.0-0'}}
</div>
<div class="flex lg:flex-col lg:ml-3">
<mat-icon class="icon-size-5 text-red-500" [svgIcon]="'heroicons_solid:trending-down'">
<mat-icon class="icon-size-5 text-red-500" [svgIcon]="'heroicons_solid:trending-down'"
*ngIf="salesRate < 0">
</mat-icon>
<mat-icon class="icon-size-5 text-green-500" [svgIcon]="'heroicons_solid:trending-up'"
*ngIf="salesRate > 0">
</mat-icon>
<div class="flex items-center ml-1 lg:ml-0 lg:mt-0.5 text-md leading-none whitespace-nowrap text-secondary">
<span class="font-medium text-red-500">4%</span>
<span class="ml-1">below target</span>
<mat-icon class="icon-size-5 text-gray-500" [svgIcon]="'heroicons_solid:minus'"
*ngIf="salesRate == 0">
</mat-icon>
<div
class="flex items-center ml-1 lg:ml-0 lg:mt-0.5 text-md leading-none whitespace-nowrap text-secondary">
<span class="font-medium text-red-500" *ngIf="salesRate < 0">{{salesRate | number :
'1.2-2'}}%</span>
<span class="font-medium text-green-500" *ngIf="salesRate > 0">{{salesRate | number :
'1.2-2'}}%</span>
<span class="ml-1 font-medium text-red-500" *ngIf="salesRate < 0">decrement</span>
<span class="ml-1 font-medium text-green-500" *ngIf="salesRate > 0">increment</span>
</div>
</div>
</div>
<div class="flex flex-col flex-auto h-20">
<apx-chart class="flex-auto w-full h-full" [chart]="chartVisits.chart" [colors]="chartVisits.colors" [series]="chartVisits.series" [stroke]="chartVisits.stroke" [tooltip]="chartVisits.tooltip" [xaxis]="chartVisits.xaxis" [yaxis]="chartVisits.yaxis"></apx-chart>
<apx-chart class="flex-auto w-full h-full" [chart]="chartImpressions.chart"
[colors]="chartImpressions.colors" [series]="chartImpressions.series"
[stroke]="chartImpressions.stroke" [tooltip]="chartImpressions.tooltip"
[xaxis]="chartImpressions.xaxis" [yaxis]="chartImpressions.yaxis" *ngIf="salesRendered">
</apx-chart>
</div>
</div>
</div>
@ -146,10 +191,11 @@
<div class="xl:col-span-2 flex flex-col flex-auto bg-card shadow rounded-2xl overflow-hidden">
<div class="p-6">
<div class="mr-4 text-lg font-medium tracking-tight leading-6 truncate">Recent transactions</div>
<!-- <div class="text-secondary font-medium">1 pending, 4 completed</div> -->
<div class="text-secondary font-medium">Top 10 recent transactions</div>
</div>
<div class="overflow-x-auto mx-6">
<table class="w-full bg-transparent" mat-table matSort [dataSource]="recentTransactionsDataSource" [trackBy]="trackByFn" #recentTransactionsTable>
<table class="w-full bg-transparent" mat-table matSort [dataSource]="recentTransactionsDataSource"
[trackBy]="trackByFn" #recentTransactionsTable>
<!-- Transaction ID -->
<ng-container matColumnDef="transactionId">
@ -158,7 +204,7 @@
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 font-medium text-sm text-secondary whitespace-nowrap">
{{transaction.transactionId}}
{{transaction.countId}}
</span>
</td>
</ng-container>
@ -170,7 +216,7 @@
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-nowrap">
{{transaction.date | date:'MMM dd, y'}}
{{transaction.timestamp | date:'MMM dd, y'}}
</span>
</td>
</ng-container>
@ -182,58 +228,58 @@
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-nowrap">
{{transaction.name}}
{{transaction.firstname}} {{transaction.surname}}
</span>
</td>
</ng-container>
<!-- Amount -->
<ng-container matColumnDef="discount">
<!-- Product -->
<ng-container matColumnDef="productName">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
Amount Discounted
Item
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 font-medium whitespace-nowrap">
{{(transaction.amount - 900) | currency:'GH¢'}}
{{transaction.productName }}
</span>
</td>
</ng-container>
<!-- Amount -->
<ng-container matColumnDef="amount">
<ng-container matColumnDef="discount">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
Amount Paid
Amount Discounted
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 font-medium whitespace-nowrap">
{{transaction.amount | currency:'GH¢'}}
{{(transaction.worth) | currency:'GH¢'}}
</span>
</td>
</ng-container>
<!-- Status -->
<ng-container matColumnDef="status">
<!-- Amount -->
<ng-container matColumnDef="amount">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
Status
Amount Paid
</th>
<td mat-cell *matCellDef="let transaction">
<span class="inline-flex items-center font-bold text-xs px-2.5 py-0.5 rounded-full tracking-wide uppercase" [ngClass]="{'bg-red-200 text-red-800 dark:bg-red-600 dark:text-red-50': transaction.status === 'pending',
'bg-green-200 text-green-800 dark:bg-green-600 dark:text-green-50': transaction.status === 'completed'}">
<span class="leading-relaxed whitespace-nowrap">{{transaction.status}}</span>
<span class="pr-6 font-medium whitespace-nowrap">
{{transaction.paid | currency:'GH¢'}}
</span>
</td>
</ng-container>
<!-- Footer -->
<!-- Footer
<ng-container matColumnDef="recentOrdersTableFooter">
<td class="py-6 px-0 border-0" mat-footer-cell *matFooterCellDef colspan="6">
<button mat-stroked-button>See all transactions</button>
</td>
</ng-container>
</ng-container> -->
<tr mat-header-row *matHeaderRowDef="recentTransactionsTableColumns"></tr>
<tr class="order-row h-16" mat-row *matRowDef="let row; columns: recentTransactionsTableColumns;"></tr>
<tr class="h-16 border-0" mat-footer-row *matFooterRowDef="['recentOrdersTableFooter']"></tr>
<tr class="order-row h-16" mat-row
*matRowDef="let row; columns: recentTransactionsTableColumns;"></tr>
<!-- <tr class="h-16 border-0" mat-footer-row *matFooterRowDef="['recentOrdersTableFooter']"></tr> -->
</table>
</div>
</div>
@ -245,7 +291,7 @@
<div class="mr-4 text-lg font-medium tracking-tight leading-6 truncate" *ngIf="couponStats">
Coupon Statistics</div>
<div class="mr-4 text-lg font-medium tracking-tight leading-6 truncate" *ngIf="!couponStats">
Coupon Views</div>
Product Views</div>
<!-- <div class="text-secondary font-medium">Monthly budget summary</div> -->
</div>
<div class="ml-auto -mt-2 -mr-2">
@ -256,65 +302,118 @@
</div>
</div>
<div class="mt-6" *ngIf="!couponStats">
The following Teso users have shown interest in some of your coupons. Click to further engage them by sending them personalized coupons
The following Teso users have shown interest in some of your products. Click to further engage them
by sending them personalized coupons
</div>
<div class="mt-6" *ngIf="couponStats">
The chart below depicts your shops top performing types of coupons
</div>
<div class="my-8 space-y-8">
<div class="my-8 space-y-8" *ngIf="couponStats">
<div class="flex flex-col">
<div class="flex items-center">
<div class="flex items-center justify-center w-14 h-14 rounded bg-red-100 text-red-800 dark:bg-red-600 dark:text-red-50">
<div
class="flex items-center justify-center w-14 h-14 rounded bg-red-100 text-red-800 dark:bg-red-600 dark:text-red-50">
<mat-icon class="text-current" [svgIcon]="'heroicons_outline:credit-card'"></mat-icon>
</div>
<div class="flex-auto ml-4 leading-none">
<div class="text-sm font-medium text-secondary">Freebies</div>
<div class="mt-2 font-medium text-2xl">{{dataE.budget.expenses | currency:'GH¢'}}</div>
<mat-progress-bar class="mt-3 rounded-full" [color]="'warn'" [mode]="'determinate'" [value]="(dataE.budget.expenses * 100) / dataE.budget.expensesLimit">
<div class="mt-2 font-medium text-2xl">{{financialStats.totalFreebie | currency:'GH¢'}}
</div>
<mat-progress-bar class="mt-3 rounded-full" [color]="'warn'" [mode]="'determinate'"
[value]="freebieRate">
</mat-progress-bar>
</div>
<div class="flex items-end justify-end min-w-18 mt-auto ml-6">
<div class="text-lg leading-none">2.6%</div>
<mat-icon class="text-green-600 icon-size-4 ml-1" [svgIcon]="'heroicons_solid:arrow-narrow-down'"></mat-icon>
<div class="text-lg leading-none">{{freebieRate | number : '1.2-2'}}%</div>
<mat-icon class="text-green-600 icon-size-4 ml-1"
[svgIcon]="'heroicons_solid:arrow-narrow-down'"></mat-icon>
</div>
</div>
</div>
<div class="flex flex-col">
<div class="flex items-center">
<div class="flex items-center justify-center w-14 h-14 rounded bg-indigo-100 text-indigo-800 dark:bg-indigo-600 dark:text-indigo-50">
<div
class="flex items-center justify-center w-14 h-14 rounded bg-indigo-100 text-indigo-800 dark:bg-indigo-600 dark:text-indigo-50">
<mat-icon class="text-current" [svgIcon]="'heroicons_outline:cash'"></mat-icon>
</div>
<div class="flex-auto ml-4 leading-none">
<div class="text-sm font-medium text-secondary">Discount</div>
<div class="mt-2 font-medium text-2xl">{{dataE.budget.savings | currency:'GH¢'}}</div>
<mat-progress-bar class="mt-3 rounded-full" [mode]="'determinate'" [value]="(dataE.budget.savings * 100) / dataE.budget.savingsGoal">
<div class="mt-2 font-medium text-2xl">{{financialStats.totalDiscount | currency:'GH¢'}}
</div>
<mat-progress-bar class="mt-3 rounded-full" [mode]="'determinate'"
[value]="discountRate">
</mat-progress-bar>
</div>
<div class="flex items-end justify-end min-w-18 mt-auto ml-6">
<div class="text-lg leading-none">12.7%</div>
<mat-icon class="text-red-600 icon-size-4 ml-1" [svgIcon]="'heroicons_solid:arrow-narrow-up'"></mat-icon>
<div class="text-lg leading-none">{{discountRate | number : '1.2-2'}}%</div>
<mat-icon class="text-red-600 icon-size-4 ml-1"
[svgIcon]="'heroicons_solid:arrow-narrow-up'"></mat-icon>
</div>
</div>
</div>
<div class="flex flex-col">
<div class="flex items-center">
<div class="flex items-center justify-center w-14 h-14 rounded bg-teal-100 text-teal-800 dark:bg-teal-600 dark:text-teal-50">
<div
class="flex items-center justify-center w-14 h-14 rounded bg-teal-100 text-teal-800 dark:bg-teal-600 dark:text-teal-50">
<mat-icon class="text-current" [svgIcon]="'heroicons_outline:light-bulb'"></mat-icon>
</div>
<div class="flex-auto ml-4 leading-none">
<div class="text-sm font-medium text-secondary">Proximity Coupons</div>
<div class="mt-2 font-medium text-2xl">{{dataE.budget.bills | currency:'GH¢'}}</div>
<mat-progress-bar class="mt-3 rounded-full" [mode]="'determinate'" [value]="(dataE.budget.bills * 100) / dataE.budget.billsLimit"></mat-progress-bar>
<div class="mt-2 font-medium text-2xl">{{financialStats.totalProximity |
currency:'GH¢'}}</div>
<mat-progress-bar class="mt-3 rounded-full" [mode]="'determinate'"
[value]="proximityRate">
</mat-progress-bar>
</div>
<div class="flex items-end justify-end min-w-18 mt-auto ml-6">
<div class="text-lg leading-none">105.7%</div>
<mat-icon class="text-red-600 icon-size-4 ml-1" [svgIcon]="'heroicons_solid:arrow-narrow-up'"></mat-icon>
<div class="text-lg leading-none">{{proximityRate | number : '1.2-2'}}%</div>
<mat-icon class="text-red-600 icon-size-4 ml-1"
[svgIcon]="'heroicons_solid:arrow-narrow-up'"></mat-icon>
</div>
</div>
</div>
</div>
<div class="my-8 space-y-8" *ngIf="!couponStats ">
<div class="flex flex-col">
<div class="flex items-center" *ngFor="let view of data.views">
<div style="display: flex;padding:10px;width: 100%;justify-content: space-between; border-bottom: lightgrey solid 0.4px;">
<div class="users-row" style="display: flex;padding:10px;width: 100%;">
<div id="circle">
<div>
<span>{{view.firstname.substring(0,2)}}</span>
</div>
</div>
<div class="flex flex-col">
<span class="pr-6 font-medium text-sm text-secondary whitespace-nowrap">
{{view.firstname}} {{view.surname}}
</span>
<span class="pr-6 font-small text-sm text-secondary wrap"
style=" word-break: break-word;">
{{view.pname}}
</span>
<span class="pr-6 font-small text-sm text-secondary whitespace-nowrap">
Number of Times Viewed : {{view.timesViewed}}
</span>
<div style="align-items: center;display: flex;margin-top: 10px;"
*ngIf="isScreenSmall">
<button mat-button style="background-color: #003445;color: white;"
(click)="generateCoupon(view)">Send
Coupon</button>
</div>
</div>
</div>
<div style="align-items: center;display: flex;justify-content: end;"
*ngIf="!isScreenSmall">
<button mat-button style="background-color: #003445;color: white;"
(click)="generateCoupon(view)">Send
Coupon</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

21
src/app/pages/admin/Dashboard/dashboard.component.scss

@ -0,0 +1,21 @@
.users-row {
cursor: pointer;
}
#circle {
background: #00a4be;
width: 45px;
height: 45px;
min-width: 45px;
border-radius: 64px;
color: #003445;
font-weight: bold;
text-align: center;
justify-content: center;
display: flex;
align-content: center;
align-items: center;
font-size: initial;
margin-right: 15px;
}

3468
src/app/pages/admin/Dashboard/dashboard.component.ts

File diff suppressed because it is too large

8
src/app/pages/admin/Dashboard/dashboard.module.ts

@ -13,11 +13,16 @@ import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { PercentagePipe } from 'app/pipes/percentage-pipe'
import {ChartModule} from 'primeng/chart';
import { CommonModule } from '@angular/common';
import { CarouselModule } from 'primeng/carousel';;
@NgModule({
declarations: [
DashboardComponent
DashboardComponent,
PercentagePipe
],
imports : [
RouterModule.forChild(dashboardRoutes),
@ -30,6 +35,7 @@ import { MatTableModule } from '@angular/material/table';
MatSortModule,
MatTableModule,
MatTooltipModule,
ChartModule,
NgApexchartsModule,
SharedModule
]

37
src/app/pages/admin/Dashboard/dashboard.service.ts

@ -3,6 +3,8 @@ import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { analytics as analyticsData } from 'app/mock-api/dashboards/analytics/data';
import { environment } from 'environments/environment';
import { DashSummary } from 'app/models/dashSummary';
@Injectable({
providedIn: 'root'
@ -10,6 +12,17 @@ import { analytics as analyticsData } from 'app/mock-api/dashboards/analytics/da
export class DashboardService
{
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
private _dataGraph: BehaviorSubject<any> = new BehaviorSubject(null);
private dashboard: DashSummary = {
summary: undefined,
views: [],
recent: [],
visitorsCurrent: [],
visitorsPrevious: [],
redemptions: [],
visits: [],
sales: []
};
/**
* Constructor
@ -25,10 +38,14 @@ export class DashboardService
/**
* Getter for data
*/
get data$(): Observable<any>
get data$(): Observable<DashSummary>
{
return this._data.asObservable();
}
get dataGraph$(): Observable<DashSummary>
{
return this._dataGraph.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
@ -37,13 +54,19 @@ export class DashboardService
/**
* Get data
*/
getData(): Observable<any>
getData(): Observable<DashSummary>
{
return this._httpClient.get(environment.apiURL + 'dashboard/summary').pipe(
tap((response: DashSummary) => {
this._data.next(response);
})
);
}
getDataGraph(): Observable<DashSummary>
{
console.log(analyticsData)
return this._httpClient.get('api/dashboards/analytics').pipe(
tap((response: any) => {
console.log(response)
this._data.next(analyticsData);
return this._httpClient.get(environment.apiURL + 'dashboard/dash-graph-overview').pipe(
tap((response: DashSummary) => {
this._dataGraph.next(response);
})
);
}

47
src/app/pages/admin/Products/EditProduct/edit-product.component.html

@ -28,12 +28,13 @@
<strong class="tileHead">Product Name </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="product.productName">
<input matInput style="margin-left:10px;" type="text"
[(ngModel)]="product.productName">
</mat-form-field>
</div>
<div style="display:flex;">
<mat-label style="margin-right: 20px;">
<strong>Product Category</strong>
<mat-label style="margin-right: 30px;">
<strong class="tileHead">Product Category </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<mat-select [(ngModel)]="product.categoryID">
@ -50,7 +51,8 @@
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<span matPrefix>GH¢ &nbsp;</span>
<input matInput style="margin-left:10px;" type="number" [(ngModel)]="product.unitPrice">
<input matInput style="margin-left:10px;" type="number"
[(ngModel)]="product.unitPrice">
</mat-form-field>
</div>
@ -78,26 +80,37 @@
<div class="font-medium tracking-tight text-secondary">Add up to 10 high
quality images of the product
</div>
<div class="font-medium tracking-tight text-secondary">
{{product.images.length}} out of
<div class="font-medium tracking-tight text-secondary"
*ngIf="product.images != null">{{product.images.length}} out of
<strong>10</strong>
</div>
</div>
<input style="display: none" type="file" (change)="onFileSelected($event)"
#fileInput accept="image/*" capture="user">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button
(click)="fileInput.click()" [disabled]="product.images.length > 9">Add Image
(click)="fileInput.click()" [disabled]="product.images.length > 9"
*ngIf="product.images != null">Add Image
</button>
</div>
<div class="imageList">
<div class="imageHolder" *ngFor="let images of product.images">
<img src="{{images.imageSRC}}" class="productImage">
<div *ngIf="images.productID != '%local%'">
<img [src]="imageLoader(images.path)" class="productImage">
<button class="deleteelement" style="border:none;"
(click)="removeImage(images)">
<mat-icon [svgIcon]="'heroicons_outline:trash'" style="color: red;">
</mat-icon>
</button>
</div>
<div *ngIf="images.productID == '%local%'">
<img src="{{images.path}}" class="productImage">
<button class="deleteelement" style="border:none;"
(click)="removeImage(images)">
<mat-icon [svgIcon]="'heroicons_outline:trash'" style="color: red;">
</mat-icon>
</button>
</div>
</div>
</div>
</div>
</mat-tab>
@ -193,7 +206,8 @@
<div class="font-medium tracking-tight text-secondary">Add up to 10 high
quality images of the product
</div>
<div class="font-medium tracking-tight text-secondary">0 out of
<div class="font-medium tracking-tight text-secondary"
*ngIf="product.images != null">{{product.images.length}} out of
<strong>10</strong>
</div>
</div>
@ -207,19 +221,30 @@
</div>
<div class="imageList">
<div class="imageHolder" *ngFor="let images of productImages">
<img src="{{images.imageSRC}}" class="productImage">
<div *ngIf="images.productID != '%local%'">
<img [src]="imageLoader(images.path)" class="productImage">
<button class="deleteelement" style="border:none;"
(click)="removeImage(images)">
<mat-icon [svgIcon]="'heroicons_outline:trash'" style="color: red;">
</mat-icon>
</button>
</div>
<div *ngIf="images.productID == '%local%'">
<img src="{{images.path}}" class="productImage">
<button class="deleteelement" style="border:none;"
(click)="removeImage(images)">
<mat-icon [svgIcon]="'heroicons_outline:trash'" style="color: red;">
</mat-icon>
</button>
</div>
</div>
</div>
</div>
</mat-tab>
</mat-tab-group>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: green;color:white;margin-right:20px;" mat-button (click)="submit()">Save Changes</button>
<button style="background-color: green;color:white;margin-right:20px;" mat-button
(click)="submit()">Save Changes</button>
<button style="background-color: firebrick;color:white;margin-left:10px;" mat-button
cdkFocusInitial>Cancel</button>
</div>

63
src/app/pages/admin/Products/EditProduct/edit-product.component.ts

@ -4,6 +4,7 @@ import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { ProductUpload } from 'app/models/generalModel';
import { ProductCategory, ProductImages, ProductsModel } from 'app/models/productsModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProductsService } from '../products.service';
@ -47,7 +48,9 @@ export class EditProductComponent implements OnInit {
isScreenSmall: boolean;
productImages: ProductUpload[] = [];
product: ProductsModel;
initalImages: string[];
categories: ProductCategory[] = [];
isLoading = true;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService, private changeDetector: ChangeDetectorRef,
@ -58,11 +61,14 @@ export class EditProductComponent implements OnInit {
if (this.product == null) {
this.router.navigate(['products']);
}
_productService.getImages(this.productID).subscribe((response: ProductImages[]) => {
this.initalImages = response.map(e => e.path);
});
this._productService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.categories = d;
this.changeDetector.markForCheck();
// this.isLoading = false;
this.isLoading = false;
if (this.product != null) {
this.product.categoryID = d.find((e) => e.catName = this.product.categoryID).catCode;
}
@ -81,16 +87,7 @@ export class EditProductComponent implements OnInit {
});
}
quillConfig = modules;
items = [
{ id: 1, name: 'Python' },
{ id: 2, name: 'Node Js' },
{ id: 3, name: 'Java' },
{ id: 4, name: 'PHP', disabled: true },
{ id: 5, name: 'Django' },
{ id: 6, name: 'Angular' },
{ id: 7, name: 'Vue' },
{ id: 8, name: 'ReactJs' },
];
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
@ -101,7 +98,6 @@ export class EditProductComponent implements OnInit {
const file: File = event.target.files[0];
if (file.type.includes("image")) {
console.log(file)
if (this.productImages.length == 0) {
var productImage: ProductUpload = {
file: file,
@ -112,9 +108,6 @@ export class EditProductComponent implements OnInit {
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
var prodImage: ProductImages = {
id: productImage.id,
productID: '%local%',
@ -124,6 +117,10 @@ export class EditProductComponent implements OnInit {
this.product.images.push(prodImage);
this.productImages.push(productImage);
}
reader.readAsDataURL(file);
} else {
var productImage: ProductUpload = {
file: file,
@ -135,9 +132,6 @@ export class EditProductComponent implements OnInit {
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
var prodImage: ProductImages = {
id: productImage.id,
productID: '%local%',
@ -146,6 +140,9 @@ export class EditProductComponent implements OnInit {
this.product.images.push(prodImage);
this.productImages.push(productImage);
}
reader.readAsDataURL(file);
}
} else {
this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe();
}
@ -159,11 +156,33 @@ export class EditProductComponent implements OnInit {
}
submit() {
var imagesToDelete: any[] = [];
this.initalImages.forEach((e) => {
if (!this.product.images.map((e) => e.path).includes(e)) {
imagesToDelete.push(e);
}
});
this.isLoading = true;
if (this.product.images.length == 0) {
this.confirmBoxEvokeService.warning("Add Product Images", "To add a new product, you must add at least one image to support it", "OK").subscribe();
} else {
this.confirmBoxEvokeService.warning("Action Required", "You must add at least one image of the product", "OK").subscribe();
} else if (this.product.categoryID == '' || this.product.productDesc == '' || this.product.productName == '' || this.product.unitPrice == 0) {
this.confirmBoxEvokeService.warning("Missing Fields", "To make changes to a product, you must fill up all fields", "OK").subscribe();
}
else {
this.confirmBoxEvokeService.success("Confirm Action", `Are you sure would like to make changes ${this.product.productName} ?`, "YES", "NO").subscribe(response => {
if (response.clickedButtonID == "yes") {
this._productService.editProduct(this.product, this.productImages, imagesToDelete).subscribe(() => {
this.confirmBoxEvokeService.success("Success", "Changes saved successfully !!!", "OK").subscribe( response =>{
this._productService.getData();
this.router.navigate(['products']);
});
});
}
});
}
this.isLoading = false;
}
imageLoader(path: string): string {
return environment.apiURL + `imagingproducts/${path}`;
}
}

29
src/app/pages/admin/Products/products.service.ts

@ -70,6 +70,14 @@ export class ProductsService {
return found;
}
getImages(id: string): Observable<ProductImages[]> {
var found = this.products.find(item => item.productID === id);
return this._httpClient.get(environment.apiURL + `allimages/${id}`).pipe(
tap((response: ProductImages[]) => {
}));
}
getCategories(): Observable<ProductCategory[]> {
return this._httpClient.get(environment.apiURL + `productcategories`).pipe(
tap((response: ProductCategory[]) => {
@ -94,4 +102,25 @@ export class ProductsService {
this.getData();
}));
}
editProduct(product: ProductsModel, productImage: ProductUpload[], remove: string[]): Observable<any> {
const formData = new FormData();
const images = productImage.map((e) => e.file);
formData.set("productID", product.productID);
formData.set("productName", product.productName);
formData.set("productDesc", product.productDesc);
formData.set("categoryId", product.categoryID);
formData.set("unitPrice", product.unitPrice.toString());
for (var i = 0; i < images.length; i++) {
formData.append("file[]", images[i]);
}
for (var i = 0; i < remove.length; i++) {
formData.append("removeImage", remove[i]);
console.log(remove[i])
}
// console.log(remove)
return this._httpClient.post(environment.apiURL + "products/editproduct", formData);
}
}

2
src/app/pages/admin/Profile/InfoDialog/information-dialog.component.html

@ -77,7 +77,7 @@
</div>
<div style="display: flex;justify-content:center;width: 100%;">
<button mat-button style="background-color: green;color: white;">Confirm Changes</button>
<button mat-button style="background-color: green;color: white;">Ok</button>
</div>
</div>
</mat-dialog-content>

2
src/app/pages/admin/Profile/InfoDialog/information-dialog.component.ts

@ -28,7 +28,7 @@ export class InformationDialogComponent implements OnInit {
closeDialog() {
this.data.details.businessCategory = this.selectedCategory.categoryCode;
this._userService.update(this.data.details);
this._userService.updateSub(this.data.details);
this.dialogRef.close(true);
}

3
src/app/pages/admin/Profile/Information/information.component.html

@ -48,6 +48,3 @@
</tbody>
</table>
</div>
<div class="text-center" style="margin-top:10px;">
<!-- <RadzenButton Text="Save Changes" Click="editProduct" Style="background-color:forestgreen !important;" /> -->
</div>

2
src/app/pages/admin/Profile/Information/information.component.ts

@ -6,6 +6,7 @@ import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { UserService } from 'app/core/user/user.service';
import { BusinessCategory, TesoBusinessDetail } from 'app/models/businessModel';
import { ProfileImage } from 'app/models/generalModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FollowersService } from '../../Followers/followers.service';
@ -22,6 +23,7 @@ import { ProfileService } from '../profile.service';
})
export class InformationComponent implements OnInit {
@Input() details: TesoBusinessDetail;
@Input() uploads:ProfileImage;
currentCategory: string;
categories: BusinessCategory[] = [];
private _unsubscribeAll: Subject<any> = new Subject<any>();

4
src/app/pages/admin/Profile/QRCode/qrcode-dialog.component.html

@ -0,0 +1,4 @@
<img [src]="image" class="productImage">
<a class="btn btn-primary" [href]="image" download style="display: flex;justify-content: center;">
<button mat-button style="background-color: #0152cc;color: white;" >Download</button>
</a>

0
src/app/pages/admin/Profile/QRCode/qrcode-dialog.component.scss

25
src/app/pages/admin/Profile/QRCode/qrcode-dialog.component.spec.ts

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { QrcodeDialogComponent } from './qrcode-dialog.component';
describe('QrcodeDialogComponent', () => {
let component: QrcodeDialogComponent;
let fixture: ComponentFixture<QrcodeDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ QrcodeDialogComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(QrcodeDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

20
src/app/pages/admin/Profile/QRCode/qrcode-dialog.component.ts

@ -0,0 +1,20 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
selector: 'app-qrcode-dialog',
templateUrl: './qrcode-dialog.component.html',
styleUrls: ['./qrcode-dialog.component.scss']
})
export class QrcodeDialogComponent implements OnInit {
image: any = "";
constructor(@Inject(MAT_DIALOG_DATA) public data: { image: string }, private domSanitizer: DomSanitizer,
public dialogRef: MatDialogRef<QrcodeDialogComponent>) {
this.image = this.domSanitizer.bypassSecurityTrustUrl(data.image);
}
ngOnInit(): void {
}
}

22
src/app/pages/admin/Profile/profile.component.html

@ -9,15 +9,15 @@
#fileInput accept="image/*">
<div class="text-center">
<img id="logo" class="profile-user-img img-fluid img-circle" [src]="imageLoader(profile.businessLogo)"
style="height:150px;width:150px;" *ngIf="newProfilePicture === undefined">
style="height:150px;width:150px;" *ngIf="newProfilePicture.imageSRC === undefined">
<img id="logo" class="profile-user-img img-fluid img-circle" src="{{newProfilePicture.imageSRC}}"
style="height:150px;width:150px;" *ngIf="newProfilePicture != undefined">
style="height:150px;width:150px;" *ngIf="newProfilePicture.imageSRC != undefined">
</div>
<div class="text-center" style="margin-top:10px;">
<button mat-button style="background-color: #0152cc;color: white;"
(click)="fileInput.click()" *ngIf="newProfilePicture === undefined">Select</button>
(click)="fileInput.click()" *ngIf="newProfilePicture.imageSRC === undefined">Select</button>
<button mat-button style="background-color: rgb(163, 35, 35);color: white;"
(click)="clear()" *ngIf="newProfilePicture != undefined">Clear</button>
(click)="clear()" *ngIf="newProfilePicture.imageSRC != undefined">Clear</button>
</div>
</form>
@ -29,7 +29,7 @@
<a href="followers" class="float-right" style="text-decoration: none;color: #0152cc;"> {{subscribers.length}}</a>
</li>
<li class="list-group-item">
<button class=" sm:inline-flex" mat-flat-button style="background-color: #0152cc;margin-right:10px;">
<button class=" sm:inline-flex" mat-flat-button style="background-color: #0152cc;margin-right:10px;" (click)="generateCode()">
<a href="javascript:void(0)" style="color: #fff;text-decoration: none;margin-right:10px;" >Generate QR-Code</a>
</button>
<!-- <a class="btn btn-secondary btn-lg" onclick="generateCode"></a> -->
@ -49,16 +49,22 @@
<div class="card-body">
<p class="text-muted">
<strong><i class="fas fa-book mr-1"></i> </strong>
<span id="shop-desc">{{profile.businessDescription}}</span>
<span id="shop-desc">
<div [innerHTML]="profile.businessDescription">
</div>
</span>
</p>
</div>
</div>
</div>
<div class="col-md-9">
<div class="card">
<business-information [details]="profile"></business-information>
<business-information [details]="profile" [uploads]="newProfilePicture"></business-information>
<div class="text-center" style="margin-top:10px;margin-bottom: 20px;">
<button mat-button style="background-color: #0152cc;color: white;" (click)="SaveChanges()">Save Changes</button>
</div>
</div>
</div>
</div>

25
src/app/pages/admin/Profile/profile.component.ts

@ -5,6 +5,7 @@ import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { UserService } from 'app/core/user/user.service';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { ProfileImage } from 'app/models/generalModel';
import { TesoUserDetails } from 'app/models/userModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs';
@ -12,6 +13,7 @@ import { takeUntil } from 'rxjs/operators';
import { FollowersService } from '../Followers/followers.service';
import { DescriptionDialogComponent } from './DescriptionDialog/description-dialog.component';
import { ProfileService } from './profile.service';
import { QrcodeDialogComponent } from './QRCode/qrcode-dialog.component';
@Component({
selector: 'app-profile',
@ -22,7 +24,7 @@ import { ProfileService } from './profile.service';
export class ProfileComponent implements OnInit {
profile: TesoBusinessDetail = {};
subscribers: TesoUserDetails[] = [];
newProfilePicture:any;
newProfilePicture: ProfileImage = {};
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
private _profileService: ProfileService, private _userService: UserService,
@ -52,12 +54,10 @@ export class ProfileComponent implements OnInit {
});
}
onFileSelected(event) {
const file: File = event.target.files[0];
if (file.type.includes("image")) {
this.newProfilePicture = {
file: file,
highlight: true,
imageSRC: "",
};
let reader = new FileReader();
@ -69,7 +69,26 @@ export class ProfileComponent implements OnInit {
this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe();
}
}
clear() {
console.log(this.newProfilePicture)
this.newProfilePicture = {};
}
SaveChanges() {
this._userService.update(this.profile, this.newProfilePicture).subscribe((response) => {
console.log(response)
});
}
generateCode() {
var dataImagge;
this._profileService.getQRCode().subscribe((response) => {
dataImagge = response;
this.dialog.open(QrcodeDialogComponent, {
hasBackdrop: true,
width: "400px",
height: "450px",
data: { image: dataImagge }
});
});
}
}

4
src/app/pages/admin/Profile/profile.module.ts

@ -27,6 +27,7 @@ import { InformationComponent } from './Information/information.component';
import { SettingsComponent } from './Settings/settings.component';
import { DescriptionDialogComponent } from './DescriptionDialog/description-dialog.component';
import { InformationDialogComponent } from './InfoDialog/information-dialog.component';
import { QrcodeDialogComponent } from './QRCode/qrcode-dialog.component';
@ -36,7 +37,8 @@ import { InformationDialogComponent } from './InfoDialog/information-dialog.comp
InformationComponent,
SettingsComponent,
DescriptionDialogComponent,
InformationDialogComponent
InformationDialogComponent,
QrcodeDialogComponent
],
imports: [
RouterModule.forChild(profileRoutes),

23
src/app/pages/admin/Profile/profile.service.ts

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';
import { switchMap, tap } from 'rxjs/operators';
import { analytics as analyticsData } from 'app/mock-api/dashboards/analytics/data';
import { environment } from 'environments/environment';
import { BusinessCategory, TesoBusinessDetail } from 'app/models/businessModel';
@ -61,4 +61,21 @@ export class ProfileService {
this._businessCategory.next(response);
}));
}
getQRCode(): Observable<any> {
const headers = new HttpHeaders().set('Content-Type', 'text/plain; charset=utf-8');
const requestOptions: Object = {
headers: headers,
responseType: 'text'
}
return this._httpClient.get<string>(environment.apiURL + `business/generate-qrcode`, requestOptions).pipe(
switchMap((response: any) => {
return of(response);
})
);
}
editProfile(profile:TesoBusinessDetail){
}
}

2
src/app/pages/admin/TempShop/temp-shop.component.ts

@ -17,7 +17,7 @@ export class TempShopComponent implements OnInit {
selectedCategory: BusinessCategory;
newBusiness: TesoBusinessDetail = {
businessId: "",
Handle: "",
handle: false,
businessName: "",
businessEmail: "",
businessTin: "",

62
src/app/pages/auth/confirmation-required/confirmation-required.component.html

@ -1,62 +0,0 @@
<div class="flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<div class="md:flex md:items-center md:justify-end w-full sm:w-auto md:h-full md:w-1/2 py-8 px-4 sm:p-12 md:p-16 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none sm:bg-card">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0">
<!-- Logo -->
<div class="w-12">
<img src="assets/images/logo/logo.svg">
</div>
<!-- Title -->
<div class="mt-8 text-4xl font-extrabold tracking-tight leading-tight">Confirmation required</div>
<div class="mt-4">
A confirmation mail with instructions has been sent to your email address. Follow those instructions to confirm your email address and activate your account.
</div>
<!-- Form footer -->
<div class="mt-8 text-md font-medium text-secondary">
<span>Return to</span>
<a class="ml-1 text-primary-500 hover:underline" [routerLink]="['/sign-in']">sign in
</a>
</div>
</div>
</div>
<div class="relative hidden md:flex flex-auto items-center justify-center w-1/2 h-full p-16 lg:px-28 overflow-hidden bg-gray-800 dark:border-l">
<!-- Background - @formatter:off -->
<!-- Rings -->
<svg class="absolute inset-0 pointer-events-none" viewBox="0 0 960 540" width="100%" height="100%" preserveAspectRatio="xMidYMax slice" xmlns="http://www.w3.org/2000/svg">
<g class="text-gray-700 opacity-25" fill="none" stroke="currentColor" stroke-width="100">
<circle r="234" cx="196" cy="23"></circle>
<circle r="234" cx="790" cy="491"></circle>
</g>
</svg>
<!-- Dots -->
<svg class="absolute -top-16 -right-16 text-gray-700" viewBox="0 0 220 192" width="220" height="192" fill="none">
<defs>
<pattern id="837c3e70-6c3a-44e6-8854-cc48c737b659" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="4" height="4" fill="currentColor"></rect>
</pattern>
</defs>
<rect width="220" height="192" fill="url(#837c3e70-6c3a-44e6-8854-cc48c737b659)"></rect>
</svg>
<!-- @formatter:on -->
<!-- Content -->
<div class="z-10 relative w-full max-w-2xl">
<div class="text-7xl font-bold leading-none text-gray-100">
<div>Welcome to</div>
<div>our community</div>
</div>
<div class="mt-6 text-lg tracking-tight leading-6 text-gray-400">
teso helps developers to build organized and well coded dashboards full of beautiful and rich modules. Join us and start building your application today.
</div>
<div class="flex items-center mt-8">
<div class="flex flex-0 items-center -space-x-1.5">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-18.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-11.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-09.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-16.jpg">
</div>
<div class="ml-4 font-medium tracking-tight text-gray-400">More than 17k people joined us, it's your turn</div>
</div>
</div>
</div>
</div>

18
src/app/pages/auth/confirmation-required/confirmation-required.component.ts

@ -1,18 +0,0 @@
import { Component, ViewEncapsulation } from '@angular/core';
import { tesoAnimations } from '@teso/animations';
@Component({
selector : 'auth-confirmation-required',
templateUrl : './confirmation-required.component.html',
encapsulation: ViewEncapsulation.None,
animations : tesoAnimations
})
export class AuthConfirmationRequiredComponent
{
/**
* Constructor
*/
constructor()
{
}
}

22
src/app/pages/auth/confirmation-required/confirmation-required.module.ts

@ -1,22 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { tesoCardModule } from '@teso/components/card';
import { SharedModule } from 'app/shared/shared.module';
import { AuthConfirmationRequiredComponent } from 'app/pages/auth/confirmation-required/confirmation-required.component';
import { authConfirmationRequiredRoutes } from 'app/pages/auth/confirmation-required/confirmation-required.routing';
@NgModule({
declarations: [
AuthConfirmationRequiredComponent
],
imports : [
RouterModule.forChild(authConfirmationRequiredRoutes),
MatButtonModule,
tesoCardModule,
SharedModule
]
})
export class AuthConfirmationRequiredModule
{
}

9
src/app/pages/auth/confirmation-required/confirmation-required.routing.ts

@ -1,9 +0,0 @@
import { Route } from '@angular/router';
import { AuthConfirmationRequiredComponent } from 'app/pages/auth/confirmation-required/confirmation-required.component';
export const authConfirmationRequiredRoutes: Route[] = [
{
path : '',
component: AuthConfirmationRequiredComponent
}
];

93
src/app/pages/auth/forgot-password/forgot-password.component.html

@ -1,93 +0,0 @@
<div class="flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<div class="md:flex md:items-center md:justify-end w-full sm:w-auto md:h-full md:w-1/2 py-8 px-4 sm:p-12 md:p-16 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none sm:bg-card">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0">
<!-- Logo -->
<div class="w-12">
<img src="assets/images/logo/logo.svg">
</div>
<!-- Title -->
<div class="mt-8 text-4xl font-extrabold tracking-tight leading-tight">Forgot password?</div>
<div class="mt-0.5 font-medium">Fill the form to reset your password</div>
<!-- Alert -->
<teso-alert class="mt-8 -mb-4" *ngIf="showAlert" [appearance]="'outline'" [showIcon]="false" [type]="alert.type" [@shake]="alert.type === 'error'">
{{alert.message}}
</teso-alert>
<!-- Forgot password form -->
<form class="mt-8" [formGroup]="forgotPasswordForm" #forgotPasswordNgForm="ngForm">
<!-- Email field -->
<mat-form-field class="w-full">
<mat-label>Email address</mat-label>
<input id="email" matInput [formControlName]="'email'">
<mat-error *ngIf="forgotPasswordForm.get('email').hasError('required')">
Email address is required
</mat-error>
<mat-error *ngIf="forgotPasswordForm.get('email').hasError('email')">
Please enter a valid email address
</mat-error>
</mat-form-field>
<!-- Submit button -->
<button class="teso-mat-button-large w-full mt-3" mat-flat-button [color]="'primary'" [disabled]="forgotPasswordForm.disabled" (click)="sendResetLink()">
<span *ngIf="!forgotPasswordForm.disabled">
Send reset link
</span>
<mat-progress-spinner
*ngIf="forgotPasswordForm.disabled"
[diameter]="24"
[mode]="'indeterminate'"></mat-progress-spinner>
</button>
<!-- Form footer -->
<div class="mt-8 text-md font-medium text-secondary">
<span>Return to</span>
<a class="ml-1 text-primary-500 hover:underline" [routerLink]="['/sign-in']">sign in
</a>
</div>
</form>
</div>
</div>
<div class="relative hidden md:flex flex-auto items-center justify-center w-1/2 h-full p-16 lg:px-28 overflow-hidden bg-gray-800 dark:border-l">
<!-- Background - @formatter:off -->
<!-- Rings -->
<svg class="absolute inset-0 pointer-events-none" viewBox="0 0 960 540" width="100%" height="100%" preserveAspectRatio="xMidYMax slice" xmlns="http://www.w3.org/2000/svg">
<g class="text-gray-700 opacity-25" fill="none" stroke="currentColor" stroke-width="100">
<circle r="234" cx="196" cy="23"></circle>
<circle r="234" cx="790" cy="491"></circle>
</g>
</svg>
<!-- Dots -->
<svg class="absolute -top-16 -right-16 text-gray-700" viewBox="0 0 220 192" width="220" height="192" fill="none">
<defs>
<pattern id="837c3e70-6c3a-44e6-8854-cc48c737b659" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="4" height="4" fill="currentColor"></rect>
</pattern>
</defs>
<rect width="220" height="192" fill="url(#837c3e70-6c3a-44e6-8854-cc48c737b659)"></rect>
</svg>
<!-- @formatter:on -->
<!-- Content -->
<div class="z-10 relative w-full max-w-2xl">
<div class="text-7xl font-bold leading-none text-gray-100">
<div>Welcome to</div>
<div>our community</div>
</div>
<div class="mt-6 text-lg tracking-tight leading-6 text-gray-400">
teso helps developers to build organized and well coded dashboards full of beautiful and rich modules. Join us and start building your application today.
</div>
<div class="flex items-center mt-8">
<div class="flex flex-0 items-center -space-x-1.5">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-18.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-11.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-09.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-16.jpg">
</div>
<div class="ml-4 font-medium tracking-tight text-gray-400">More than 17k people joined us, it's your turn</div>
</div>
</div>
</div>
</div>

105
src/app/pages/auth/forgot-password/forgot-password.component.ts

@ -1,105 +0,0 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { tesoAnimations } from '@teso/animations';
import { tesoAlertType } from '@teso/components/alert';
import { AuthService } from 'app/core/auth/auth.service';
@Component({
selector : 'auth-forgot-password',
templateUrl : './forgot-password.component.html',
encapsulation: ViewEncapsulation.None,
animations : tesoAnimations
})
export class AuthForgotPasswordComponent implements OnInit
{
@ViewChild('forgotPasswordNgForm') forgotPasswordNgForm: NgForm;
alert: { type: tesoAlertType; message: string } = {
type : 'success',
message: ''
};
forgotPasswordForm: FormGroup;
showAlert: boolean = false;
/**
* Constructor
*/
constructor(
private _authService: AuthService,
private _formBuilder: FormBuilder
)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Create the form
this.forgotPasswordForm = this._formBuilder.group({
email: ['', [Validators.required, Validators.email]]
});
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Send the reset link
*/
sendResetLink(): void
{
// Return if the form is invalid
if ( this.forgotPasswordForm.invalid )
{
return;
}
// Disable the form
this.forgotPasswordForm.disable();
// Hide the alert
this.showAlert = false;
// Forgot password
this._authService.forgotPassword(this.forgotPasswordForm.get('email').value)
.pipe(
finalize(() => {
// Re-enable the form
this.forgotPasswordForm.enable();
// Reset the form
this.forgotPasswordNgForm.resetForm();
// Show the alert
this.showAlert = true;
})
)
.subscribe(
(response) => {
// Set the alert
this.alert = {
type : 'success',
message: 'Password reset sent! You\'ll receive an email if you are registered on our system.'
};
},
(response) => {
// Set the alert
this.alert = {
type : 'error',
message: 'Email does not found! Are you sure you are already a member?'
};
}
);
}
}

32
src/app/pages/auth/forgot-password/forgot-password.module.ts

@ -1,32 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { tesoCardModule } from '@teso/components/card';
import { tesoAlertModule } from '@teso/components/alert';
import { SharedModule } from 'app/shared/shared.module';
import { AuthForgotPasswordComponent } from 'app/pages/auth/forgot-password/forgot-password.component';
import { authForgotPasswordRoutes } from 'app/pages/auth/forgot-password/forgot-password.routing';
@NgModule({
declarations: [
AuthForgotPasswordComponent
],
imports : [
RouterModule.forChild(authForgotPasswordRoutes),
MatButtonModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatProgressSpinnerModule,
tesoCardModule,
tesoAlertModule,
SharedModule
]
})
export class AuthForgotPasswordModule
{
}

9
src/app/pages/auth/forgot-password/forgot-password.routing.ts

@ -1,9 +0,0 @@
import { Route } from '@angular/router';
import { AuthForgotPasswordComponent } from 'app/pages/auth/forgot-password/forgot-password.component';
export const authForgotPasswordRoutes: Route[] = [
{
path : '',
component: AuthForgotPasswordComponent
}
];

122
src/app/pages/auth/reset-password/reset-password.component.html

@ -1,122 +0,0 @@
<div class="flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<div class="md:flex md:items-center md:justify-end w-full sm:w-auto md:h-full md:w-1/2 py-8 px-4 sm:p-12 md:p-16 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none sm:bg-card">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0">
<!-- Logo -->
<div class="w-12">
<img src="assets/images/logo/logo.svg">
</div>
<!-- Title -->
<div class="mt-8 text-4xl font-extrabold tracking-tight leading-tight">Reset your password</div>
<div class="mt-0.5 font-medium">Create a new password for your account</div>
<!-- Alert -->
<teso-alert class="mt-8 -mb-4" *ngIf="showAlert" [appearance]="'outline'" [showIcon]="false" [type]="alert.type" [@shake]="alert.type === 'error'">
{{alert.message}}
</teso-alert>
<!-- Reset password form -->
<form class="mt-8" [formGroup]="resetPasswordForm" #resetPasswordNgForm="ngForm">
<!-- Password field -->
<mat-form-field class="w-full">
<mat-label>Password</mat-label>
<input id="password" matInput type="password" [formControlName]="'password'" #passwordField>
<button mat-icon-button type="button" (click)="passwordField.type === 'password' ? passwordField.type = 'text' : passwordField.type = 'password'" matSuffix>
<mat-icon
class="icon-size-5"
*ngIf="passwordField.type === 'password'"
[svgIcon]="'heroicons_solid:eye'"></mat-icon>
<mat-icon
class="icon-size-5"
*ngIf="passwordField.type === 'text'"
[svgIcon]="'heroicons_solid:eye-off'"></mat-icon>
</button>
<mat-error>
Password is required
</mat-error>
</mat-form-field>
<!-- Password confirm field -->
<mat-form-field class="w-full">
<mat-label>Password (Confirm)</mat-label>
<input id="password-confirm" matInput type="password" [formControlName]="'passwordConfirm'" #passwordConfirmField>
<button mat-icon-button type="button" (click)="passwordConfirmField.type === 'password' ? passwordConfirmField.type = 'text' : passwordConfirmField.type = 'password'" matSuffix>
<mat-icon
class="icon-size-5"
*ngIf="passwordConfirmField.type === 'password'"
[svgIcon]="'heroicons_solid:eye'"></mat-icon>
<mat-icon
class="icon-size-5"
*ngIf="passwordConfirmField.type === 'text'"
[svgIcon]="'heroicons_solid:eye-off'"></mat-icon>
</button>
<mat-error *ngIf="resetPasswordForm.get('passwordConfirm').hasError('required')">
Password confirmation is required
</mat-error>
<mat-error *ngIf="resetPasswordForm.get('passwordConfirm').hasError('mustMatch')">
Passwords must match
</mat-error>
</mat-form-field>
<!-- Submit button -->
<button class="teso-mat-button-large w-full mt-3" mat-flat-button [color]="'primary'" [disabled]="resetPasswordForm.disabled" (click)="resetPassword()">
<span *ngIf="!resetPasswordForm.disabled">
Reset your password
</span>
<mat-progress-spinner
*ngIf="resetPasswordForm.disabled"
[diameter]="24"
[mode]="'indeterminate'"></mat-progress-spinner>
</button>
<!-- Form footer -->
<div class="mt-8 text-md font-medium text-secondary">
<span>Return to</span>
<a class="ml-1 text-primary-500 hover:underline" [routerLink]="['/sign-in']">sign in
</a>
</div>
</form>
</div>
</div>
<div class="relative hidden md:flex flex-auto items-center justify-center w-1/2 h-full p-16 lg:px-28 overflow-hidden bg-gray-800 dark:border-l">
<!-- Background - @formatter:off -->
<!-- Rings -->
<svg class="absolute inset-0 pointer-events-none" viewBox="0 0 960 540" width="100%" height="100%" preserveAspectRatio="xMidYMax slice" xmlns="http://www.w3.org/2000/svg">
<g class="text-gray-700 opacity-25" fill="none" stroke="currentColor" stroke-width="100">
<circle r="234" cx="196" cy="23"></circle>
<circle r="234" cx="790" cy="491"></circle>
</g>
</svg>
<!-- Dots -->
<svg class="absolute -top-16 -right-16 text-gray-700" viewBox="0 0 220 192" width="220" height="192" fill="none">
<defs>
<pattern id="837c3e70-6c3a-44e6-8854-cc48c737b659" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="4" height="4" fill="currentColor"></rect>
</pattern>
</defs>
<rect width="220" height="192" fill="url(#837c3e70-6c3a-44e6-8854-cc48c737b659)"></rect>
</svg>
<!-- @formatter:on -->
<!-- Content -->
<div class="z-10 relative w-full max-w-2xl">
<div class="text-7xl font-bold leading-none text-gray-100">
<div>Welcome to</div>
<div>our community</div>
</div>
<div class="mt-6 text-lg tracking-tight leading-6 text-gray-400">
teso helps developers to build organized and well coded dashboards full of beautiful and rich modules. Join us and start building your application today.
</div>
<div class="flex items-center mt-8">
<div class="flex flex-0 items-center -space-x-1.5">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-18.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-11.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-09.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-16.jpg">
</div>
<div class="ml-4 font-medium tracking-tight text-gray-400">More than 17k people joined us, it's your turn</div>
</div>
</div>
</div>
</div>

111
src/app/pages/auth/reset-password/reset-password.component.ts

@ -1,111 +0,0 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { tesoAnimations } from '@teso/animations';
import { tesoValidators } from '@teso/validators';
import { tesoAlertType } from '@teso/components/alert';
import { AuthService } from 'app/core/auth/auth.service';
@Component({
selector : 'auth-reset-password',
templateUrl : './reset-password.component.html',
encapsulation: ViewEncapsulation.None,
animations : tesoAnimations
})
export class AuthResetPasswordComponent implements OnInit
{
@ViewChild('resetPasswordNgForm') resetPasswordNgForm: NgForm;
alert: { type: tesoAlertType; message: string } = {
type : 'success',
message: ''
};
resetPasswordForm: FormGroup;
showAlert: boolean = false;
/**
* Constructor
*/
constructor(
private _authService: AuthService,
private _formBuilder: FormBuilder
)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Create the form
this.resetPasswordForm = this._formBuilder.group({
password : ['', Validators.required],
passwordConfirm: ['', Validators.required]
},
{
validators: tesoValidators.mustMatch('password', 'passwordConfirm')
}
);
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Reset password
*/
resetPassword(): void
{
// Return if the form is invalid
if ( this.resetPasswordForm.invalid )
{
return;
}
// Disable the form
this.resetPasswordForm.disable();
// Hide the alert
this.showAlert = false;
// Send the request to the server
this._authService.resetPassword(this.resetPasswordForm.get('password').value)
.pipe(
finalize(() => {
// Re-enable the form
this.resetPasswordForm.enable();
// Reset the form
this.resetPasswordNgForm.resetForm();
// Show the alert
this.showAlert = true;
})
)
.subscribe(
(response) => {
// Set the alert
this.alert = {
type : 'success',
message: 'Your password has been reset.'
};
},
(response) => {
// Set the alert
this.alert = {
type : 'error',
message: 'Something went wrong, please try again.'
};
}
);
}
}

32
src/app/pages/auth/reset-password/reset-password.module.ts

@ -1,32 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { tesoCardModule } from '@teso/components/card';
import { tesoAlertModule } from '@teso/components/alert';
import { SharedModule } from 'app/shared/shared.module';
import { AuthResetPasswordComponent } from 'app/pages/auth/reset-password/reset-password.component';
import { authResetPasswordRoutes } from 'app/pages/auth/reset-password/reset-password.routing';
@NgModule({
declarations: [
AuthResetPasswordComponent
],
imports : [
RouterModule.forChild(authResetPasswordRoutes),
MatButtonModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatProgressSpinnerModule,
tesoCardModule,
tesoAlertModule,
SharedModule
]
})
export class AuthResetPasswordModule
{
}

9
src/app/pages/auth/reset-password/reset-password.routing.ts

@ -1,9 +0,0 @@
import { Route } from '@angular/router';
import { AuthResetPasswordComponent } from 'app/pages/auth/reset-password/reset-password.component';
export const authResetPasswordRoutes: Route[] = [
{
path : '',
component: AuthResetPasswordComponent
}
];

94
src/app/pages/auth/sign-in/sign-in.component.css

@ -21,3 +21,97 @@
letter-spacing: 5px;
text-shadow: 3px 3px 20px #C3C76C, -2px 1px 30px #C3C76C;
}
.img-circle {
border-radius: 50%;
}
.profile-user-img {
border: 3px solid #adb5bd;
margin: 0 auto;
padding: 3px;
width: 80px;
}
.img-fluid {
max-width: 100%;
height: auto;
}
img {
vertical-align: middle;
border-style: none;
}
.text-center {
text-align: center !important;
}
.card-title {
float: left;
font-size: 1.1rem;
font-weight: 400;
margin: 0;
}
.card-title {
margin-bottom: .75rem;
}
.card {
position: relative;
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border: 0 solid rgba(0,0,0,.125);
border-radius: .25rem;
}
.card {
position: relative;
display: -ms-flexbox;
display: flex;
-ms-flex-direction: column;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border: 0 solid rgba(0,0,0,.125);
border-radius: .25rem;
padding-top: 30px;
}
.ui-button.btn-primary, .btn-primary.ui-paginator-element {
background-color: #0152cc;
}
.logo {
display: block;
width: 100px;
margin: 0px auto;
border-radius: 20px;
background: #222;
padding: 10px;
}
#circle {
background: #d7e8eb;
width: 150px;
height: 150px;
min-width: 45px;
border-radius: 80px;
color: #003445;
font-weight: bold;
text-align: center;
justify-content: center;
display: flex;
align-content: center;
align-items: center;
font-size: initial;
}

122
src/app/pages/auth/sign-in/sign-in.component.html

@ -1,7 +1,7 @@
<div class="flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<div *ngIf="isScreenSmall"
class="mobileAuth md:flex md:items-center md:justify-end w-full sm:w-auto md:h-full md:w-1/2 py-8 px-4 sm:p-12 md:p-16 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none sm:bg-card">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0" style="background-color: rgb(6 6 6 / 44%);color: white;">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0" style="background-color: rgb(6 6 6 / 44%);color: white;" *ngIf="!hide">
<div class="w-full sm:w-auto py-8 px-4 sm:p-12 sm:rounded-2xl sm:shadow sm:bg-card">
<!-- Logo -->
<div class="w-20">
@ -21,14 +21,15 @@
</teso-alert>
<!-- Sign in form -->
<form #signInNgForm="ngForm" [formGroup]="signInForm">
<mat-form-field appearance="outline">
<ngx-mat-intl-tel-input [preferredCountries]="['gh', 'ng']" [onlyCountries]="['gh', 'ng']"
[enablePlaceholder]="true" [enableSearch]="false" name="phone">
[enablePlaceholder]="true" [enableSearch]="false" name="phone" formControlName="phone">
</ngx-mat-intl-tel-input>
<mat-hint style="color: wheat;">Standard call, message, or data rates may apply.</mat-hint>
</mat-form-field>
</form>
<!-- Actions -->
<div class="inline-flex items-end justify-between w-full mt-3.5">
<mat-checkbox [color]="'primary'">
@ -37,25 +38,63 @@
</div>
<!-- Submit button -->
<button class="teso-mat-button-large w-full mt-6" mat-flat-button [color]="'primary'"
(click)="signIn()">
<span>
<button class="teso-mat-button-large w-full mt-6" id="tesoSubmit" mat-flat-button [color]="'primary'">
<span *ngIf="!signInForm.disabled">
Sign in
</span>
<!-- <mat-progress-spinner
<mat-progress-spinner
*ngIf="signInForm.disabled"
[diameter]="24"
[mode]="'indeterminate'"></mat-progress-spinner> -->
[mode]="'indeterminate'"></mat-progress-spinner>
</button>
</div>
</div>
<div class="d-flex justify-content-center align-items-center" *ngIf="hide"
style="background-color: #484444c4 !important;padding: 20px;">
<div class="position-relative" style="display: grid;justify-content: center;">
<div class="card p-2 text-center" style="display: contents;">
<input style="display: none" type="file" (change)="onFileSelected($event)" #fileInput
accept="image/*">
<div class="text-center" style="width: 100%;justify-content: center;display: flex;">
<div class="text-center" *ngIf="newProfilePicture.imageSRC === undefined">
<div id="circle" (click)="fileInput.click()">
<div>
<span style="cursor:pointer;font-size: 63px;">+</span>
</div>
</div>
</div>
<img id="logo" class="profile-user-img img-fluid img-circle"
src="{{newProfilePicture.imageSRC}}" style="height:150px;width:150px;"
*ngIf="newProfilePicture.imageSRC != undefined">
</div>
<div class="text-center" style="margin-top:10px;margin-bottom:10px;">
<button mat-button style="background-color: rgb(163, 35, 35);color: white;" (click)="clear()"
*ngIf="newProfilePicture.imageSRC != undefined">Clear</button>
</div>
<mat-form-field appearance="outline" style="width: 300px; align-items: center;">
<mat-label style="color: white;">Business Name</mat-label>
<input matInput style="margin-left:10px;" type="text" style="color: white;">
</mat-form-field>
<mat-form-field appearance="outline" style="width: 300px; align-items: center;">
<mat-label style="color: white;">Digital Address</mat-label>
<input matInput style="margin-left:10px;" type="text" style="color: white;">
</mat-form-field>
<button mat-button style="background-color: #0152cc;color: white;" (click)="signUP()">Continue</button>
</div>
</div>
</div>
</div>
<div id="welcomeBoard"
class="relative hidden md:flex flex-auto items-center justify-center w-1/2 h-full p-5 lg:px-20 overflow-hidden bg-gray-800 dark:border-l">
class="relative hidden md:flex flex-auto items-center justify-center w-1/2 h-full p-5 lg:px-20 overflow-hidden bg-gray-800 dark:border-l"
style="height: 100vh !important;">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0">
<div class="w-full sm:w-auto py-5 px-4 sm:p-8 sm:rounded-2xl sm:shadow sm:bg-card" style="background-color: #484444c4 !important;color: white !important;">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0" *ngIf="!hide">
<form [formGroup]="signInForm">
<div class="w-full sm:w-auto py-5 px-4 sm:p-8 sm:rounded-2xl sm:shadow sm:bg-card"
style="background-color: #484444c4 !important;color: white !important;">
<!-- Logo -->
<div class="w-30" style="width: 100%;justify-content: center;display: flex;">
<img src="assets/images/logo.png">
@ -76,30 +115,73 @@
<!-- Sign in form -->
<mat-form-field appearance="outline">
<ngx-mat-intl-tel-input [preferredCountries]="['gh', 'ng']" [onlyCountries]="['gh', 'ng']"
[enablePlaceholder]="true" [enableSearch]="false" name="phone">
[enablePlaceholder]="true" [enableSearch]="false" name="phone" formControlName="phone">
</ngx-mat-intl-tel-input>
<mat-hint style="color: white !important;">Standard call, message, or data rates may apply.</mat-hint>
<mat-hint style="color: wheat;">Standard call, message, or data rates may apply.</mat-hint>
</mat-form-field>
<!-- Actions -->
<div class="inline-flex items-end justify-between w-full mt-3.5">
<!-- <div class="inline-flex items-end justify-between w-full mt-3.5">
<mat-checkbox [color]="'primary'">
Remember me
</mat-checkbox>
</div>
</div> -->
<mat-error *ngIf="signInForm.controls['phone']?.errors?.required">Required Field</mat-error>
<mat-error *ngIf="signInForm.controls['phone']?.errors?.validatePhoneNumber">Invalid Number
</mat-error>
<!-- Submit button -->
<button class="teso-mat-button-large w-full mt-6" mat-flat-button [color]="'primary'"
(click)="signIn()">
<span>
<button class="teso-mat-button-large w-full mt-6" id="tesoSubmit" mat-flat-button [color]="'primary'"
(click)="authenticate()">
<span *ngIf="!signInForm.disabled">
Sign in
</span>
<!-- <mat-progress-spinner
<mat-progress-spinner
*ngIf="signInForm.disabled"
[diameter]="24"
[mode]="'indeterminate'"></mat-progress-spinner> -->
[mode]="'indeterminate'"></mat-progress-spinner>
</button>
</div>
</form>
</div>
<div class="d-flex justify-content-center align-items-center" *ngIf="hide"
style="background-color: #484444c4 !important;width: 400px;padding: 20px;">
<div class="position-relative" style="display: grid;justify-content: center;">
<div class="card p-2 text-center" style="display: contents;">
<input style="display: none" type="file" (change)="onFileSelected($event)" #fileInput
accept="image/*">
<div class="text-center" style="width: 100%;justify-content: center;display: flex;">
<div class="text-center" *ngIf="newProfilePicture.imageSRC === undefined">
<div id="circle" (click)="fileInput.click()">
<div>
<span style="cursor:pointer;font-size: 63px;">+</span>
</div>
</div>
</div>
<img id="logo" class="profile-user-img img-fluid img-circle"
src="{{newProfilePicture.imageSRC}}" style="height:150px;width:150px;"
*ngIf="newProfilePicture.imageSRC != undefined">
</div>
<div class="text-center" style="margin-top:10px;margin-bottom:10px;">
<button mat-button style="background-color: rgb(163, 35, 35);color: white;" (click)="clear()"
*ngIf="newProfilePicture.imageSRC != undefined">Clear</button>
</div>
<mat-form-field appearance="outline" style="width: 350px; align-items: center;">
<mat-label style="color: white;">Business Name</mat-label>
<input matInput style="margin-left:10px;" type="text" style="color: white;" [(ngModel)]="businessName">
</mat-form-field>
<mat-form-field appearance="outline" style="width: 350px; align-items: center;">
<mat-label style="color: white;">Digital Address</mat-label>
<input matInput style="margin-left:10px;" type="text" style="color: white;" [(ngModel)]="digitalAddress">
</mat-form-field>
<button mat-button style="background-color: #0152cc;color: white;" (click)="signUP()">Continue</button>
</div>
</div>
</div>
</div>
</div>

211
src/app/pages/auth/sign-in/sign-in.component.ts

@ -1,12 +1,23 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { FormBuilder, FormControl, FormGroup, NgForm, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { tesoAnimations } from '@teso/animations';
import { tesoAlertType } from '@teso/components/alert';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { AuthService } from 'app/core/auth/auth.service';
import { WindowService } from 'app/window.service';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { getAuth, RecaptchaVerifier, signInWithPhoneNumber } from "firebase/auth";
import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import 'firebase/compat/firestore';
import { environment } from 'environments/environment';
import { MatDialog } from '@angular/material/dialog';
import { VerificationDialogComponent } from '../verification/verification-dialog.component';
import { ProfileImage } from 'app/models/generalModel';
declare var grecaptcha: any;
declare var $: any;
@Component({
selector: 'auth-sign-in',
@ -17,14 +28,29 @@ import { takeUntil } from 'rxjs/operators';
})
export class AuthSignInComponent implements OnInit {
signInForm: FormGroup;
alert: { type: tesoAlertType; message: string } = {
type: 'success',
message: ''
};
isScreenSmall: boolean;
showAlert: boolean = false;
windowRef: any;
private _unsubscribeAll: Subject<any> = new Subject<any>();
unregistered: boolean;
responseError: string;
renderLoading: boolean;
phonenumber;
hide = false;
businessName: String = "";
digitalAddress: String = "";
newProfilePicture: ProfileImage = {};
appVerifier;
recaptchedID;
tryagin = 0;
previousNumber = "";
running = false;
signLabel = "Sign in";
/**
* Constructor
*/
@ -33,8 +59,14 @@ export class AuthSignInComponent implements OnInit {
private _authService: AuthService,
private _formBuilder: FormBuilder,
private _router: Router,
private _tesoMediaWatcherService: tesoMediaWatcherService
private win: WindowService,
public dialog: MatDialog,
private _tesoMediaWatcherService: tesoMediaWatcherService,
private fb: FormBuilder
) {
this.signInForm = this.fb.group({
phone: [undefined, [Validators.required]],
});
}
// -----------------------------------------------------------------------------------------------------
@ -45,6 +77,9 @@ export class AuthSignInComponent implements OnInit {
* On init
*/
ngOnInit(): void {
this.signInForm = new FormGroup({
phone: new FormControl()
});
// Create the form
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
@ -62,49 +97,149 @@ export class AuthSignInComponent implements OnInit {
/**
* Sign in
*/
signIn(): void {
// Return if the form is invalid
var signInForm = this._formBuilder.group({
email: ['support@tesoapp.com', [Validators.required, Validators.email]],
password: ['admin', Validators.required],
rememberMe: ['']
});
// Hide the alert
authenticate(): void {
console.log(this.tryagin)
console.log(this.phonenumber)
// if(!this.phonenumber && this.phonenumber != this.previousNumber){
this.phonenumber = this.signInForm.get('phone').value;
// }
console.log(this.phonenumber)
this.showAlert = false;
if (this.phonenumber)
if (this.tryagin < 3 || this.phonenumber != this.previousNumber) {
this.tryagin++;
if (this.recaptchedID != undefined)
grecaptcha.reset(this.recaptchedID);
const auth = firebase.auth();
this.unregistered = false;
this.responseError = "";
this.signInForm.disable();
this.renderLoading = true;
this.windowRef = this.win.windowRef
if (this.appVerifier === undefined) {
this.windowRef.recaptchaVerifier = new firebase.auth.RecaptchaVerifier('tesoSubmit', {
'size': 'invisible',
'callback': (response) => {
// this.renderLoading = false;
// console.log(response)
}
});
this.appVerifier = this.windowRef.recaptchaVerifier;
this.appVerifier.render().then((widgetId) => {
this.recaptchedID = widgetId
});
}
auth.signInWithPhoneNumber(this.phonenumber, this.appVerifier)
.then((confirmationResult) => {
this.windowRef.confirmationResult = confirmationResult;
const dialogResult = this.dialog.open(VerificationDialogComponent, {
disableClose: true,
hasBackdrop: true,
height: '400px',
data: {
windowRef: this.windowRef,
phone: this.phonenumber
},
});
// Sign in
this._authService.signIn(signInForm.value)
.subscribe(
() => {
// Set the redirect url.
// The '/signed-in-redirect' is a dummy url to catch the request and redirect the user
// to the correct page after a successful sign in. This way, that url can be set via
// routing file and we don't have to touch here.
dialogResult.afterClosed().subscribe((response) => {
if (response == "verified") {
const redirectURL = this._activatedRoute.snapshot.queryParamMap.get('redirectURL') || '/signed-in-redirect';
// Navigate to the redirect url
this._router.navigateByUrl(redirectURL);
} else if (response == "unregistered") {
this.hide = true;
} else if (response == "wrong verification code") {
this.signInForm.enable();
grecaptcha.reset(this.recaptchedID);
this.alert = {
type: "error",
message: "You enter the wrong OTP code. Try again (" + this.tryagin + "/3)"
}
if (this.previousNumber != this.phonenumber) {
this.tryagin = 1;
}
// this.signInForm.reset();
this.previousNumber = this.phonenumber;
this.showAlert = true;
} else if (response == "resend") {
this.signInForm.enable();
grecaptcha.reset(this.recaptchedID);
this.alert = {
type: "error",
message: "Make sure you entered the right phone number and try again (" + this.tryagin + "/3)"
}
if (this.previousNumber != this.phonenumber) {
this.tryagin = 1;
}
// this.signInForm.reset();
this.previousNumber = this.phonenumber;
this.showAlert = true;
}
else {
this.signInForm.enable();
// this.signInForm.reset();
grecaptcha.reset(this.recaptchedID);
this.previousNumber = this.phonenumber;
this.alert = {
type: "error",
message: "An error occurred while authenticating you please try again after a while. " +
"This could be as a result of entering the OTP or poor internet connection"
}
this.showAlert = true;
}
});
}).catch((error) => {
// this.signInForm.reset();
grecaptcha.reset(this.recaptchedID);
this.previousNumber = this.phonenumber;
});
} else {
// this.signInForm.reset();
grecaptcha.reset(this.recaptchedID);
this.previousNumber = this.phonenumber;
if (this.tryagin >= 3) {
this.alert = {
type: "error",
message: "An error occurred while authenticating you please try again after a while. This could be as a result of entering the OTP"
}
this.showAlert = true;
} else {
this.alert = {
type: "error",
message: "An error occurred while authenticating you please try again after a while this could poor internet connection"
}
this.showAlert = true;
}
}
},
(response) => {
// Re-enable the form
signInForm.enable();
// Reset the form
}
// Set the alert
this.alert = {
type: 'error',
message: 'Wrong email or password'
onFileSelected(event) {
const file: File = event.target.files[0];
if (file.type.includes("image")) {
this.newProfilePicture = {
file: file,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
this.newProfilePicture.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
}
}
// Show the alert
this.showAlert = true;
clear() {
console.log(this.newProfilePicture)
this.newProfilePicture = {};
}
signUP() {
this._authService.signUp(this.phonenumber, this.businessName, this.digitalAddress, this.newProfilePicture).subscribe((response) => {
if (response.business != undefined) {
const redirectURL = this._activatedRoute.snapshot.queryParamMap.get('redirectURL') || '/signed-in-redirect';
this._router.navigateByUrl(redirectURL);
}
);
})
}
}

8
src/app/pages/auth/sign-in/sign-in.module.ts

@ -13,10 +13,12 @@ import { AuthSignInComponent } from 'app/pages/auth/sign-in/sign-in.component';
import { authSignInRoutes } from 'app/pages/auth/sign-in/sign-in.routing';
import { NgxMatIntlTelInputModule } from 'ngx-mat-intl-tel-input';
import {MatCardModule} from '@angular/material/card';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [
AuthSignInComponent
AuthSignInComponent,
],
imports : [
RouterModule.forChild(authSignInRoutes),
@ -30,8 +32,10 @@ import {MatCardModule} from '@angular/material/card';
MatProgressSpinnerModule,
tesoCardModule,
tesoAlertModule,
SharedModule
SharedModule,
CommonModule,
]
})
export class AuthSignInModule
{

106
src/app/pages/auth/sign-in/signOld.txt

@ -1,106 +0,0 @@
<!-- <div class="flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<div *ngIf="isScreenSmall"
class="md:flex md:items-center md:justify-end w-full sm:w-auto md:h-full md:w-1/2 py-8 px-4 sm:p-12 md:p-16 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none sm:bg-card">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0">
<div class="w-full sm:w-auto py-8 px-4 sm:p-12 sm:rounded-2xl sm:shadow sm:bg-card">
<!-- Logo -->
<div class="w-20">
<img src="assets/images/logo.png">
</div>
<!-- Title -->
<div class="mt-8 text-4xl font-extrabold tracking-tight leading-tight">Sign in</div>
<div class="flex items-baseline mt-0.5 font-medium">
<div>Enter phone number of business</div>
</div>
<!-- Alert -->
<teso-alert class="mt-8 -mb-4" *ngIf="showAlert" [appearance]="'outline'" [showIcon]="false"
[type]="alert.type" [@shake]="alert.type === 'error'">
{{alert.message}}
</teso-alert>
<!-- Sign in form -->
<mat-form-field appearance="outline">
<ngx-mat-intl-tel-input [preferredCountries]="['gh', 'ng']" [onlyCountries]="['gh', 'ng']"
[enablePlaceholder]="true" [enableSearch]="false" name="phone">
</ngx-mat-intl-tel-input>
<mat-hint>Standard call, message, or data rates may apply.</mat-hint>
</mat-form-field>
<!-- Actions -->
<div class="inline-flex items-end justify-between w-full mt-3.5">
<mat-checkbox [color]="'primary'">
Remember me
</mat-checkbox>
</div>
<!-- Submit button -->
<button class="teso-mat-button-large w-full mt-6" mat-flat-button [color]="'primary'"
(click)="signIn()">
<span>
Sign in
</span>
<!-- <mat-progress-spinner
*ngIf="signInForm.disabled"
[diameter]="24"
[mode]="'indeterminate'"></mat-progress-spinner> -->
</button>
</div>
</div>
</div>
<div id="welcomeBoard"
class="relative hidden md:flex flex-auto items-center justify-center w-1/2 h-full p-5 lg:px-20 overflow-hidden bg-gray-800 dark:border-l">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0">
<div class="w-full sm:w-auto py-5 px-4 sm:p-8 sm:rounded-2xl sm:shadow sm:bg-card" style="background-color: #484444c4 !important;color: white !important;">
<!-- Logo -->
<div class="w-30" style="width: 100%;justify-content: center;display: flex;">
<img src="assets/images/logo.png">
</div>
<!-- Title -->
<div class="mt-8 text-4xl font-extrabold tracking-tight leading-tight">Sign in</div>
<div class="flex items-baseline mt-0.5 font-medium">
<div>Enter phone number of business</div>
</div>
<!-- Alert -->
<teso-alert class="mt-8 -mb-4" *ngIf="showAlert" [appearance]="'outline'" [showIcon]="false"
[type]="alert.type" [@shake]="alert.type === 'error'">
{{alert.message}}
</teso-alert>
<!-- Sign in form -->
<mat-form-field appearance="outline">
<ngx-mat-intl-tel-input [preferredCountries]="['gh', 'ng']" [onlyCountries]="['gh', 'ng']"
[enablePlaceholder]="true" [enableSearch]="false" name="phone">
</ngx-mat-intl-tel-input>
<mat-hint style="color: white !important;">Standard call, message, or data rates may apply.</mat-hint>
</mat-form-field>
<!-- Actions -->
<div class="inline-flex items-end justify-between w-full mt-3.5">
<mat-checkbox [color]="'primary'">
Remember me
</mat-checkbox>
</div>
<!-- Submit button -->
<button class="teso-mat-button-large w-full mt-6" mat-flat-button [color]="'primary'"
(click)="signIn()">
<span>
Sign in
</span>
<!-- <mat-progress-spinner
*ngIf="signInForm.disabled"
[diameter]="24"
[mode]="'indeterminate'"></mat-progress-spinner> -->
</button>
</div>
</div>
</div>
</div> -->

136
src/app/pages/auth/sign-up/sign-up.component.html

@ -1,136 +0,0 @@
<div class="flex flex-col sm:flex-row items-center md:items-start sm:justify-center md:justify-start flex-auto min-w-0">
<div class="md:flex md:items-center md:justify-end w-full sm:w-auto md:h-full md:w-1/2 py-8 px-4 sm:p-12 md:p-16 sm:rounded-2xl md:rounded-none sm:shadow md:shadow-none sm:bg-card">
<div class="w-full max-w-80 sm:w-80 mx-auto sm:mx-0">
<!-- Logo -->
<div class="w-12">
<img src="assets/images/logo/logo.svg">
</div>
<!-- Title -->
<div class="mt-8 text-4xl font-extrabold tracking-tight leading-tight">Sign up</div>
<div class="flex items-baseline mt-0.5 font-medium">
<div>Already have an account?</div>
<a class="ml-1 text-primary-500 hover:underline" [routerLink]="['/pages/authentication/sign-in']">Sign in
</a>
</div>
<!-- Alert -->
<teso-alert class="mt-8 -mb-4" *ngIf="showAlert" [appearance]="'outline'" [showIcon]="false" [type]="alert.type" [@shake]="alert.type === 'error'">
{{alert.message}}
</teso-alert>
<!-- Sign Up form -->
<form class="mt-8" [formGroup]="signUpForm">
<!-- Name field -->
<mat-form-field class="w-full">
<mat-label>Full name</mat-label>
<input id="name" matInput [formControlName]="'name'">
<mat-error *ngIf="signUpForm.get('name').hasError('required')">
Full name is required
</mat-error>
</mat-form-field>
<!-- Email field -->
<mat-form-field class="w-full">
<mat-label>Email address</mat-label>
<input id="email" matInput [formControlName]="'email'">
<mat-error *ngIf="signUpForm.get('email').hasError('required')">
Email address is required
</mat-error>
<mat-error *ngIf="signUpForm.get('email').hasError('email')">
Please enter a valid email address
</mat-error>
</mat-form-field>
<!-- Password field -->
<mat-form-field class="w-full">
<mat-label>Password</mat-label>
<input id="password" matInput type="password" [formControlName]="'password'" #passwordField>
<button mat-icon-button type="button" (click)="passwordField.type === 'password' ? passwordField.type = 'text' : passwordField.type = 'password'" matSuffix>
<mat-icon
class="icon-size-5"
*ngIf="passwordField.type === 'password'"
[svgIcon]="'heroicons_solid:eye'"></mat-icon>
<mat-icon
class="icon-size-5"
*ngIf="passwordField.type === 'text'"
[svgIcon]="'heroicons_solid:eye-off'"></mat-icon>
</button>
<mat-error>
Password is required
</mat-error>
</mat-form-field>
<!-- Company field -->
<mat-form-field class="w-full">
<mat-label>Company</mat-label>
<input id="company-confirm" matInput [formControlName]="'company'">
</mat-form-field>
<!-- ToS and PP -->
<div class="inline-flex items-end w-full mt-1.5">
<mat-checkbox [color]="'primary'" [formControlName]="'agreements'">
<span>I agree to the</span>
<a class="ml-1 text-primary-500 hover:underline" [routerLink]="['./']">Terms of Service
</a>
<span>and</span>
<a class="ml-1 text-primary-500 hover:underline" [routerLink]="['./']">Privacy Policy
</a>
</mat-checkbox>
</div>
<!-- Submit button -->
<button class="teso-mat-button-large w-full mt-6" mat-flat-button [color]="'primary'" [disabled]="signUpForm.disabled" (click)="signUp()">
<span *ngIf="!signUpForm.disabled">
Create your free account
</span>
<mat-progress-spinner
*ngIf="signUpForm.disabled"
[diameter]="24"
[mode]="'indeterminate'"></mat-progress-spinner>
</button>
</form>
</div>
</div>
<div class="relative hidden md:flex flex-auto items-center justify-center w-1/2 h-full p-16 lg:px-28 overflow-hidden bg-gray-800 dark:border-l">
<!-- Background - @formatter:off -->
<!-- Rings -->
<svg class="absolute inset-0 pointer-events-none" viewBox="0 0 960 540" width="100%" height="100%" preserveAspectRatio="xMidYMax slice" xmlns="http://www.w3.org/2000/svg">
<g class="text-gray-700 opacity-25" fill="none" stroke="currentColor" stroke-width="100">
<circle r="234" cx="196" cy="23"></circle>
<circle r="234" cx="790" cy="491"></circle>
</g>
</svg>
<!-- Dots -->
<svg class="absolute -top-16 -right-16 text-gray-700" viewBox="0 0 220 192" width="220" height="192" fill="none">
<defs>
<pattern id="837c3e70-6c3a-44e6-8854-cc48c737b659" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="4" height="4" fill="currentColor"></rect>
</pattern>
</defs>
<rect width="220" height="192" fill="url(#837c3e70-6c3a-44e6-8854-cc48c737b659)"></rect>
</svg>
<!-- @formatter:on -->
<!-- Content -->
<div class="z-10 relative w-full max-w-2xl">
<div class="text-7xl font-bold leading-none text-gray-100">
<div>Welcome to</div>
<div>our community</div>
</div>
<div class="mt-6 text-lg tracking-tight leading-6 text-gray-400">
teso helps developers to build organized and well coded dashboards full of beautiful and rich modules. Join us and start building your application today.
</div>
<div class="flex items-center mt-8">
<div class="flex flex-0 items-center -space-x-1.5">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-18.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/female-11.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-09.jpg">
<img class="flex-0 w-10 h-10 rounded-full ring-4 ring-offset-1 ring-gray-800 ring-offset-gray-800 object-cover" src="assets/images/avatars/male-16.jpg">
</div>
<div class="ml-4 font-medium tracking-tight text-gray-400">More than 17k people joined us, it's your turn</div>
</div>
</div>
</div>
</div>

104
src/app/pages/auth/sign-up/sign-up.component.ts

@ -1,104 +0,0 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { tesoAnimations } from '@teso/animations';
import { tesoAlertType } from '@teso/components/alert';
import { AuthService } from 'app/core/auth/auth.service';
@Component({
selector : 'auth-sign-up',
templateUrl : './sign-up.component.html',
encapsulation: ViewEncapsulation.None,
animations : tesoAnimations
})
export class AuthSignUpComponent implements OnInit
{
@ViewChild('signUpNgForm') signUpNgForm: NgForm;
alert: { type: tesoAlertType; message: string } = {
type : 'success',
message: ''
};
signUpForm: FormGroup;
showAlert: boolean = false;
/**
* Constructor
*/
constructor(
private _authService: AuthService,
private _formBuilder: FormBuilder,
private _router: Router
)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Create the form
this.signUpForm = this._formBuilder.group({
name : ['', Validators.required],
email : ['', [Validators.required, Validators.email]],
password : ['', Validators.required],
company : [''],
agreements: ['', Validators.requiredTrue]
}
);
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Sign up
*/
signUp(): void
{
// Do nothing if the form is invalid
if ( this.signUpForm.invalid )
{
return;
}
// Disable the form
this.signUpForm.disable();
// Hide the alert
this.showAlert = false;
// Sign up
this._authService.signUp(this.signUpForm.value)
.subscribe(
(response) => {
// Navigate to the confirmation required page
this._router.navigateByUrl('/confirmation-required');
},
(response) => {
// Re-enable the form
this.signUpForm.enable();
// Reset the form
this.signUpNgForm.resetForm();
// Set the alert
this.alert = {
type : 'error',
message: 'Something went wrong, please try again.'
};
// Show the alert
this.showAlert = true;
}
);
}
}

34
src/app/pages/auth/sign-up/sign-up.module.ts

@ -1,34 +0,0 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { tesoCardModule } from '@teso/components/card';
import { tesoAlertModule } from '@teso/components/alert';
import { SharedModule } from 'app/shared/shared.module';
import { AuthSignUpComponent } from 'app/pages/auth/sign-up/sign-up.component';
import { authSignupRoutes } from 'app/pages/auth/sign-up/sign-up.routing';
@NgModule({
declarations: [
AuthSignUpComponent
],
imports : [
RouterModule.forChild(authSignupRoutes),
MatButtonModule,
MatCheckboxModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatProgressSpinnerModule,
tesoCardModule,
tesoAlertModule,
SharedModule
]
})
export class AuthSignUpModule
{
}

9
src/app/pages/auth/sign-up/sign-up.routing.ts

@ -1,9 +0,0 @@
import { Route } from '@angular/router';
import { AuthSignUpComponent } from 'app/pages/auth/sign-up/sign-up.component';
export const authSignupRoutes: Route[] = [
{
path : '',
component: AuthSignUpComponent
}
];

25
src/app/pages/auth/verification/verification-dialog.component.html

@ -0,0 +1,25 @@
<div class="d-flex justify-content-center align-items-center">
<div class="position-relative">
<div class="card p-2 text-center" style="display: contents;">
<div class="w-30" style="width: 100% !important;justify-content: center;display: flex;">
<img src="assets/images/logo.png" style="width:140px">
</div>
<h6>Please enter the one time password <br> </h6>
<div> <span> sent to {{data.phone}}</span> </div>
<div id="otp" class="inputs d-flex flex-row justify-content-center mt-2">
<ng-otp-input (onInputChange)="onOtpChange($event)" [config]="config"></ng-otp-input>
</div>
<div class="mt-4"> <button class="btn btn-danger px-4 validate" mat-flat-button (click)="verifyCode()"
*ngIf="!signInForm.disabled"> <span>
Verify
</span>
</button> </div>
<div class="lds-dual-ring" *ngIf="signInForm.disabled"></div>
<div class="mt-3 content flex justify-content-center align-items-center"
style="width: 100% !important;justify-content: space-between !important;">
<span>Didn't get the code ? </span>
<a href="javascript:void(0)" class="text-decoration-none ms-3" (click)="closeDialog()">Try again</a>
</div>
</div>
</div>
</div>

82
src/app/pages/auth/verification/verification-dialog.component.scss

@ -0,0 +1,82 @@
.height-100 {
height: 100vh
}
.card {
width: 400px;
border: none;
height: 300px;
box-shadow: 0px 5px 20px 0px #252f3b;
z-index: 1;
display: flex;
justify-content: center;
align-items: center
}
.card h6 {
color: #252f3b;
font-size: 20px
}
.inputs input {
width: 40px;
height: 40px
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
margin: 0
}
.form-control:focus {
box-shadow: none;
border: 2px solid #252f3b
}
.validate {
border-radius: 20px;
height: 40px;
background-color: #252f3b;
border: 1px solid #252f3b;
width: 100%;
color:white;
}
.content a {
color: royalblue;
transition: all 0.5s
}
.content a:hover {
color: rgb(179, 155, 24)
}
.lds-dual-ring {
display: flex;
justify-content: center;
width: 100%;
height: 40px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 40px;
height: 40px;
margin: 8px;
border-radius: 50%;
border: 6px solid #003445;
border-color: #003445 transparent #003445 transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

90
src/app/pages/auth/verification/verification-dialog.component.ts

@ -0,0 +1,90 @@
import { Component, Inject, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { AuthService } from 'app/core/auth/auth.service';
import { WindowService } from 'app/window.service';
import { NgOtpInputComponent, NgOtpInputConfig } from 'ng-otp-input';
@Component({
selector: 'app-verification-dialog',
templateUrl: './verification-dialog.component.html',
styleUrls: ['./verification-dialog.component.scss'],
encapsulation: ViewEncapsulation.Emulated,
})
export class VerificationDialogComponent implements OnInit {
@ViewChild(NgOtpInputComponent, { static: false }) ngOtpInput: NgOtpInputComponent;
config: NgOtpInputConfig = {
allowNumbersOnly: true,
length: 6,
isPasswordInput: false,
disableAutoFocus: false,
placeholder: ''
};
otp: string;
showOtpComponent = true;
signInForm: FormGroup;
verificationCode;
errorString = "FirebaseError: Firebase: The SMS verification code used to create the phone auth credential is invalid. Please resend the verification code sms and be sure to use the verification code provided by the user"
constructor(@Inject(MAT_DIALOG_DATA) public data: { windowRef: any, phone: String },
public dialogRef: MatDialogRef<VerificationDialogComponent>,
private _authService: AuthService, private fb: FormBuilder
) {
this.signInForm = this.fb.group({
phone: [undefined, [Validators.required]],
});
}
ngOnInit(): void {
// this.verifyCode();
}
verifyCode() {
this.signInForm.disable();
this.data.windowRef.confirmationResult
.confirm(this.verificationCode)
.then(result => {
if (result.user !== "") {
this._authService.authenticate(this.data.phone)
.subscribe(
() => {
this.dialogRef.close("verified")
},
(response) => {
if (response.error = "Unregistered") {
this.dialogRef.close("unregistered")
} else {
this.dialogRef.close("error")
}
}
);
}
}).catch((error) => {
if (error.toString().includes(this.errorString)) {
this.dialogRef.close("wrong verification code")
} else {
this.dialogRef.close("error")
}
});
}
closeDialog(){
this.dialogRef.close("resend")
}
onOtpChange(otp) {
this.otp = otp;
if (otp.toString().length == 6)
this.verificationCode = otp;
}
toggleDisable() {
if (this.ngOtpInput.otpForm) {
if (this.ngOtpInput.otpForm.disabled) {
this.ngOtpInput.otpForm.enable();
} else {
this.ngOtpInput.otpForm.disable();
}
}
}
}

13
src/app/pipes/percentage-pipe.ts

@ -0,0 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'percentage'
})
export class PercentagePipe implements PipeTransform {
transform(value: number): string {
let newvale = parseFloat((Math.round(value*10^2)).toString());
return newvale + "%";
}
}

11
src/app/window.service.ts

@ -0,0 +1,11 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class WindowService {
get windowRef() {
return window
}
}

BIN
src/assets/icons/notransaction.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 KiB

12
src/environments/environment.prod.ts

@ -1,5 +1,15 @@
export const environment = {
production: true,
apiURL:"https://test.tesoapp.com/v2/"
apiURL:"https://test.tesoapp.com/v2/",
// apiURL:"https://localhost:7076/"
firebaseConfig :{
apiKey: "AIzaSyDrFGHgBJxi4EgCThwBZaT7IUtQUhbhS8s",
authDomain: "teso-ghana.firebaseapp.com",
databaseURL: "https://teso-ghana-default-rtdb.firebaseio.com",
projectId: "teso-ghana",
storageBucket: "teso-ghana.appspot.com",
messagingSenderId: "280510379185",
appId: "1:280510379185:web:7a3e0afbfd43e93d19de90",
measurementId: "G-82HYCB3QDE"
}
};

14
src/environments/environment.ts

@ -4,8 +4,18 @@
export const environment = {
production: false,
apiURL:"https://test.tesoapp.com/v2/"
// apiURL:"https://localhost:7076/"
apiURL:"https://test.tesoapp.com/v2/",
// apiURL:"https://localhost:7076/",
firebaseConfig :{
apiKey: "AIzaSyDrFGHgBJxi4EgCThwBZaT7IUtQUhbhS8s",
authDomain: "teso-ghana.firebaseapp.com",
databaseURL: "https://teso-ghana-default-rtdb.firebaseio.com",
projectId: "teso-ghana",
storageBucket: "teso-ghana.appspot.com",
messagingSenderId: "280510379185",
appId: "1:280510379185:web:7a3e0afbfd43e93d19de90",
measurementId: "G-82HYCB3QDE"
}
};
/*

Loading…
Cancel
Save