Skip to content

Commit

Permalink
enhancements to login and setup-wizard modules
Browse files Browse the repository at this point in the history
  • Loading branch information
bwp91 committed Oct 6, 2024
1 parent 17604e3 commit 253b3f6
Show file tree
Hide file tree
Showing 55 changed files with 944 additions and 581 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to `homebridge-config-ui-x` will be documented in this file.
### UI Changes

- switch from a top menu to a sidebar menu
- enhancements to `login` and `setup-wizard` modules

### Other Changes

Expand Down
3 changes: 3 additions & 0 deletions scripts/lang-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ async function main() {
// Check each key
const unusedKeys = []
for (const key of keys) {
if (key.startsWith('login.tips_')) {
continue
}
const isUsed = await Promise.all(allFiles.map(file => isKeyUsedInFile(key, file)))
.then(results => results.some(result => result))
if (!isUsed) {
Expand Down
2 changes: 1 addition & 1 deletion src/core/config/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export class ConfigService {
formAuth: Boolean(this.ui.auth !== 'none'),
lightingMode: this.ui.lightingMode || 'auto',
serverTimestamp: new Date().toISOString(),
theme: this.ui.theme || 'orange',
theme: this.ui.theme || 'purple',
}

if (!authorized) {
Expand Down
12 changes: 6 additions & 6 deletions ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions ui/src/app/core/settings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ export class SettingsService {
}

setTheme(theme: string) {
// Default theme is orange
// Default theme is purple
if (!theme || !this.themeList.includes(theme)) {
theme = 'orange'
theme = 'purple'
}

// Grab the body element
Expand Down
83 changes: 56 additions & 27 deletions ui/src/app/modules/login/login.component.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
<div
class="login-container d-flex align-items-center justify-content-center"
[ngStyle]="{'background': backgroundStyle}"
class="login-container d-flex align-items-start justify-content-center"
[ngStyle]="{ 'background': backgroundStyle }"
>
<div class="card card-body mx-2 login-card">
<div class="w-100 login-card d-flex py-4 flex-column">
<img
class="homebridge-logo mx-auto my-3"
ngSrc="/assets/homebridge-color-round.svg"
alt="Homebridge Logo"
height="100"
width="100"
priority="true"
/>
<form novalidate (ngSubmit)="onSubmit()" [formGroup]="form">
<p class="h4 text-center mb-4">Homebridge</p>

<h4 class="mb-4 text-center">{{ 'setup.welcome_to_homebridge' | translate }}</h4>
<div *ngIf="!twoFactorCodeRequired">
<div class="md-form">
<i class="fas fa-fw fa-user prefix grey-text"></i>
<div class="input-group mb-4 no-border" *ngIf="!isMobile">
<div class="input-group-text custom-input">
<i class="fas fa-fw fa-lightbulb yellow-text fa-lg"></i>
</div>
<div class="form-control custom-input">
<div class="small grey-text fw-semibold">{{ 'login.tips' | translate }}</div>
<div class="small grey-text">{{ 'login.tips_' + randomTip | translate }}</div>
</div>
</div>
<div class="input-group mb-4">
<span class="input-group-text custom-input"><i class="fas fa-fw fa-user primary-text fa-lg"></i></span>
<input
#username
formControlName="username"
Expand All @@ -18,59 +34,72 @@
autocomplete="username"
autocapitalize="none"
tabindex="1"
class="form-control px-0"
class="form-control custom-input"
[ngClass]="{
'is-invalid': form.controls.username.dirty && form.controls.username.errors
}"
placeholder="{{ 'login.label_username' | translate }}"
required
/>
<label for="form-username">{{ 'login.label_username' | translate }}</label>
</div>
<div class="md-form">
<i class="fas fa-fw fa-lock prefix grey-text"></i>
<div class="input-group mb-4">
<span class="input-group-text custom-input"><i class="fas fa-fw fa-lock primary-text fa-lg"></i></span>
<input
#password
formControlName="password"
type="password"
id="form-pass"
autocomplete="current-password"
tabindex="2"
class="form-control px-0"
class="form-control custom-input"
[ngClass]="{
'is-invalid': form.controls.password.dirty && form.controls.password.errors
}"
placeholder="{{ 'login.label_password' | translate }}"
required
/>
<label for="form-pass">{{ 'login.label_password' | translate }}</label>
</div>
</div>

<div *ngIf="twoFactorCodeRequired">
<p class="text-center">{{ 'users.setup_2fa_enter_code' | translate }}</p>
<div class="md-form">
<i class="fas fa-fw fa-key prefix grey-text"></i>
<div class="input-group mb-4">
<span class="input-group-text custom-input"><i class="fas fa-fw fa-key primary-text fa-lg"></i></span>
<input
#otp
formControlName="otp"
type="text"
id="form-ota"
autofocus
autocomplete="one-time-code"
autocapitalize="none"
inputmode="numeric"
pattern="[0-9]*"
tabindex="1"
class="form-control px-0"
[ngClass]="{
'is-invalid': form.controls.otp.dirty && form.controls.otp.errors
}"
class="form-control custom-input"
placeholder="{{ 'login.label_2fa_code' | translate }}"
/>
<label for="form-ota">{{ 'login.label_2fa_code' | translate }}</label>
</div>
</div>

<div class="input-group no-border mb-4" *ngIf="invalidCredentials">
<div class="input-group-text custom-input">
<i class="fas fa-fw fa-exclamation-triangle pink-text fa-lg"></i>
</div>
<div class="form-control custom-input">
<div class="small grey-text fw-semibold">{{ 'login.invalid_credentials' | translate }}</div>
<div class="small grey-text" [innerHTML]="'login.invalid_credentials_2' | translate"></div>
</div>
</div>
<div class="input-group no-border mb-4" *ngIf="invalid2faCode">
<div class="input-group-text custom-input">
<i class="fas fa-fw fa-exclamation-triangle pink-text fa-lg"></i>
</div>
<div class="form-control custom-input">
<div class="small grey-text fw-semibold">{{ 'login.invalid_code' | translate }}</div>
<div class="small grey-text" [innerHTML]="'login.invalid_credentials_2' | translate"></div>
</div>
</div>
<div class="text-center">
<p class="red-text" *ngIf="invalidCredentials"><small>{{ 'login.invalid_credentials' | translate }}</small></p>
<p class="red-text" *ngIf="invalid2faCode"><small>{{ 'login.message_invalid_2fa_code' | translate }}</small></p>
<button tabindex="3" class="btn btn-primary" type="submit" [disabled]="form.invalid">
{{ 'login.button_login' | translate }}
<button tabindex="3" id="submit-button" class="btn btn-primary mb-4" type="submit" [disabled]="form.invalid">
{{ 'form.button_continue' | translate }}
</button>
</div>
</form>
Expand Down
69 changes: 43 additions & 26 deletions ui/src/app/modules/login/login.component.scss
Original file line number Diff line number Diff line change
@@ -1,42 +1,59 @@
::ng-deep body {
overflow: scroll !important;
}

::ng-deep body.dark-mode {
.login-card {
background-color: #2b2b2b;
color: #ffffff;
}
}

.login-container {
background-color: #f4f4f4;
background-size: cover;
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: -1;
padding-top: 2em;
padding-bottom: 2em;
min-height: 100%;
background: linear-gradient(-45deg, #ffa000, #4a266c, #2196f3, #c2185b);
background-size: 400% 400%;
animation: gradient 30s ease infinite;

@keyframes gradient {
0% {
background-position: 0 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}
}

.login-card {
max-width: 500px;
max-width: 550px;
border-radius: 1rem;
padding-right: 25px;
padding-left: 25px;
background-color: rgba(255, 255, 255, 0.9);

.form-control:focus {
background-color: inherit !important;
}

@media screen and (min-height: 800px) {
margin-bottom: 12em;
@media screen and (max-width: 575px) {
margin-left: 1em;
margin-right: 1em;
}
}

.login-logo {
position: absolute;
top: -35%;
z-index: 2000;
max-width: 450px;

@media screen and (max-width: 450px) {
max-width: 320px;
top: -25%;
}

@media screen and (max-width: 320px) {
max-width: 280px;
}
.homebridge-logo {
margin-bottom: 10px;
}

@media screen and (max-height: 450px) {
display: none;
}
.btn-primary {
background-color: #4a266c !important;
border-color: #4a266c !important;
}
22 changes: 16 additions & 6 deletions ui/src/app/modules/login/login.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AuthService } from '@/app/core/auth/auth.service'
import { MobileDetectService } from '@/app/core/mobile-detect.service'
import { SettingsService } from '@/app/core/settings.service'
import { environment } from '@/environments/environment'
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
Expand All @@ -16,24 +17,32 @@ export class LoginComponent implements OnInit {
@ViewChild('username') private usernameInput: ElementRef
@ViewChild('otp') private otpInput: ElementRef

protected readonly Math = Math

public form: FormGroup<{
username: FormControl<string>
password: FormControl<string>
otp?: FormControl<string>
}>

public isMobile: any = false
public backgroundStyle: string
public invalidCredentials = false
public invalid2faCode = false
public twoFactorCodeRequired = false
public inProgress = false
public randomTip = Math.floor(Math.random() * 3) + 1

private targetRoute: string

constructor(
private $auth: AuthService,
private $md: MobileDetectService,
private $router: Router,
private $settings: SettingsService,
) {}
) {
this.isMobile = this.$md.detect.mobile()
}

ngOnInit() {
this.form = new FormGroup({
Expand Down Expand Up @@ -69,6 +78,7 @@ export class LoginComponent implements OnInit {
this.invalidCredentials = false
this.invalid2faCode = false
this.inProgress = true
document.getElementById('submit-button')?.blur()

// grab the values from the native element as they may be "populated" via autofill.
const passwordInputValue = this.passwordInput?.nativeElement.value
Expand All @@ -88,11 +98,12 @@ export class LoginComponent implements OnInit {
}
}

await this.$auth.login(this.form.getRawValue()).then(() => {
try {
await this.$auth.login(this.form.getRawValue())
this.$router.navigateByUrl(this.targetRoute)
window.sessionStorage.removeItem('target_route')
}).catch((err) => {
if (err.status === 412) {
} catch (error) {
if (error.status === 412) {
if (!this.form.controls.otp) {
this.form.addControl('otp', new FormControl('', [
Validators.required,
Expand All @@ -110,8 +121,7 @@ export class LoginComponent implements OnInit {
} else {
this.invalidCredentials = true
}
})

}
this.inProgress = false
}
}
3 changes: 2 additions & 1 deletion ui/src/app/modules/login/login.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { LoginComponent } from '@/app/modules/login/login.component'
import { LoginGuard } from '@/app/modules/login/login.guard'
import { CommonModule } from '@angular/common'
import { CommonModule, NgOptimizedImage } from '@angular/common'
import { NgModule } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { TranslateModule } from '@ngx-translate/core'
Expand All @@ -14,6 +14,7 @@ import { TranslateModule } from '@ngx-translate/core'
FormsModule,
ReactiveFormsModule,
TranslateModule,
NgOptimizedImage,
],
providers: [
LoginGuard,
Expand Down
Loading

0 comments on commit 253b3f6

Please sign in to comment.