From 051a0ac0e4320e30dcdc8e20538dc938c2a67f08 Mon Sep 17 00:00:00 2001 From: Djesika Vacheva Date: Mon, 22 Jun 2026 13:49:20 +0300 Subject: [PATCH 1/4] add login form --- .../core/shared/main-panel/main-panel.html | 9 +- .../core/shared/main-panel/main-panel.scss | 40 +++++++ .../app/core/shared/main-panel/main-panel.ts | 4 +- .../navmenu-component/navmenu-component.html | 23 ++-- .../navmenu-component/navmenu-component.ts | 13 +++ .../features/login/login-form/login-form.html | 31 ++++++ .../features/login/login-form/login-form.scss | 100 ++++++++++++++++++ .../login/login-form/login-form.spec.ts | 23 ++++ .../features/login/login-form/login-form.ts | 52 +++++++++ .../src/app/services/login-auth-service.ts | 23 +++- 10 files changed, 304 insertions(+), 14 deletions(-) create mode 100644 uniplanWeb/src/app/features/login/login-form/login-form.html create mode 100644 uniplanWeb/src/app/features/login/login-form/login-form.scss create mode 100644 uniplanWeb/src/app/features/login/login-form/login-form.spec.ts create mode 100644 uniplanWeb/src/app/features/login/login-form/login-form.ts diff --git a/uniplanWeb/src/app/core/shared/main-panel/main-panel.html b/uniplanWeb/src/app/core/shared/main-panel/main-panel.html index 5a3465d..8955f00 100644 --- a/uniplanWeb/src/app/core/shared/main-panel/main-panel.html +++ b/uniplanWeb/src/app/core/shared/main-panel/main-panel.html @@ -1,5 +1,4 @@ -
-
+
@if (currentView === 'faculty') {
@@ -13,6 +12,12 @@

Main!

} + @if (currentView === 'login') { + + } + @if (currentView === 'major') {
diff --git a/uniplanWeb/src/app/core/shared/main-panel/main-panel.scss b/uniplanWeb/src/app/core/shared/main-panel/main-panel.scss index 420c285..4f6858d 100644 --- a/uniplanWeb/src/app/core/shared/main-panel/main-panel.scss +++ b/uniplanWeb/src/app/core/shared/main-panel/main-panel.scss @@ -6,6 +6,16 @@ justify-content: center; } +.component-panel.login { + width: 100%; + min-height: calc(100vh - 80px); + display: flex; + align-items: center; + justify-content: center; + padding: 32px; + box-sizing: border-box; +} + .components-wrapper { position: relative; margin-left: 8%; @@ -56,3 +66,33 @@ .options { padding-bottom: 20px; } + +.mainPanel.login-view { + align-items: center; + justify-content: center; +} + +.mainPanel.login-view .components-wrapper { + margin-left: 0; + margin-right: 0; + padding-left: 0; + width: 100%; + max-width: none; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.component-panel.login { + width: 100%; + min-height: 100%; + margin-top: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 32px; + box-sizing: border-box; +} + + diff --git a/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts b/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts index d205b1b..cd64c75 100644 --- a/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts +++ b/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts @@ -15,6 +15,7 @@ import { StudentFilters } from '../../../features/student/student-filters/studen import { MajorElm } from '../../interfaces/major-elm'; import { MajorService } from '../../../features/major/major-service'; import { FacultyService } from '../../../features/faculty/faculty-service'; +import { LoginForm } from '../../../features/login/login-form/login-form'; @Component({ selector: 'app-main-panel', @@ -26,7 +27,8 @@ import { FacultyService } from '../../../features/faculty/faculty-service'; MajorFilters, StudentOptions, StudentTable, - StudentFilters + StudentFilters, + LoginForm, ], standalone: true, templateUrl: './main-panel.html', diff --git a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html index e221950..24ab65e 100644 --- a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html +++ b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html @@ -51,23 +51,32 @@

Logo

} @if (!authService.isLoggedIn()) {
  • - Вход + + + Вход +
  • } diff --git a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts index d28b20a..c6d68e1 100644 --- a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts +++ b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts @@ -59,4 +59,17 @@ export class NavmenuComponent implements OnInit { onStudentClick(): void { this.viewService.setView('student'); } + + protected onLoginClick(): void { + this.viewService.setView('login'); + } + + protected onLogoutClick(): void { + this.authService.logout(); + this.viewService.setView('home'); + } + + protected onProfileOpenClick(): void { + this.viewService.setView('home'); + } } diff --git a/uniplanWeb/src/app/features/login/login-form/login-form.html b/uniplanWeb/src/app/features/login/login-form/login-form.html new file mode 100644 index 0000000..9ed19d4 --- /dev/null +++ b/uniplanWeb/src/app/features/login/login-form/login-form.html @@ -0,0 +1,31 @@ +

    diff --git a/uniplanWeb/src/app/features/login/login-form/login-form.scss b/uniplanWeb/src/app/features/login/login-form/login-form.scss new file mode 100644 index 0000000..24f943d --- /dev/null +++ b/uniplanWeb/src/app/features/login/login-form/login-form.scss @@ -0,0 +1,100 @@ +:host { + display: block; + width: 100%; + max-width: 900px; +} + +* { + box-sizing: border-box; +} + +.login-form { + width: 100%; + padding: 64px 96px; + border-radius: 24px; + background: #ffffff; + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1); +} + +.login-form h2 { + margin: 0 0 32px; + font-size: 28px; + font-weight: 700; + color: #1f2937; + text-align: center; +} + +.login-form label { + display: block; + margin-bottom: 8px; + font-size: 15px; + font-weight: 600; + color: #374151; +} + +.login-form input { + width: 100%; + height: 56px; + padding: 0 16px; + border: 1px solid #d1d5db; + border-radius: 12px; + font-size: 15px; + color: #111827; + background: #ffffff; + outline: none; +} + +.login-form button { + width: 100%; + height: 58px; + border: none; + border-radius: 12px; + font-size: 16px; + font-weight: 700; + color: #ffffff; + background: #3f51b5; + cursor: pointer; +} + +.error-message { + margin: 0; + padding: 12px 14px; + border-radius: 10px; + font-size: 14px; + color: #b91c1c; + background: #fee2e2; +} + +.login-form form { + display: flex; + flex-direction: column; + gap: 28px; +} + + +.login-form input:focus { + border-color: #3f51b5; + box-shadow: 0 0 0 4px rgba(63, 81, 181, 0.14); +} + +.login-form button:hover { + background: #303f9f; +} + +.login-form button:active { + transform: translateY(1px); +} + +@media screen and (max-width: 768px) { + :host { + max-width: 100%; + } + + .login-form { + padding: 40px 28px; + } + + .login-form h2 { + font-size: 30px; + } +} diff --git a/uniplanWeb/src/app/features/login/login-form/login-form.spec.ts b/uniplanWeb/src/app/features/login/login-form/login-form.spec.ts new file mode 100644 index 0000000..4aba92a --- /dev/null +++ b/uniplanWeb/src/app/features/login/login-form/login-form.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginForm } from './login-form'; + +describe('LoginForm', () => { + let component: LoginForm; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [LoginForm] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LoginForm); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/uniplanWeb/src/app/features/login/login-form/login-form.ts b/uniplanWeb/src/app/features/login/login-form/login-form.ts new file mode 100644 index 0000000..82b4eb2 --- /dev/null +++ b/uniplanWeb/src/app/features/login/login-form/login-form.ts @@ -0,0 +1,52 @@ +import { Component, inject } from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {LoginAuthService} from '../../../services/login-auth-service'; +import {ViewService} from '../../../core/shared/main-panel/view.service'; + +@Component({ + selector: 'app-login-form', + imports: [FormsModule], + templateUrl: './login-form.html', + styleUrl: './login-form.scss', +}) +export class LoginForm { + protected username: string = ''; + protected password: string = ''; + protected errorMessage: string = ''; + + private readonly authService: LoginAuthService = inject(LoginAuthService); + private readonly viewService: ViewService = inject(ViewService); + + protected login(event: Event): void { + this.errorMessage = ''; + + if (!this.username.trim()) { + this.errorMessage = 'Моля, въведете потребителско име.'; + return; + } + + if (!this.password.trim()) { + this.errorMessage = 'Моля въведете парола.'; + return; + } + + const isLoggedIn: boolean = this.authService.login(this.username, this.password); + + if (!isLoggedIn) { + this.errorMessage = 'Невалидни потребителски данни.'; + return; + } + + this.viewService.setView('home'); + } + + protected onUsernameChange(event: Event): void { + const input = event.target as HTMLInputElement; + this.username = input.value; + } + + protected onPasswordChange(event: Event): void { + const input = event.target as HTMLInputElement; + this.password = input.value; + } +} diff --git a/uniplanWeb/src/app/services/login-auth-service.ts b/uniplanWeb/src/app/services/login-auth-service.ts index 3ec49fe..5433a8c 100644 --- a/uniplanWeb/src/app/services/login-auth-service.ts +++ b/uniplanWeb/src/app/services/login-auth-service.ts @@ -1,21 +1,36 @@ import { Injectable } from '@angular/core'; @Injectable({ - providedIn: 'root' + providedIn: 'root', }) export class LoginAuthService { private loggedIn = false; - constructor() { + public constructor() { this.loggedIn = !!localStorage.getItem('user'); } - isLoggedIn(): boolean { + public login(username: string, password: string): boolean { + if (!username || !password) { + return false; + } + + localStorage.setItem('user', username); + this.loggedIn = true; + + return true; + } + + public isLoggedIn(): boolean { return this.loggedIn; } - logout(): void { + public logout(): void { localStorage.removeItem('user'); this.loggedIn = false; } + + public getUsername(): string { + return localStorage.getItem('user') ?? ''; + } } From dffef943b0f7a84e98b092ae65de33105cc97dbc Mon Sep 17 00:00:00 2001 From: Djesika Vacheva Date: Wed, 24 Jun 2026 11:41:53 +0300 Subject: [PATCH 2/4] Use localStorage as login state source --- uniplanWeb/src/app/services/login-auth-service.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/uniplanWeb/src/app/services/login-auth-service.ts b/uniplanWeb/src/app/services/login-auth-service.ts index 5433a8c..3dc20f6 100644 --- a/uniplanWeb/src/app/services/login-auth-service.ts +++ b/uniplanWeb/src/app/services/login-auth-service.ts @@ -4,11 +4,6 @@ import { Injectable } from '@angular/core'; providedIn: 'root', }) export class LoginAuthService { - private loggedIn = false; - - public constructor() { - this.loggedIn = !!localStorage.getItem('user'); - } public login(username: string, password: string): boolean { if (!username || !password) { @@ -16,18 +11,16 @@ export class LoginAuthService { } localStorage.setItem('user', username); - this.loggedIn = true; return true; } public isLoggedIn(): boolean { - return this.loggedIn; + return !!localStorage.getItem('user'); } public logout(): void { localStorage.removeItem('user'); - this.loggedIn = false; } public getUsername(): string { From 0339be5b1f76915685f82f77b5416bbf7b22ce10 Mon Sep 17 00:00:00 2001 From: Djesika Vacheva Date: Wed, 29 Jul 2026 11:44:49 +0300 Subject: [PATCH 3/4] fix build errors --- .../app/core/shared/main-panel/main-panel.ts | 26 ++++++------------- .../navmenu-component/navmenu-component.ts | 22 ++++++++-------- .../features/login/login-form/login-form.ts | 6 ++--- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts b/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts index cd64c75..bf7fe0d 100644 --- a/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts +++ b/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts @@ -1,7 +1,6 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; +import {Component, ChangeDetectionStrategy, inject, OnInit} from '@angular/core'; import { FacultyOptions } from '../../../features/faculty/faculty-options/faculty-options'; -import { ViewService } from './view.service'; import { MajorOptions } from '../../../features/major/major-options/major-options'; import { FacultyTable } from '../../../features/faculty/faculty-table/faculty-table'; import { MajorTable } from '../../../features/major/major-table/major-table'; @@ -13,9 +12,11 @@ import { } from '../../../features/student/student-table/student-table'; import { StudentFilters } from '../../../features/student/student-filters/student-filters'; import { MajorElm } from '../../interfaces/major-elm'; -import { MajorService } from '../../../features/major/major-service'; -import { FacultyService } from '../../../features/faculty/faculty-service'; import { LoginForm } from '../../../features/login/login-form/login-form'; +import {LoginAuthService} from '../../../services/login-auth-service'; +import {Router} from '@angular/router'; +import {FacultyService} from '../../../features/faculty/faculty-service'; +import {MajorService} from '../../../features/major/major-service'; @Component({ selector: 'app-main-panel', @@ -35,7 +36,7 @@ import { LoginForm } from '../../../features/login/login-form/login-form'; changeDetection: ChangeDetectionStrategy.Eager, styleUrl: './main-panel.scss', }) -export class MainPanel { +export class MainPanel implements OnInit { currentView = 'home'; majors: MajorElm[] = []; @@ -57,15 +58,8 @@ export class MainPanel { private facultyMap = new Map(); - constructor( - private viewService: ViewService, - private majorService: MajorService, - private facultyService: FacultyService - ) { - this.viewService.currentView$.subscribe((view) => { - this.currentView = view; - }); - } + private readonly majorService = inject(MajorService); + private readonly facultyService = inject(FacultyService); ngOnInit(): void { this.loadMajorFilters(); @@ -74,10 +68,6 @@ export class MainPanel { this.majorService.refreshNeeded.subscribe(() => { this.loadMajorFilters(); }); - - this.viewService.currentView$.subscribe((view) => { - this.currentView = view; - }); } private loadMajorFilters(): void { diff --git a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts index 527ca1d..5456e8e 100644 --- a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts +++ b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.ts @@ -1,9 +1,8 @@ -import { Component, HostListener, OnInit, ChangeDetectionStrategy } from '@angular/core'; +import { Component, HostListener, OnInit, ChangeDetectionStrategy, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { RouterLink, RouterLinkActive } from '@angular/router'; +import { Router, RouterLink, RouterLinkActive } from '@angular/router'; import { TranslatePipe } from '@ngx-translate/core'; -import { ViewService } from '../../main-panel/view.service'; import { LoginAuthService } from '../../../services/login-auth-service'; @Component({ @@ -18,7 +17,8 @@ export class NavmenuComponent implements OnInit { isSidebarCollapsed = false; isMobileView = window.innerWidth <= 768; - constructor(public authService: LoginAuthService) {} + protected readonly authService = inject(LoginAuthService); + private readonly router = inject(Router); ngOnInit(): void { this.checkViewport(); @@ -44,31 +44,31 @@ export class NavmenuComponent implements OnInit { } onHomeClick(): void { - this.viewService.setView('home'); + this.router.navigate(['/']); } onFacultyClick(): void { - this.viewService.setView('faculty'); + this.router.navigate(['/faculties]']); } onMajorClick(): void { - this.viewService.setView('major'); + this.router.navigate(['/majors']); } onStudentClick(): void { - this.viewService.setView('student'); + this.router.navigate(['/students']); } protected onLoginClick(): void { - this.viewService.setView('login'); + this.router.navigate(['/login']); } protected onLogoutClick(): void { this.authService.logout(); - this.viewService.setView('home'); + this.router.navigate(['/']); } protected onProfileOpenClick(): void { - this.viewService.setView('home'); + this.router.navigate(['/']); } } diff --git a/uniplanWeb/src/app/features/login/login-form/login-form.ts b/uniplanWeb/src/app/features/login/login-form/login-form.ts index 82b4eb2..479f8cc 100644 --- a/uniplanWeb/src/app/features/login/login-form/login-form.ts +++ b/uniplanWeb/src/app/features/login/login-form/login-form.ts @@ -1,7 +1,7 @@ import { Component, inject } from '@angular/core'; +import { Router } from '@angular/router'; import {FormsModule} from '@angular/forms'; import {LoginAuthService} from '../../../services/login-auth-service'; -import {ViewService} from '../../../core/shared/main-panel/view.service'; @Component({ selector: 'app-login-form', @@ -15,7 +15,7 @@ export class LoginForm { protected errorMessage: string = ''; private readonly authService: LoginAuthService = inject(LoginAuthService); - private readonly viewService: ViewService = inject(ViewService); + private readonly router = inject(Router); protected login(event: Event): void { this.errorMessage = ''; @@ -37,7 +37,7 @@ export class LoginForm { return; } - this.viewService.setView('home'); + this.router.navigate(['/']); } protected onUsernameChange(event: Event): void { From 534b745fb82cd89e5626dc67536ec9493e90fd25 Mon Sep 17 00:00:00 2001 From: Djesika Vacheva Date: Wed, 29 Jul 2026 12:58:45 +0300 Subject: [PATCH 4/4] fix lint errors --- uniplanWeb/src/app/core/shared/main-panel/main-panel.ts | 2 -- .../app/core/shared/navmenu-component/navmenu-component.html | 2 +- uniplanWeb/src/app/features/login/login-form/login-form.ts | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts b/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts index bf7fe0d..935ada5 100644 --- a/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts +++ b/uniplanWeb/src/app/core/shared/main-panel/main-panel.ts @@ -13,8 +13,6 @@ import { import { StudentFilters } from '../../../features/student/student-filters/student-filters'; import { MajorElm } from '../../interfaces/major-elm'; import { LoginForm } from '../../../features/login/login-form/login-form'; -import {LoginAuthService} from '../../../services/login-auth-service'; -import {Router} from '@angular/router'; import {FacultyService} from '../../../features/faculty/faculty-service'; import {MajorService} from '../../../features/major/major-service'; diff --git a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html index bcccf1e..b66f541 100644 --- a/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html +++ b/uniplanWeb/src/app/core/shared/navmenu-component/navmenu-component.html @@ -66,7 +66,7 @@

    Logo