diff --git a/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.html b/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.html new file mode 100644 index 0000000..834abcf --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.html @@ -0,0 +1,72 @@ +
+ +
+ + + +

Videoconference rooms in one click

+
+ +
+
+ +
+
+ + + + Photo by + + Daniel Leone + + on + + Unsplash + + +
+ {{ version }} +
+
+
diff --git a/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.scss b/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.scss new file mode 100644 index 0000000..830a0ab --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.scss @@ -0,0 +1,255 @@ +$loginBorderRadius: 0.35rem; +$loginColor: #e6e6e6; +$loginError: #770000; +$loginInputBackgroundColor: #434a52; +$loginInputHoverBackgroundColor: #3c4249; +$loginLabelBackgroundColor: #363b41; +$loginSubmitBackgroundColor: #0087a9; +$loginSubmitDisabledBackgroundColor: #264b55; + +$loginSubmitColor: #eee; +$loginSubmitDisabledColor: #bdbdbd; + +$loginSubmitHoverBackgroundColor: #006a85; +$iconFill: #606468; +$formGap: 0.375rem; + +.section1 { + background: + linear-gradient(rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2)), + var(--background-image-url) top center no-repeat; + background-size: cover; + height: 100%; + text-align: center; + position: relative; + color: $loginColor; +} + +#header, +.footer { + background-color: transparent; + color: $loginColor; +} + +.loginError { + font-size: 14px; + color: $loginError; + text-shadow: 0.2px 0px #ffffff; + text-align: left; + font-weight: 600; +} + +.loginError mat-icon { + vertical-align: bottom; + padding-right: 4px; +} + +.footer { + position: absolute; + bottom: 0; + font-size: 9px; + height: auto; +} + +.footer a { + color: $loginColor; +} + +#meet-version, +#logout-content { + right: 15px; + position: absolute; + font-size: 13px; +} + +h4 { + font-size: 25px; + font-weight: 300; + color: #ffffff; + position: relative; + padding-bottom: 5px; +} + +/* Extra small devices (phones, 600px and down) */ +@media only screen and (max-width: 600px) { + #header_img, + .ovVersion, + .footer { + display: none; + } +} + +.openvidu-slogan-container { + margin-top: 80px; +} + +#form-img { + max-width: 290px; + margin-right: 10px; + margin-top: 10px; + padding: 15px; +} + +/* Extra large devices (large laptops and desktops, 1200px and up) */ +@media only screen and (min-width: 1200px) { + .section1 { + background-attachment: fixed; + } +} + +.grid { + inline-size: 90%; + margin-inline: auto; + max-inline-size: 26rem; +} + +.hidden { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +.icons { + display: none; +} + +.icon { + block-size: 1em; + display: inline-block; + fill: $iconFill; + inline-size: 1em; + vertical-align: middle; +} + +input { + background-image: none; + border: 0; + color: inherit; + font: inherit; + margin: 0; + outline: 0; + padding: 0; + transition: background-color 0.3s; +} + +input[type='submit'] { + cursor: pointer; +} + +.form { + display: grid; + gap: $formGap; +} + +.form input[type='password'], +.form input[type='text'], +.form button[type='submit'] { + inline-size: 100%; +} + +.form-field { + display: flex; +} + +.form-input { + flex: 1; +} + +.login { + color: $loginColor; +} + +.login label.error { + background-color: $loginError; +} +.login label, +.login input[type='text'], +.login input[type='password'] { + border-radius: $loginBorderRadius; + padding: 0.85rem; +} +#room-name-input { + border-radius: 0; +} + +.login button[type='submit'] { + border-radius: $loginBorderRadius; + padding: 0.4rem; + cursor: pointer; +} + +.login button:disabled[type='submit'] { + cursor: auto; + color: $loginSubmitDisabledColor !important; + background-color: $loginSubmitDisabledBackgroundColor !important; +} + +.login label { + background-color: $loginLabelBackgroundColor; + border-bottom-right-radius: 0; + border-top-right-radius: 0; + padding-inline: 1.25rem; +} + +.login input[type='password'], +.login input[type='text'] { + background-color: $loginInputBackgroundColor; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + font-size: 16px; +} + +.login input[type='password']:focus, +.login input[type='password']:hover, +.login input[type='text']:focus, +.login input[type='text']:hover { + background-color: $loginInputHoverBackgroundColor; +} + +.login button[type='submit'] { + background-color: $loginSubmitBackgroundColor; + color: $loginSubmitColor; + font-weight: 500; + text-transform: uppercase; +} + +.login button[type='submit']:focus, +.login button[type='submit']:hover { + background-color: $loginSubmitHoverBackgroundColor; +} +#clear-room-name-btn { + height: auto; + background-color: $loginInputBackgroundColor; + border-radius: 0; + color: $loginColor; + mat-icon { + vertical-align: middle; + } +} +#room-name-generator-btn { + height: auto; + background-color: $loginInputBackgroundColor; + border-radius: $loginBorderRadius; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + color: $loginColor; + mat-icon { + vertical-align: middle; + } + &:hover { + background-color: $loginInputHoverBackgroundColor; + } +} + +p { + margin-block: 1.5rem; +} + +.text--center { + text-align: center; +} diff --git a/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.spec.ts b/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.spec.ts new file mode 100644 index 0000000..e95c3a1 --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LoginComponent] + }).compileComponents(); + + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.ts b/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.ts new file mode 100644 index 0000000..e9f325f --- /dev/null +++ b/frontend/projects/shared-meet-components/src/lib/pages/login/login.component.ts @@ -0,0 +1,58 @@ +import { NgClass } from '@angular/common'; +import { Component } from '@angular/core'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { MatButton } from '@angular/material/button'; +import { MatIcon } from '@angular/material/icon'; +import { MatToolbar } from '@angular/material/toolbar'; +import { MatTooltip } from '@angular/material/tooltip'; +import { Router } from '@angular/router'; +import { ContextService, AuthService } from '../../services/index'; +import { HttpErrorResponse } from '@angular/common/http'; + +@Component({ + selector: 'app-login', + standalone: true, + imports: [MatToolbar, MatTooltip, MatIcon, FormsModule, ReactiveFormsModule, NgClass, MatButton], + templateUrl: './login.component.html', + styleUrl: './login.component.scss' +}) +export class LoginComponent { + version = ''; + openviduLogoUrl = ''; + backgroundImageUrl = ''; + + loginForm = new FormGroup({ + username: new FormControl('', [Validators.required, Validators.minLength(4)]), + password: new FormControl('', [Validators.required, Validators.minLength(4)]) + }); + loginErrorMessage: string | undefined; + + constructor( + private router: Router, + private authService: AuthService, + private contextService: ContextService + ) {} + + async ngOnInit() { + this.version = this.contextService.getVersion(); + this.openviduLogoUrl = this.contextService.getOpenViduLogoUrl(); + this.backgroundImageUrl = this.contextService.getBackgroundImageUrl(); + } + + async login() { + this.loginErrorMessage = undefined; + const { username, password } = this.loginForm.value; + + try { + // TODO: Replace with user login + await this.authService.adminLogin(username!, password!); + this.router.navigate(['']); + } catch (error) { + if ((error as HttpErrorResponse).status === 429) { + this.loginErrorMessage = 'Too many login attempts. Please try again later'; + } else { + this.loginErrorMessage = 'Invalid username or password'; + } + } + } +} diff --git a/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.html b/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.html index 68bd98e..62f6ae1 100644 --- a/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.html +++ b/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.html @@ -1,6 +1,6 @@
- @if (isUserLogged) { + @if (username) {
Hi {{ username }}, do you want to logout?
- @if (!loading) { -
-
- @if (isPrivateAccess && !isUserLogged) { - - } - @if (isUserLogged || !isPrivateAccess) { - + @if (roomForm.get('roomNamePrefix')?.hasError('minlength')) { +
Room name is too short!
+ } +
+ +
+
- } +
diff --git a/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.scss b/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.scss index 920d113..c3755fc 100644 --- a/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.scss +++ b/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.scss @@ -1,16 +1,16 @@ -$loginBorderRadus: 0.35rem; -$loginColor: #e6e6e6; -$loginError: #770000; -$loginInputBackgroundColor: #434a52; -$loginInputHoverBackgroundColor: #3c4249; -$loginLabelBackgroundColor: #363b41; -$loginSubmitBackgroundColor: #0087a9; -$loginSubmitDisabledBackgroundColor: #264b55; +$formBorderRadius: 0.35rem; +$formColor: #e6e6e6; +$formError: #770000; +$formInputBackgroundColor: #434a52; +$formInputHoverBackgroundColor: #3c4249; +$formLabelBackgroundColor: #363b41; +$formSubmitBackgroundColor: #0087a9; +$formSubmitDisabledBackgroundColor: #264b55; -$loginSubmitColor: #eee; -$loginSubmitDisabledColor: #bdbdbd; +$formSubmitColor: #eee; +$formSubmitDisabledColor: #bdbdbd; -$loginSubmitHoverBackgroundColor: #006a85; +$formSubmitHoverBackgroundColor: #006a85; $iconFill: #606468; $formGap: 0.375rem; @@ -22,18 +22,18 @@ $formGap: 0.375rem; height: 100%; text-align: center; position: relative; - color: $loginColor; + color: $formColor; } #header, .footer { background-color: transparent; - color: $loginColor; + color: $formColor; } .roomError { font-size: 14px; - color: $loginError; + color: $formError; text-shadow: 0.2px 0px #ffffff; text-align: left; font-weight: 600; @@ -52,7 +52,7 @@ $formGap: 0.375rem; } .footer a { - color: $loginColor; + color: $formColor; } #meet-version, @@ -160,89 +160,90 @@ input[type='submit'] { flex: 1; } -.login { - color: $loginColor; +.room-prefix { + color: $formColor; } -.login label.error { - background-color: $loginError; +.room-prefix label.error { + background-color: $formError; } -.login label, -.login input[type='text'], -.login input[type='password'] { - border-radius: $loginBorderRadus; +.room-prefix label, +.room-prefix input[type='text'], +.room-prefix input[type='password'] { + border-radius: $formBorderRadius; padding: 0.85rem; } + #room-name-input { border-radius: 0; } -.login button[type='submit'] { - border-radius: $loginBorderRadus; +.room-prefix button[type='submit'] { + border-radius: $formBorderRadius; padding: 0.4rem; cursor: pointer; } -.login button:disabled[type='submit'] { +.room-prefix button:disabled[type='submit'] { cursor: auto; - color: $loginSubmitDisabledColor !important; - background-color: $loginSubmitDisabledBackgroundColor !important; + color: $formSubmitDisabledColor !important; + background-color: $formSubmitDisabledBackgroundColor !important; } -.login label { - background-color: $loginLabelBackgroundColor; +.room-prefix label { + background-color: $formLabelBackgroundColor; border-bottom-right-radius: 0; border-top-right-radius: 0; padding-inline: 1.25rem; } -.login input[type='password'], -.login input[type='text'] { - background-color: $loginInputBackgroundColor; +.room-prefix input[type='password'], +.room-prefix input[type='text'] { + background-color: $formInputBackgroundColor; border-bottom-left-radius: 0; border-top-left-radius: 0; font-size: 16px; } -.login input[type='password']:focus, -.login input[type='password']:hover, -.login input[type='text']:focus, -.login input[type='text']:hover { - background-color: $loginInputHoverBackgroundColor; +.room-prefix input[type='password']:focus, +.room-prefix input[type='password']:hover, +.room-prefix input[type='text']:focus, +.room-prefix input[type='text']:hover { + background-color: $formInputHoverBackgroundColor; } -.login button[type='submit'] { - background-color: $loginSubmitBackgroundColor; - color: $loginSubmitColor; +.room-prefix button[type='submit'] { + background-color: $formSubmitBackgroundColor; + color: $formSubmitColor; font-weight: 500; text-transform: uppercase; } -.login button[type='submit']:focus, -.login button[type='submit']:hover { - background-color: $loginSubmitHoverBackgroundColor; +.room-prefix button[type='submit']:focus, +.room-prefix button[type='submit']:hover { + background-color: $formSubmitHoverBackgroundColor; } #clear-room-name-btn { height: auto; - background-color: $loginInputBackgroundColor; + background-color: $formInputBackgroundColor; border-radius: 0; - color: $loginColor; + color: $formColor; mat-icon { vertical-align: middle; } } #room-name-generator-btn { height: auto; - background-color: $loginInputBackgroundColor; - border-radius: $loginBorderRadus; + background-color: $formInputBackgroundColor; + border-radius: $formBorderRadius; border-bottom-left-radius: 0; border-top-left-radius: 0; - color: $loginColor; + color: $formColor; mat-icon { vertical-align: middle; } &:hover { - background-color: $loginInputHoverBackgroundColor; + background-color: $formInputHoverBackgroundColor; } } diff --git a/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.ts b/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.ts index 3fda72a..56bea40 100644 --- a/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.ts +++ b/frontend/projects/shared-meet-components/src/lib/pages/room-creator/room-creator.component.ts @@ -1,19 +1,11 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { - FormBuilder, - FormGroup, - UntypedFormBuilder, - Validators, - FormsModule, - ReactiveFormsModule -} from '@angular/forms'; +import { Component, OnInit } from '@angular/core'; +import { FormGroup, Validators, FormsModule, ReactiveFormsModule, FormControl } from '@angular/forms'; import { MatIcon } from '@angular/material/icon'; import { MatTooltip } from '@angular/material/tooltip'; import { MatIconButton, MatButton } from '@angular/material/button'; import { NgClass } from '@angular/common'; import { MatToolbar } from '@angular/material/toolbar'; -import { ActivatedRoute, Router } from '@angular/router'; -import { Subscription } from 'rxjs'; +import { Router } from '@angular/router'; import { ContextService, HttpService } from '../../services/index'; import { OpenViduMeetRoom, OpenViduMeetRoomOptions } from '../../typings/ce/room'; import { animals, colors, Config, uniqueNamesGenerator } from 'unique-names-generator'; @@ -25,68 +17,29 @@ import { animals, colors, Config, uniqueNamesGenerator } from 'unique-names-gene standalone: true, imports: [MatToolbar, MatIconButton, MatTooltip, MatIcon, FormsModule, ReactiveFormsModule, NgClass, MatButton] }) -export class RoomCreatorComponent implements OnInit, OnDestroy { +export class RoomCreatorComponent implements OnInit { version = ''; openviduLogoUrl = ''; backgroundImageUrl = ''; - roomForm: FormGroup; - loginForm: FormGroup; - isPrivateAccess = false; + roomForm = new FormGroup({ + roomNamePrefix: new FormControl(this.getRandomName(), [Validators.required, Validators.minLength(6)]) + }); username = ''; - loginError = false; - serverConnectionError = false; - isUserLogged = false; - loading = true; - private queryParamSubscription!: Subscription; constructor( private router: Router, - public formBuilder: UntypedFormBuilder, private httpService: HttpService, - // private callService: ConfigService, - private fb: FormBuilder, - private route: ActivatedRoute, private contextService: ContextService - ) { - this.loginForm = this.fb.group({ - username: [ - /*this.storageService.getParticipantName() ??*/ '', - [Validators.required, Validators.minLength(4)] - ], - password: ['', [Validators.required, Validators.minLength(4)]] - }); - - this.roomForm = this.fb.group({ - roomNamePrefix: [this.getRandomName(), [Validators.required, Validators.minLength(6)]] - }); - } + ) {} async ngOnInit() { this.version = this.contextService.getVersion(); this.openviduLogoUrl = this.contextService.getOpenViduLogoUrl(); this.backgroundImageUrl = this.contextService.getBackgroundImageUrl(); - this.subscribeToQueryParams(); - try { - // await this.callService.initialize(); - // this.isPrivateAccess = this.callService.isPrivateAccess(); - - if (this.isPrivateAccess) { - this.isUserLogged = true; - this.loginError = false; - } - } catch (error) { - this.isUserLogged = false; - // this.serverConnectionError = true; - this.loginError = true; - } finally { - this.loading = false; - } - } - - ngOnDestroy(): void { - if (this.queryParamSubscription) this.queryParamSubscription.unsubscribe(); + // TODO: Retrieve actual username + this.username = 'user'; } generateRoomName(event: any) { @@ -98,34 +51,9 @@ export class RoomCreatorComponent implements OnInit, OnDestroy { this.roomForm.get('roomNamePrefix')?.setValue(''); } - keyDown(event: KeyboardEvent) { - if (event.keyCode === 13) { - event.preventDefault(); - this.goToVideoRoom(); - } - } - - async login() { - // Invoked when login form is valid - this.loginError = false; - this.username = this.loginForm.get('username')?.value; - const password = this.loginForm.get('password')?.value; - - try { - await this.httpService.userLogin({ username: this.username, password }); - this.isUserLogged = true; - } catch (error) { - this.isUserLogged = false; - this.loginError = true; - console.error('Error doing login ', error); - } - } - async logout() { try { await this.httpService.userLogout(); - this.loginError = false; - this.isUserLogged = false; } catch (error) { console.error('Error doing logout ', error); } @@ -137,7 +65,7 @@ export class RoomCreatorComponent implements OnInit, OnDestroy { return; } - const roomNamePrefix = this.roomForm.get('roomNamePrefix')?.value.replace(/ /g, '-'); + const roomNamePrefix = this.roomForm.get('roomNamePrefix')?.value!.replace(/ /g, '-'); try { // TODO: Fix expiration date @@ -148,8 +76,6 @@ export class RoomCreatorComponent implements OnInit, OnDestroy { const room: OpenViduMeetRoom = await this.httpService.createRoom(options); - this.roomForm.get('roomNamePrefix')?.setValue(roomNamePrefix); - const accessRoomUrl = new URL(room.moderatorRoomUrl); const secret = accessRoomUrl.searchParams.get('secret'); const roomName = accessRoomUrl.pathname; @@ -160,16 +86,6 @@ export class RoomCreatorComponent implements OnInit, OnDestroy { } } - private subscribeToQueryParams(): void { - // this.queryParamSubscription = this.route.queryParams.subscribe((params) => { - // const roomName = params['roomName']; - // if (roomName) { - // this.loginError = true; - // this.roomForm.get('roomNamePrefix')?.setValue(roomName.replace(/[^\w-]/g, '')); - // } - // }); - } - private getRandomName(): string { const configName: Config = { dictionaries: [colors, animals], diff --git a/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts b/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts index 2aa9686..af805b7 100644 --- a/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts +++ b/frontend/projects/shared-meet-components/src/lib/routes/base-routes.ts @@ -25,9 +25,11 @@ import { SecurityPreferencesComponent, VideoRoomComponent } from '../pages'; +import { LoginComponent } from '@lib/pages/login/login.component'; export const baseRoutes: Routes = [ { path: '', component: RoomCreatorComponent }, + { path: 'login', component: LoginComponent }, { path: 'disconnected', component: DisconnectedComponent }, { path: 'unauthorized', component: UnauthorizedComponent }, {