-
Notifications
You must be signed in to change notification settings - Fork 537
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Feat] Implement magic login (#8387)
* feat: create reusable workspace selection component * feat: create debounce click directive * feat: create reusable avatar component * feat: implement login magic component * feat: implement login magic workspace component * feat: implement login workspace component * feat: reuse shared core styles * feat: add new component to login * feat: create authentication routes * feat: use lazy loading for authentications routes * fix: add code rabbit suggestions * fix: apply code rabbit suggestions * fix: apply code rabbit suggestions
- Loading branch information
Showing
23 changed files
with
2,110 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
import { Route } from '@angular/router'; | ||
import { | ||
NbAuthComponent, | ||
NbLogoutComponent, | ||
NbRegisterComponent, | ||
NbRequestPasswordComponent, | ||
NbResetPasswordComponent | ||
} from '@nebular/auth'; | ||
import { NgxLoginComponent } from '../login'; | ||
import { NgxLoginMagicComponent } from '../login/features/login-magic/login-magic.component'; | ||
import { NgxLoginWorkspaceComponent } from '../login/features/login-workspace/login-workspace.component'; | ||
import { NgxMagicSignInWorkspaceComponent } from '../login/features/magic-login-workspace/magic-login-workspace.component'; | ||
import { NoAuthGuard } from './no-auth.guard'; | ||
|
||
export const authRoutes: Route[] = [ | ||
{ | ||
path: '', | ||
component: NbAuthComponent, | ||
children: [ | ||
{ | ||
path: '', | ||
redirectTo: 'login', | ||
pathMatch: 'full' | ||
}, | ||
{ | ||
path: 'login', | ||
component: NgxLoginComponent, | ||
canActivate: [NoAuthGuard] | ||
}, | ||
{ | ||
path: 'register', | ||
component: NbRegisterComponent, | ||
canActivate: [NoAuthGuard] | ||
}, | ||
{ | ||
path: 'logout', | ||
component: NbLogoutComponent | ||
}, | ||
{ | ||
path: 'request-password', | ||
component: NbRequestPasswordComponent, | ||
canActivate: [NoAuthGuard] | ||
}, | ||
{ | ||
path: 'reset-password', | ||
component: NbResetPasswordComponent, | ||
canActivate: [NoAuthGuard] | ||
}, | ||
{ | ||
// Register the path 'login-workspace' | ||
path: 'login-workspace', | ||
// Register the component to load component: NgxLoginWorkspaceComponent, | ||
component: NgxLoginWorkspaceComponent, | ||
// Register the data object | ||
canActivate: [NoAuthGuard] | ||
}, | ||
{ | ||
// Register the path 'login-magic' | ||
path: 'login-magic', | ||
// Register the component to load component: NgxLoginMagicComponent, | ||
component: NgxLoginMagicComponent, | ||
// Register the data object | ||
canActivate: [NoAuthGuard] | ||
}, | ||
{ | ||
// Register the path 'magic-sign-in' | ||
path: 'magic-sign-in', | ||
// Register the component to load component: NgxMagicSignInWorkspaceComponent, | ||
component: NgxMagicSignInWorkspaceComponent, | ||
// Register the data object | ||
canActivate: [NoAuthGuard] | ||
} | ||
] | ||
} | ||
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
export * from './services'; | ||
export * from './auth.guard'; | ||
export * from './no-auth.guard'; | ||
export * from './auth.module'; | ||
export * from './auth.routes'; | ||
export * from './no-auth.guard'; | ||
export * from './services'; |
38 changes: 38 additions & 0 deletions
38
packages/desktop-ui-lib/src/lib/directives/debounce-click.directive.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { Directive, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output } from '@angular/core'; | ||
import { Subject, Subscription, tap } from 'rxjs'; | ||
import { debounceTime } from 'rxjs/operators'; | ||
|
||
@Directive({ | ||
selector: '[debounceClick]' | ||
}) | ||
export class DebounceClickDirective implements OnInit, OnDestroy { | ||
private clicks: Subject<Event> = new Subject<Event>(); | ||
private subscription: Subscription; | ||
|
||
@Input() debounceTime = 300; | ||
@Output() throttledClick: EventEmitter<Event> = new EventEmitter<Event>(); | ||
|
||
/** | ||
* Handles the click event and emits it after a debounce time. | ||
* | ||
* @param {Event} event - The click event object. | ||
* @return {void} This function does not return a value. | ||
*/ | ||
@HostListener('click', ['$event']) | ||
clickEvent(event: Event): void { | ||
this.clicks.next(event); | ||
} | ||
|
||
ngOnInit() { | ||
this.subscription = this.clicks | ||
.pipe( | ||
debounceTime(this.debounceTime), | ||
tap((e) => this.throttledClick.emit(e)) | ||
) | ||
.subscribe(); | ||
} | ||
|
||
ngOnDestroy() { | ||
this.subscription.unsubscribe(); | ||
} | ||
} |
5 changes: 3 additions & 2 deletions
5
packages/desktop-ui-lib/src/lib/directives/desktop-directive.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
import { CommonModule } from '@angular/common'; | ||
import { NgModule } from '@angular/core'; | ||
import { NbSpinnerModule } from '@nebular/theme'; | ||
import { DebounceClickDirective } from './debounce-click.directive'; | ||
import { DynamicDirective } from './dynamic.directive'; | ||
import { SpinnerButtonDirective } from './spinner-button.directive'; | ||
import { TextMaskDirective } from './text-mask.directive'; | ||
|
||
@NgModule({ | ||
declarations: [SpinnerButtonDirective, DynamicDirective, TextMaskDirective], | ||
exports: [SpinnerButtonDirective, DynamicDirective, TextMaskDirective], | ||
declarations: [SpinnerButtonDirective, DynamicDirective, TextMaskDirective, DebounceClickDirective], | ||
exports: [SpinnerButtonDirective, DynamicDirective, TextMaskDirective, DebounceClickDirective], | ||
imports: [CommonModule, NbSpinnerModule] | ||
}) | ||
export class DesktopDirectiveModule {} |
183 changes: 183 additions & 0 deletions
183
packages/desktop-ui-lib/src/lib/login/features/login-magic/login-magic.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
<section class="login-container"> | ||
<div class="login-wrapper"> | ||
<div class="svg-wrapper"> | ||
<gauzy-logo></gauzy-logo> | ||
<gauzy-switch-theme></gauzy-switch-theme> | ||
</div> | ||
<div class="headings" [class]="isDemo ? 'headings-demo' : ''"> | ||
<div class="headings-inner"> | ||
<h2 id="title" class="title">{{ 'LOGIN_PAGE.TITLE' | translate }}</h2> | ||
<p class="sub-title">{{ 'LOGIN_PAGE.LOGIN_MAGIC.TITLE' | translate }}</p> | ||
</div> | ||
<ng-template [ngIf]="isCodeSent"> | ||
<div class="sent-code-container"> | ||
<p | ||
[ngClass]="{ | ||
'normal-text': email?.value.length < 30, | ||
'minimum-text': email?.value.length >= 30 | ||
}" | ||
> | ||
{{ 'LOGIN_PAGE.LOGIN_MAGIC.SUCCESS_SENT_CODE_TITLE' | translate }} | ||
<b class="title">{{ email?.value }}</b> | ||
<br /> | ||
<span>{{ 'LOGIN_PAGE.LOGIN_MAGIC.SUCCESS_SENT_CODE_SUB_TITLE' | translate }}</span> | ||
</p> | ||
</div> | ||
</ng-template> | ||
</div> | ||
<div class="hr-div-strong"></div> | ||
<!-- --> | ||
<form #formDirective="ngForm" [formGroup]="form" (ngSubmit)="confirmSignInCode()"> | ||
<!-- Email input --> | ||
<div class="form-control-group"> | ||
<label class="label" for="input-email">{{ 'LOGIN_PAGE.LABELS.EMAIL' | translate }}</label> | ||
<nb-form-field> | ||
<input | ||
name="input-email" | ||
id="input-email" | ||
nbInput | ||
fullWidth | ||
formControlName="email" | ||
[placeholder]="'LOGIN_PAGE.PLACEHOLDERS.EMAIL' | translate" | ||
[status]="email.dirty ? (email.invalid ? 'danger' : 'success') : 'basic'" | ||
[attr.aria-invalid]="email.invalid && email.touched ? true : null" | ||
autofocus | ||
autocomplete="off" | ||
[ngClass]="isCodeSent ? 'not-allowed' : ''" | ||
/> | ||
<nb-icon | ||
class="edit-email" | ||
*ngIf="isCodeSent" | ||
nbSuffix | ||
nbButton | ||
size="small" | ||
ghost | ||
icon="edit-outline" | ||
nbTooltip="edit email" | ||
nbTooltipPosition="top" | ||
></nb-icon> | ||
</nb-form-field> | ||
<ng-container *ngIf="email.invalid && email.touched && !email.pristine"> | ||
<p class="caption status-danger" *ngIf="email?.errors?.required"> | ||
{{ 'LOGIN_PAGE.VALIDATION.EMAIL_REQUIRED' | translate }} | ||
</p> | ||
<p class="caption status-danger" *ngIf="email?.errors?.pattern"> | ||
{{ 'LOGIN_PAGE.VALIDATION.EMAIL_REAL_REQUIRED' | translate }} | ||
</p> | ||
</ng-container> | ||
</div> | ||
<!-- Code input --> | ||
<ng-container *ngIf="isCodeSent"> | ||
<div class="form-control-group"> | ||
<label class="label" for="input-code">{{ 'LOGIN_PAGE.LABELS.CODE' | translate }}</label> | ||
<input | ||
name="input-code" | ||
id="input-code" | ||
nbInput | ||
fullWidth | ||
formControlName="code" | ||
noSpaceEdges | ||
[placeholder]="'LOGIN_PAGE.PLACEHOLDERS.CODE' | translate" | ||
[status]="code.dirty ? (code.invalid ? 'danger' : 'success') : 'basic'" | ||
[attr.aria-invalid]="code.invalid && code.touched ? true : null" | ||
maxlength="6" | ||
autofocus | ||
autocomplete="off" | ||
/> | ||
<ng-container *ngIf="code.invalid && code.touched"> | ||
<p class="caption status-danger" *ngIf="code.errors?.required"> | ||
{{ 'LOGIN_PAGE.VALIDATION.CODE_REQUIRED' | translate }} | ||
</p> | ||
<p class="caption status-danger" *ngIf="code.errors?.minlength"> | ||
{{ | ||
'LOGIN_PAGE.VALIDATION.CODE_REQUIRED_LENGTH' | ||
| translate : { requiredLength: code.errors?.minlength?.requiredLength } | ||
}} | ||
</p> | ||
</ng-container> | ||
<!-- Resend Code Button & Text --> | ||
<ng-template [ngIf]="isCodeSent"> | ||
<p class="new-code-wrapper"> | ||
<ng-template [ngIf]="isCodeResent" [ngIfElse]="resendButton"> | ||
<span class="request-new-code"> | ||
{{ | ||
'LOGIN_PAGE.LOGIN_MAGIC.REQUEST_NEW_CODE_TITLE' | ||
| translate : { countdown: countdown } | ||
}} | ||
</span> | ||
</ng-template> | ||
|
||
<ng-template #resendButton> | ||
<a class="resend-code" debounceClick (throttledClick)="onResendCode()"> | ||
{{ 'LOGIN_PAGE.LOGIN_MAGIC.RESEND_CODE_TITLE' | translate }} | ||
</a> | ||
</ng-template> | ||
</p> | ||
</ng-template> | ||
</div> | ||
</ng-container> | ||
<!-- Submit Button --> | ||
<div class="submit-btn-wrapper"> | ||
<a class="forgot-email caption-2 forgot-email-big" href="mailto:forgot@gauzy.co"> | ||
{{ 'LOGIN_PAGE.FORGOT_EMAIL_TITLE' | translate }} | ||
</a> | ||
<div class="submit-inner-wrapper"> | ||
<ng-template [ngIf]="isCodeSent" [ngIfElse]="sendCodeButtonTemplate"> | ||
<button | ||
type="submit" | ||
nbButton | ||
size="tiny" | ||
class="submit-btn" | ||
[disabled]="form.invalid || isLoading" | ||
> | ||
<span class="btn-text"> | ||
{{ 'BUTTONS.LOGIN' | translate }} | ||
</span> | ||
<ng-template [ngIf]="isLoading"> | ||
<nb-icon [ngStyle]="{ display: 'none' }" *gauzySpinnerButton="isLoading"></nb-icon> | ||
</ng-template> | ||
</button> | ||
</ng-template> | ||
<ng-template #sendCodeButtonTemplate> | ||
<button | ||
type="button" | ||
nbButton | ||
size="tiny" | ||
class="submit-btn" | ||
[disabled]="email.invalid || isLoading" | ||
(click)="sendLoginCode()" | ||
> | ||
<span class="btn-text"> | ||
{{ 'BUTTONS.SEND_CODE' | translate }} | ||
</span> | ||
<ng-template [ngIf]="isLoading"> | ||
<nb-icon [ngStyle]="{ display: 'none' }" *gauzySpinnerButton="isLoading"></nb-icon> | ||
</ng-template> | ||
</button> | ||
</ng-template> | ||
</div> | ||
</div> | ||
<div class="magic-description"> | ||
<p class="sub-title"> | ||
{{ 'LOGIN_PAGE.LOGIN_MAGIC.DESCRIPTION_TITLE' | translate }} | ||
<span class="sub-title"> | ||
<a routerLink="/auth/login"> | ||
{{ 'LOGIN_PAGE.LOGIN_MAGIC.OR_SIGN_IN_WITH_PASSWORD' | translate }} | ||
</a> | ||
</span> | ||
</p> | ||
</div> | ||
</form> | ||
<div class="hr-div-soft"></div> | ||
<section> | ||
<ngx-social-links></ngx-social-links> | ||
</section> | ||
<div class="hr-div-soft"></div> | ||
<section class="another-action" aria-label="Register"> | ||
{{ 'LOGIN_PAGE.DO_NOT_HAVE_ACCOUNT_TITLE' | translate }} | ||
<a class="text-link" routerLink="/auth/register"> | ||
{{ 'BUTTONS.REGISTER' | translate }} | ||
</a> | ||
</section> | ||
</div> | ||
</section> |
Oops, something went wrong.