Update 2fa Styling

- Add copy button for secret
This commit is contained in:
Jack Anderson 2025-01-17 10:32:01 +00:00 committed by j.anderson
parent 90bff782b5
commit ae05bdb653
3 changed files with 333 additions and 227 deletions

View file

@ -41,7 +41,7 @@
</div>
<div class='container'>
<div class='row'>
<div class='row mb-3'>
<div class='col'>
<scrm-widget-panel [title]="appMethodHeaderLabel">
<span widget-header-icon-area>
@ -64,52 +64,64 @@
<div class='d-flex col-md-12 pl-4 pr-4 pt-2 pb-2'>
<scrm-label labelKey='LBL_OTP_SETUP' class='small'></scrm-label>
</div>
<ng-container *ngIf="isQrCodeGenerated()">
<div *ngIf="qrCodeSvg" class='qr-code-container'>
<div *ngIf="!showSecret()" class='qr-code'>
<div [innerHTML]="qrCodeSvg | trustHtml">
</div>
<div>
<a class="small show-secret-link pl-1" (click)="setShowSecret(true)">
<scrm-label labelKey='LBL_USE_SECRET'>
</scrm-label>
</a>
<div *ngIf="isQrCodeGenerated()" class="row-container">
<div class="qr-code-container">
<div class="col">
<div *ngIf="qrCodeSvg" class='qr-code-col'>
<div class='qr-code'>
<div [innerHTML]="qrCodeSvg | trustHtml">
</div>
<div *ngIf="!showSecret()" class="secret-container">
<a class="small show-secret-link pl-1"
(click)="setShowSecret(true)">
<scrm-label labelKey='LBL_USE_SECRET'>
</scrm-label>
</a>
</div>
<div *ngIf="showSecret()" class="secret-container">
<a class="small show-secret-link pl-1"
(click)="setShowSecret(false)">
<scrm-label labelKey='LBL_HIDE_SECRET'>
</scrm-label>
</a>
</div>
</div>
</div>
</div>
<div *ngIf="showSecret()" class='qr-code'>
<scrm-label class="pb-3" labelKey='LBL_USE_SECRET_DESC'>
</scrm-label>
<div class="col">
<div *ngIf="showSecret()" class='qr-code-secret'>
<scrm-label class="pb-3" labelKey='LBL_USE_SECRET_DESC'>
</scrm-label>
<span class="small font-weight-bold pb-3 secret">{{ secret }}</span>
<span class="font-weight-bold pb-2 secret">{{ secret }}</span>
<div>
<a class="small show-secret-link" (click)="setShowSecret(false)">
<scrm-label labelKey='LBL_SHOW_QR_CODE'>
</scrm-label>
</a>
</div>
</div>
<div>
<scrm-label labelKey='LBL_QR_CODE_HELP'
class='pl-3 d-inline-block qr-code-label'>
</scrm-label>
<div class='d-flex flex-column pt-4 align-items-center'>
<input [(ngModel)]="authCode"
id='auth_code'
type='text'
name='auth_code'
autocomplete='off'
class='mb-3 auth-input'/>
<scrm-button id='submit-2fa-code'
[config]="verifyCodeButtonConfig">
</scrm-button>
<scrm-button class="pb-2"
[config]="copySecretButtonConfig"></scrm-button>
</div>
</div>
</div>
</ng-container>
<div class="row">
<div class="col">
<div>
<scrm-label labelKey='LBL_QR_CODE_HELP'
class='pl-3 d-inline-block qr-code-label'>
</scrm-label>
<div class='d-flex flex-column pt-4 align-items-center'>
<input [(ngModel)]="authCode"
id='auth_code'
type='text'
name='auth_code'
autocomplete='off'
class='mb-3 auth-input'/>
<scrm-button id='submit-2fa-code'
[config]="verifyCodeButtonConfig">
</scrm-button>
</div>
</div>
</div>
</div>
</div>
</div>
</scrm-widget-panel>
</div>
@ -137,7 +149,7 @@
<ng-container *ngIf="areRecoveryCodesGenerated() && backupCodes">
<div class='d-flex flex-column'>
<scrm-button [config]="copyButtonConfig"></scrm-button>
<scrm-button [config]="copyBackupButtonConfig"></scrm-button>
</div>
<div class='d-flex col-md-12 pl-4 pr-4 pt-2 pb-3'>
<div>

View file

