diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts index 8becc5f..3adeba8 100644 --- a/frontend/src/app/app.routes.ts +++ b/frontend/src/app/app.routes.ts @@ -1,17 +1,17 @@ import { Routes } from '@angular/router'; -import { HomeComponent } from './components/home/home.component'; import { ImprintComponent } from './shared/components/legal-information/imprint/imprint.component'; import { PrivacyPolicyComponent } from './shared/components/legal-information/privacy-policy/privacy-policy.component'; -import { BrowseComponent } from './components/home/browse/browse.component'; +import { HomeComponent } from './components/home/home.component'; +import { AuthComponent } from './components/auth/auth.component'; import { AuthGuard } from './auth.guard'; export const routes: Routes = [ - { path: '', component: HomeComponent }, - { path: 'login', component: HomeComponent }, - { path: 'register', component: HomeComponent }, - { path: 'forgot-password', component: HomeComponent }, - { path: 'verify-email', component: HomeComponent }, + { path: '', component: AuthComponent }, + { path: 'login', component: AuthComponent }, + { path: 'register', component: AuthComponent }, + { path: 'forgot-password', component: AuthComponent }, + { path: 'verify-email', component: AuthComponent }, { path: 'imprint', component: ImprintComponent }, { path: 'privacy-policy', component: PrivacyPolicyComponent }, - { path: 'browse', component: BrowseComponent, canActivate: [AuthGuard] }, + { path: 'browse', component: HomeComponent, canActivate: [AuthGuard] }, ]; diff --git a/frontend/src/app/components/auth/auth.component.html b/frontend/src/app/components/auth/auth.component.html index 218fc95..fd667f2 100644 --- a/frontend/src/app/components/auth/auth.component.html +++ b/frontend/src/app/components/auth/auth.component.html @@ -1,48 +1,22 @@ -
-
-

Movies, TV shows, and more

-

Watch whenever you want wherever you want

-

Enter your email to create or restart your subscription.

