Browse Source

24th Feb

devBranch
Benjamin Arhen 3 years ago
parent
commit
9051e3c6ae
  1. 11
      package-lock.json
  2. 1
      package.json
  3. 1
      src/app/app.component.ts
  4. 5
      src/app/app.module.ts
  5. 26
      src/app/app.resolvers.ts
  6. 3
      src/app/app.routing.ts
  7. 21
      src/app/core/auth/auth.interceptor.ts
  8. 32
      src/app/core/auth/auth.service.ts
  9. 18
      src/app/core/user/user.service.ts
  10. 2
      src/app/layout/common/goldcoin/goldcoin.component.html
  11. 85
      src/app/layout/common/goldcoin/goldcoin.component.ts
  12. 140
      src/app/layout/common/goldcoin/goldcoin.service.ts
  13. 29
      src/app/layout/common/user/user.component.html
  14. 54
      src/app/layout/common/user/user.component.ts
  15. 3
      src/app/layout/common/user/user.module.ts
  16. 25
      src/app/models/businessModel.ts
  17. 21
      src/app/models/couponsModel.ts
  18. 40
      src/app/models/productsModel.ts
  19. 14
      src/app/models/userModel.ts
  20. 22
      src/app/pages/admin/Coupons/Active/active-coupons.component.html
  21. 76
      src/app/pages/admin/Coupons/AdminNewCoupons/admin-new-coupons.component.html
  22. 47
      src/app/pages/admin/Coupons/AdminNewCoupons/admin-new-coupons.component.ts
  23. 11
      src/app/pages/admin/Coupons/Inactive/inactive-coupons.component.html
  24. 75
      src/app/pages/admin/Coupons/NewCoupons/new-coupons.component.html
  25. 45
      src/app/pages/admin/Coupons/NewCoupons/new-coupons.component.ts
  26. 2
      src/app/pages/admin/Coupons/coupons.component.html
  27. 221
      src/app/pages/admin/Coupons/coupons.component.ts
  28. 4
      src/app/pages/admin/Coupons/coupons.module.ts
  29. 59
      src/app/pages/admin/Coupons/coupons.service.ts
  30. 139
      src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.html
  31. 11
      src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.scss
  32. 25
      src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.spec.ts
  33. 50
      src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.ts
  34. 16
      src/app/pages/admin/Followers/followers.component.html
  35. 129
      src/app/pages/admin/Followers/followers.component.ts
  36. 10
      src/app/pages/admin/Followers/followers.module.ts
  37. 4
      src/app/pages/admin/Followers/followers.resolvers.ts
  38. 98
      src/app/pages/admin/Followers/followers.service.ts
  39. 23
      src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.html
  40. 3
      src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.scss
  41. 114
      src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.ts
  42. 32
      src/app/pages/admin/Monthly_Desires/desires.resolvers.ts
  43. 7
      src/app/pages/admin/Monthly_Desires/desires.routing.ts
  44. 49
      src/app/pages/admin/Monthly_Desires/desires.service.ts
  45. 144
      src/app/pages/admin/Products/AdminCouponDialog/admin-coupon-dialog.component.html
  46. 44
      src/app/pages/admin/Products/AdminCouponDialog/admin-coupon-dialog.component.ts
  47. 22
      src/app/pages/admin/Products/BusinessLookUp/business-look-up.component.html
  48. 115
      src/app/pages/admin/Products/BusinessLookUp/business-look-up.component.ts
  49. 130
      src/app/pages/admin/Products/CouponDialog/coupon-dialog.component.html
  50. 43
      src/app/pages/admin/Products/CouponDialog/coupon-dialog.component.ts
  51. 201
      src/app/pages/admin/Products/Details/details-product.component.html
  52. 78
      src/app/pages/admin/Products/Details/details-product.component.scss
  53. 25
      src/app/pages/admin/Products/Details/details-product.component.spec.ts
  54. 151
      src/app/pages/admin/Products/Details/details-product.component.ts
  55. 4
      src/app/pages/admin/Products/EditProduct/edit-product.component.html
  56. 20
      src/app/pages/admin/Products/EditProduct/edit-product.component.ts
  57. 71
      src/app/pages/admin/Products/NewProduct/new-product.component.html
  58. 5
      src/app/pages/admin/Products/NewProduct/new-product.component.scss
  59. 183
      src/app/pages/admin/Products/NewProduct/new-product.component.ts
  60. 48
      src/app/pages/admin/Products/ProductList/product-list.component.html
  61. 12
      src/app/pages/admin/Products/ProductList/product-list.component.scss
  62. 119
      src/app/pages/admin/Products/ProductList/product-list.component.ts
  63. 6
      src/app/pages/admin/Products/products.module.ts
  64. 8
      src/app/pages/admin/Products/products.routing.ts
  65. 73
      src/app/pages/admin/Products/products.service.ts
  66. 12
      src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.html
  67. 0
      src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.scss
  68. 25
      src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.spec.ts
  69. 44
      src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.ts
  70. 83
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.html
  71. 28
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.scss
  72. 25
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.spec.ts
  73. 35
      src/app/pages/admin/Profile/InfoDialog/information-dialog.component.ts
  74. 53
      src/app/pages/admin/Profile/Information/information.component.html
  75. 12
      src/app/pages/admin/Profile/Information/information.component.scss
  76. 12
      src/app/pages/admin/Profile/Information/information.component.spec.ts
  77. 51
      src/app/pages/admin/Profile/Information/information.component.ts
  78. 33
      src/app/pages/admin/Profile/Settings/settings.component.html
  79. 0
      src/app/pages/admin/Profile/Settings/settings.component.scss
  80. 0
      src/app/pages/admin/Profile/Settings/settings.component.spec.ts
  81. 17
      src/app/pages/admin/Profile/Settings/settings.component.ts
  82. 67
      src/app/pages/admin/Profile/profile.component.html
  83. 173
      src/app/pages/admin/Profile/profile.component.scss
  84. 68
      src/app/pages/admin/Profile/profile.component.ts
  85. 58
      src/app/pages/admin/Profile/profile.module.ts
  86. 35
      src/app/pages/admin/Profile/profile.routing.ts
  87. 64
      src/app/pages/admin/Profile/profile.service.ts
  88. 1
      src/app/pages/admin/Settings/settings.component.html
  89. 15
      src/app/pages/admin/Settings/settings.component.ts
  90. 12
      src/app/pages/admin/Settings/settings.module.ts
  91. 105
      src/app/pages/auth/unlock-session/unlock-session.component.html
  92. 129
      src/app/pages/auth/unlock-session/unlock-session.component.ts
  93. 32
      src/app/pages/auth/unlock-session/unlock-session.module.ts
  94. 9
      src/app/pages/auth/unlock-session/unlock-session.routing.ts
  95. 13
      src/app/pipes/coupon-worth.pipe.ts
  96. 18
      src/app/pipes/productDescriptionShort.pipe.ts
  97. BIN
      src/assets/images/300.png
  98. 3
      src/environments/environment.ts

11
package-lock.json

@ -34,6 +34,7 @@
"crypto-js": "3.3.0",
"dayjs": "^1.10.7",
"highlight.js": "11.2.0",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.9.49",
"lodash-es": "4.17.21",
"moment": "2.29.1",
@ -9627,6 +9628,11 @@
"node >= 0.2.0"
]
},
"node_modules/jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"node_modules/karma": {
"version": "6.3.16",
"resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz",
@ -22416,6 +22422,11 @@
"integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
"dev": true
},
"jwt-decode": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz",
"integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
},
"karma": {
"version": "6.3.16",
"resolved": "https://registry.npmjs.org/karma/-/karma-6.3.16.tgz",

1
package.json

@ -38,6 +38,7 @@
"crypto-js": "3.3.0",
"dayjs": "^1.10.7",
"highlight.js": "11.2.0",
"jwt-decode": "^3.1.2",
"libphonenumber-js": "^1.9.49",
"lodash-es": "4.17.21",
"moment": "2.29.1",

1
src/app/app.component.ts

@ -13,4 +13,5 @@ export class AppComponent
constructor()
{
}
}

5
src/app/app.module.ts