@ -62,7 +62,8 @@ export class TwoFactorComponent implements OnInit {
cancelAppMethodButtonConfig: ButtonInterface;
regenerateBackupCodesButtonConfig: ButtonInterface;
verifyCodeButtonConfig: ButtonInterface;
copyButtonConfig: ButtonInterface;
copyBackupButtonConfig: ButtonInterface;
copySecretButtonConfig: ButtonInterface;
recoveryCodesHeaderLabel: string = '';
@HostListener('keyup.control.enter')
@ -175,6 +176,10 @@ export class TwoFactorComponent implements OnInit {
this.clipboard.copy(this.backupCodes);
}
public copySecret(): void {
this.clipboard.copy(this.secret);
}
public generateCodes(): void {
this.backupCodes = null;
this.generateBackupCodesService.generate().subscribe({
@ -248,7 +253,7 @@ export class TwoFactorComponent implements OnInit {
} as ButtonInterface;
this.verifyCodeButtonConfig = {
klass: 'btn btn-sm btn-main',
klass: 'btn btn-sm btn-main mb-2',
onClick: ((): void => {
this.finalize2fa()
}) as ButtonCallback,
@ -256,7 +261,7 @@ export class TwoFactorComponent implements OnInit {
titleKey: ''
} as ButtonInterface;
this.copyButtonConfig = {
this.copyBackupButtonConfig = {
klass: 'btn btn-sm btn-main copy-button',
onClick: ((): void => {
this.copyBackupCodes()
@ -265,5 +270,15 @@ export class TwoFactorComponent implements OnInit {
titleKey: '',
icon: 'clipboard'
} as ButtonInterface;
this.copySecretButtonConfig = {
klass: 'btn btn-sm btn-main ml-0',
onClick: ((): void => {
this.copySecret()
}) as ButtonCallback,
labelKey: 'LBL_COPY',
titleKey: '',
icon: 'clipboard'
} as ButtonInterface;
}
}

View file

@ -12,50 +12,266 @@
}
@media (min-width: 991px) {
.backup-codes-container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
#two-factor {
.container {
display: flex;
flex-direction: column;
.backup-codes {
padding: 10px;
border: 1px solid lightgrey;
border-radius: .25rem;
.col {
.backup-code-warning {
width: fit-content;
margin-left: 1.5rem;
}
.qr-code-container {
flex: 0 0 25%;
max-width: 25%;
display: flex;
flex-direction: column;
.qr-code-col {
display: flex;
position: relative;
width: 100%;
.qr-code {
display: flex;
flex-flow: column;
padding-bottom: .5rem;
}
}
}
.qr-code-secret {
font-size: 80%;
font-weight: 400;
padding-left: .25rem;
display: flex;
flex-direction: column;
}
.qr-code .show-secret-link,
.qr-code-secret .show-secret-link {
clear: both;
color: $coral-pink;
}
.qr-code .show-secret-link:hover,
.qr-code-secret .show-secret-link:hover {
color: $light-orange;
cursor: pointer;
}
.auth-input {
border: .03em solid $nepal-grey;
padding: .45em;
margin: 0 1em 0 0;
color: #666;
font-size: .8em;
background-color: #fff;
width: 30%;
text-align: center;
}
}
.row {
display: flex;
flex-direction: row;
.row-container {
display: flex;
flex-direction: row;
}
.qr-code-label {
font-size: 95%;
font-weight: 500;
}
}
}
.backup-codes-container {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
.backup-codes {
padding: 10px;
border: 1px solid lightgrey;
border-radius: .25rem;
}
}
.copy-button {
margin-left: 1.5rem;
width: fit-content;
}
}
}
@media (min-width: 768px) and (max-width: 991px) {
#two-factor {
.container {
display: flex;
flex-direction: column;
.col {
.backup-code-warning {
width: fit-content;
margin-left: 1.5rem;
}
.qr-code-container {
flex: 0 0 41.666667%;
max-width: 41.666667%;
display: flex;
flex-direction: column;
.qr-code-col {
display: flex;
position: relative;
width: 100%;
.qr-code {
display: flex;
flex-flow: column;
padding-bottom: .5rem;
}
}
}
.qr-code-secret {
font-size: 80%;
font-weight: 400;
padding-left: .25rem;
display: flex;
flex-direction: column;
}
.qr-code .show-secret-link,
.qr-code-secret .show-secret-link {
clear: both;
color: $coral-pink;
}
.qr-code .show-secret-link:hover,
.qr-code-secret .show-secret-link:hover {
color: $light-orange;
cursor: pointer;
}
.auth-input {
border: .03em solid $nepal-grey;
padding: .45em;
margin: 0 1em 0 0;
color: #666;
font-size: .8em;
background-color: #fff;
width: 30%;
text-align: center;
}
}
.row {
display: flex;
flex-direction: row;
.row-container {
display: flex;
flex-direction: row;
}
.qr-code-label {
font-size: 95%;
font-weight: 500;
}
}
}
.backup-codes-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
.backup-codes {
padding: 10px;
border: 1px solid lightgrey;
border-radius: .25rem;
}
}
.copy-button {
margin-left: 1.5rem;
width: fit-content;
}
}
}
@media (min-width: 768px) {
@media(max-width: 767px) {
#two-factor {
.container {
.row {
display: flex;
flex-direction: column;
.col {
.backup-code-warning {
width: fit-content;
margin-left: 1.5rem;
margin-right: 2rem;
}
.secret-container {
text-align: center;
}
.qr-code-container {
display: flex;
padding: .25rem 1.5rem .5rem 1.5rem;
position: relative;
width: 100%;
flex-direction: column;
.qr-code-col {
display: flex;
position: relative;
width: 100%;
justify-content: center;
.qr-code {
display: flex;
flex-flow: column;
padding-bottom: .5rem;
}
}
}
.qr-code {
.qr-code-secret {
font-size: 80%;
font-weight: 400;
padding-left: .25rem;
display: flex;
flex-flow: column;
flex-direction: column;
text-align: center !important;
}
.show-secret-link {
clear: both;
color: $coral-pink;
}
.qr-code .show-secret-link,
.qr-code-secret .show-secret-link {
clear: both;
color: $coral-pink;
}
.show-secret-link:hover {
color: $light-orange;
cursor: pointer;
}
.qr-code .show-secret-link:hover,
.qr-code-secret .show-secret-link:hover {
color: $light-orange;
cursor: pointer;
}
.auth-input {
@ -69,96 +285,25 @@
text-align: center;
}
.qr-code-label {
font-size: 95%;
font-weight: 500;
}
}
}
}
.row {
@media (max-width: 991px) {
.backup-codes-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
.backup-codes {
padding: 10px;
border: 1px solid lightgrey;
border-radius: .25rem;
.row-container {
display: flex;
flex-direction: column;
}
.qr-code-label {
font-size: 95%;
font-weight: 500;
text-align: center;
}
}
}
.copy-button {
margin-left: 1.5rem;
width: fit-content;
}
}
}
@media(min-width: 375px) and (max-width: 767px) {
#two-factor {
.container {
.row {
.col {
.backup-code-warning {
margin-left: 1.5rem;
margin-right: 2rem;
}
.qr-code-container {
display: flex;
flex-direction: column;
padding: .25rem 1.5rem .5rem 1.5rem;
position: relative;
width: 100%;
.auth-input {
border: .03em solid $nepal-grey;
padding: .75rem .5rem;
margin: 0 1em 0 0;
color: #666;
font-size: .8em;
background-color: #fff;
width: 75%;
text-align: center;
}
.qr-code {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: .25rem;
.secret {
width: 50%;
}
.show-secret-link {
clear: both;
color: $coral-pink;
}
.show-secret-link:hover {
color: $light-orange;
cursor: pointer;
}
}
.qr-code-label {
font-size: 80%;
font-weight: 400;
}
}
}
}
}
@media(min-width: 374px) {
.backup-codes-container {
display: grid;
grid-template-columns: repeat(2, 1fr);
@ -170,91 +315,25 @@
border-radius: .25rem;
}
}
}
.copy-button {
margin-left: 1.5rem;
margin-bottom: .5rem;
width: fit-content;
}
.copy-button {
margin-left: 1.5rem;
margin-bottom: .5rem;
width: fit-content;
}
}
@media(max-width: 374px) {
#two-factor {
.backup-codes-container {
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 10px;
.container {
.row {
.col {
.backup-code-warning {
margin-left: 1.5rem;
margin-right: 2rem;
}
.qr-code-container {
display: flex;
flex-direction: column;
padding: .25rem 1.5rem .5rem 1.5rem;
position: relative;
width: 100%;
.auth-input {
border: .03em solid $nepal-grey;
padding: .75rem .5rem;
margin: 0 1em 0 0;
color: #666;
font-size: .8em;
background-color: #fff;
width: 75%;
text-align: center;
}
.qr-code {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: .25rem;
.secret {
width: 50%;
}
.show-secret-link {
clear: both;
color: $coral-pink;
}
.show-secret-link:hover {
color: $light-orange;
cursor: pointer;
}
}
.qr-code-label {
font-size: 80%;
font-weight: 400;
}
}
}
}
}
.backup-codes-container {
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 10px;
.backup-codes {
padding: 5px;
border: 1px solid lightgrey;
border-radius: .25rem;
}
}
.copy-button {
margin-left: 1.5rem;
margin-bottom: .5rem;
width: fit-content;
.backup-codes {
padding: 5px;
border: 1px solid lightgrey;
border-radius: .25rem;
}
}
}