-
-
- -
- @if (!mail.valid && mail.touched) { -

Please enter your email

- } @else { @if (mail.touched && authData.mail.length > 0 && - !isUserEmailValid(authData.mail.toLowerCase())) { -

This is not a valid email format

- }} -
- -
+
+ + + + @switch (currentRoute) { @case ('') { + + } @case ('login') { + + } @case ('register') { + + } @case ('forgot-password') { + + } @case ('verify-email') { + + } } + + + + @if (errorService.displayError) { + + }
diff --git a/frontend/src/app/components/auth/auth.component.scss b/frontend/src/app/components/auth/auth.component.scss index f8d8399..04e99a5 100644 --- a/frontend/src/app/components/auth/auth.component.scss +++ b/frontend/src/app/components/auth/auth.component.scss @@ -1,88 +1,4 @@ -@use "./../../../assets/style/colors.scss" as *; -@use "./../../../assets/style/form.scss" as *; -@use "./../../../assets/style/auth-layout.scss" as *; - section { - background-image: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), - url(./../../../assets/img/backgrounds/home.png); -} - -.headline { - p:nth-child(1) { - font-size: 48px; - font-weight: 700; - padding: 12px; - } - p:nth-child(2) { - font-size: 24px; - font-weight: 500; - padding: 12px; - } - p:nth-child(3) { - font-size: 18px; - font-weight: 400; - padding: 12px; - } -} - -form { - flex-direction: row; - position: relative; - width: 500px; - padding: 24px; - input { - width: 65%; - padding: 12px 18px; - margin: 8px 0; - color: $white; - border: 2px solid $white; - border-radius: 24px; - font-size: 18px; - &::placeholder { - color: rgba($white, 0.6); - } - } - .error-border { - border: 2px solid $red; - } -} - -.error-msg { - position: absolute; - bottom: 0; - padding: 6px; - width: 57%; -} - -/*------------- RESPONSIVE -------------*/ - -@media screen and (max-width: 550px) { - form { - flex-direction: column; - width: 90%; - input { - width: 100%; - } - } - - .error-msg { - position: relative; - padding: 0; - height: 12px; - margin-bottom: 8px; - } -} - -@media screen and (max-width: 500px) { - .headline { - p:nth-child(1) { - font-size: 36px; - } - p:nth-child(2) { - font-size: 20px; - } - p:nth-child(3) { - font-size: 16px; - } - } + width: 100dvw; + height: 100dvh; } diff --git a/frontend/src/app/components/auth/auth.component.ts b/frontend/src/app/components/auth/auth.component.ts index 34ad1ce..bd2d304 100644 --- a/frontend/src/app/components/auth/auth.component.ts +++ b/frontend/src/app/components/auth/auth.component.ts @@ -1,56 +1,44 @@ -import { Component } from '@angular/core'; -import { FormsModule, NgForm } from '@angular/forms'; -import { BtnLargeComponent } from '../../shared/components/buttons/btn-large/btn-large.component'; -import { Router } from '@angular/router'; +import { Component, OnInit } from '@angular/core'; +import { HeaderComponent } from '../../shared/components/header/header.component'; +import { FooterComponent } from '../../shared/components/footer/footer.component'; +import { ActivatedRoute } from '@angular/router'; +import { RegisterComponent } from '../auth/register/register.component'; import { CommonModule } from '@angular/common'; +import { LoginComponent } from '../auth/login/login.component'; +import { ForgotPasswordComponent } from '../auth/forgot-password/forgot-password.component'; +import { VerifyEmailComponent } from '../auth/verify-email/verify-email.component'; +import { ErrorToastComponent } from '../../shared/components/error-toast/error-toast.component'; import { ErrorService } from '../../services/error.service'; -import { AuthService } from '../../services/auth.service'; +import { LandingPageComponent } from './landing-page/landing-page.component'; @Component({ selector: 'app-auth', standalone: true, - imports: [CommonModule, BtnLargeComponent, FormsModule], + imports: [ + CommonModule, + HeaderComponent, + FooterComponent, + LandingPageComponent, + RegisterComponent, + LoginComponent, + ForgotPasswordComponent, + VerifyEmailComponent, + ErrorToastComponent, + ], templateUrl: './auth.component.html', styleUrl: './auth.component.scss', }) -export class AuthComponent { - authData = { - mail: '', - send: false, - }; +export class AuthComponent implements OnInit { + currentRoute: any; constructor( - private router: Router, - public errorService: ErrorService, - private authService: AuthService + private route: ActivatedRoute, + public errorService: ErrorService ) {} - isUserEmailValid(emailValue: string) { - const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/; - return emailRegex.test(emailValue); - } - - async onSubmit(ngForm: NgForm, mailInput: any) { - if (ngForm.submitted && ngForm.form.valid) { - await this.checkDuplicatesEmail(); - } else { - mailInput.control.markAsTouched(); - } - } - - async checkDuplicatesEmail() { - const body = { - email: this.authData.mail, - }; - try { - this.authData.send = true; - await this.authService.checkAuthUserMail(body); - const queryParams = { mail: this.authData.mail }; - this.router.navigate(['/register'], { queryParams }); - this.errorService.clearError(); - } catch (error) { - this.authData.send = false; - this.errorService.handleError(error); - } + ngOnInit(): void { + this.route.url.subscribe((url) => { + this.currentRoute = url[0]?.path || ''; + }); } } diff --git a/frontend/src/app/components/auth/landing-page/landing-page.component.html b/frontend/src/app/components/auth/landing-page/landing-page.component.html new file mode 100644 index 0000000..218fc95 --- /dev/null +++ b/frontend/src/app/components/auth/landing-page/landing-page.component.html @@ -0,0 +1,48 @@ +
+
+

Movies, TV shows, and more

+

Watch whenever you want wherever you want

+

Enter your email to create or restart your subscription.

+
+
+ +
+ @if (!mail.valid && mail.touched) { +

Please enter your email

+ } @else { @if (mail.touched && authData.mail.length > 0 && + !isUserEmailValid(authData.mail.toLowerCase())) { +

This is not a valid email format

+ }} +
+ +
+
diff --git a/frontend/src/app/components/auth/landing-page/landing-page.component.scss b/frontend/src/app/components/auth/landing-page/landing-page.component.scss new file mode 100644 index 0000000..8f798d0 --- /dev/null +++ b/frontend/src/app/components/auth/landing-page/landing-page.component.scss @@ -0,0 +1,88 @@ +@use "./../../../../assets/style/colors.scss" as *; +@use "./../../../../assets/style/form.scss" as *; +@use "./../../../../assets/style/auth-layout.scss" as *; + +section { + background-image: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), + url(./../../../../assets/img/backgrounds/home.png); +} + +.headline { + p:nth-child(1) { + font-size: 48px; + font-weight: 700; + padding: 12px; + } + p:nth-child(2) { + font-size: 24px; + font-weight: 500; + padding: 12px; + } + p:nth-child(3) { + font-size: 18px; + font-weight: 400; + padding: 12px; + } +} + +form { + flex-direction: row; + position: relative; + width: 500px; + padding: 24px; + input { + width: 65%; + padding: 12px 18px; + margin: 8px 0; + color: $white; + border: 2px solid $white; + border-radius: 24px; + font-size: 18px; + &::placeholder { + color: rgba($white, 0.6); + } + } + .error-border { + border: 2px solid $red; + } +} + +.error-msg { + position: absolute; + bottom: 0; + padding: 6px; + width: 57%; +} + +/*------------- RESPONSIVE -------------*/ + +@media screen and (max-width: 550px) { + form { + flex-direction: column; + width: 90%; + input { + width: 100%; + } + } + + .error-msg { + position: relative; + padding: 0; + height: 12px; + margin-bottom: 8px; + } +} + +@media screen and (max-width: 500px) { + .headline { + p:nth-child(1) { + font-size: 36px; + } + p:nth-child(2) { + font-size: 20px; + } + p:nth-child(3) { + font-size: 16px; + } + } +} diff --git a/frontend/src/app/components/home/browse/browse.component.spec.ts b/frontend/src/app/components/auth/landing-page/landing-page.component.spec.ts similarity index 52% rename from frontend/src/app/components/home/browse/browse.component.spec.ts rename to frontend/src/app/components/auth/landing-page/landing-page.component.spec.ts index 0b23307..21ffbea 100644 --- a/frontend/src/app/components/home/browse/browse.component.spec.ts +++ b/frontend/src/app/components/auth/landing-page/landing-page.component.spec.ts @@ -1,18 +1,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { BrowseComponent } from './browse.component'; +import { LandingPageComponent } from './landing-page.component'; -describe('BrowseComponent', () => { - let component: BrowseComponent; - let fixture: ComponentFixture; +describe('LandingPageComponent', () => { + let component: LandingPageComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [BrowseComponent] + imports: [LandingPageComponent] }) .compileComponents(); - - fixture = TestBed.createComponent(BrowseComponent); + + fixture = TestBed.createComponent(LandingPageComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/frontend/src/app/components/auth/landing-page/landing-page.component.ts b/frontend/src/app/components/auth/landing-page/landing-page.component.ts new file mode 100644 index 0000000..a9c5b03 --- /dev/null +++ b/frontend/src/app/components/auth/landing-page/landing-page.component.ts @@ -0,0 +1,56 @@ +import { Component } from '@angular/core'; +import { FormsModule, NgForm } from '@angular/forms'; +import { BtnLargeComponent } from '../../../shared/components/buttons/btn-large/btn-large.component'; +import { Router } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { ErrorService } from '../../../services/error.service'; +import { AuthService } from '../../../services/auth.service'; + +@Component({ + selector: 'app-landing-page', + standalone: true, + imports: [CommonModule, BtnLargeComponent, FormsModule], + templateUrl: './landing-page.component.html', + styleUrl: './landing-page.component.scss', +}) +export class LandingPageComponent { + authData = { + mail: '', + send: false, + }; + + constructor( + private router: Router, + public errorService: ErrorService, + private authService: AuthService + ) {} + + isUserEmailValid(emailValue: string) { + const emailRegex = /^[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}$/; + return emailRegex.test(emailValue); + } + + async onSubmit(ngForm: NgForm, mailInput: any) { + if (ngForm.submitted && ngForm.form.valid) { + await this.checkDuplicatesEmail(); + } else { + mailInput.control.markAsTouched(); + } + } + + async checkDuplicatesEmail() { + const body = { + email: this.authData.mail, + }; + try { + this.authData.send = true; + await this.authService.checkAuthUserMail(body); + const queryParams = { mail: this.authData.mail }; + this.router.navigate(['/register'], { queryParams }); + this.errorService.clearError(); + } catch (error) { + this.authData.send = false; + this.errorService.handleError(error); + } + } +} diff --git a/frontend/src/app/components/home/browse/browse.component.html b/frontend/src/app/components/home/browse/browse.component.html deleted file mode 100644 index 2d50e67..0000000 --- a/frontend/src/app/components/home/browse/browse.component.html +++ /dev/null @@ -1,118 +0,0 @@ -
-
- @if (!isLoading && movies.length > 0) { - - @if (playMovie === "") { - - - - -
- - - } @else { -
-
-
- Back -
-
-
- - - -
-

- (If a button cannot be selected, the video will be converted, which - may take a few seconds.) -

-
- -
- -
- } } @if (!isLoading && movies.length === 0) { - - -
-

Unfortunately there are no films available in the database.

-
- } -
- -
-
-@if (uploadMovieOverview) { - -} diff --git a/frontend/src/app/components/home/browse/browse.component.scss b/frontend/src/app/components/home/browse/browse.component.scss deleted file mode 100644 index f7cd243..0000000 --- a/frontend/src/app/components/home/browse/browse.component.scss +++ /dev/null @@ -1,183 +0,0 @@ -@use "./../../../../assets/style/colors.scss" as *; - -section { - height: 100dvh; - width: 100dvw; - background-color: $black; - overflow: hidden; -} - -.video-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: $black; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - z-index: 1000; - overflow: hidden; - position: relative; - video { - width: 100%; - height: 100%; - } - &:hover .video-header { - opacity: 1; - } -} - -.video-header { - position: absolute; - top: 0; - left: 0; - right: 0; - display: flex; - justify-content: space-between; - align-items: center; - padding: 24px 48px; - background: rgba($black, 0.5); - z-index: 1001; - opacity: 0; - transition: opacity 300ms ease-in-out; -} - -.center { - display: flex; - flex-direction: column; - p { - font-size: 14px; - font-weight: 400; - color: $light-blue; - text-shadow: 1px 1px 2px $black; - } -} - -.spacer { - margin-top: 120px; -} - -.add-button { - position: absolute; - bottom: 24px; - right: 24px; -} - -.back-button { - cursor: pointer; - background: none; - border: none; - padding: 0; - img { - width: 24px; - height: 24px; - filter: brightness(0) saturate(100%) invert(100%) sepia(6%) saturate(312%) - hue-rotate(234deg) brightness(113%) contrast(100%); - transition: filter 300ms ease-in-out; - &:hover { - filter: brightness(0) saturate(100%) invert(13%) sepia(90%) - saturate(7329%) hue-rotate(344deg) brightness(105%) contrast(117%); - } - } -} - -.logo img { - width: 32px; - height: auto; -} - -.error { - display: flex; - justify-content: center; - position: fixed; - top: 120px; - left: 0; - right: 0; - padding: 24px; - text-align: center; -} - -// Resolution Buttons - -.resolution-controls { - display: flex; - justify-content: center; - gap: 12px; - margin: 18px 0; -} - -.resolution-btn { - background-color: $light-blue; - color: $white; - border: none; - border-radius: 4px; - padding: 6px 12px; - font-size: 16px; - cursor: pointer; - transition: background-color 0.3s ease, transform 0.2s ease; -} - -.resolution-btn.active { - background-color: $blue; - cursor: default; -} - -.resolution-btn:disabled { - cursor: auto; -} - -.resolution-btn:not(.active):hover { - background-color: $blue; - transform: scale(1.05); -} - -.resolution-btn:focus { - outline: none; - box-shadow: 0 0 0 3px rgba(38, 143, 255, 0.5); -} - -.not-available { - opacity: 0.5; - background-color: $gray; -} - -.not-available:not(.active):hover { - background-color: $gray; - transform: scale(1); -} - -/*------------- RESPONSIVE -------------*/ - -@media screen and (max-width: 600px) { - .video-header { - padding: 12px 24px; - } -} - -@media screen and (max-width: 550px) { - form { - flex-direction: column; - width: 90%; - input { - width: 100%; - } - } - - .error-msg { - position: relative; - padding: 0; - height: 12px; - margin-bottom: 8px; - } -} - -@media screen and (max-width: 500px) { - .resolution-btn { - border-radius: 2px; - padding: 3px 6px; - font-size: 14px; - } -} diff --git a/frontend/src/app/components/home/browse/browse.component.ts b/frontend/src/app/components/home/browse/browse.component.ts deleted file mode 100644 index 6baaf2a..0000000 --- a/frontend/src/app/components/home/browse/browse.component.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; -import { HeaderComponent } from '../../../shared/components/header/header.component'; -import { HeroBannerComponent } from './hero-banner/hero-banner.component'; -import { CategoriesComponent } from './categories/categories.component'; -import { MovieService } from '../../../services/movie.service'; -import { CommonModule } from '@angular/common'; -import { VideoPlayerComponent } from './video-player/video-player.component'; -import { BtnSmallComponent } from '../../../shared/components/buttons/btn-small/btn-small.component'; -import { UploadMovieComponent } from './upload-movie/upload-movie.component'; -import { UserService } from '../../../services/user.service'; - -@Component({ - selector: 'app-browse', - standalone: true, - imports: [ - CommonModule, - HeaderComponent, - HeroBannerComponent, - CategoriesComponent, - VideoPlayerComponent, - BtnSmallComponent, - UploadMovieComponent, - ], - templateUrl: './browse.component.html', - styleUrl: './browse.component.scss', -}) -export class BrowseComponent implements OnInit { - @ViewChild(VideoPlayerComponent) videoPlayer!: VideoPlayerComponent; - movies: any[] = []; - favoriteMovies: number[] = []; - watchedMovies: number[] = []; - currentMovie: any[] = []; - playMovie: string = ''; - isLoading: boolean = true; - uploadMovieOverview: boolean = false; - currentResolution: '360p' | '720p' | '1080p' = '720p'; - movieIsUploaded: { [resolution: string]: boolean } = { - '360': false, - '720': false, - '1080': false, - }; - - constructor( - private movieService: MovieService, - public userService: UserService - ) {} - - async ngOnInit() { - this.loadLikedAndWatchedMovies(); - await this.loadAllMovies(); - if (this.isWideScreen()) { - this.currentMovie.length === 0 ? this.loadRandomMovie() : null; - } - } - - async loadLikedAndWatchedMovies() { - try { - const userData = await this.userService.getLikedAndWatchedMovies(); - this.favoriteMovies = userData.liked_videos; - this.watchedMovies = userData.watched_videos; - } catch (error) { - console.error(error); - } - } - - updateLikeMovies() { - const body = { - liked_videos: this.favoriteMovies, - }; - this.userService.updateLikedMovies(body); - } - - onRefreshPage(updatedMovies: any[]) { - this.currentMovie = []; - setTimeout(() => { - this.currentMovie = updatedMovies; - }, 1); - } - - onMoviesChange(updatedMovies: any[]) { - if (this.isWideScreen()) { - this.loadRandomMovie(); - } else { - this.currentMovie = updatedMovies; - } - } - - isWideScreen() { - return window.innerWidth > 600; - } - - onMovieIsUploadedChange(newStatus: { [resolution: string]: boolean }) { - this.movieIsUploaded = newStatus; - } - - onFavoriteMovieChange(favoriteMovies: any) { - this.favoriteMovies = favoriteMovies; - this.updateLikeMovies(); - } - - changeResolution(resolution: '360p' | '720p' | '1080p') { - if (this.videoPlayer && this.movieIsUploaded[resolution.replace('p', '')]) { - this.videoPlayer.switchResolution(resolution); - this.currentResolution = resolution; - } - } - - async loadAllMovies() { - this.isLoading = true; - try { - this.movies = await this.movieService.getAllMovies(); - } finally { - this.isLoading = false; - } - } - - closeVideo(): void { - this.playMovie = ''; - } - - playVideo(videoPath: string) { - this.currentResolution = '720p'; - this.playMovie = videoPath; - } - - loadRandomMovie(): void { - const randomIndex = Math.floor(Math.random() * this.movies.length); - this.currentMovie = [this.movies[randomIndex]]; - } - - currentMovieId(movieId: number) { - let index = this.movies.findIndex((movie) => movie.id === movieId); - if (index !== -1) { - this.currentMovie = []; - this.currentMovie.push(this.movies[index]); - } - } - - toggleUploadMovieOverview(value: any) { - this.uploadMovieOverview = value; - } -} diff --git a/frontend/src/app/components/home/browse/categories/categories.component.html b/frontend/src/app/components/home/categories/categories.component.html similarity index 100% rename from frontend/src/app/components/home/browse/categories/categories.component.html rename to frontend/src/app/components/home/categories/categories.component.html diff --git a/frontend/src/app/components/home/browse/categories/categories.component.scss b/frontend/src/app/components/home/categories/categories.component.scss similarity index 92% rename from frontend/src/app/components/home/browse/categories/categories.component.scss rename to frontend/src/app/components/home/categories/categories.component.scss index a143806..32fc693 100644 --- a/frontend/src/app/components/home/browse/categories/categories.component.scss +++ b/frontend/src/app/components/home/categories/categories.component.scss @@ -1,4 +1,4 @@ -@use "./../../../../../assets/style/colors.scss" as *; +@use "./../../../../assets/style/colors.scss" as *; section { margin-top: 12px; diff --git a/frontend/src/app/components/home/browse/categories/categories.component.spec.ts b/frontend/src/app/components/home/categories/categories.component.spec.ts similarity index 100% rename from frontend/src/app/components/home/browse/categories/categories.component.spec.ts rename to frontend/src/app/components/home/categories/categories.component.spec.ts diff --git a/frontend/src/app/components/home/browse/categories/categories.component.ts b/frontend/src/app/components/home/categories/categories.component.ts similarity index 97% rename from frontend/src/app/components/home/browse/categories/categories.component.ts rename to frontend/src/app/components/home/categories/categories.component.ts index 8c518ee..39ee272 100644 --- a/frontend/src/app/components/home/browse/categories/categories.component.ts +++ b/frontend/src/app/components/home/categories/categories.component.ts @@ -7,7 +7,7 @@ import { Input, Output, } from '@angular/core'; -import { environment } from '../../../../../environments/environment'; +import { environment } from '../../../../environments/environment'; import { MoviesListComponent } from './movie-list/movie-list.component'; @Component({ diff --git a/frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.html b/frontend/src/app/components/home/categories/movie-list/movie-list.component.html similarity index 100% rename from frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.html rename to frontend/src/app/components/home/categories/movie-list/movie-list.component.html diff --git a/frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.scss b/frontend/src/app/components/home/categories/movie-list/movie-list.component.scss similarity index 96% rename from frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.scss rename to frontend/src/app/components/home/categories/movie-list/movie-list.component.scss index c9e6404..b6b8f6b 100644 --- a/frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.scss +++ b/frontend/src/app/components/home/categories/movie-list/movie-list.component.scss @@ -1,4 +1,4 @@ -@use "./../../../../../../assets/style/colors.scss" as *; +@use "./../../../../../assets/style/colors.scss" as *; .movies { display: flex; diff --git a/frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.spec.ts b/frontend/src/app/components/home/categories/movie-list/movie-list.component.spec.ts similarity index 100% rename from frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.spec.ts rename to frontend/src/app/components/home/categories/movie-list/movie-list.component.spec.ts diff --git a/frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.ts b/frontend/src/app/components/home/categories/movie-list/movie-list.component.ts similarity index 94% rename from frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.ts rename to frontend/src/app/components/home/categories/movie-list/movie-list.component.ts index 50809f2..bc565c4 100644 --- a/frontend/src/app/components/home/browse/categories/movie-list/movie-list.component.ts +++ b/frontend/src/app/components/home/categories/movie-list/movie-list.component.ts @@ -1,5 +1,5 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { environment } from '../../../../../../environments/environment'; +import { environment } from '../../../../../environments/environment'; import { CommonModule } from '@angular/common'; @Component({ diff --git a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.html b/frontend/src/app/components/home/hero-banner/hero-banner.component.html similarity index 100% rename from frontend/src/app/components/home/browse/hero-banner/hero-banner.component.html rename to frontend/src/app/components/home/hero-banner/hero-banner.component.html diff --git a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.scss b/frontend/src/app/components/home/hero-banner/hero-banner.component.scss similarity index 97% rename from frontend/src/app/components/home/browse/hero-banner/hero-banner.component.scss rename to frontend/src/app/components/home/hero-banner/hero-banner.component.scss index f9ec39f..203fc87 100644 --- a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.scss +++ b/frontend/src/app/components/home/hero-banner/hero-banner.component.scss @@ -1,4 +1,4 @@ -@use "./../../../../../assets/style/colors.scss" as *; +@use "./../../../../assets/style/colors.scss" as *; section { height: 400px; diff --git a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.spec.ts b/frontend/src/app/components/home/hero-banner/hero-banner.component.spec.ts similarity index 100% rename from frontend/src/app/components/home/browse/hero-banner/hero-banner.component.spec.ts rename to frontend/src/app/components/home/hero-banner/hero-banner.component.spec.ts diff --git a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.ts b/frontend/src/app/components/home/hero-banner/hero-banner.component.ts similarity index 90% rename from frontend/src/app/components/home/browse/hero-banner/hero-banner.component.ts rename to frontend/src/app/components/home/hero-banner/hero-banner.component.ts index e937160..0ff436a 100644 --- a/frontend/src/app/components/home/browse/hero-banner/hero-banner.component.ts +++ b/frontend/src/app/components/home/hero-banner/hero-banner.component.ts @@ -9,11 +9,11 @@ import { SimpleChanges, ViewChild, } from '@angular/core'; -import { BtnLargeComponent } from '../../../../shared/components/buttons/btn-large/btn-large.component'; -import { MovieService } from '../../../../services/movie.service'; -import { environment } from '../../../../../environments/environment'; -import { BtnSmallComponent } from '../../../../shared/components/buttons/btn-small/btn-small.component'; -import { UserService } from '../../../../services/user.service'; +import { BtnLargeComponent } from '../../../shared/components/buttons/btn-large/btn-large.component'; +import { MovieService } from '../../../services/movie.service'; +import { environment } from '../../../../environments/environment'; +import { BtnSmallComponent } from '../../../shared/components/buttons/btn-small/btn-small.component'; +import { UserService } from '../../../services/user.service'; @Component({ selector: 'app-hero-banner', diff --git a/frontend/src/app/components/home/home.component.html b/frontend/src/app/components/home/home.component.html index 22e6d11..2d50e67 100644 --- a/frontend/src/app/components/home/home.component.html +++ b/frontend/src/app/components/home/home.component.html @@ -1,12 +1,118 @@ -
- - - - - - - - +
+
+ @if (!isLoading && movies.length > 0) { + + @if (playMovie === "") { + + + + +
+ + + } @else { +
+
+
+ Back +
+
+
+ + + +
+

+ (If a button cannot be selected, the video will be converted, which + may take a few seconds.) +

+
+ +
+ +
+ } } @if (!isLoading && movies.length === 0) { + + +
+

Unfortunately there are no films available in the database.

+
+ } +
+ +
+@if (uploadMovieOverview) { + +} diff --git a/frontend/src/app/components/home/home.component.scss b/frontend/src/app/components/home/home.component.scss index 04e99a5..90f5667 100644 --- a/frontend/src/app/components/home/home.component.scss +++ b/frontend/src/app/components/home/home.component.scss @@ -1,4 +1,183 @@ +@use "./../../../assets/style/colors.scss" as *; + section { - width: 100dvw; height: 100dvh; + width: 100dvw; + background-color: $black; + overflow: hidden; +} + +.video-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: $black; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 1000; + overflow: hidden; + position: relative; + video { + width: 100%; + height: 100%; + } + &:hover .video-header { + opacity: 1; + } +} + +.video-header { + position: absolute; + top: 0; + left: 0; + right: 0; + display: flex; + justify-content: space-between; + align-items: center; + padding: 24px 48px; + background: rgba($black, 0.5); + z-index: 1001; + opacity: 0; + transition: opacity 300ms ease-in-out; +} + +.center { + display: flex; + flex-direction: column; + p { + font-size: 14px; + font-weight: 400; + color: $light-blue; + text-shadow: 1px 1px 2px $black; + } +} + +.spacer { + margin-top: 120px; +} + +.add-button { + position: absolute; + bottom: 24px; + right: 24px; +} + +.back-button { + cursor: pointer; + background: none; + border: none; + padding: 0; + img { + width: 24px; + height: 24px; + filter: brightness(0) saturate(100%) invert(100%) sepia(6%) saturate(312%) + hue-rotate(234deg) brightness(113%) contrast(100%); + transition: filter 300ms ease-in-out; + &:hover { + filter: brightness(0) saturate(100%) invert(13%) sepia(90%) + saturate(7329%) hue-rotate(344deg) brightness(105%) contrast(117%); + } + } +} + +.logo img { + width: 32px; + height: auto; +} + +.error { + display: flex; + justify-content: center; + position: fixed; + top: 120px; + left: 0; + right: 0; + padding: 24px; + text-align: center; +} + +// Resolution Buttons + +.resolution-controls { + display: flex; + justify-content: center; + gap: 12px; + margin: 18px 0; +} + +.resolution-btn { + background-color: $light-blue; + color: $white; + border: none; + border-radius: 4px; + padding: 6px 12px; + font-size: 16px; + cursor: pointer; + transition: background-color 0.3s ease, transform 0.2s ease; +} + +.resolution-btn.active { + background-color: $blue; + cursor: default; +} + +.resolution-btn:disabled { + cursor: auto; +} + +.resolution-btn:not(.active):hover { + background-color: $blue; + transform: scale(1.05); +} + +.resolution-btn:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(38, 143, 255, 0.5); +} + +.not-available { + opacity: 0.5; + background-color: $gray; +} + +.not-available:not(.active):hover { + background-color: $gray; + transform: scale(1); +} + +/*------------- RESPONSIVE -------------*/ + +@media screen and (max-width: 600px) { + .video-header { + padding: 12px 24px; + } +} + +@media screen and (max-width: 550px) { + form { + flex-direction: column; + width: 90%; + input { + width: 100%; + } + } + + .error-msg { + position: relative; + padding: 0; + height: 12px; + margin-bottom: 8px; + } +} + +@media screen and (max-width: 500px) { + .resolution-btn { + border-radius: 2px; + padding: 3px 6px; + font-size: 14px; + } } diff --git a/frontend/src/app/components/home/home.component.spec.ts b/frontend/src/app/components/home/home.component.spec.ts index 60c47c4..7737f1c 100644 --- a/frontend/src/app/components/home/home.component.spec.ts +++ b/frontend/src/app/components/home/home.component.spec.ts @@ -8,10 +8,9 @@ describe('HomeComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [HomeComponent] - }) - .compileComponents(); - + imports: [HomeComponent], + }).compileComponents(); + fixture = TestBed.createComponent(HomeComponent); component = fixture.componentInstance; fixture.detectChanges(); diff --git a/frontend/src/app/components/home/home.component.ts b/frontend/src/app/components/home/home.component.ts index ab8d52f..ca9c755 100644 --- a/frontend/src/app/components/home/home.component.ts +++ b/frontend/src/app/components/home/home.component.ts @@ -1,15 +1,13 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { HeaderComponent } from '../../shared/components/header/header.component'; -import { FooterComponent } from '../../shared/components/footer/footer.component'; -import { AuthComponent } from '../auth/auth.component'; -import { ActivatedRoute } from '@angular/router'; -import { RegisterComponent } from '../auth/register/register.component'; +import { HeroBannerComponent } from './hero-banner/hero-banner.component'; +import { CategoriesComponent } from './categories/categories.component'; +import { MovieService } from '../../services/movie.service'; import { CommonModule } from '@angular/common'; -import { LoginComponent } from '../auth/login/login.component'; -import { ForgotPasswordComponent } from '../auth/forgot-password/forgot-password.component'; -import { VerifyEmailComponent } from '../auth/verify-email/verify-email.component'; -import { ErrorToastComponent } from '../../shared/components/error-toast/error-toast.component'; -import { ErrorService } from '../../services/error.service'; +import { VideoPlayerComponent } from './video-player/video-player.component'; +import { BtnSmallComponent } from '../../shared/components/buttons/btn-small/btn-small.component'; +import { UploadMovieComponent } from './upload-movie/upload-movie.component'; +import { UserService } from '../../services/user.service'; @Component({ selector: 'app-home', @@ -17,28 +15,128 @@ import { ErrorService } from '../../services/error.service'; imports: [ CommonModule, HeaderComponent, - FooterComponent, - AuthComponent, - RegisterComponent, - LoginComponent, - ForgotPasswordComponent, - VerifyEmailComponent, - ErrorToastComponent, + HeroBannerComponent, + CategoriesComponent, + VideoPlayerComponent, + BtnSmallComponent, + UploadMovieComponent, ], templateUrl: './home.component.html', styleUrl: './home.component.scss', }) export class HomeComponent implements OnInit { - currentRoute: any; + @ViewChild(VideoPlayerComponent) videoPlayer!: VideoPlayerComponent; + movies: any[] = []; + favoriteMovies: number[] = []; + watchedMovies: number[] = []; + currentMovie: any[] = []; + playMovie: string = ''; + isLoading: boolean = true; + uploadMovieOverview: boolean = false; + currentResolution: '360p' | '720p' | '1080p' = '720p'; + movieIsUploaded: { [resolution: string]: boolean } = { + '360': false, + '720': false, + '1080': false, + }; constructor( - private route: ActivatedRoute, - public errorService: ErrorService + private movieService: MovieService, + public userService: UserService ) {} - ngOnInit(): void { - this.route.url.subscribe((url) => { - this.currentRoute = url[0]?.path || ''; - }); + async ngOnInit() { + this.loadLikedAndWatchedMovies(); + await this.loadAllMovies(); + if (this.isWideScreen()) { + this.currentMovie.length === 0 ? this.loadRandomMovie() : null; + } + } + + async loadLikedAndWatchedMovies() { + try { + const userData = await this.userService.getLikedAndWatchedMovies(); + this.favoriteMovies = userData.liked_videos; + this.watchedMovies = userData.watched_videos; + } catch (error) { + console.error(error); + } + } + + updateLikeMovies() { + const body = { + liked_videos: this.favoriteMovies, + }; + this.userService.updateLikedMovies(body); + } + + onRefreshPage(updatedMovies: any[]) { + this.currentMovie = []; + setTimeout(() => { + this.currentMovie = updatedMovies; + }, 1); + } + + onMoviesChange(updatedMovies: any[]) { + if (this.isWideScreen()) { + this.loadRandomMovie(); + } else { + this.currentMovie = updatedMovies; + } + } + + isWideScreen() { + return window.innerWidth > 600; + } + + onMovieIsUploadedChange(newStatus: { [resolution: string]: boolean }) { + this.movieIsUploaded = newStatus; + } + + onFavoriteMovieChange(favoriteMovies: any) { + this.favoriteMovies = favoriteMovies; + this.updateLikeMovies(); + } + + changeResolution(resolution: '360p' | '720p' | '1080p') { + if (this.videoPlayer && this.movieIsUploaded[resolution.replace('p', '')]) { + this.videoPlayer.switchResolution(resolution); + this.currentResolution = resolution; + } + } + + async loadAllMovies() { + this.isLoading = true; + try { + this.movies = await this.movieService.getAllMovies(); + } finally { + this.isLoading = false; + } + } + + closeVideo(): void { + this.playMovie = ''; + } + + playVideo(videoPath: string) { + this.currentResolution = '720p'; + this.playMovie = videoPath; + } + + loadRandomMovie(): void { + const randomIndex = Math.floor(Math.random() * this.movies.length); + this.currentMovie = [this.movies[randomIndex]]; + } + + currentMovieId(movieId: number) { + let index = this.movies.findIndex((movie) => movie.id === movieId); + if (index !== -1) { + this.currentMovie = []; + this.currentMovie.push(this.movies[index]); + } + } + + toggleUploadMovieOverview(value: any) { + this.uploadMovieOverview = value; } } diff --git a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html b/frontend/src/app/components/home/upload-movie/upload-movie.component.html similarity index 100% rename from frontend/src/app/components/home/browse/upload-movie/upload-movie.component.html rename to frontend/src/app/components/home/upload-movie/upload-movie.component.html diff --git a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.scss b/frontend/src/app/components/home/upload-movie/upload-movie.component.scss similarity index 73% rename from frontend/src/app/components/home/browse/upload-movie/upload-movie.component.scss rename to frontend/src/app/components/home/upload-movie/upload-movie.component.scss index 1b340e1..0c42876 100644 --- a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.scss +++ b/frontend/src/app/components/home/upload-movie/upload-movie.component.scss @@ -1,6 +1,6 @@ -@use "./../../../../../assets/style/colors.scss" as *; -@use "./../../../../../assets/style/form.scss" as *; -@use "./../../../../../assets/style/auth-layout.scss" as *; +@use "./../../../../assets/style/colors.scss" as *; +@use "./../../../../assets/style/form.scss" as *; +@use "./../../../../assets/style/auth-layout.scss" as *; .center { display: flex; diff --git a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.spec.ts b/frontend/src/app/components/home/upload-movie/upload-movie.component.spec.ts similarity index 100% rename from frontend/src/app/components/home/browse/upload-movie/upload-movie.component.spec.ts rename to frontend/src/app/components/home/upload-movie/upload-movie.component.spec.ts diff --git a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts b/frontend/src/app/components/home/upload-movie/upload-movie.component.ts similarity index 87% rename from frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts rename to frontend/src/app/components/home/upload-movie/upload-movie.component.ts index b672ac4..130e752 100644 --- a/frontend/src/app/components/home/browse/upload-movie/upload-movie.component.ts +++ b/frontend/src/app/components/home/upload-movie/upload-movie.component.ts @@ -1,10 +1,10 @@ import { CommonModule } from '@angular/common'; import { Component, EventEmitter, Output } from '@angular/core'; import { FormsModule, NgForm } from '@angular/forms'; -import { ErrorService } from '../../../../services/error.service'; -import { BtnLargeComponent } from '../../../../shared/components/buttons/btn-large/btn-large.component'; -import { MovieService } from '../../../../services/movie.service'; -import { LoadingDialogComponent } from '../../../../shared/components/loading-dialog/loading-dialog.component'; +import { ErrorService } from '../../../services/error.service'; +import { BtnLargeComponent } from '../../../shared/components/buttons/btn-large/btn-large.component'; +import { MovieService } from '../../../services/movie.service'; +import { LoadingDialogComponent } from '../../../shared/components/loading-dialog/loading-dialog.component'; @Component({ selector: 'app-upload-movie', diff --git a/frontend/src/app/components/home/browse/video-player/video-player.component.html b/frontend/src/app/components/home/video-player/video-player.component.html similarity index 100% rename from frontend/src/app/components/home/browse/video-player/video-player.component.html rename to frontend/src/app/components/home/video-player/video-player.component.html diff --git a/frontend/src/app/components/home/browse/video-player/video-player.component.scss b/frontend/src/app/components/home/video-player/video-player.component.scss similarity index 100% rename from frontend/src/app/components/home/browse/video-player/video-player.component.scss rename to frontend/src/app/components/home/video-player/video-player.component.scss diff --git a/frontend/src/app/components/home/browse/video-player/video-player.component.spec.ts b/frontend/src/app/components/home/video-player/video-player.component.spec.ts similarity index 100% rename from frontend/src/app/components/home/browse/video-player/video-player.component.spec.ts rename to frontend/src/app/components/home/video-player/video-player.component.spec.ts diff --git a/frontend/src/app/components/home/browse/video-player/video-player.component.ts b/frontend/src/app/components/home/video-player/video-player.component.ts similarity index 100% rename from frontend/src/app/components/home/browse/video-player/video-player.component.ts rename to frontend/src/app/components/home/video-player/video-player.component.ts