@ -12,9 +12,10 @@ import { LayoutModule } from 'app/layout/layout.module';
import { AppComponent } from 'app/app.component';
import { appRoutes } from 'app/app.routing';
import { LocationStrategy, PathLocationStrategy } from '@angular/common';
import { SettingsComponent } from './pages/admin/Settings/settings.component';
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';
const routerConfig: ExtraOptions = {
preloadingStrategy : PreloadAllModules,
@ -24,7 +25,7 @@ const routerConfig: ExtraOptions = {
@NgModule({
declarations: [
AppComponent,
SettingsComponent
],
imports : [
BrowserModule,

26
src/app/app.resolvers.ts

@ -6,6 +6,11 @@ import { NavigationService } from 'app/core/navigation/navigation.service';
import { NotificationsService } from 'app/layout/common/notifications/notifications.service';
import { QuickChatService } from 'app/layout/common/quick-chat/quick-chat.service';
import { UserService } from 'app/core/user/user.service';
import { GoldCoinService } from './layout/common/goldcoin/goldcoin.service';
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';
@Injectable({
providedIn: 'root'
@ -20,9 +25,13 @@ export class InitialDataResolver implements Resolve<any>
private _navigationService: NavigationService,
private _notificationsService: NotificationsService,
private _quickChatService: QuickChatService,
private _userService: UserService
)
{
private _userService: UserService,
private _couponService: CouponsService,
private _productService: ProductsService,
private _goldCoinService: GoldCoinService,
private _subscriberService:FollowersService,
private _profileService:ProfileService,
) {
}
// -----------------------------------------------------------------------------------------------------
@ -35,15 +44,20 @@ export class InitialDataResolver implements Resolve<any>
* @param route
* @param state
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
{
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
// Fork join multiple API endpoint calls to wait all of them to finish
return forkJoin([
this._navigationService.get(),
this._messagesService.getAll(),
this._notificationsService.getAll(),
this._quickChatService.getChats(),
this._userService.get()
this._userService.get(),
this._goldCoinService.getAll(),
this._couponService.getCategory(),
this._productService.getCategories(),
this._productService.getData(),
this._subscriberService.getData(),
this._profileService.getCategories()
]);
}
}

3
src/app/app.routing.ts

@ -48,7 +48,7 @@ export const appRoutes: Route[] = [
},
children: [
{path: 'sign-out', loadChildren: () => import('app/pages/auth/sign-out/sign-out.module').then(m => m.AuthSignOutModule)},
{path: 'unlock-session', loadChildren: () => import('app/pages/auth/unlock-session/unlock-session.module').then(m => m.AuthUnlockSessionModule)}
]
},
@ -67,6 +67,7 @@ export const appRoutes: Route[] = [
{path: 'products', loadChildren: () => import('app/pages/admin/Products/products.module').then(m => m.ProductsModule)},
{path: 'followers', loadChildren: () => import('app/pages/admin/Followers/followers.module').then(m => m.FollowersModule)},
{path: 'desires', loadChildren: () => import('app/pages/admin/Monthly_Desires/desires.module').then(m => m.DesiresModule)},
{path: 'profile', loadChildren: () => import('app/pages/admin/Profile/profile.module').then(m => m.ProfileModule)},
]
}
];

21
src/app/core/auth/auth.interceptor.ts

@ -6,13 +6,11 @@ import { AuthService } from 'app/core/auth/auth.service';
import { AuthUtils } from 'app/core/auth/auth.utils';
@Injectable()
export class AuthInterceptor implements HttpInterceptor
{
export class AuthInterceptor implements HttpInterceptor {
/**
* Constructor
*/
constructor(private _authService: AuthService)
{
constructor(private _authService: AuthService) {
}
/**
@ -21,8 +19,7 @@ export class AuthInterceptor implements HttpInterceptor
* @param req
* @param next
*/
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
{
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Clone the request object
let newReq = req.clone();
@ -34,10 +31,15 @@ export class AuthInterceptor implements HttpInterceptor
// for the protected API routes which our response interceptor will
// catch and delete the access token from the local storage while logging
// the user out from the app.
if ( this._authService.accessToken && !AuthUtils.isTokenExpired(this._authService.accessToken) )
if (this._authService.relevantToken)//&& !AuthUtils.isTokenExpired() )
{
newReq = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + this._authService.accessToken)
headers: req.headers.set('Authorization', 'Bearer ' + this._authService.relevantToken)
.append('Accept', '*/*')
.append('Content-Type', 'application/json; charset=utf-8;')
.append("Access-Control-Allow-Origin", "*")
.append("Access-Control-Allow-Methods", "DELETE, POST, GET, OPTIONS")
.append("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
});
}
@ -46,8 +48,7 @@ export class AuthInterceptor implements HttpInterceptor
catchError((error) => {
// Catch "401 Unauthorized" responses
if ( error instanceof HttpErrorResponse && error.status === 401 )
{
if (error instanceof HttpErrorResponse && error.status === 401) {
// Sign out
this._authService.signOut();

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

@ -37,6 +37,16 @@ export class AuthService
return localStorage.getItem('accessToken') ?? '';
}
set relevantToken(token: string)
{
localStorage.setItem('relevantToken', token);
}
get relevantToken(): string
{
return localStorage.getItem('relevantToken') ?? '';
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
@ -79,7 +89,7 @@ export class AuthService
// Store the access token in the local storage
this.accessToken = response.accessToken;
this.relevantToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOiIyMjY0ODQyNzciLCJqdGkiOiI2ZWJiYzhlZC1hNTFkLTRjNTMtODBkOC05YWU2OTYyMzU4M2YiLCJpc3MiOiJURVNPIEFVVEggU0VSVkVSIiwiZXhwIjoxNjQ2NDA4NTU4LCJuYmYiOiIyMjY1NDQyNzciLCJzdWIiOiIxVEVTQlUwMDAwMDAwMCIsImJ1c2luZXNzSUQiOiIxVEVTQlUwMDAwMDAwMCIsInN1YnNjcmlwdGlvblBsYW4iOiJUVFMwMDEiLCJidXNpbmVzc05hbWUiOiJUZXNvIEdoYW5hIiwiYXVkIjoiVEVTTyJ9.HPgBtbrw8yFzzCUwMx7_3QbfX7LloANFZ-HFzLglDio";
// Set the authenticated flag to true
this._authenticated = true;
@ -161,25 +171,23 @@ export class AuthService
/**
* Check the authentication status
*/
check(): Observable<boolean>
{
check(): Observable<boolean> {
// Check if the user is logged in
if ( this._authenticated )
{
if (this._authenticated) {
return of(true);
}
// Check the access token availability
if ( !this.accessToken )
{
if (!this.accessToken) {
this.signOut();
return of(false);
}
// Check the access token expire date
if ( AuthUtils.isTokenExpired(this.accessToken) )
{
return of(false);
}
// // Check the access token expire date
// if (AuthUtils.isTokenExpired(this.accessToken)) {
// return of(false);
// }
// If the access token exists and it didn't expire, sign in using it
return this.signInUsingToken();

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

@ -3,13 +3,15 @@ import { HttpClient } from '@angular/common/http';
import { Observable, ReplaySubject } from 'rxjs';
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';
@Injectable({
providedIn: 'root'
})
export class UserService
{
private _user: ReplaySubject<User> = new ReplaySubject<User>(1);
private _user: ReplaySubject<TesoBusinessDetail> = new ReplaySubject<TesoBusinessDetail>(1);
/**
* Constructor
@ -27,13 +29,13 @@ export class UserService
*
* @param value
*/
set user(value: User)
set user(value: TesoBusinessDetail)
{
// Store the value
this._user.next(value);
}
get user$(): Observable<User>
get user$(): Observable<TesoBusinessDetail>
{
return this._user.asObservable();
}
@ -45,9 +47,9 @@ export class UserService
/**
* Get the current logged in user data
*/
get(): Observable<User>
get(): Observable<TesoBusinessDetail>
{
return this._httpClient.get<User>('api/common/user').pipe(
return this._httpClient.get<TesoBusinessDetail>(environment.apiURL +'business/getProfile').pipe(
tap((user) => {
this._user.next(user);
})
@ -59,11 +61,11 @@ export class UserService
*
* @param user
*/
update(user: User): Observable<any>
update(user: TesoBusinessDetail): Observable<any>
{
return this._httpClient.patch<User>('api/common/user', {user}).pipe(
this._user.next(user);
return this._httpClient.patch<TesoBusinessDetail>('api/common/user', {user}).pipe(
map((response) => {
this._user.next(response);
})
);
}

2
src/app/layout/common/goldcoin/goldcoin.component.html

@ -1,6 +1,6 @@
<!-- <button mat-icon-button> -->
<div style="display: flex;justify-content:space-around;" class="goldDiv">
<img class="w-10" src="assets/images/gold1.png" alt="Logo image">
<span style="padding-top: 9px;font-size: medium;font-weight: 700;color: #64738b;">+{{gold}}</span>
<span style="padding-top: 9px;font-size: medium;font-weight: 700;color: #64738b;">{{gold}}</span>
</div>
<!-- </button> -->

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

@ -1,25 +1,26 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { MatButton } from '@angular/material/button';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { takeUntil, takeWhile, tap } from 'rxjs/operators';
import { GoldCoinService } from './goldcoin.service';
import { Finance } from 'app/models/businessModel';
import { interval, Subject, Subscription } from 'rxjs';
@Component({
selector : 'goldcoins',
templateUrl : './goldcoin.component.html',
encapsulation : ViewEncapsulation.Emulated,
styleUrls :["./goldcoin.component.scss"],
selector: 'goldcoins',
templateUrl: './goldcoin.component.html',
encapsulation: ViewEncapsulation.Emulated,
styleUrls: ["./goldcoin.component.scss"],
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs : 'goldcoins'
exportAs: 'goldcoins'
})
export class GoldCoinComponent implements OnInit, OnDestroy
{
gold: number = 50;
export class GoldCoinComponent implements OnInit, OnDestroy, AfterViewInit {
gold: number = 0;
private _overlayRef: OverlayRef;
private _unsubscribeAll: Subject<any> = new Subject<any>();
sub: Subscription
/**
* Constructor
@ -27,8 +28,15 @@ export class GoldCoinComponent implements OnInit, OnDestroy
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _goldcoinsService: GoldCoinService,
)
{
) {
}
ngAfterViewInit(): void {
interval(1000)
// .pipe(takeWhile(() => !stop))
.subscribe(() => {
this._goldcoinsService.getAll();
});
}
// -----------------------------------------------------------------------------------------------------
@ -38,36 +46,36 @@ export class GoldCoinComponent implements OnInit, OnDestroy
/**
* On init
*/
ngOnInit(): void
{
// Subscribe to notification changes
// this._notificationsService.notifications$
// .pipe(takeUntil(this._unsubscribeAll))
// .subscribe((notifications: Notification[]) => {
// // Load the notifications
// this.notifications = notifications;
// // Calculate the unread count
// this._calculateUnreadCount();
// // Mark for check
// this._changeDetectorRef.markForCheck();
// });
ngOnInit(): void {
this._goldcoinsService.notifications$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((gold: Finance) => {
// Load the notifications
this.gold = gold.gold;
// Mark for check
this._changeDetectorRef.markForCheck();
});
this.sub = interval(100000)
.subscribe((val) => {
this._goldcoinsService.getUpdate();
this._changeDetectorRef.markForCheck();
});
}
/**
* On destroy
*/
ngOnDestroy(): void
{
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
this.sub.unsubscribe();
// Dispose the overlay
if ( this._overlayRef )
{
if (this._overlayRef) {
this._overlayRef.dispose();
}
}
@ -82,15 +90,14 @@ export class GoldCoinComponent implements OnInit, OnDestroy
/**
* Mark all notifications as read
*/
/**
* Track by function for ngFor loops
*
* @param index
* @param item
*/
trackByFn(index: number, item: any): any
{
trackByFn(index: number, item: any): any {
return item.id || index;
}
@ -108,5 +115,5 @@ export class GoldCoinComponent implements OnInit, OnDestroy
*
* @private
*/
}

140
src/app/layout/common/goldcoin/goldcoin.service.ts

@ -1,21 +1,21 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, ReplaySubject } from 'rxjs';
import { interval, Observable, ReplaySubject } from 'rxjs';
import { Notification } from 'app/layout/common/notifications/notifications.types';
import { map, switchMap, take, tap } from 'rxjs/operators';
import { map, switchMap, take, takeWhile, tap } from 'rxjs/operators';
import { Finance } from 'app/models/businessModel';
import { environment } from 'environments/environment';
@Injectable({
providedIn: 'root'
})
export class GoldCoinService
{
private _notifications: ReplaySubject<Notification[]> = new ReplaySubject<Notification[]>(1);
export class GoldCoinService {
private _notifications: ReplaySubject<Finance> = new ReplaySubject<Finance>(1);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient)
{
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
@ -25,8 +25,7 @@ export class GoldCoinService
/**
* Getter for notifications
*/
get notifications$(): Observable<Notification[]>
{
get notifications$(): Observable<Finance> {
return this._notifications.asObservable();
}
@ -37,119 +36,34 @@ export class GoldCoinService
/**
* Get all notifications
*/
getAll(): Observable<Notification[]>
{
return this._httpClient.get<Notification[]>('api/common/notifications').pipe(
getAll(): Observable<Finance> {
return this._httpClient.get<Finance>(environment.apiURL + 'api/business-finance').pipe(
tap((notifications) => {
if (notifications == null) {
notifications = {
businessID: "",
gold: 0,
}
}
this._notifications.next(notifications);
})
);
}
/**
* Create a notification
*
* @param notification
*/
create(notification: Notification): Observable<Notification>
{
return this.notifications$.pipe(
take(1),
switchMap(notifications => this._httpClient.post<Notification>('api/common/notifications', {notification}).pipe(
map((newNotification) => {
// Update the notifications with the new notification
this._notifications.next([...notifications, newNotification]);
// Return the new notification from observable
return newNotification;
})
))
);
getUpdate(): void {
this._httpClient.get<Finance>(environment.apiURL + 'api/business-finance').subscribe((notifications) => {
if (notifications == null) {
notifications = {
businessID: "",
gold: 0,
}
}
this._notifications.next(notifications);
})
}
/**
* Update the notification
* Create a notification
*
* @param id
* @param notification
*/
update(id: string, notification: Notification): Observable<Notification>
{
return this.notifications$.pipe(
take(1),
switchMap(notifications => this._httpClient.patch<Notification>('api/common/notifications', {
id,
notification
}).pipe(
map((updatedNotification: Notification) => {
// Find the index of the updated notification
const index = notifications.findIndex(item => item.id === id);
// Update the notification
notifications[index] = updatedNotification;
// Update the notifications
this._notifications.next(notifications);
// Return the updated notification
return updatedNotification;
})
))
);
}
/**
* Delete the notification
*
* @param id
*/
delete(id: string): Observable<boolean>
{
return this.notifications$.pipe(
take(1),
switchMap(notifications => this._httpClient.delete<boolean>('api/common/notifications', {params: {id}}).pipe(
map((isDeleted: boolean) => {
// Find the index of the deleted notification
const index = notifications.findIndex(item => item.id === id);
// Delete the notification
notifications.splice(index, 1);
// Update the notifications
this._notifications.next(notifications);
// Return the deleted status
return isDeleted;
})
))
);
}
/**
* Mark all notifications as read
*/
markAllAsRead(): Observable<boolean>
{
return this.notifications$.pipe(
take(1),
switchMap(notifications => this._httpClient.get<boolean>('api/common/notifications/mark-all-as-read').pipe(
map((isUpdated: boolean) => {
// Go through all notifications and set them as read
notifications.forEach((notification, index) => {
notifications[index].read = true;
});
// Update the notifications
this._notifications.next(notifications);
// Return the updated status
return isUpdated;
})
))
);
}
}

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

@ -4,20 +4,13 @@
[matMenuTriggerFor]="userActions">
<span class="relative">
<img
class="w-7 h-7 rounded-full"
*ngIf="showAvatar && user.avatar"
[src]="user.avatar">
class="w-10 h-10 rounded-full"
*ngIf="showAvatar && user.businessLogo"
[src]="imageLoader(user.businessLogo)">
<mat-icon
*ngIf="!showAvatar || !user.avatar"
*ngIf="!showAvatar || !user.businessLogo"
[svgIcon]="'heroicons_outline:user-circle'"></mat-icon>
<span
class="absolute right-0 bottom-0 w-2 h-2 rounded-full"
[ngClass]="{'mr-px mb-px': !showAvatar || !user.avatar,
'bg-green-500': user.status === 'online',
'bg-amber-500': user.status === 'away',
'bg-red-500': user.status === 'busy',
'bg-gray-400': user.status === 'not-visible'}"
></span>
</span>
</button>
@ -27,19 +20,17 @@
<button mat-menu-item>
<span class="flex flex-col leading-none">
<span>Signed in as</span>
<span class="mt-1.5 text-md font-medium">{{user.email}}</span>
<span class="mt-1.5 text-md font-medium">{{user.businessName}}</span>
</span>
</button>
<mat-divider class="my-2"></mat-divider>
<button mat-menu-item>
<button mat-menu-item (click)="profile()">
<mat-icon [svgIcon]="'heroicons_outline:user-circle'"></mat-icon>
<span>Profile</span>
</button>
<button
mat-menu-item
[matMenuTriggerFor]="userStatus">
<mat-icon [svgIcon]="'heroicons_outline:dots-circle-horizontal'"></mat-icon>
<span>Status</span>
<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

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

@ -5,22 +5,25 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { User } from 'app/core/user/user.types';
import { UserService } from 'app/core/user/user.service';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { environment } from 'environments/environment';
import { MatDialog } from '@angular/material/dialog';
import { SettingsComponent } from 'app/pages/admin/Profile/Settings/settings.component';
@Component({
selector : 'user',
templateUrl : './user.component.html',
encapsulation : ViewEncapsulation.None,
selector: 'user',
templateUrl: './user.component.html',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs : 'user'
exportAs: 'user'
})
export class UserComponent implements OnInit, OnDestroy
{
export class UserComponent implements OnInit, OnDestroy {
/* eslint-disable @typescript-eslint/naming-convention */
static ngAcceptInputType_showAvatar: BooleanInput;
/* eslint-enable @typescript-eslint/naming-convention */
@Input() showAvatar: boolean = true;
user: User;
user: TesoBusinessDetail;
private _unsubscribeAll: Subject<any> = new Subject<any>();
@ -30,9 +33,9 @@ export class UserComponent implements OnInit, OnDestroy
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _router: Router,
private _userService: UserService
)
{
private _userService: UserService,
private dialog: MatDialog,
) {
}
// -----------------------------------------------------------------------------------------------------
@ -42,12 +45,11 @@ export class UserComponent implements OnInit, OnDestroy
/**
* On init
*/
ngOnInit(): void
{
ngOnInit(): void {
// Subscribe to user changes
this._userService.user$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((user: User) => {
.subscribe((user: TesoBusinessDetail) => {
this.user = user;
// Mark for check
@ -58,8 +60,7 @@ export class UserComponent implements OnInit, OnDestroy
/**
* On destroy
*/
ngOnDestroy(): void
{
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
@ -74,26 +75,35 @@ export class UserComponent implements OnInit, OnDestroy
*
* @param status
*/
updateUserStatus(status: string): void
{
updateUserStatus(status: string): void {
// Return if user is not available
if ( !this.user )
{
if (!this.user) {
return;
}
// Update the user
this._userService.update({
...this.user,
status
}).subscribe();
}
/**
* Sign out
*/
signOut(): void
{
signOut(): void {
this._router.navigate(['/sign-out']);
}
profile(): void {
this._router.navigate(['/profile']);
}
imageLoader(path: string): string {
return environment.apiURL + `shoplogo/${path}`;
}
changePassword() {
const dialogRef = this.dialog.open(SettingsComponent,{
hasBackdrop: true,
disableClose:true,
})
}
}

3
src/app/layout/common/user/user.module.ts

@ -4,11 +4,12 @@ import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { UserComponent } from 'app/layout/common/user/user.component';
import { ProfileComponent } from 'app/pages/admin/Profile/profile.component';
import { SharedModule } from 'app/shared/shared.module';
@NgModule({
declarations: [
UserComponent
UserComponent,
],
imports : [
MatButtonModule,

25
src/app/models/businessModel.ts

@ -0,0 +1,25 @@
export interface Finance {
businessID: string;
gold: number;
}
export interface TesoBusinessDetail {
businessId?: string,
Handle?: string,
businessName?: string,
businessEmail?: string,
businessTin?: string,
businessDescription?: string,
businessCategory?: string,
businessAddress?: string,
businessContact?: string,
businessLogo?: string,
dateOfEst?: Date,
businessDigitalAddress?: string,
businessLatitude?: string,
businessLongitude?: string,
}
export interface BusinessCategory {
categoryCode: string,
categoryName: string,
}

21
src/app/models/couponsModel.ts

@ -0,0 +1,21 @@
import { TesoBusinessDetail } from "./businessModel"
export interface CouponsModel {
couponID: string,
businessID: string,
targetProduct: string,
type: string,
quantity: number,
lower: number,
condition: string,
upper: number,
numberClaimed: number,
status: string,
expiration: Date,
target?: TesoBusinessDetail,
}
export interface CouponsType {
typeCode: string;
typeName: string;
}

40
src/app/models/productsModel.ts

@ -0,0 +1,40 @@
export interface ProductsModel {
productName: string;
businessID: string;
productDesc: string;
productID: string;
categoryID: string;
unitPrice: number;
productImage: string;
images: ProductImages[];
}
export interface ProductImages {
id: string;
productID: string;
path: string;
}
export interface ProductCategory {
catCode: string;
catName: string;
}
export interface DesiredProduct{
productID:string,
productName:string,
productImage:string,
cost:number,
category:string,
enlisted:boolean,
desires:number
}
export interface DesiredList{
mostWishedProduct?: DesiredProduct[],
mostWishedCategory?: DesiredProduct[],
availableProducts?: DesiredProduct[],
availableCategory?: DesiredProduct[],
}

14
src/app/models/userModel.ts

@ -0,0 +1,14 @@
export interface TesoUserDetails {
userGuid?: string;
username?: string;
firstname?: string;
surname?: string;
description?: string;
address?: string;
email?: string;
thumbnailDp?: string;
phonenumber?: string;
country?: string;
gender?: string;
dateOfBirth: Date;
}

22
src/app/pages/admin/Coupons/Active/active-coupons.component.html

@ -9,7 +9,7 @@
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap">
{{transaction.name}}
{{transaction.targetProduct}}
</span>
</td>
</ng-container>
@ -51,8 +51,11 @@
Range
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;">
{{transaction.range}}
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;" *ngIf="!transaction.type.includes('FREEBIES')">
{{transaction.lower}} - {{transaction.upper}}
</span>
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;" *ngIf="transaction.type.includes('FREEBIES')">
100% OFF
</span>
</td>
</ng-container>
@ -62,7 +65,7 @@
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;">
{{transaction.claimed}}
{{transaction.numberClaimed}}
</span>
</td>
</ng-container>
@ -93,9 +96,16 @@
</th>
<td mat-cell *matCellDef="let transaction">
<div style="display: flex;">
<button class=" sm:inline-flex" mat-flat-button style="background-color: #96260a;color:white;">Cancel
<button class=" sm:inline-flex" mat-flat-button>
<a href="#" style="color: #0152cc;margin-right:10px;">Show Details</a>
</button>
<button class=" sm:inline-flex" mat-flat-button [matMenuTriggerFor]="actionsMenu">
<mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:dots-horizontal'">
</mat-icon>
</button>
<mat-menu #actionsMenu="matMenu">
<button mat-menu-item>Cancel</button>
</mat-menu>
</div>
</td>
</ng-container>

76
src/app/pages/admin/Coupons/AdminNewCoupons/admin-new-coupons.component.html

@ -1,6 +1,6 @@
<div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">New Coupons </h2>
<h2 mat-dialog-title style="text-align: center;">New Coupons </h2>
</div>
<!-- <button mat-button mat-dialog-close>Cancel</button> -->
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button>
@ -15,26 +15,27 @@
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
Target Name
{{businessTargetted.businessName}}
</mat-label>
<button style="background-color: blue;color:white;margin-right:20px;" mat-button (click)="lookupBusiness()">Search</button>
<button style="background-color: blue;color:white;margin-right:20px;" mat-button
(click)="lookupBusiness()">Search</button>
</div>
</div>
<hr/>
<hr />
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Name </strong>
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required>
<mat-option value="low">Product 1</mat-option>
<mat-option value="standard">Product 2</mat-option>
<mat-option value="high">Product 3</mat-option>
<mat-option value="high">Product 4</mat-option>
<mat-select [(ngModel)]="selectedProduct">
<mat-option *ngFor="let product of products" [value]="product">
{{product.productName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
@ -46,7 +47,7 @@
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
GH¢200
{{selectedProduct.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
@ -58,12 +59,12 @@
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required>
<mat-option value="low">Freebie</mat-option>
<mat-option value="standard">Discount</mat-option>
<mat-option value="high">Proximity Freebie</mat-option>
<mat-option value="high">Proximity Discount</mat-option>
<mat-select [(ngModel)]="selectedType">
<mat-option *ngFor="let type of couponTypes" [value]="type">
{{type.typeName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
@ -91,7 +92,7 @@
</div>
</div>
<div style="display: flex;align-items: baseline">
<div style="display: flex;align-items: baseline" *ngIf="!isScreenSmall">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
@ -106,15 +107,34 @@
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div *ngIf="isScreenSmall">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number" [(ngModel)]="fromWorth">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number" [(ngModel)]="toWorth">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong>
</mat-label>
<mat-label>
From GH¢200 To GH¢800
From {{selectedProduct.unitPrice | couponWorth:fromWorth}} To {{selectedProduct.unitPrice | couponWorth:toWorth}}
</mat-label>
</div>
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<div style="display: flex;align-items: baseline;margin-top: 20px;" *ngIf="!isScreenSmall">
<mat-label style="margin-right: 30px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
@ -123,9 +143,25 @@
</mat-form-field>
<mat-form-field appearance="fill" style="width: 200px;">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
<input matInput type="time">
</mat-form-field>
</div>
<div *ngIf="isScreenSmall">
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline;">
<mat-form-field appearance="outline">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>

47
src/app/pages/admin/Coupons/AdminNewCoupons/admin-new-coupons.component.ts

@ -1,22 +1,63 @@
import { Component, Inject, OnInit } from '@angular/core';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { BusinessLookUpComponent } from '../../Products/BusinessLookUp/business-look-up.component';
import { ProductsService } from '../../Products/products.service';
import { CouponsService } from '../coupons.service';
@Component({
selector: 'app-admin-new-coupons',
templateUrl: './admin-new-coupons.component.html',
styleUrls: ['./admin-new-coupons.component.scss']
})
export class AdminNewCouponsComponent implements OnInit {
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any }, public dialog: MatDialog,) { }
export class AdminNewCouponsComponent implements OnInit, OnDestroy {
businessTargetted: TesoBusinessDetail = {};
products: ProductsModel[] = [];
fromWorth:number;
toWorth:number;
selectedProduct: any = {};
selectedType: any = {};
isLoading: boolean = false;
isScreenSmall: boolean;
couponTypes: CouponsType[] = [];
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any },
public dialog: MatDialog, private _productService: ProductsService,
private _couponService: CouponsService, private _tesoMediaWatcherService: tesoMediaWatcherService,) { }
ngOnInit(): void {
this._couponService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.couponTypes = d;
});
this._productService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.products = d;
});
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
}
lookupBusiness() {
const dialogRef = this.dialog.open(BusinessLookUpComponent, {
disableClose: true,
hasBackdrop: true,
});
dialogRef.afterClosed().subscribe((result) => {
this.businessTargetted = result;
});
}
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
}

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

@ -9,7 +9,7 @@
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap">
{{transaction.name}}
{{transaction.targetProduct}}
</span>
</td>
</ng-container>
@ -51,8 +51,11 @@
Range
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;">
{{transaction.range}}
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;" *ngIf="!transaction.type.includes('FREEBIES')">
{{transaction.lower}} - {{transaction.upper}}
</span>
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;font-weight: bold;" *ngIf="transaction.type.includes('FREEBIES')">
100% OFF
</span>
</td>
</ng-container>
@ -62,7 +65,7 @@
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap" style="display: flex;justify-content: center;">
{{transaction.claimed}}
{{transaction.numberClaimed}}
</span>
</td>
</ng-container>

75
src/app/pages/admin/Coupons/NewCoupons/new-coupons.component.html

@ -1,6 +1,6 @@
<div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">New Coupons </h2>
<h2 mat-dialog-title style="text-align: center;">New Coupons </h2>
</div>
<!-- <button mat-button mat-dialog-close>Cancel</button> -->
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button>
@ -10,17 +10,17 @@
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Product </strong>
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required>
<mat-option value="low">Product 1</mat-option>
<mat-option value="standard">Product 2</mat-option>
<mat-option value="high">Product 3</mat-option>
<mat-option value="high">Product 4</mat-option>
<mat-select [(ngModel)]="selectedProduct">
<mat-option *ngFor="let product of products" [value]="product">
{{product.productName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
@ -32,7 +32,7 @@
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
GH¢200
{{selectedProduct.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
@ -44,12 +44,12 @@
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required>
<mat-option value="low">Freebie</mat-option>
<mat-option value="standard">Discount</mat-option>
<mat-option value="high">Proximity Freebie</mat-option>
<mat-option value="high">Proximity Discount</mat-option>
<mat-select [(ngModel)]="selectedType">
<mat-option *ngFor="let type of couponTypes" [value]="type">
{{type.typeName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
@ -77,7 +77,7 @@
</div>
</div>
<div style="display: flex;align-items: baseline">
<div style="display: flex;align-items: baseline" *ngIf="!isScreenSmall">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
@ -92,29 +92,66 @@
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div *ngIf="isScreenSmall">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number" [(ngModel)]="fromWorth" min="0" max="100">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number" [(ngModel)]="toWorth" min="0" max="100">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong>
</mat-label>
<mat-label>
From GH¢200 To GH¢800
From {{selectedProduct.unitPrice | couponWorth:fromWorth}} To {{selectedProduct.unitPrice |
couponWorth:toWorth}}
</mat-label>
</div>
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-label style="margin-right: 48px;">
<div style="display: flex;align-items: baseline;margin-top: 20px;" *ngIf="!isScreenSmall">
<mat-label style="margin-right: 30px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<mat-form-field appearance="outline">
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
<mat-form-field appearance="fill">
<mat-form-field appearance="fill" style="width: 200px;">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
<input matInput type="time">
</mat-form-field>
</div>
<div *ngIf="isScreenSmall">
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline;">
<mat-form-field appearance="outline">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>
</div>
</div>
</mat-dialog-content>

45
src/app/pages/admin/Coupons/NewCoupons/new-coupons.component.ts

@ -1,15 +1,50 @@
import { Component, OnInit } from '@angular/core';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProductsService } from '../../Products/products.service';
import { CouponsService } from '../coupons.service';
@Component({
selector: 'app-new-coupons',
templateUrl: './new-coupons.component.html',
styleUrls: ['./new-coupons.component.scss']
})
export class NewCouponsComponent implements OnInit {
constructor() { }
export class NewCouponsComponent implements OnInit,OnDestroy {
products: ProductsModel[] = [];
selectedProduct:any={};
selectedType:any={};
isLoading: boolean = false;
couponTypes:CouponsType[]=[];
fromWorth:number;
toWorth:number;
isScreenSmall:boolean;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any },
public dialog: MatDialog, private _productService: ProductsService,
private _couponService: CouponsService, private _tesoMediaWatcherService: tesoMediaWatcherService,) { }
ngOnInit(): void {
}
this._couponService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.couponTypes = d;
});
this._productService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.products = d;
});
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
}
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
}

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

@ -6,7 +6,7 @@
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;">
<div>
<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 (850)
<div class="font-medium tracking-tight text-secondary">Here is a list of all your coupons on Teso ({{allcoupons.length}})
</div>
</div>

221
src/app/pages/admin/Coupons/coupons.component.ts

@ -4,8 +4,13 @@ import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { CouponsModel } from 'app/models/couponsModel';
import { throwIfEmpty } from 'rxjs/operators';
import { AdminNewCouponsComponent } from './AdminNewCoupons/admin-new-coupons.component';
import { CouponsService } from './coupons.service';
import { NewCouponsComponent } from './NewCoupons/new-coupons.component';
import jwt_decode from 'jwt-decode';
import { AuthService } from 'app/core/auth/auth.service';
@Component({
selector: 'app-coupons',
@ -15,210 +20,48 @@ import { NewCouponsComponent } from './NewCoupons/new-coupons.component';
export class CouponsComponent implements OnInit {
activecouponsData: MatTableDataSource<any> = new MatTableDataSource();
inactivecouponsData: MatTableDataSource<any> = new MatTableDataSource();
allcoupons: CouponsModel[] = [];
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService) { }
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService,
private _couponsService: CouponsService, private _authService: AuthService) {
}
ngOnInit(): void {
this.activecouponsData.data = [
{
id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14',
expiration:"2022-03-15",
name: 'Nadia Mcknight',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '77a4383b-b5a5-4943-bc46-04c3431d1566',
expiration:"2022-03-15",
name: 'Best Blackburn',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '8bb0f597-673a-47ca-8c77-2f83219cb9af',
expiration:"2022-03-15",
name: 'Duncan Carver',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
expiration:"2022-03-15",
name: 'Martin Richards',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '0a8bc517-631a-4a93-aacc-000fa2e8294c',
expiration:"2022-03-15",
name: 'Candice Munoz',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'a4c9945a-757b-40b0-8942-d20e0543cabd',
expiration:"2022-03-15",
name: 'Vickie Mosley',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
expiration:"2022-03-15",
name: 'Tina Harris',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
expiration:"2022-03-15",
name: 'Holt Manning',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
expiration:"2022-03-15",
name: 'Misty Ramsey',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
}
];
this.inactivecouponsData.data = [
{
id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14',
expiration:"2022-03-15",
name: 'Nadia Mcknight',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '77a4383b-b5a5-4943-bc46-04c3431d1566',
expiration:"2022-03-15",
name: 'Best Blackburn',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '8bb0f597-673a-47ca-8c77-2f83219cb9af',
expiration:"2022-03-15",
name: 'Duncan Carver',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
expiration:"2022-03-15",
name: 'Martin Richards',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '0a8bc517-631a-4a93-aacc-000fa2e8294c',
expiration:"2022-03-15",
name: 'Candice Munoz',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'a4c9945a-757b-40b0-8942-d20e0543cabd',
expiration:"2022-03-15",
name: 'Vickie Mosley',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
expiration:"2022-03-15",
name: 'Tina Harris',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
expiration:"2022-03-15",
name: 'Holt Manning',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
},
{
id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
expiration:"2022-03-15",
name: 'Misty Ramsey',
range: '60% - 95%',
condition: 'By one get one free',
quantity:17,
type: "Freebie",
claimed: 50,
}
]
this._couponsService.getData().subscribe((response) => {
this.allcoupons = response;
this.activecouponsData.data = this.allcoupons.filter(t => t.status.toLowerCase() == "active");
this.inactivecouponsData.data = this.allcoupons.filter(t => t.status.toLowerCase() != "active");
});
}
generateCoupons(){
if (!true) {
const dialogRef = this.dialog.open(NewCouponsComponent, {
generateCoupons() {
const tokenInfo = this.getDecodedAccessToken(this._authService.relevantToken);
if (tokenInfo.businessID != "1TESBU00000000") {
const dialogRef = this.dialog.open(AdminNewCouponsComponent, {
disableClose: true,
hasBackdrop: true,
});
} else {
const dialogRef = this.dialog.open(AdminNewCouponsComponent, {
const dialogRef = this.dialog.open(NewCouponsComponent, {
disableClose: true,
hasBackdrop: true,
});
}
}
getDecodedAccessToken(token: string): any {
try {
return jwt_decode(token);
} catch (Error) {
return null;
}
}
}

4
src/app/pages/admin/Coupons/coupons.module.ts

@ -26,6 +26,7 @@ import { ActiveCouponsComponent } from './Active/active-coupons.component';
import { InactiveCouponsComponent } from './Inactive/inactive-coupons.component';
import { NewCouponsComponent } from './NewCoupons/new-coupons.component';
import { AdminNewCouponsComponent } from './AdminNewCoupons/admin-new-coupons.component';
import { CouponWorthPipe } from 'app/pipes/coupon-worth.pipe';
@NgModule({
@ -34,7 +35,8 @@ import { AdminNewCouponsComponent } from './AdminNewCoupons/admin-new-coupons.co
ActiveCouponsComponent,
InactiveCouponsComponent,
NewCouponsComponent,
AdminNewCouponsComponent
AdminNewCouponsComponent,
CouponWorthPipe
],
imports: [
RouterModule.forChild(couponsRoutes),

59
src/app/pages/admin/Coupons/coupons.service.ts

@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
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 { CouponsModel, CouponsType } from 'app/models/couponsModel';
@Injectable({
providedIn: 'root'
})
export class CouponsService {
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
private _couponCategory: BehaviorSubject<CouponsType[]> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<any> {
return this._data.asObservable();
}
get categories$(): Observable<CouponsType[]> {
return this._couponCategory.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(): Observable<any> {
return this._httpClient.get(environment.apiURL + `allcoupons`).pipe(
tap((response: any) => {
this._data.next(response);
})
);
}
getCategory(): Observable<CouponsType[]> {
return this._httpClient.get(environment.apiURL + `api/CouponTypes`).pipe(
tap((response: CouponsType[]) => {
this._couponCategory.next(response);
})
);
}
}

139
src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.html

@ -0,0 +1,139 @@
<div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">Send a coupon to {{data.subscriber.firstname}}
{{data.subscriber.surname}}</h2>
</div>
<!-- <button mat-button mat-dialog-close>Cancel</button> -->
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button>
</div>
<mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6">
<!-- <div class="flex flex-col flex-auto w-full p-8 text-center">
<div class="w-32 h-32 mx-auto rounded-full overflow-hidden">
<img class="w-full h-full object-cover" [src]="imageLoader(data.subscriber.thumbnailDp)">
</div>
</div> -->
<div class="columns" style="display: flex;align-items: center" *ngIf="tesoGhana">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Business </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{businessTargetted.businessName}}
</mat-label>
<button style="background-color: blue;color:white;margin-right:20px;" mat-button
(click)="lookupBusiness()">Search</button>
</div>
</div>
<hr *ngIf="tesoGhana" />
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select [(ngModel)]="selectedProduct">
<mat-option *ngFor="let product of products" [value]="product">
{{product.productName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Original Price </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{selectedProduct.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Coupon Type</strong>
</mat-label>
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select [(ngModel)]="selectedType">
<mat-option value="TESCP005">DISCOUNT </mat-option>
<mat-option value="TESCP006">FREEBIE </mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Condition</strong>
</mat-label>
</div>
<div class="column">
<textarea name="" id="" cols="30" rows="5" style="border: 1px solid #cbd5e1;"></textarea>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Number Of Coupons</strong>
</mat-label>
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<input matInput style="margin-left:10px;" type="number">
</mat-form-field>
</div>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<mat-form-field appearance="fill" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
<mat-form-field appearance="fill" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong>
</mat-label>
<mat-label>
From GH¢200 To GH¢800
</mat-label>
</div>
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<mat-form-field appearance="outline">
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
<mat-form-field appearance="fill">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>
</div>
</div>
</mat-dialog-content>

11
src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.scss

@ -0,0 +1,11 @@
.columns {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.column {
flex: 50%;
font-size: medium;
}

25
src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.spec.ts

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

50
src/app/pages/admin/Followers/PersonalizedCoupons/personalized-coupons.component.ts

@ -0,0 +1,50 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { TesoUserDetails } from 'app/models/userModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CouponsService } from '../../Coupons/coupons.service';
import { BusinessLookUpComponent } from '../../Products/BusinessLookUp/business-look-up.component';
import { ProductsService } from '../../Products/products.service';
@Component({
selector: 'app-personalized-coupons',
templateUrl: './personalized-coupons.component.html',
styleUrls: ['./personalized-coupons.component.scss']
})
export class PersonalizedCouponsComponent implements OnInit {
tesoGhana:boolean = false;
businessTargetted: TesoBusinessDetail = {};
private _unsubscribeAll: Subject<any> = new Subject<any>();
isLoading: boolean = false;
products: ProductsModel[] = [];
selectedProduct: any = {};
selectedType: string;
couponTypes: CouponsType[] = [];
constructor(private _productService: ProductsService,
@Inject(MAT_DIALOG_DATA) public data: { subscriber: TesoUserDetails },
public dialog: MatDialog,) { }
ngOnInit(): void {
this._productService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.products = d;
});
}
imageLoader(path: string): string {
return environment.apiURL + `followeruserdp/${path}`;
}
lookupBusiness() {
const dialogRef = this.dialog.open(BusinessLookUpComponent, {
disableClose: true,
hasBackdrop: true,
});
dialogRef.afterClosed().subscribe((result) => {
this.businessTargetted = result;
});
}
}

16
src/app/pages/admin/Followers/followers.component.html

@ -13,26 +13,26 @@
<!-- Search -->
<mat-form-field class="teso-mat-dense teso-mat-no-subscript teso-mat-rounded min-w-64">
<mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:search'"></mat-icon>
<input matInput [autocomplete]="'off'" [placeholder]="'Look-up subscriber'">
<input matInput [autocomplete]="'off'" [placeholder]="'Look-up subscriber'" [formControl]="searchInputControl">
</mat-form-field>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0">
<ng-container *ngFor="let member of data.teamMembers">
<ng-container *ngFor="let member of data">
<div class="flex flex-col flex-auto items-center bg-card shadow rounded-2xl overflow-hidden">
<!-- Avatar & Info -->
<div class="flex flex-col flex-auto w-full p-8 text-center">
<div class="w-32 h-32 mx-auto rounded-full overflow-hidden">
<img class="w-full h-full object-cover" [src]="member.avatar">
<img class="w-full h-full object-cover" [src]="imageLoader(member.thumbnailDp)">
</div>
<div class="mt-6 font-medium">{{member.name}}</div>
<div class="text-secondary">{{member.title}}</div>
<div class="mt-6 font-medium">{{member.firstname}} {{member.surname}}</div>
<div class="text-secondary">{{member.username}}</div>
</div>
<!-- Email & Phone -->
<div class="flex items-center w-full border-t divide-x">
<a class="flex flex-auto items-center justify-center py-4 hover:bg-hover" [href]="'mailto:' + member.email">
<a class="flex flex-auto items-center justify-center py-4 hover:bg-hover" href="javascript:void(0)" (click)="personalizedCoupon(member)">
<mat-icon class="icon-size-5 text-hint" [svgIcon]="'heroicons_solid:credit-card'">
</mat-icon>
<span class="ml-2">Send Coupon</span>
@ -41,10 +41,10 @@
<mat-icon class="icon-size-5 text-hint" [svgIcon]="'heroicons_solid:mail'"></mat-icon>
<span class="ml-2">Send Message</span>
</a> -->
<a class="flex flex-auto items-center justify-center py-4 hover:bg-hover" [href]="'tel:' + member.phone">
<!-- <a class="flex flex-auto items-center justify-center py-4 hover:bg-hover" [href]="'tel:' + member.phone">
<mat-icon class="icon-size-5 text-hint" [svgIcon]="'heroicons_solid:user'"></mat-icon>
<span class="ml-2">View Profile</span>
</a>
</a> -->
</div>
<!-- <div class="flex items-center w-full border-t divide-x">
</div> -->

129
src/app/pages/admin/Followers/followers.component.ts

@ -1,8 +1,14 @@
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { TesoUserDetails } from 'app/models/userModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { debounceTime, map, switchMap, takeUntil } from 'rxjs/operators';
import { FollowersService } from './followers.service';
import { PersonalizedCouponsComponent } from './PersonalizedCoupons/personalized-coupons.component';
@Component({
selector: 'app-followers',
@ -11,100 +17,34 @@ import { FollowersService } from './followers.service';
})
export class FollowersComponent implements OnInit {
data: any;
data: TesoUserDetails[] = [];
private _unsubscribeAll: Subject<any> = new Subject<any>();
searchInputControl: FormControl = new FormControl();
constructor(
private _followersService: FollowersService,
private _router: Router
private _router: Router,
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService,
) { }
ngOnInit(): void {
this.data = {
teamMembers : [
{
id : '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14',
avatar: 'assets/images/avatars/female-10.jpg',
name : 'Nadia Mcknight',
email : 'nadiamcknight@mail.com',
phone : '+1-943-511-2203',
title : 'Project Director'
},
{
id : '77a4383b-b5a5-4943-bc46-04c3431d1566',
avatar: 'assets/images/avatars/male-19.jpg',
name : 'Best Blackburn',
email : 'blackburn.best@beadzza.me',
phone : '+1-814-498-3701',
title : 'Senior Developer'
},
{
id : '8bb0f597-673a-47ca-8c77-2f83219cb9af',
avatar: 'assets/images/avatars/male-14.jpg',
name : 'Duncan Carver',
email : 'duncancarver@mail.info',
phone : '+1-968-547-2111',
title : 'Senior Developer'
},
{
id : 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
avatar: 'assets/images/avatars/male-01.jpg',
name : 'Martin Richards',
email : 'martinrichards@mail.biz',
phone : '+1-902-500-2668',
title : 'Junior Developer'
},
{
id : '0a8bc517-631a-4a93-aacc-000fa2e8294c',
avatar: 'assets/images/avatars/female-20.jpg',
name : 'Candice Munoz',
email : 'candicemunoz@mail.co.uk',
phone : '+1-838-562-2769',
title : 'Lead Designer'
},
{
id : 'a4c9945a-757b-40b0-8942-d20e0543cabd',
avatar: 'assets/images/avatars/female-01.jpg',
name : 'Vickie Mosley',
email : 'vickiemosley@mail.net',
phone : '+1-939-555-3054',
title : 'Designer'
},
{
id : 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
avatar: 'assets/images/avatars/female-02.jpg',
name : 'Tina Harris',
email : 'tinaharris@mail.ca',
phone : '+1-933-464-2431',
title : 'Designer'
},
{
id : 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
avatar: 'assets/images/avatars/male-02.jpg',
name : 'Holt Manning',
email : 'holtmanning@mail.org',
phone : '+1-822-531-2600',
title : 'Marketing Manager'
},
{
id : '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
avatar: 'assets/images/avatars/female-03.jpg',
name : 'Misty Ramsey',
email : 'mistyramsey@mail.us',
phone : '+1-990-457-2106',
title : 'Consultant'
}
]
}
// this._followersService.data$
// .pipe(takeUntil(this._unsubscribeAll))
// .subscribe((data) => {
// // Store the data
// this.data = data;
// console.log(this.data)
// // Prepare the chart data
// });
this._followersService.data$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((data: TesoUserDetails[]) => {
this.data = data;
});
this.searchInputControl.valueChanges
.pipe(
takeUntil(this._unsubscribeAll),
debounceTime(300),
switchMap((query) => {
// this.isLoading = true;
return this.data = this._followersService.filterFollowers(query);
}),
map(() => {
// this.isLoading = false;
})
)
.subscribe();
}
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
@ -125,4 +65,15 @@ export class FollowersComponent implements OnInit {
trackByFn(index: number, item: any): any {
return item.id || index;
}
imageLoader(path: string): string {
return environment.apiURL + `followeruserdp/${path}`;
}
personalizedCoupon(user: TesoUserDetails) {
const dialogRef = this.dialog.open(PersonalizedCouponsComponent, {
disableClose: true,
hasBackdrop: true,
data: { subscriber: user },
});
}
}

10
src/app/pages/admin/Followers/followers.module.ts

@ -16,12 +16,16 @@ import { TranslocoModule } from '@ngneat/transloco';
import { followersRoutes } from './followers.routing';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { PersonalizedCouponsComponent } from './PersonalizedCoupons/personalized-coupons.component';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSelectModule } from '@angular/material/select';
@NgModule({
declarations: [
FollowersComponent
FollowersComponent,
PersonalizedCouponsComponent
],
imports: [
RouterModule.forChild(followersRoutes),
@ -32,12 +36,12 @@ import { MatInputModule } from '@angular/material/input';
MatFormFieldModule,
MatInputModule,
MatMenuModule,
MatDialogModule,
MatProgressBarModule,
MatRippleModule,
// MatSidenavModule,
MatSortModule,
MatTableModule,
// MatTabsModule,
MatSelectModule,
TranslocoModule,
SharedModule
]

4
src/app/pages/admin/Followers/followers.resolvers.ts

@ -11,7 +11,7 @@ export class FollowersResolver implements Resolve<any>
/**
* Constructor
*/
constructor(private _projectService: FollowersService)
constructor(private _subscriberService: FollowersService)
{
}
@ -27,6 +27,6 @@ export class FollowersResolver implements Resolve<any>
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
{
return ;//this._projectService.getData();
return this._subscriberService.getData();
}
}

98
src/app/pages/admin/Followers/followers.service.ts

@ -2,13 +2,15 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { TesoUserDetails } from 'app/models/userModel';
import { environment } from 'environments/environment';
@Injectable({
providedIn: 'root'
})
export class FollowersService
{
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
private _data: BehaviorSubject<TesoUserDetails[]> = new BehaviorSubject(null);
/**
* Constructor
@ -24,7 +26,7 @@ export class FollowersService
/**
* Getter for data
*/
get data$(): Observable<any>
get data$(): Observable<TesoUserDetails[]>
{
return this._data.asObservable();
}
@ -36,89 +38,15 @@ export class FollowersService
/**
* Get data
*/
getData()
getData():Observable<TesoUserDetails[]>
{
var project = {
teamMembers : [
{
id : '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14',
avatar: 'assets/images/avatars/female-10.jpg',
name : 'Nadia Mcknight',
email : 'nadiamcknight@mail.com',
phone : '+1-943-511-2203',
title : 'Project Director'
},
{
id : '77a4383b-b5a5-4943-bc46-04c3431d1566',
avatar: 'assets/images/avatars/male-19.jpg',
name : 'Best Blackburn',
email : 'blackburn.best@beadzza.me',
phone : '+1-814-498-3701',
title : 'Senior Developer'
},
{
id : '8bb0f597-673a-47ca-8c77-2f83219cb9af',
avatar: 'assets/images/avatars/male-14.jpg',
name : 'Duncan Carver',
email : 'duncancarver@mail.info',
phone : '+1-968-547-2111',
title : 'Senior Developer'
},
{
id : 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
avatar: 'assets/images/avatars/male-01.jpg',
name : 'Martin Richards',
email : 'martinrichards@mail.biz',
phone : '+1-902-500-2668',
title : 'Junior Developer'
},
{
id : '0a8bc517-631a-4a93-aacc-000fa2e8294c',
avatar: 'assets/images/avatars/female-20.jpg',
name : 'Candice Munoz',
email : 'candicemunoz@mail.co.uk',
phone : '+1-838-562-2769',
title : 'Lead Designer'
},
{
id : 'a4c9945a-757b-40b0-8942-d20e0543cabd',
avatar: 'assets/images/avatars/female-01.jpg',
name : 'Vickie Mosley',
email : 'vickiemosley@mail.net',
phone : '+1-939-555-3054',
title : 'Designer'
},
{
id : 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
avatar: 'assets/images/avatars/female-02.jpg',
name : 'Tina Harris',
email : 'tinaharris@mail.ca',
phone : '+1-933-464-2431',
title : 'Designer'
},
{
id : 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
avatar: 'assets/images/avatars/male-02.jpg',
name : 'Holt Manning',
email : 'holtmanning@mail.org',
phone : '+1-822-531-2600',
title : 'Marketing Manager'
},
{
id : '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
avatar: 'assets/images/avatars/female-03.jpg',
name : 'Misty Ramsey',
email : 'mistyramsey@mail.us',
phone : '+1-990-457-2106',
title : 'Consultant'
}
]
}
// return this._httpClient.get('api/dashboards/project').pipe(
// tap((response: any) => {
// this._data.next(response);
// })
// );
return project
return this._httpClient.get(environment.apiURL + 'business/getsubscribers').pipe(
tap((response: any) => {
this._data.next(response);
})
);
}
filterFollowers(name: string): TesoUserDetails[] {
return this._data.getValue().filter(p => p.firstname.toLowerCase().includes(name) || p.surname.toLowerCase().includes(name) ||p.username.toLowerCase().includes(name));
}
}

23
src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.html

@ -5,7 +5,7 @@
<!-- Title and action buttons -->
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="!isScreenSmall">
<div>
<div class="text-3xl font-semibold tracking-tight leading-8">Products</div>
<div class="text-3xl font-semibold tracking-tight leading-8">Monthly Desires</div>
<div class="font-medium tracking-tight text-secondary">Here is a list of all products in your Teso (850)
</div>
</div>
@ -21,7 +21,7 @@
</div>
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="isScreenSmall">
<div>
<div class="text-3xl font-semibold tracking-tight leading-8">Products</div>
<div class="text-3xl font-semibold tracking-tight leading-8">Monthly Desires</div>
<div class="font-medium tracking-tight text-secondary">(850) products
</div>
</div>
@ -41,18 +41,18 @@
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0">
<ng-container *ngFor="let product of data.teamproducts">
<!-- <div class="flex flex-col flex-auto items-center bg-card shadow rounded-2xl overflow-hidden"> -->
<ng-container *ngFor="let product of data.mostWishedProduct">
<div class="product">
<div class="product-content">
<div class="product-img">
<img [src]="product.image" alt="product image" style="max-height:200px;">
<img [src]="imageLoader(product.productImage)" alt="product image" style="max-height:200px;" *ngIf="product.productImage != null">
<!-- <img [src]="imageLoader(product.productImage)" alt="product image" style="max-height:200px;" *ngIf="productImage == null"> -->
</div>
<div class="product-btns">
<button type="button" class="btn-buy" (click)="generateCoupon(product)" *ngIf="product.title.includes('Developer')">
<button type="button" class="btn-buy" (click)="generateCoupon(product.productID)" *ngIf="availableP.includes(product.productID)">
Generate Coupons
</button>
<button type="button" class="btn-cart" (click)="createProduct()" *ngIf="!product.title.includes('Developer')">
<button type="button" class="btn-cart" (click)="createProduct()" *ngIf="!availableP.includes(product.productID)">
Add Product
</button>
</div>
@ -61,12 +61,11 @@
<div class="product-info">
<div class="product-info-top">
<h2 class="sm-title">@product.CategoryID</h2>
<h2 class="sm-title">{{product.category}}</h2>
</div>
<a href="#" class="product-name">@product.ProductName</a>
<p class="product-price"><span></span></p>
<p class="product-price"><span>&#8373;</span> @product.UnitPrice</p>
<p>@product.ProductDesc</p>
<a href="javascript:void(0)" class="product-name" *ngIf="product.productName === null">{{product.productID}}</a>
<a href="javascript:void(0)" class="product-name" *ngIf="product.productName != null">{{product.productName}}</a>
<p class="product-price"><mat-icon style="color: red;" class="icon-size-5" [svgIcon]="'heroicons_solid:heart'"></mat-icon> {{product.desires}}</p>
</div>
<!-- </div> -->
</div>

3
src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.scss

@ -257,8 +257,7 @@ img {
}
.product-price:first-of-type {
text-decoration: line-through;
color: #40c9a2;
color: #003445;
}
.product-img img {

114
src/app/pages/admin/Monthly_Desires/ListDesires/list-desires.component.ts

@ -5,10 +5,14 @@ import { MatSort } from '@angular/material/sort';
import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { DesiredList, DesiredProduct } from 'app/models/productsModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { AdminCouponDialogComponent } from '../../Products/AdminCouponDialog/admin-coupon-dialog.component';
import { CouponDialogComponent } from '../../Products/CouponDialog/coupon-dialog.component';
import { ProductsService } from '../../Products/products.service';
import { DesiresService } from '../desires.service';
@Component({
selector: 'app-list-desires',
@ -17,13 +21,15 @@ import { CouponDialogComponent } from '../../Products/CouponDialog/coupon-dialog
})
export class ListDesiresComponent implements OnInit {
data: { teamproducts: { id: string; image: string; name: string; email: string; phone: string; title: string; }[]; };
data: DesiredList;
@ViewChild(MatPaginator) private _paginator: MatPaginator;
@ViewChild(MatSort) private _sort: MatSort;
isScreenSmall: boolean;
availableP:any[]=[];
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService) { }
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService,
private _desireService: DesiresService,private _productService: ProductsService) { }
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
@ -40,115 +46,37 @@ export class ListDesiresComponent implements OnInit {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
this.data = {
teamproducts: [
{
id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14',
image: 'assets/images/brands/bagset.png',
name: 'Nadia Mcknight',
email: 'nadiamcknight@mail.com',
phone: '+1-943-511-2203',
title: 'Project Director'
},
{
id: '77a4383b-b5a5-4943-bc46-04c3431d1566',
image: 'assets/images/brands/bagset.png',
name: 'Best Blackburn',
email: 'blackburn.best@beadzza.me',
phone: '+1-814-498-3701',
title: 'Senior Developer'
},
{
id: '8bb0f597-673a-47ca-8c77-2f83219cb9af',
image: 'assets/images/brands/bagset.png',
name: 'Duncan Carver',
email: 'duncancarver@mail.info',
phone: '+1-968-547-2111',
title: 'Senior Developer'
},
{
id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
image: 'assets/images/brands/bagset.png',
name: 'Martin Richards',
email: 'martinrichards@mail.biz',
phone: '+1-902-500-2668',
title: 'Junior Developer'
},
{
id: '0a8bc517-631a-4a93-aacc-000fa2e8294c',
image: 'assets/images/brands/bagset.png',
name: 'Candice Munoz',
email: 'candicemunoz@mail.co.uk',
phone: '+1-838-562-2769',
title: 'Lead Designer'
},
{
id: 'a4c9945a-757b-40b0-8942-d20e0543cabd',
image: 'assets/images/brands/bagset.png',
name: 'Vickie Mosley',
email: 'vickiemosley@mail.net',
phone: '+1-939-555-3054',
title: 'Designer'
},
{
id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
image: 'assets/images/brands/bagset.png',
name: 'Tina Harris',
email: 'tinaharris@mail.ca',
phone: '+1-933-464-2431',
title: 'Designer'
},
{
id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
image: 'assets/images/brands/bagset.png',
name: 'Holt Manning',
email: 'holtmanning@mail.org',
phone: '+1-822-531-2600',
title: 'Marketing Manager'
},
{
id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
image: 'assets/images/brands/bagset.png',
name: 'Misty Ramsey',
email: 'mistyramsey@mail.us',
phone: '+1-990-457-2106',
title: 'Consultant'
}
]
}
this._desireService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((data: DesiredList) => {
this.data = data;
this.availableP = data.availableProducts.map(p => p.productID);
});
}
createProduct() {
this.router.navigate(['products/create-product']);
}
editProduct(productID: any) {
this.router.navigate(['products/' + productID]);
}
generateCoupon(product: any) {
const selectedproduct = this._productService.getProduct(product);
if (!true) {
const dialogRef = this.dialog.open(CouponDialogComponent, {
disableClose: true,
hasBackdrop: true,
data: { product: product },
data: { product: selectedproduct },
});
} else {
const dialogRef = this.dialog.open(AdminCouponDialogComponent, {
disableClose: true,
hasBackdrop: true,
data: { product: product },
data: { product: selectedproduct },
});
}
}
deleteProduct(product: any) {
this.confirmBoxEvokeService.danger("Delete Product",
"Are you sure you would like to delete this product from your inventory ?",
"Yes", "No")
.subscribe((response: any) => {
console.log(response)
if (response.clickedButtonID === "yes") {
this.data.teamproducts = this.data.teamproducts.filter((e) => e == product);
}
});
imageLoader(path: string): string {
return environment.apiURL + `imagingproducts/${path}`;
}
}

32
src/app/pages/admin/Monthly_Desires/desires.resolvers.ts

@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { DesiresService } from './desires.service';
@Injectable({
providedIn: 'root'
})
export class DesiresResolver implements Resolve<any>
{
/**
* Constructor
*/
constructor(private _desiresService: DesiresService)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Resolver
*
* @param route
* @param state
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
{
return this._desiresService.getData();
}
}

7
src/app/pages/admin/Monthly_Desires/desires.routing.ts

@ -2,15 +2,16 @@ import { Route } from '@angular/router';
import { EditProductComponent } from '../Products/EditProduct/edit-product.component';
import { NewProductComponent } from '../Products/NewProduct/new-product.component';
import { DesiresComponent } from './desires.component';
import { DesiresResolver } from './desires.resolvers';
import { ListDesiresComponent } from './ListDesires/list-desires.component';
export const desiresRoutes: Route[] = [
{
path : '',
component: DesiresComponent,
// resolve : {
// data: FollowersResolver
// }
resolve : {
data: DesiresResolver
},
children : [
{
path : '',

49
src/app/pages/admin/Monthly_Desires/desires.service.ts

@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
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 { DesiredList, DesiredProduct, ProductCategory, ProductsModel } from 'app/models/productsModel';
@Injectable({
providedIn: 'root'
})
export class DesiresService {
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<DesiredList> {
return this._data.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(): Observable<DesiredList> {
return this._httpClient.get(environment.apiURL + `desires/pull-monthly`).pipe(
tap((response: DesiredList) => {
this._data.next(response);
})
);
}
}

144
src/app/pages/admin/Products/AdminCouponDialog/admin-coupon-dialog.component.html

@ -1,12 +1,12 @@
<div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">Coupons for {{data.product.name}}</h2>
<h2 mat-dialog-title style="text-align: center;">Coupons for {{data.product.productName}}</h2>
</div>
<!-- <button mat-button mat-dialog-close>Cancel</button> -->
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button>
</div>
<mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" *ngIf="!isScreenSmall">
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
@ -15,7 +15,7 @@
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
Target Name
{{businessTargetted.businessName}}
</mat-label>
<button style="background-color: blue;color:white;margin-right:20px;" mat-button (click)="lookupBusiness()">Search</button>
</div>
@ -24,12 +24,12 @@
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Name </strong>
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
Target Name
{{data.product.productName}}
</mat-label>
</div>
</div>
@ -41,7 +41,7 @@
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
GH¢200
{{data.product.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
@ -53,12 +53,12 @@
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required>
<mat-option value="low">Freebie</mat-option>
<mat-option value="standard">Discount</mat-option>
<mat-option value="high">Proximity Freebie</mat-option>
<mat-option value="high">Proximity Discount</mat-option>
<mat-select [(ngModel)]="selectedType">
<mat-option *ngFor="let type of couponTypes" [value]="type">
{{type.typeName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
@ -126,4 +126,126 @@
</div>
</div>
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" *ngIf="isScreenSmall">
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Business </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{businessTargetted.businessName}}
</mat-label>
<button style="background-color: blue;color:white;margin-right:20px;" mat-button (click)="lookupBusiness()">Search</button>
</div>
</div>
<hr/>
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{data.product.productName}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Original Price </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{data.product.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column">
<mat-form-field appearance="outline" style="min-width: 200px;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Coupon Type</strong>
</mat-label>
<mat-select [(ngModel)]="selectedType">
<mat-option *ngFor="let type of couponTypes" [value]="type">
{{type.typeName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Condition</strong>
</mat-label>
</div>
<div class="column">
<textarea name="" id="" cols="30" rows="5" style="border: 1px solid #cbd5e1;"></textarea>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-form-field appearance="outline" style="min-width: 200px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Number Of Coupons</strong>
</mat-label>
<input matInput style="margin-left:10px;" type="number">
</mat-form-field>
</div>
</div>
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong>
</mat-label>
<mat-label>
From GH¢200 To GH¢800
</mat-label>
</div>
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline;">
<mat-form-field appearance="outline">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>
</div>
</div>
</mat-dialog-content>

44
src/app/pages/admin/Products/AdminCouponDialog/admin-coupon-dialog.component.ts

@ -1,17 +1,45 @@
import { Component, Inject, OnInit } from '@angular/core';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CouponsService } from '../../Coupons/coupons.service';
import { BusinessLookUpComponent } from '../BusinessLookUp/business-look-up.component';
import { ProductsService } from '../products.service';
@Component({
selector: 'admin-coupon-dialog',
templateUrl: './admin-coupon-dialog.component.html',
styleUrls: ['./admin-coupon-dialog.component.scss']
})
export class AdminCouponDialogComponent implements OnInit {
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any }, public dialog: MatDialog,) { }
export class AdminCouponDialogComponent implements OnInit,OnDestroy {
businessTargetted:TesoBusinessDetail={};
private _unsubscribeAll: Subject<any> = new Subject<any>();
isLoading:boolean=false;
selectedProduct:any={};
selectedType:any={};
couponTypes:CouponsType[]=[];
isScreenSmall: boolean;
constructor(
private _couponService: CouponsService,@Inject(MAT_DIALOG_DATA) public data: { product: ProductsModel },
public dialog: MatDialog,private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,) { }
ngOnInit(): void {
this._couponService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.couponTypes = d;
});
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
}
lookupBusiness() {
const dialogRef = this.dialog.open(BusinessLookUpComponent, {
@ -19,5 +47,13 @@ export class AdminCouponDialogComponent implements OnInit {
hasBackdrop: true,
});
dialogRef.afterClosed().subscribe((result) => {
this.businessTargetted = result;
});
}
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
}

22
src/app/pages/admin/Products/BusinessLookUp/business-look-up.component.html

@ -6,7 +6,7 @@
<!-- Search -->
<mat-form-field class="teso-mat-dense teso-mat-no-subscript teso-mat-rounded min-w-64">
<mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:search'"></mat-icon>
<input matInput [autocomplete]="'off'" [placeholder]="'Enter Business Name or email here'">
<input matInput [autocomplete]="'off'" [placeholder]="'Enter business Name or email here'">
</mat-form-field>
<button class="ml-3" mat-flat-button [color]="'primary'" style="margin-left: 10px;">
<mat-icon class="icon-size-5" [svgIcon]="'heroicons_solid:search'"></mat-icon>
@ -16,52 +16,52 @@
<div class="flex flex-shrink-0 items-center justify-center" style="margin-top: 10px !important;">
<a href="javascript:void(0)"
style="color: #1890ff;text-decoration: none;background-color: transparent;outline: 0;cursor: pointer;">Target
Business not registered on teso ?</a>
business not registered on teso ?</a>
</div>
<table class="w-full bg-transparent" mat-table matSort [dataSource]="typesDataSource" [trackBy]="trackByFn"
#typesTable>
<!-- Transaction ID -->
<ng-container matColumnDef="transactionId">
<ng-container matColumnDef="businessId">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
Business
</th>
<td mat-cell *matCellDef="let transaction" style="display: flex;padding:10px;">
<div id="circle">
<div>
<span>{{transaction.name.substring(0,2).toUpperCase()}}</span>
<span>{{transaction.businessName.substring(0,2).toUpperCase()}}</span>
</div>
</div>
<div class="flex flex-col">
<span class="pr-6 font-medium text-sm text-secondary whitespace-nowrap">
{{transaction.name}}
{{transaction.businessName}}
</span>
<span class="pr-6 font-small text-sm text-secondary whitespace-nowrap">
{{transaction.email}}
{{transaction.businessEmail}}
</span>
</div>
</td>
</ng-container>
<!-- Request Name -->
<ng-container matColumnDef="name">
<ng-container matColumnDef="businessContact">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
Contact
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap">
{{transaction.phone}}
{{transaction.businessContact}}
</span>
</td>
</ng-container>
<ng-container matColumnDef="members">
<ng-container matColumnDef="businessDigitalAddress">
<th mat-header-cell mat-sort-header *matHeaderCellDef>
Address
</th>
<td mat-cell *matCellDef="let transaction">
<span class="pr-6 whitespace-wrap">
AK-151-0000
{{transaction.businessDigitalAddress}}
</span>
</td>
</ng-container>
@ -72,7 +72,7 @@
</th>
<td mat-cell *matCellDef="let transaction">
<div style="display: flex;">
<button class="sm:inline-flex" mat-flat-button [color]="'primary'">
<button class="sm:inline-flex" mat-flat-button [color]="'primary'" (click)="selectBusiness(transaction)">
Select
</button>
</div>

115
src/app/pages/admin/Products/BusinessLookUp/business-look-up.component.ts

@ -1,101 +1,12 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { TesoBusinessDetail } from 'app/models/businessModel';
import { Subject } from 'rxjs';
export const finance = {
types: [
{
id: '1b6fd296-bc6a-4d45-bf4f-e45519a58cf5',
transactionId: '528651571NT',
name: 'Morgan Page',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-10-07T22:22:37.274Z'
},
{
id: '2dec6074-98bd-4623-9526-6480e4776569',
transactionId: '421436904YT',
name: 'Nita Hebert',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-12-18T14:51:24.461Z'
},
{
id: 'ae7c065f-4197-4021-a799-7a221822ad1d',
transactionId: '685377421YT',
name: 'Marsha Chambers',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-12-25T17:52:14.304Z'
},
{
id: '0c43dd40-74f6-49d5-848a-57a4a45772ab',
transactionId: '884960091RT',
name: 'Charmaine Jackson',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-11-29T06:32:16.111Z'
},
{
id: 'e5c9f0ed-a64c-4bfe-a113-29f80b4e162c',
transactionId: '361402213NT',
name: 'Maura Carey',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-11-24T12:13:23.064Z'
}, {
id: '1b6fd296-bc6a-4d45-bf4f-e45519a58cf5',
transactionId: '528651571NT',
name: 'Morgan Page',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-10-07T22:22:37.274Z'
},
{
id: '2dec6074-98bd-4623-9526-6480e4776569',
transactionId: '421436904YT',
name: 'Nita Hebert',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-12-18T14:51:24.461Z'
},
{
id: 'ae7c065f-4197-4021-a799-7a221822ad1d',
transactionId: '685377421YT',
name: 'Marsha Chambers',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-12-25T17:52:14.304Z'
},
{
id: '0c43dd40-74f6-49d5-848a-57a4a45772ab',
transactionId: '884960091RT',
name: 'Charmaine Jackson',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-11-29T06:32:16.111Z'
},
{
id: 'e5c9f0ed-a64c-4bfe-a113-29f80b4e162c',
transactionId: '361402213NT',
name: 'Maura Carey',
phone: "+233 244 419 000",
email:"business@bacware.com",
status: 'Active',
date: '2019-11-24T12:13:23.064Z'
},
]
};
import { ProfileService } from '../../Profile/profile.service';
@Component({
selector: 'app-business-look-up',
templateUrl: './business-look-up.component.html',
@ -105,17 +16,23 @@ export class BusinessLookUpComponent implements OnInit {
@ViewChild('typesTable', { read: MatSort }) typesTableMatSort: MatSort;
searchInputControl: FormControl = new FormControl();
data: any;
typesDataSource: MatTableDataSource<any> = new MatTableDataSource();
typesTableColumns: string[] = ['transactionId', 'name', 'members', 'actions'];
typesDataSource: MatTableDataSource<TesoBusinessDetail> = new MatTableDataSource();
typesTableColumns: string[] = ['businessId', 'businessContact', 'businessDigitalAddress', 'actions'];
htmlRequest: string;
transferType: string;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor() { }
constructor(private _businessService: ProfileService, public dialogRef: MatDialogRef<BusinessLookUpComponent>,
) { }
ngOnInit(): void {
this.typesDataSource.data = finance.types;
this._businessService.getData().subscribe((data) => {
this.typesDataSource.data = data;
});
}
trackByFn(index: number, item: any): any {
return item.id || index;
return item.businessId || index;
}
selectBusiness(business: TesoBusinessDetail) {
this.dialogRef.close(business);
}
}

130
src/app/pages/admin/Products/CouponDialog/coupon-dialog.component.html

@ -1,21 +1,21 @@
<div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">Coupons for {{data.product.name}}</h2>
<h2 mat-dialog-title style="text-align: center;">Coupons for {{data.product.productName}}</h2>
</div>
<!-- <button mat-button mat-dialog-close>Cancel</button> -->
<button mat-button [mat-dialog-close]="true" cdkFocusInitial>x</button>
</div>
<mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" *ngIf="!isScreenSmall">
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Target Name </strong>
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
Target Name
{{data.product.productName}}
</mat-label>
</div>
</div>
@ -27,7 +27,7 @@
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
GH¢200
{{data.product.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
@ -39,12 +39,12 @@
</div>
<div class="column">
<mat-form-field appearance="fill" style="min-width: 200px;">
<mat-select required>
<mat-option value="low">Freebie</mat-option>
<mat-option value="standard">Discount</mat-option>
<mat-option value="high">Proximity Freebie</mat-option>
<mat-option value="high">Proximity Discount</mat-option>
<mat-select [(ngModel)]="selectedType">
<mat-option *ngFor="let type of couponTypes" [value]="type">
{{type.typeName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
@ -104,7 +104,115 @@
</mat-form-field>
<mat-form-field appearance="fill">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
<input matInput type="time">
</mat-form-field>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button>Generate Coupons</button>
</div>
</div>
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" *ngIf="isScreenSmall">
<div class="columns" style="display: flex;align-items: center">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Product Name </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{data.product.productName}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column" style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Original Price </strong>
</mat-label>
</div>
<div class="column">
<mat-label style="margin-right: 48px;">
{{data.product.unitPrice | currency:"GHS "}}
</mat-label>
</div>
</div>
<div class="columns" style="display: flex;align-items: center;margin-top: 15px;">
<div class="column">
<mat-form-field appearance="outline" style="min-width: 200px;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Coupon Type</strong>
</mat-label>
<mat-select [(ngModel)]="selectedType">
<mat-option *ngFor="let type of couponTypes" [value]="type">
{{type.typeName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Condition</strong>
</mat-label>
</div>
<div class="column">
<textarea name="" id="" cols="30" rows="5" style="border: 1px solid #cbd5e1;"></textarea>
</div>
</div>
<div class="columns" style="display: flex;align-items: center">
<div class="column">
<mat-form-field appearance="outline" style="min-width: 200px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Number Of Coupons</strong>
</mat-label>
<input matInput style="margin-left:10px;" type="number">
</mat-form-field>
</div>
</div>
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Percentage Off </strong>
</mat-label>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-right:10px;">
<span matPrefix>From &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-form-field appearance="outline" style="margin-left:10px;">
<span matPrefix>To &nbsp;</span>
<input matInput type="number">
<span matSuffix>% &nbsp;</span>
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Coupon Worth </strong>
</mat-label>
<mat-label>
From GH¢200 To GH¢800
</mat-label>
</div>
<div style="display: flex;align-items: baseline;margin-top: 20px;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Expiration </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="date">
</mat-form-field>
</div>
<div style="display: flex;align-items: baseline;">
<mat-form-field appearance="outline">
<span matPrefix>Time &nbsp;</span>
<input matInput type="time">
</mat-form-field>
</div>
<div class="row" style="display: flex;justify-content:space-around;width:100%;">

43
src/app/pages/admin/Products/CouponDialog/coupon-dialog.component.ts

@ -1,16 +1,47 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
import { MatDialog, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { CouponsType } from 'app/models/couponsModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { CouponsService } from '../../Coupons/coupons.service';
import { ProductsService } from '../products.service';
@Component({
selector: 'app-coupon-dialog',
templateUrl: './coupon-dialog.component.html',
styleUrls: ['./coupon-dialog.component.scss']
})
export class CouponDialogComponent implements OnInit {
constructor(@Inject(MAT_DIALOG_DATA) public data: { product: any },) { }
export class CouponDialogComponent implements OnInit,OnDestroy {
private _unsubscribeAll: Subject<any> = new Subject<any>();
isLoading:boolean=false;
selectedProduct:any={};
selectedType:any={};
couponTypes:CouponsType[]=[];
isScreenSmall: boolean;
constructor(
private _couponService: CouponsService,@Inject(MAT_DIALOG_DATA) public data: { product: ProductsModel },
public dialog: MatDialog,private _productService: ProductsService,private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,) { }
ngOnInit(): void {
}
this._couponService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.couponTypes = d;
});
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
}
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
}

201
src/app/pages/admin/Products/Details/details-product.component.html

@ -0,0 +1,201 @@
<div class="absolute inset-0 flex flex-col min-w-0 " *ngIf="!isScreenSmall">
<mat-drawer-container class="flex-auto h-full">
<!-- Drawer content -->
<mat-drawer-content class="flex flex-col">
<!-- Header -->
<div
class="flex flex-0 items-center py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 border-b lg:border-b-0 bg-card dark:bg-transparent">
<!-- Title & Actions -->
<button mat-icon-button [routerLink]="['../../']">
<mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon>
</button>
<h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate">
Product # {{product.productID}}
</h2>
</div>
<div
class=" flex flex-0 items-center py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 border-b lg:border-b-0 bg-card dark:bg-transparent">
<div class="flex-auto" cdkScrollable>
<div class="min-h-180 border-2 border-dashed border-gray-300 rounded-2xl py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 mx-auto"
style="max-width: 780px;">
<mat-tab-group mat-align-tabs="center">
<mat-tab label="Product Information">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6"
style="overflow-y: hidden !important;">
<div style="display: flex;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Product Name </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input disabled matInput style="margin-left:10px;" type="text"
value="{{product.productName}}">
</mat-form-field>
</div>
<div style="display:flex;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Product Category</strong>
</mat-label>
<mat-label style="width: 350px;">
<span class="tileHead">{{product.categoryID}}</span>
</mat-label>
</div>
<div style="display: flex;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Original Price </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<span matPrefix>GH¢ &nbsp;</span>
<input disabled matInput style="margin-left:10px;" type="number"
value={{product.unitPrice}}>
</mat-form-field>
</div>
<div class="columns" style="display: flex;justify-content:space-around;">
<div class="column">
<div>
<strong class="tileHead">Product Description :</strong>
</div>
</div>
</div>
<div class="column">
<quill-editor [modules]="quillConfig" [(ngModel)]=product.productDesc
placeholder="Further details">
</quill-editor>
</div>
</div>
</mat-tab>
<mat-tab label="Images">
<div class="py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8"
style="min-height: 550px !important;">
<div class="flex justify-between">
<div>
<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">
{{productImages.length}} out of
<strong>10</strong>
</div>
</div>
</div>
<div class="imageList">
<div class="imageHolder" *ngFor="let images of product.images">
<img src="{{images.imageSRC}}" 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>
</mat-tab>
</mat-tab-group>
</div>
</div>
</div>
</mat-drawer-content>
</mat-drawer-container>
</div>
<div class="absolute inset-0 flex flex-col min-w-0 " *ngIf="isScreenSmall">
<mat-drawer-container class="flex-auto h-full">
<!-- Drawer content -->
<mat-drawer-content class="flex flex-col">
<!-- Header -->
<div
class="flex flex-0 items-center py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 border-b lg:border-b-0 bg-card dark:bg-transparent">
<!-- Title & Actions -->
<button mat-icon-button [routerLink]="['../../']">
<mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon>
</button>
<h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate">
Product # {{product.productID}}
</h2>
</div>
<div
class=" flex flex-0 items-center py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 border-b lg:border-b-0 bg-card dark:bg-transparent">
<div class="flex-auto overflow-y-auto" cdkScrollable>
<div class="min-h-180 border-2 border-dashed border-gray-300 rounded-2xl py-2 pl-4 pr-6 sm:py-4 md:pl-6 md:pr-8 mx-auto"
style="max-width: 780px;">
<mat-tab-group mat-align-tabs="center">
<mat-tab label="Product Information">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6"
style="overflow-y: hidden !important;">
<div style="display: flex;">
<mat-label style="margin-right: 20px;">
<strong>Product Name </strong>
</mat-label>
<mat-label>{{product.productName}}</mat-label>
</div>
<hr/>
<div style="display:flex;">
<mat-label style="margin-right: 20px;">
<strong>Product Category</strong>
</mat-label>
<mat-label style="margin-right: 20px;">
<span style="font-size: 13px;">{{product.categoryID}}</span>
</mat-label>
</div>
<hr/>
<div style="display: flex;">
<mat-label style="margin-right: 48px;">
<strong>Original Price </strong>
</mat-label>
<mat-label>
{{product.unitPrice | currency:"GHS "}}
</mat-label>
</div>
<hr/>
<div class="columns" style="display: flex;justify-content:space-around;">
<div class="column">
<div>
<strong>Product Description :</strong>
</div>
</div>
</div>
<div class="column">
<quill-editor [modules]="quillConfig" [(ngModel)]="product.productDesc"
placeholder="Further details">
</quill-editor>
</div>
</div>
</mat-tab>
<mat-tab label="Images">
<div class="py-2 pl-2 pr-2 sm:py-4 md:pl-6 md:pr-8">
<div class="flex justify-between">
<div>
<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
<strong>10</strong>
</div>
</div>
</div>
<div class="imageList">
<div class="imageHolder" *ngFor="let images of productImages">
<img src="{{images.imageSRC}}" 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>
</mat-tab>
</mat-tab-group>
</div>
</div>
</div>
</mat-drawer-content>
</mat-drawer-container>
</div>

78
src/app/pages/admin/Products/Details/details-product.component.scss

@ -0,0 +1,78 @@
.columns {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.column {
flex: 50%;
font-size: medium;
}
.tileHead {
font-size: 17px;
color: #003445;
font-family: Roboto, "Helvetica Neue", sans-serif;
line-height: 3rem;
}
.ticketLine > a:hover {
color: cornflowerblue;
text-decoration: underline;
}
.ticketLine > a {
color: cornflowerblue;
font-style: italic;
}
::ng-deep {
@media screen and (min-width: 992px) {
.ql-container .ql-editor {
min-height: 160px;
max-height: 270px;
height: 270px;
}
}
}
.imageList {
display: flex;
flex-wrap: wrap;
}
.imageHolder {
flex-grow: 1;
width: 22%;
max-width: 180px;
height: 180px;
margin: 10px;
border: 3px solid #003445;
padding: 5px;
position: relative;
}
.productImage{
max-width: 130px;
margin: 10px;
}
.deleteelement {
position: absolute;
top: 5px;
right: 5px;
}
@media screen and (max-width: 992px) {
.imageHolder {
flex-grow: 1;
width: 22%;
max-width: 180px;
min-width: 180px;
height: 180px;
margin: 10px;
border: 3px solid #003445;
padding: 5px;
position: relative;
}
}

25
src/app/pages/admin/Products/Details/details-product.component.spec.ts

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

151
src/app/pages/admin/Products/Details/details-product.component.ts

@ -0,0 +1,151 @@
import { Component, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router, ActivatedRoute } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { ProductUpload } from 'app/models/generalModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProductsService } from '../products.service';
const modules = {
toolbar: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
]
};
@Component({
selector: 'app-details-product',
templateUrl: './details-product.component.html',
styleUrls: ['./details-product.component.scss']
})
export class DetailsProductComponent implements OnInit {
productID: string;
title: string;
status: string = "Status";
htmlRequest: string;
transferType: string;
placeHolder: string = "";
selected: any;
isScreenSmall: boolean;
productImages: ProductUpload[] = [];
product: ProductsModel;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,
private router: Router, private route: ActivatedRoute, private productServie: ProductsService) {
var snapshot = route.snapshot;
this.productID = snapshot.paramMap.get('id');
this.product = productServie.getProduct(this.productID);
if(this.product == null){
this.router.navigate(['products']);
}
}
ngOnInit(): void {
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
}
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();
this._unsubscribeAll.complete();
}
// readURL(event: Event): void {
// if (event.target.files && event.target.files[0]) {
// const file = event.target.files[0];
// const reader = new FileReader();
// reader.onload = e => this.imageSrc = reader.result;
// reader.readAsDataURL(file);
// }
// }
onFileSelected(event) {
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,
highlight: true,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
this.productImages.push(productImage);
} else {
var productImage: ProductUpload = {
file: file,
highlight: false,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
this.productImages.push(productImage);
}
} else {
this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe();
}
}
removeImage(item: ProductUpload) {
this.productImages = this.productImages.filter((e) => e != item);
}
submit() {
if (this.productImages.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 {
}
}
}

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

@ -11,7 +11,7 @@
<mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon>
</button>
<h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate">
Product #
Product # {{product.productID}}
</h2>
</div>
<div
@ -127,7 +127,7 @@
<mat-icon [svgIcon]="'heroicons_outline:arrow-sm-left'"></mat-icon>
</button>
<h2 class="ml-2.5 text-md sm:text-xl font-medium tracking-tight truncate">
Product #
Product # {{product.productID}}
</h2>
</div>
<div

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

@ -1,9 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { ProductUpload } from 'app/models/generalModel';
import { ProductsModel } from 'app/models/productsModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProductsService } from '../products.service';
const modules = {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
@ -23,7 +26,7 @@ const modules = {
[{ 'align': [] }],
['clean'],
['link']
]
};
@ -34,14 +37,27 @@ const modules = {
styleUrls: ['./edit-product.component.scss']
})
export class EditProductComponent implements OnInit {
productID: string;
title: string;
status: string = "Status";
htmlRequest: string;
transferType: string;
placeHolder: string = "";
selected: any;
isScreenSmall: boolean;
productImages: ProductUpload[] = [];
product: ProductsModel;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService, private confirmBoxEvokeService: ConfirmBoxEvokeService) { }
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,
private router: Router, private route: ActivatedRoute, private productServie: ProductsService) {
var snapshot = route.snapshot;
this.productID = snapshot.paramMap.get('id');
this.product = productServie.getProduct(this.productID);
if(this.product == null){
this.router.navigate(['products']);
}
}
ngOnInit(): void {
this._tesoMediaWatcherService.onMediaChange$

71
src/app/pages/admin/Products/NewProduct/new-product.component.html

@ -36,10 +36,11 @@
<strong class="tileHead">Product Category</strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<mat-select required>
<mat-option value="low">Lowdsadsadsadsadsadsa</mat-option>
<mat-option value="standard">Standardasdsadasdas</mat-option>
<mat-option value="high">Highsadsadsadas</mat-option>
<mat-select [(ngModel)]="selected">
<mat-option *ngFor="let category of categories"
[value]="category.catCode">
{{category.catName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
@ -61,7 +62,7 @@
</div>
</div>
<div class="column">
<quill-editor [modules]="quillConfig" [(ngModel)]="htmlRequest"
<quill-editor [modules]="quillConfig" [(ngModel)]="newProduct.productDesc"
placeholder="Further details">
</quill-editor>
@ -83,10 +84,11 @@
</div>
</div>
<input style="display: none" type="file" (change)="onFileSelected($event)"
#fileInput accept="image/*" capture="user">
#fileInput accept="image/*">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button
(click)="fileInput.click()" [disabled]="productImages.length > 9">Add Image
</button>
</div>
<div class="imageList">
<div class="imageHolder" *ngFor="let images of productImages">
@ -139,32 +141,33 @@
<mat-tab label="Product Information">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6"
style="overflow-y: hidden !important;">
<div style="display: flex;">
<mat-label style="margin-right: 20px;">
<strong>Product Name </strong>
</mat-label>
<mat-form-field appearance="fill">
<div style="display: flex;justify-content: space-between;">
<mat-form-field appearance="outline">
<mat-label style="margin-right: 20px;">
<strong>Product Name </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="text">
</mat-form-field>
</div>
<div style="display:flex;">
<mat-label style="margin-right: 20px;">
<strong>Product Category</strong>
</mat-label>
<mat-form-field appearance="fill">
<mat-select required>
<mat-option value="low">Lowdsadsadsadsadsadsa</mat-option>
<mat-option value="standard">Standardasdsadasdas</mat-option>
<mat-option value="high">Highsadsadsadas</mat-option>
<div style="display: flex;justify-content: space-between;">
<mat-form-field appearance="outline" style="width: 350px;">
<mat-label style="margin-right: 20px;">
<strong>Product Category</strong>
</mat-label>
<mat-select [(ngModel)]="selected">
<mat-option *ngFor="let category of categories"
[value]="category.catCode">
{{category.catName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div style="display: flex;">
<mat-label style="margin-right: 48px;">
<strong>Original Price </strong>
</mat-label>
<mat-form-field appearance="fill">
<span matPrefix>GH¢ &nbsp;</span>
<div style="display: flex;justify-content: space-between;">
<mat-form-field appearance="outline">
<mat-label>
<strong> Price </strong>
</mat-label>
<span matSuffix>GH¢ &nbsp;</span>
<input matInput style="margin-left:10px;" type="number">
</mat-form-field>
</div>
@ -192,17 +195,24 @@
<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">
{{productImages.length}} out of
<strong>10</strong>
</div>
</div>
<input style="display: none" type="file" (change)="onFileSelected($event)"
#fileInput accept="image/*" capture="user">
#takePhoto accept="image/*" capture="user">
<input style="display: none" type="file" (change)="onFileSelected($event)"
#fileInput accept="image/*">
<button style="background-color: blue;color:white;margin-right:20px;" mat-button
(click)="fileInput.click()" [disabled]="productImages.length > 9">
[disabled]="productImages.length > 9" [matMenuTriggerFor]="actionsMenu">
<mat-icon [svgIcon]="'heroicons_outline:plus'" style="color: white;">
</mat-icon>
</button>
<mat-menu #actionsMenu="matMenu">
<button (click)="takePhoto.click()" mat-menu-item>Take Photo</button>
<button (click)="takePhoto.click()" mat-menu-item>Add From Library</button>
</mat-menu>
</div>
<div class="imageList">
<div class="imageHolder" *ngFor="let images of productImages">
@ -218,7 +228,8 @@
</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()">Add
<button style="background-color: green;color:white;margin-right:20px;" mat-button
(click)="submit()">Add
Product</button>
<button style="background-color: firebrick;color:white;margin-left:10px;" mat-button
cdkFocusInitial>Cancel</button>

5
src/app/pages/admin/Products/NewProduct/new-product.component.scss

@ -46,7 +46,7 @@
flex-grow: 1;
width: 22%;
max-width: 180px;
height: 180px;
height: 200px;
margin: 10px;
border: 3px solid #003445;
padding: 5px;
@ -55,6 +55,7 @@
.productImage{
max-width: 130px;
margin: 10px;
max-height: 170px;
}
.deleteelement {
position: absolute;
@ -69,7 +70,7 @@
width: 22%;
max-width: 180px;
min-width: 180px;
height: 180px;
height: 200px;
margin: 10px;
border: 3px solid #003445;
padding: 5px;

183
src/app/pages/admin/Products/NewProduct/new-product.component.ts

@ -1,9 +1,11 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { ProductUpload } from 'app/models/generalModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { ProductsService } from '../products.service';
import { ProductCategory, ProductsModel } from 'app/models/productsModel';
const modules = {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
@ -23,7 +25,7 @@ const modules = {
[{ 'align': [] }],
['clean'],
['link']
]
};
@ -41,93 +43,114 @@ export class NewProductComponent implements OnInit, OnDestroy {
selected: any;
isScreenSmall: boolean;
productImages: ProductUpload[] = [];
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService, private confirmBoxEvokeService: ConfirmBoxEvokeService) { }
categories: ProductCategory[] = [];
newProduct: ProductsModel={
productName: '',
businessID: '',
productDesc: '',
productID: '',
categoryID: '',
unitPrice: 0,
productImage: '',
images: []
};
private _unsubscribeAll: Subject < any > = new Subject<any>();
constructor(private _tesoMediaWatcherService: tesoMediaWatcherService,
private confirmBoxEvokeService: ConfirmBoxEvokeService,
private changeDetector:ChangeDetectorRef,
private _productService: ProductsService) { }
ngOnInit(): void {
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
this._productService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.categories = d;
this.changeDetector.markForCheck();
// this.isLoading = false;
});
}
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();
this._unsubscribeAll.complete();
}
ngOnInit(): void {
this._tesoMediaWatcherService.onMediaChange$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe(({ matchingAliases }) => {
// readURL(event: Event): void {
// if (event.target.files && event.target.files[0]) {
// const file = event.target.files[0];
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
}
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();
this._unsubscribeAll.complete();
}
// const reader = new FileReader();
// reader.onload = e => this.imageSrc = reader.result;
// readURL(event: Event): void {
// if (event.target.files && event.target.files[0]) {
// const file = event.target.files[0];
// const reader = new FileReader();
// reader.onload = e => this.imageSrc = reader.result;
// reader.readAsDataURL(file);
// }
// }
onFileSelected(event) {
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,
highlight: true,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
this.productImages.push(productImage);
} else {
var productImage: ProductUpload = {
file: file,
highlight: false,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
this.productImages.push(productImage);
// reader.readAsDataURL(file);
// }
// }
onFileSelected(event) {
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,
highlight: true,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
this.productImages.push(productImage);
} else {
this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe();
var productImage: ProductUpload = {
file: file,
highlight: false,
imageSRC: "",
};
let reader = new FileReader();
reader.onload = (event: any) => {
productImage.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
this.productImages.push(productImage);
}
} else {
this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe();
}
}
removeImage(item: ProductUpload) {
this.productImages = this.productImages.filter((e) => e != item);
}
removeImage(item: ProductUpload) {
this.productImages = this.productImages.filter((e) => e != item);
}
submit() {
if (this.productImages.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 {
submit() {
if (this.productImages.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 {
}
}
}
}

48
src/app/pages/admin/Products/ProductList/product-list.component.html

@ -1,12 +1,13 @@
<div class="flex flex-col flex-auto w-full">
<div class="flex flex-wrap w-full max-w-screen-xl mx-auto p-6 md:p-8">
<!-- Title and action buttons -->
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="!isScreenSmall">
<div class="flex items-center justify-between w-full" *ngIf="!isScreenSmall">
<div>
<div class="text-3xl font-semibold tracking-tight leading-8">Products</div>
<div class="font-medium tracking-tight text-secondary">Here is a list of all products in your Teso (850)
<div class="font-medium tracking-tight text-secondary">Here is a list of all products in your Teso
({{data.length}})
</div>
</div>
@ -19,15 +20,18 @@
<!-- Search -->
<mat-form-field class="teso-mat-dense teso-mat-no-subscript teso-mat-rounded min-w-64">
<mat-icon class="icon-size-5" matPrefix [svgIcon]="'heroicons_solid:search'"></mat-icon>
<input matInput [autocomplete]="'off'" [placeholder]="'Look-up product'">
<input matInput
[formControl]="searchInputControl"
[autocomplete]="'off'" [placeholder]="'Look-up product'">
</mat-form-field>
</div>
</div>
<div class="flex items-center justify-between w-full" style="margin-bottom: 20px;" *ngIf="isScreenSmall">
<div>
<div class="text-3xl font-semibold tracking-tight leading-8">Products</div>
<div class="font-medium tracking-tight text-secondary">(850) products
<div class="font-medium tracking-tight text-secondary">({{data.length}}) products
</div>
</div>
@ -45,16 +49,21 @@
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0">
<ng-container *ngFor="let product of data.teamproducts">
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 w-full min-w-0 min-h-200"
style="margin-bottom: 50px;margin-top: 20px;">
<div class="absolute inset-x-0" *ngIf="isLoading">
<mat-progress-bar [mode]="'indeterminate'"></mat-progress-bar>
</div>
<ng-container *ngFor="let product of data">
<!-- <div class="flex flex-col flex-auto items-center bg-card shadow rounded-2xl overflow-hidden"> -->
<div class="product">
<div class="product-content">
<div class="product-img">
<img [src]="product.image" alt="product image" style="max-height:200px;">
<img [src]="imageLoader(product.productImage)" alt="product image"
style="max-height:200px;">
</div>
<div class="product-btns">
<button type="button" class="btn-cart" (click)="editProduct(product.id)">
<button type="button" class="btn-cart" (click)="editProduct(product.productID)">
Edit Product
</button>
<button type="button" class="btn-buy" (click)="generateCoupon(product)">
@ -66,25 +75,30 @@
<div class="product-info">
<div class="product-info-top">
<h2 class="sm-title">@product.CategoryID</h2>
<h2 class="sm-title">{{product.categoryID}}</h2>
<button class="btn2" style="border:none;" (click)="deleteProduct(product)">
<mat-icon [svgIcon]="'heroicons_outline:trash'" style="color: #1e293b;">
</mat-icon>
</button>
</div>
<a href="#" class="product-name">@product.ProductName</a>
<p class="product-price"><span></span></p>
<p class="product-price"><span>&#8373;</span> @product.UnitPrice</p>
<p>@product.ProductDesc</p>
<a href="javascript:void(0)" class="product-name">{{product.productName}}</a>
<p class="product-price"><span>{{product.unitPrice | currency:"GHS "}}</span></p>
<p>{{product.productDesc | productDescShort}}</p>
<div style="width: 100%;display: flex;justify-content: center;margin-top:30px;">
<button (click)="details(product.productID)">
<a href="javascript:void(0)" style="color: #469ae8;">See Details</a>
</button>
</div>
</div>
<!-- </div> -->
</div>
</ng-container>
</div>
<mat-paginator
class="sticky sm:absolute sm:inset-x-0 sm:bottom-0 border-b sm:border-t sm:border-b-0 z-10 bg-gray-50 dark:bg-transparent"
class="absolute sm:absolute sm:inset-x-0 sm:bottom-0 border-b sm:border-t sm:border-b-0 z-10 bg-gray-50 dark:bg-transparent"
[pageSizeOptions]="[5, 10, 25, 100]" [showFirstLastButtons]="true"></mat-paginator>
<ng-template #noProducts>
<ng-template *ngIf="data.length == 0">
<div class="p-8 sm:p-16 border-t text-4xl font-semibold tracking-tight text-center">There are no products!
</div>
</ng-template>

12
src/app/pages/admin/Products/ProductList/product-list.component.scss

@ -126,6 +126,7 @@ img {
}
.lg-title {
text-align: center;
font-size: 2.5rem;
font-weight: 500;
text-align: center;
@ -139,8 +140,9 @@ img {
}
.sm-title {
text-align: center;
font-weight: 300;
font-size: 1rem;
font-size: 0.89rem;
text-transform: uppercase;
}
@ -227,11 +229,13 @@ img {
}
.product-info {
text-align: center;
background: white;
padding: 2rem;
}
.product-info-top {
text-align: center;
display: flex;
justify-content: space-between;
align-items: center;
@ -242,10 +246,11 @@ img {
}
.product-name {
text-align: center;
color: black;
display: block;
text-decoration: none;
font-size: 1rem;
font-size: 0.89rem;
text-transform: uppercase;
font-weight: bold;
}
@ -257,7 +262,6 @@ img {
}
.product-price:first-of-type {
text-decoration: line-through;
color: #40c9a2;
}
@ -278,6 +282,7 @@ img {
}
.off-info .sm-title {
text-align: center;
background: #40c9a2;
color: white;
display: inline-block;
@ -324,6 +329,7 @@ img {
.product-collection .text-light {
opacity: 1;
text-align: center;
font-size: 0.8;
font-weight: 400;
line-height: 1.7;

119
src/app/pages/admin/Products/ProductList/product-list.component.ts

@ -1,14 +1,18 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { Router } from '@angular/router';
import { ConfirmBoxEvokeService } from '@costlydeveloper/ngx-awesome-popup';
import { tesoMediaWatcherService } from '@teso/services/media-watcher';
import { ProductCategory, ProductsModel } from 'app/models/productsModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { debounceTime, map, switchMap, takeUntil } from 'rxjs/operators';
import { AdminCouponDialogComponent } from '../AdminCouponDialog/admin-coupon-dialog.component';
import { CouponDialogComponent } from '../CouponDialog/coupon-dialog.component';
import { ProductsService } from '../products.service';
@Component({
selector: 'app-product-list',
@ -16,13 +20,16 @@ import { CouponDialogComponent } from '../CouponDialog/coupon-dialog.component';
styleUrls: ['./product-list.component.scss']
})
export class ProductListComponent implements OnInit, OnDestroy {
data: { teamproducts: { id: string; image: string; name: string; email: string; phone: string; title: string; }[]; };
data: ProductsModel[] = [];
@ViewChild(MatPaginator) private _paginator: MatPaginator;
@ViewChild(MatSort) private _sort: MatSort;
isScreenSmall: boolean;
isLoading: boolean = true;
searchInputControl: FormControl = new FormControl();
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService) { }
public dialog: MatDialog, private confirmBoxEvokeService: ConfirmBoxEvokeService, private _productService: ProductsService) {
}
ngOnDestroy(): void {
// Unsubscribe from all subscriptions
@ -39,82 +46,26 @@ export class ProductListComponent implements OnInit, OnDestroy {
// Check if the screen is small
this.isScreenSmall = !matchingAliases.includes('md');
});
this.data = {
teamproducts: [
{
id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14',
image: 'assets/images/brands/bagset.png',
name: 'Nadia Mcknight',
email: 'nadiamcknight@mail.com',
phone: '+1-943-511-2203',
title: 'Project Director'
},
{
id: '77a4383b-b5a5-4943-bc46-04c3431d1566',
image: 'assets/images/brands/bagset.png',
name: 'Best Blackburn',
email: 'blackburn.best@beadzza.me',
phone: '+1-814-498-3701',
title: 'Senior Developer'
},
{
id: '8bb0f597-673a-47ca-8c77-2f83219cb9af',
image: 'assets/images/brands/bagset.png',
name: 'Duncan Carver',
email: 'duncancarver@mail.info',
phone: '+1-968-547-2111',
title: 'Senior Developer'
},
{
id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb',
image: 'assets/images/brands/bagset.png',
name: 'Martin Richards',
email: 'martinrichards@mail.biz',
phone: '+1-902-500-2668',
title: 'Junior Developer'
},
{
id: '0a8bc517-631a-4a93-aacc-000fa2e8294c',
image: 'assets/images/brands/bagset.png',
name: 'Candice Munoz',
email: 'candicemunoz@mail.co.uk',
phone: '+1-838-562-2769',
title: 'Lead Designer'
},
{
id: 'a4c9945a-757b-40b0-8942-d20e0543cabd',
image: 'assets/images/brands/bagset.png',
name: 'Vickie Mosley',
email: 'vickiemosley@mail.net',
phone: '+1-939-555-3054',
title: 'Designer'
},
{
id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645',
image: 'assets/images/brands/bagset.png',
name: 'Tina Harris',
email: 'tinaharris@mail.ca',
phone: '+1-933-464-2431',
title: 'Designer'
},
{
id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d',
image: 'assets/images/brands/bagset.png',
name: 'Holt Manning',
email: 'holtmanning@mail.org',
phone: '+1-822-531-2600',
title: 'Marketing Manager'
},
{
id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37',
image: 'assets/images/brands/bagset.png',
name: 'Misty Ramsey',
email: 'mistyramsey@mail.us',
phone: '+1-990-457-2106',
title: 'Consultant'
}
]
}
this._productService.dataProductsPageOnly$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.data = d;
this.isLoading = false;
});
this.searchInputControl.valueChanges
.pipe(
takeUntil(this._unsubscribeAll),
debounceTime(300),
switchMap((query) => {
// this.isLoading = true;
return this.data = this._productService.filterProducts(query);
}),
map(()=>{
this.isLoading = false;
})
)
.subscribe((e)=>this.isLoading = false );
}
createProduct() {
this.router.navigate(['products/create-product']);
@ -144,10 +95,16 @@ export class ProductListComponent implements OnInit, OnDestroy {
"Are you sure you would like to delete this product from your inventory ?",
"Yes", "No")
.subscribe((response: any) => {
console.log(response)
if (response.clickedButtonID === "yes") {
this.data.teamproducts = this.data.teamproducts.filter((e) => e == product);
this.data = this.data.filter((e) => e != product);
}
});
}
imageLoader(path: string): string {
return environment.apiURL + `imagingproducts/${path}`;
}
details(productID: any) {
this.router.navigate(['products/details/' + productID]);
}
}

6
src/app/pages/admin/Products/products.module.ts

@ -30,6 +30,8 @@ import { CouponDialogComponent } from './CouponDialog/coupon-dialog.component';
import { MatDialogModule } from '@angular/material/dialog';
import { AdminCouponDialogComponent } from './AdminCouponDialog/admin-coupon-dialog.component';
import { BusinessLookUpComponent } from './BusinessLookUp/business-look-up.component';
import { ProductDescriptionShort } from 'app/pipes/productDescriptionShort.pipe';
import { DetailsProductComponent } from './Details/details-product.component';
@NgModule({
declarations: [
@ -40,7 +42,9 @@ import { BusinessLookUpComponent } from './BusinessLookUp/business-look-up.compo
SanitizerUrlPipe,
CouponDialogComponent,
AdminCouponDialogComponent,
BusinessLookUpComponent
BusinessLookUpComponent,
ProductDescriptionShort,
DetailsProductComponent
],
imports: [
RouterModule.forChild(productsRoutes),

8
src/app/pages/admin/Products/products.routing.ts

@ -1,4 +1,5 @@
import { Route } from '@angular/router';
import { DetailsProductComponent } from './Details/details-product.component';
import { EditProductComponent } from './EditProduct/edit-product.component';
import { NewProductComponent } from './NewProduct/new-product.component';
import { ProductListComponent } from './ProductList/product-list.component';
@ -30,6 +31,13 @@ export const productsRoutes: Route[] = [
// resolve : {
// course: AcademyCourseResolver
// }
},
{
path : 'details/:id',
component: DetailsProductComponent,
// resolve : {
// course: AcademyCourseResolver
// }
}
]
}

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

@ -0,0 +1,73 @@
import { Injectable } from '@angular/core';
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 { ProductsModule } from './products.module';
import { ProductCategory, ProductsModel } from 'app/models/productsModel';
@Injectable({
providedIn: 'root'
})
export class ProductsService {
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
private _dataFilterable: BehaviorSubject<any> = new BehaviorSubject(null);
private products: ProductsModel[] = [];
private _productsCategory: BehaviorSubject<ProductCategory[]> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<ProductsModel[]> {
return this._dataFilterable.asObservable();
}
get dataProductsPageOnly$(): Observable<ProductsModel[]> {
return this._data.asObservable();
}
get categories$(): Observable<ProductCategory[]> {
return this._productsCategory.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(): Observable<ProductsModel[]> {
return this._httpClient.get(environment.apiURL + `products/allproducts`).pipe(
tap((response: ProductsModel[]) => {
this._data.next(response);
this._dataFilterable.next(response);
this.products = response;
})
);
}
filterProducts(productName: string): ProductsModel[] {
return this._dataFilterable.getValue().filter(p => p.productName.toLowerCase().includes(productName));
// this.products = response;
}
getProduct(id: string): ProductsModel {
const found = this.products.find(item => item.productID === id);
return found;
}
getCategories(): Observable<ProductCategory[]> {
return this._httpClient.get(environment.apiURL + `productcategories`).pipe(
tap((response: ProductCategory[]) => {
this._productsCategory.next(response);
}));
}
}

12
src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.html

@ -0,0 +1,12 @@
<div class="row" style="display: flex;justify-content:space-between">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h2 mat-dialog-title style="text-align: center;">Shop Description</h2>
</div>
<button mat-button [mat-dialog-close]="data.details" cdkFocusInitial>x</button>
</div>
<mat-dialog-content class="mat-typography">
<quill-editor [modules]="quillConfig" [(ngModel)]="data.details"
placeholder="Further details">
</quill-editor>
</mat-dialog-content>

0
src/app/pages/admin/Settings/settings.component.scss → src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.scss

25
src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.spec.ts

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

44
src/app/pages/admin/Profile/DescriptionDialog/description-dialog.component.ts

@ -0,0 +1,44 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { TesoBusinessDetail } from 'app/models/businessModel';
const modules = {
toolbar: [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
[{ 'header': 1 }, { 'header': 2 }],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
[{ 'script': 'sub' }, { 'script': 'super' }],
[{ 'indent': '-1' }, { 'indent': '+1' }],
[{ 'direction': 'rtl' }],
[{ 'size': ['small', false, 'large', 'huge'] }],
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }],
[{ 'font': [] }],
[{ 'align': [] }],
['clean'],
['link']
]
};
@Component({
selector: 'app-description-dialog',
templateUrl: './description-dialog.component.html',
styleUrls: ['./description-dialog.component.scss']
})
export class DescriptionDialogComponent implements OnInit {
constructor(@Inject(MAT_DIALOG_DATA) public data: { details: string },
public dialogRef: MatDialogRef<DescriptionDialogComponent>,) { }
quillConfig = modules;
ngOnInit(): void {
}
closeDialog() {
this.dialogRef.close(this.data.details);
}
}

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

@ -0,0 +1,83 @@
<div class="row" style="display: flex;justify-content:space-between;">
<div class="row" style="display: flex;justify-content:center;width: 100%;">
<h3 mat-dialog-title style="text-align: center;">Business Information</h3>
</div>
<button mat-button [mat-dialog-close]="data.details" cdkFocusInitial>x</button>
</div>
<mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" style="overflow-y: hidden !important;">
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Shop Name </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessName">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">Shop Category</strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<mat-select [(ngModel)]="selectedCategory">
<mat-option *ngFor="let category of businessCategories" [value]="category">
{{category.categoryName}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Physical Address </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessAddress">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Digital Address </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<span matPrefix>
<a href="https://ghanapostgps.com/map/" target="blank">
<mat-icon class="icon-size-5" [svgIcon]="'heroicons_solid:information-circle'"
style="color: #0152cc;" matTooltip="visit https://ghanapostgps.com/map/">
</mat-icon> &nbsp;
</a>
</span>
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessDigitalAddress">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Date Of Establishment </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="date" [(ngModel)]="data.details.dateOfEst">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Telephone </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessContact"
disabled>
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Email Address </strong>
</mat-label>
<mat-form-field appearance="fill" style="width: 350px;">
<input matInput style="margin-left:10px;" type="text" [(ngModel)]="data.details.businessEmail" disabled>
</mat-form-field>
</div>
<div style="display: flex;justify-content:center;width: 100%;">
<button mat-button style="background-color: green;color: white;">Confirm Changes</button>
</div>
</div>
</mat-dialog-content>

28
src/app/pages/admin/Profile/InfoDialog/information-dialog.component.scss

@ -0,0 +1,28 @@
.columns {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
}
.column {
flex: 50%;
font-size: medium;
}
.tileHead {
font-size: 17px;
color: #003445;
font-family: Roboto, "Helvetica Neue", sans-serif;
line-height: 3rem;
}
.ticketLine > a:hover {
color: cornflowerblue;
text-decoration: underline;
}
.ticketLine > a {
color: cornflowerblue;
font-style: italic;
}

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

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

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

@ -0,0 +1,35 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UserService } from 'app/core/user/user.service';
import { BusinessCategory, TesoBusinessDetail } from 'app/models/businessModel';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ProfileService } from '../profile.service';
@Component({
selector: 'app-information-dialog',
templateUrl: './information-dialog.component.html',
styleUrls: ['./information-dialog.component.scss']
})
export class InformationDialogComponent implements OnInit {
businessCategories: BusinessCategory[] = [];
selectedCategory: BusinessCategory;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(@Inject(MAT_DIALOG_DATA) public data: { details: TesoBusinessDetail },
public dialogRef: MatDialogRef<InformationDialogComponent>, private _profileService: ProfileService,
private _userService:UserService) { }
ngOnInit(): void {
this._profileService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.businessCategories = d;
this.selectedCategory = this.businessCategories.find((c)=> c.categoryCode.toLowerCase() == this.data.details.businessCategory.toLowerCase());
});
}
closeDialog() {
this.data.details.businessCategory = this.selectedCategory.categoryCode;
this._userService.update(this.data.details);
this.dialogRef.close(true);
}
}

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

@ -0,0 +1,53 @@
<div class="InfoHold">
<div style="display: flex;justify-content: space-between;max-height: 46px;align-items: center;">
<div>
<h4>
<mat-icon class="icon-size-5" [svgIcon]="'heroicons_solid:information-circle'"></mat-icon> Business
Information
</h4>
</div>
<div>
<button mat-button style="background-color: #0152cc;color: white;" (click)="editShopInformation()">Edit Information</button>
</div>
</div>
<mat-divider class="my-2"></mat-divider>
<table style="width: 100%;" >
<tbody>
<tr class="tableau">
<th scope="row" class="text-dark">Shop Name: </th>
<td id="business-name-id">{{details.businessName}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Shop Category: </th>
<td id="shop-category1">{{currentCategory}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Physical Address: </th>
<td id="physicaladdress1">{{details.businessAddress}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Digital Address: </th>
<td id="digitaladdress1">{{details.businessDigitalAddress}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Date of Establishment: </th>
<td id="dateest1">
{{details.dateOfEst | date}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Phone Number: </th>
<td id="tel-id">{{details.businessContact}}</td>
</tr>
<tr class="tableau">
<th scope="row" class="text-dark">Email Address: </th>
<td id="email-id">{{details.businessEmail}}</td>
</tr>
</tbody>
</table>
</div>
<div class="text-center" style="margin-top:10px;">
<!-- <RadzenButton Text="Save Changes" Click="editProduct" Style="background-color:forestgreen !important;" /> -->
</div>

12
src/app/pages/admin/Profile/Information/information.component.scss

@ -0,0 +1,12 @@
.InfoHold{
padding-left: 5%;
padding-right: 5%;
}
.tableau{
margin: 20px;
height: 45px;
padding: 20px;
border-bottom: 1px solid gray;
text-align: start;
align-items: flex-start;
}

12
src/app/pages/admin/Products/EditProduct/edit-product.component.spec.ts → src/app/pages/admin/Profile/Information/information.component.spec.ts

@ -1,20 +1,20 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EditProductComponent } from './edit-product.component';
import { InformationComponent } from './information.component';
describe('EditProductComponent', () => {
let component: EditProductComponent;
let fixture: ComponentFixture<EditProductComponent>;
describe('InformationComponent', () => {
let component: InformationComponent;
let fixture: ComponentFixture<InformationComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ EditProductComponent ]
declarations: [ InformationComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(EditProductComponent);
fixture = TestBed.createComponent(InformationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

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

@ -0,0 +1,51 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table';
import { Router } from '@angular/router';
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 { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FollowersService } from '../../Followers/followers.service';
import { DescriptionDialogComponent } from '../DescriptionDialog/description-dialog.component';
import { InformationDialogComponent } from '../InfoDialog/information-dialog.component';
import { ProfileService } from '../profile.service';
@Component({
selector: 'business-information',
templateUrl: './information.component.html',
styleUrls: ['./information.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'business-information'
})
export class InformationComponent implements OnInit {
@Input() details: TesoBusinessDetail;
currentCategory: string;
categories: BusinessCategory[] = [];
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
private _profileService: ProfileService, private _changeRef: ChangeDetectorRef,
public dialog: MatDialog,
private confirmBoxEvokeService: ConfirmBoxEvokeService, private _userService: UserService) { }
ngOnInit(): void {
this._profileService.categories$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.categories = d;
this.currentCategory = d.find(c => c.categoryCode == this.details.businessCategory).categoryName;
});
}
editShopInformation() {
const dialogReference = this.dialog.open(InformationDialogComponent, {
disableClose: true,
hasBackdrop: true,
data: { details: this.details },
});
dialogReference.afterClosed().subscribe((d) => {
if (d)
this._userService.get();
});
}
}

33
src/app/pages/admin/Profile/Settings/settings.component.html

@ -0,0 +1,33 @@
<mat-dialog-content class="mat-typography">
<div class=" py-2 pl-3 pr-3 sm:py-4 md:pl-4 md:pr-6" style="overflow-y: hidden !important;">
<div style="display: flex;justify-content:space-between;">
<mat-form-field appearance="outline" style="width: 350px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Old Password </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="password" >
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-form-field appearance="outline" style="width: 350px;">
<mat-label style="margin-right: 20px;">
<strong class="tileHead">New Password</strong>
</mat-label>
<input matInput style="margin-left:10px;" type="password" >
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;">
<mat-form-field appearance="outline" style="width: 350px;">
<mat-label style="margin-right: 48px;">
<strong class="tileHead">Confirm Password </strong>
</mat-label>
<input matInput style="margin-left:10px;" type="password">
</mat-form-field>
</div>
<div style="display: flex;justify-content:space-between;width: 100%;">
<button mat-button style="background-color: green;color: white;">Confirm</button>
<button mat-button style="background-color: maroon;color: white;" [mat-dialog-close]="true" cdkFocusInitial>Cancel</button>
</div>
</div>
</mat-dialog-content>

0
src/app/pages/admin/Profile/Settings/settings.component.scss

0
src/app/pages/admin/Settings/settings.component.spec.ts → src/app/pages/admin/Profile/Settings/settings.component.spec.ts

17
src/app/pages/admin/Profile/Settings/settings.component.ts

@ -0,0 +1,17 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
@Component({
selector: 'settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
exportAs: 'settings'
})
export class SettingsComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

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

@ -1 +1,66 @@
<p>profile works!</p>
<!-- <div class="flex flex-col flex-auto w-full">
<div class="flex flex-wrap w-full max-w-screen-xl mx-auto p-6 md:p-8"> -->
<div class="columns">
<div class="col-md-3">
<div class="card card-primary card-outline">
<div class="card-body box-profile">
<form>
<input style="display: none" type="file" (change)="onFileSelected($event)"
#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">
<img id="logo" class="profile-user-img img-fluid img-circle" src="{{newProfilePicture.imageSRC}}"
style="height:150px;width:150px;" *ngIf="newProfilePicture != 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>
<button mat-button style="background-color: rgb(163, 35, 35);color: white;"
(click)="clear()" *ngIf="newProfilePicture != undefined">Clear</button>
</div>
</form>
<h3 class="profile-username text-center">{{profile.businessName}}</h3>
<ul class="list-group list-group-unbordered mb-3">
<li class="list-group-item">
<b style="margin-right: 10px;">Followers : </b>
<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;">
<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> -->
</li>
</ul>
</div>
</div>
<div class="card card-primary">
<div class="card-header" style="display: flex;justify-content: space-between;">
<h3 class="card-title">Shop Description</h3>
<!-- <div class="card-tools" (click)="openEdit()"> -->
<button mat-button style="background: transparent;" (click)="editShopDescription()">
<mat-icon class="icon-size-3" [svgIcon]="'heroicons_solid:pencil-alt'" style="color: #fff;"></mat-icon>
</button>
<!-- </div> -->
</div>
<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>
</p>
</div>
</div>
</div>
<div class="col-md-9">
<div class="card">
<business-information [details]="profile"></business-information>
</div>
</div>
</div>
<!-- </div>
</div> -->

173
src/app/pages/admin/Profile/profile.component.scss

@ -0,0 +1,173 @@
.card-primary.card-outline {
border-top: 3px solid #0152cc;
}
.card-primary:not(.card-outline) > .card-header, .card-primary:not(.card-outline) > .card-header a {
color: #fff;
}
.card-primary:not(.card-outline) > .card-header {
background-color: #0152cc;
}
.card {
box-shadow: 0 0 1px rgb(0 0 0 / 13%), 0 1px 3px rgb(0 0 0 / 20%);
margin-bottom: 1rem;
}
.card-header {
background-color: transparent;
border-bottom: 1px solid rgba(0,0,0,.125);
padding: .75rem 1.25rem;
position: relative;
border-top-left-radius: .25rem;
border-top-right-radius: .25rem;
}
.list-group-unbordered > .list-group-item {
border-left: 0;
border-radius: 0;
border-right: 0;
padding-left: 0;
padding-right: 0;
}
.list-group-item {
position: relative;
display: block;
padding: .75rem 1.25rem;
background-color: #fff;
border: 1px solid rgba(0,0,0,.125);
}
.card-body {
-ms-flex: 1 1 auto;
flex: 1 1 auto;
min-height: 1px;
padding: 1.25rem;
}
.card-body {
-ms-flex: 1 1 auto;
flex: 1 1 auto;
min-height: 1px;
padding: 1.25rem;
}
form {
display: block;
margin-top: 0em;
}
.img-circle {
border-radius: 50%;
}
.profile-user-img {
border: 3px solid #adb5bd;
margin: 0 auto;
padding: 3px;
width: 100px;
}
.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;
}
.columns {
display: flex;
flex-direction: row;
width: 100%;
}
@media only screen and (max-width: 600px) {
.columns {
display: flex;
flex-direction:row;
flex-wrap: wrap;
width: 100%;
}
.col-md-9{
flex: 30%;
font-size: medium;
margin: 10px;
padding: 10px;
}
}
.column {
flex: 50%;
font-size: medium;
}
.col-md-3{
flex: 30%;
font-size: medium;
margin: 10px;
padding: 10px;
}
.col-md-9{
flex: 70%;
font-size: medium;
margin: 10px;
padding: 10px;
}

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

@ -1,15 +1,75 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
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 { TesoUserDetails } from 'app/models/userModel';
import { environment } from 'environments/environment';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { FollowersService } from '../Followers/followers.service';
import { DescriptionDialogComponent } from './DescriptionDialog/description-dialog.component';
import { ProfileService } from './profile.service';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss']
styleUrls: ['./profile.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom,
})
export class ProfileComponent implements OnInit {
constructor() { }
profile: TesoBusinessDetail = {};
subscribers:TesoUserDetails[]=[];
newProfilePicture:any;
private _unsubscribeAll: Subject<any> = new Subject<any>();
constructor(private router: Router, private _tesoMediaWatcherService: tesoMediaWatcherService,
private _profileService: ProfileService, private _userService: UserService,
private _followersService: FollowersService, public dialog: MatDialog,
private confirmBoxEvokeService: ConfirmBoxEvokeService,) { }
ngOnInit(): void {
this._userService.user$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.profile = d;
});
this._followersService.data$.pipe(takeUntil(this._unsubscribeAll)).subscribe((d) => {
this.subscribers = d;
});
}
imageLoader(path: string): string {
return environment.apiURL + `shoplogo/${path}`;
}
editShopDescription(){
const dialogReference = this.dialog.open(DescriptionDialogComponent,{
disableClose: true,
hasBackdrop: true,
data: { details: this.profile.businessDescription },
});
dialogReference.afterClosed().subscribe((d)=> {
this.profile.businessDescription = d;
});
}
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();
reader.onload = (event: any) => {
this.newProfilePicture.imageSRC = event.target.result;
}
reader.readAsDataURL(file);
} else {
this.confirmBoxEvokeService.danger("Incompatible File", "Only images can be added", "OK").subscribe();
}
}
clear(){
this.newProfilePicture={};
}
}

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

@ -1,12 +1,66 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProfileComponent } from './profile.component';
import { RouterModule } from '@angular/router';
import { QuillModule } from 'ngx-quill';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatRippleModule } from '@angular/material/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslocoModule } from '@ngneat/transloco';
import { SharedModule } from 'app/shared/shared.module';
import { profileRoutes } from './profile.routing';
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';
@NgModule({
declarations: [],
declarations: [
ProfileComponent,
InformationComponent,
SettingsComponent,
DescriptionDialogComponent,
InformationDialogComponent
],
imports: [
CommonModule
RouterModule.forChild(profileRoutes),
QuillModule.forRoot(),
CommonModule,
MatButtonModule,
MatButtonToggleModule,
MatDividerModule,
MatIconModule,
MatFormFieldModule,
MatInputModule,
MatMenuModule,
MatProgressBarModule,
MatRippleModule,
MatSidenavModule,
MatSortModule,
MatTableModule,
MatTabsModule,
MatDialogModule,
TranslocoModule,
SharedModule,
MatPaginatorModule,
MatSelectModule,
MatTooltipModule,
]
})
export class ProfileModule { }

35
src/app/pages/admin/Profile/profile.routing.ts

@ -0,0 +1,35 @@
import { Route } from '@angular/router';
import { EditProductComponent } from '../Products/EditProduct/edit-product.component';
import { NewProductComponent } from '../Products/NewProduct/new-product.component';
import { ProfileComponent } from './profile.component';
export const profileRoutes: Route[] = [
{
path : '',
component: ProfileComponent,
// resolve : {
// data: DesiresResolver
// },
// children : [
// {
// path : '',
// pathMatch: 'full',
// component: ListDesiresComponent,
// },
// {
// path : 'create-product',
// component: NewProductComponent,
// // resolve : {
// // course: AcademyCourseResolver
// // }
// },
// // {
// // path : ':id',
// // component: EditProductComponent,
// // // resolve : {
// // // course: AcademyCourseResolver
// // // }
// // }
// ]
}
];

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

@ -0,0 +1,64 @@
import { Injectable } from '@angular/core';
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 { BusinessCategory, TesoBusinessDetail } from 'app/models/businessModel';
@Injectable({
providedIn: 'root'
})
export class ProfileService {
private _data: BehaviorSubject<any> = new BehaviorSubject(null);
private business: TesoBusinessDetail[] = [];
private _businessCategory: BehaviorSubject<BusinessCategory[]> = new BehaviorSubject(null);
/**
* Constructor
*/
constructor(private _httpClient: HttpClient) {
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<any> {
return this._data.asObservable();
}
get categories$(): Observable<BusinessCategory[]> {
return this._businessCategory.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(business: string = ".null."): Observable<any> {
return this._httpClient.get(environment.apiURL + `business/search/filter?businessName=${business}`).pipe(
tap((response: any) => {
this._data.next(response);
this.business = response;
})
);
}
getBusiness(id: string): TesoBusinessDetail {
const found = this.business.find(item => item.businessId === id);
return found;
}
getCategories(): Observable<BusinessCategory[]> {
return this._httpClient.get(environment.apiURL + `api/businesscategories1`).pipe(
tap((response: BusinessCategory[]) => {
this._businessCategory.next(response);
}));
}
}

1
src/app/pages/admin/Settings/settings.component.html

@ -1 +0,0 @@
<p>settings works!</p>

15
src/app/pages/admin/Settings/settings.component.ts

@ -1,15 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

12
src/app/pages/admin/Settings/settings.module.ts

@ -1,12 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [
CommonModule
]
})
export class SettingsModule { }

105
src/app/pages/auth/unlock-session/unlock-session.component.html

@ -1,105 +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">Unlock your session</div>
<div class="mt-0.5 font-medium">Your session is locked due to inactivity</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>
<!-- Unlock form -->
<form class="mt-8" [formGroup]="unlockSessionForm" #unlockSessionNgForm="ngForm">
<!-- Name field -->
<mat-form-field class="w-full">
<mat-label>Full name</mat-label>
<input id="name" matInput [formControlName]="'name'">
</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>
<!-- Submit button -->
<button class="teso-mat-button-large w-full mt-3" mat-flat-button [color]="'primary'" [disabled]="unlockSessionForm.disabled" (click)="unlock()">
<span *ngIf="!unlockSessionForm.disabled">
Unlock your session
</span>
<mat-progress-spinner
*ngIf="unlockSessionForm.disabled"
[diameter]="24"
[mode]="'indeterminate'"></mat-progress-spinner>
</button>
<!-- Form footer -->
<div class="mt-8 text-md font-medium text-secondary">
<span>I'm not</span>
<a class="ml-1 text-primary-500 hover:underline" [routerLink]="['/sign-out']">{{name}}</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>

129
src/app/pages/auth/unlock-session/unlock-session.component.ts

@ -1,129 +0,0 @@
import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { tesoAnimations } from '@teso/animations';
import { AuthService } from 'app/core/auth/auth.service';
import { UserService } from 'app/core/user/user.service';
import { tesoAlertType } from '@teso/components/alert';
@Component({
selector : 'auth-unlock-session',
templateUrl : './unlock-session.component.html',
encapsulation: ViewEncapsulation.None,
animations : tesoAnimations
})
export class AuthUnlockSessionComponent implements OnInit
{
@ViewChild('unlockSessionNgForm') unlockSessionNgForm: NgForm;
alert: { type: tesoAlertType; message: string } = {
type : 'success',
message: ''
};
name: string;
showAlert: boolean = false;
unlockSessionForm: FormGroup;
private _email: string;
/**
* Constructor
*/
constructor(
private _activatedRoute: ActivatedRoute,
private _authService: AuthService,
private _formBuilder: FormBuilder,
private _router: Router,
private _userService: UserService
)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Get the user's name
this._userService.user$.subscribe((user) => {
this.name = user.name;
this._email = user.email;
});
// Create the form
this.unlockSessionForm = this._formBuilder.group({
name : [
{
value : this.name,
disabled: true
}
],
password: ['', Validators.required]
});
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Unlock
*/
unlock(): void
{
// Return if the form is invalid
if ( this.unlockSessionForm.invalid )
{
return;
}
// Disable the form
this.unlockSessionForm.disable();
// Hide the alert
this.showAlert = false;
this._authService.unlockSession({
email : this._email ?? '',
password: this.unlockSessionForm.get('password').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.
const redirectURL = this._activatedRoute.snapshot.queryParamMap.get('redirectURL') || '/signed-in-redirect';
// Navigate to the redirect url
this._router.navigateByUrl(redirectURL);
},
(response) => {
// Re-enable the form
this.unlockSessionForm.enable();
// Reset the form
this.unlockSessionNgForm.resetForm({
name: {
value : this.name,
disabled: true
}
});
// Set the alert
this.alert = {
type : 'error',
message: 'Invalid password'
};
// Show the alert
this.showAlert = true;
}
);
}
}

32
src/app/pages/auth/unlock-session/unlock-session.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 { AuthUnlockSessionComponent } from 'app/pages/auth/unlock-session/unlock-session.component';
import { authUnlockSessionRoutes } from 'app/pages/auth/unlock-session/unlock-session.routing';
@NgModule({
declarations: [
AuthUnlockSessionComponent
],
imports : [
RouterModule.forChild(authUnlockSessionRoutes),
MatButtonModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatProgressSpinnerModule,
tesoCardModule,
tesoAlertModule,
SharedModule
]
})
export class AuthUnlockSessionModule
{
}

9
src/app/pages/auth/unlock-session/unlock-session.routing.ts

@ -1,9 +0,0 @@
import { Route } from '@angular/router';
import { AuthUnlockSessionComponent } from 'app/pages/auth/unlock-session/unlock-session.component';
export const authUnlockSessionRoutes: Route[] = [
{
path : '',
component: AuthUnlockSessionComponent
}
];

13
src/app/pipes/coupon-worth.pipe.ts

@ -0,0 +1,13 @@
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'couponWorth'
})
export class CouponWorthPipe implements PipeTransform {
transform(value: number,rate:number): string {
var worth = (rate * value)/100;
return `GH¢ ${worth}`;
}
}

18
src/app/pipes/productDescriptionShort.pipe.ts

@ -0,0 +1,18 @@
import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
name: 'productDescShort'
})
export class ProductDescriptionShort implements PipeTransform {
constructor(
) { }
transform(value: string): string {
if(value.length > 57){
return value.substring(0, 58) + "....";
}else{
return value;
}
}
}

BIN
src/assets/images/300.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

3
src/environments/environment.ts

@ -3,7 +3,8 @@
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false
production: false,
apiURL:"https://test.tesoapp.com/"
};
/*

Loading…
Cancel
Save