diff --git a/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.html b/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.html index 1f7f084..aa33dd1 100644 --- a/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.html +++ b/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.html @@ -1,47 +1,45 @@ -
-
-
-
A
-
-

{{ currentUser()?.email }}

-

System Admin

-
- +
+
A
+
+

{{ currentUser()?.email }}

+

System Admin

- + +
-
+ diff --git a/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.scss b/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.scss index fde3488..4ab3ce3 100644 --- a/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.scss +++ b/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.scss @@ -3,147 +3,129 @@ $admin-primary-light: #ffebee; $sidebar-width: 280px; $transition: all 0.2s ease-in-out; -.layout-container { +.brand-box { + padding: 24px; display: flex; - height: 100vh; - width: 100vw; - background-color: #f5f7fa; - overflow: hidden; + align-items: center; + gap: 12px; + + .logo { + font-size: 32px; + color: #2563eb; + } + h2 { + margin: 0; + font-size: 1.25rem; + color: #1f2937; + } } -.sidebar { - width: $sidebar-width; - background-color: white; - border-right: 1px solid rgba($admin-primary, 0.2); +.menu-item { display: flex; - flex-direction: column; - flex-shrink: 0; - z-index: 10; - transition: $transition; - - .brand { - height: 64px; - display: flex; - align-items: center; - padding: 0 24px; - gap: 12px; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - - .logo-icon { - font-size: 28px; - color: $admin-primary; - } - - h2 { - margin: 0; - font-size: 1.25rem; - font-weight: 600; - color: #333; - letter-spacing: -0.5px; + align-items: center; + gap: 12px; + padding: 12px 16px; + color: #4b5563; + text-decoration: none; + border-radius: 8px; + font-weight: 500; + transition: all 0.2s; + + &:hover { + background: #f3f4f6; + color: #111827; + } + &.active { + background: #eff6ff; + color: #2563eb; + .material-icons { + color: #2563eb; } } +} - .menu { - flex: 1; - padding: 24px 16px; - display: flex; - flex-direction: column; - gap: 8px; - overflow-y: auto; +.user-profile { + padding: 16px; + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 12px; + + .avatar { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; + } + .info p { + margin: 0; + font-size: 0.9rem; + line-height: 1.2; + } + .info .role { + font-size: 0.75rem; + color: #6b7280; + } - .menu-item { - display: flex; - align-items: center; - gap: 12px; - padding: 12px 16px; - text-decoration: none; - color: #666; - font-weight: 500; - border-radius: 12px; - transition: $transition; - font-size: 0.95rem; - - .material-icons { - font-size: 20px; - color: inherit; - transition: color 0.2s; - } - - &:hover { - background-color: #fafafa; - color: #333; - } - - &.active { - background-color: $admin-primary-light; - color: $admin-primary; - font-weight: 600; - - .material-icons { - color: $admin-primary; - } - } + .logout-icon-btn { + background: none; + border: none; + color: #9ca3af; + cursor: pointer; + &:hover { + color: #ef4444; } } - .user-profile { - padding: 20px 24px; - border-top: 1px solid rgba(0, 0, 0, 0.08); + .avatar-placeholder { + width: 40px; + height: 40px; + border-radius: 50%; + background-color: $admin-primary; + color: white; display: flex; align-items: center; - gap: 12px; - background-color: white; - - .avatar-placeholder { - width: 40px; - height: 40px; - border-radius: 50%; - background-color: $admin-primary; - color: white; - display: flex; - align-items: center; - justify-content: center; - font-weight: 700; - font-size: 1.1rem; - } + justify-content: center; + font-weight: 700; + font-size: 1.1rem; + } + + .info { + flex: 1; + overflow: hidden; - .info { - flex: 1; + .name { + margin: 0; + font-size: 0.9rem; + font-weight: 600; + color: #333; + white-space: nowrap; + text-overflow: ellipsis; overflow: hidden; + } - .name { - margin: 0; - font-size: 0.9rem; - font-weight: 600; - color: #333; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - - .role { - margin: 0; - font-size: 0.75rem; - color: $admin-primary; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - } + .role { + margin: 0; + font-size: 0.75rem; + color: $admin-primary; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; } + } - .logout-btn { - background: none; - border: none; - cursor: pointer; - padding: 8px; - border-radius: 50%; - color: #9ca3af; - transition: $transition; - - &:hover { - background-color: $admin-primary-light; - color: $admin-primary; - } + .logout-btn { + background: none; + border: none; + cursor: pointer; + padding: 8px; + border-radius: 50%; + color: #9ca3af; + transition: $transition; + + &:hover { + background-color: $admin-primary-light; + color: $admin-primary; } } } diff --git a/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.ts b/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.ts index 73ad227..d94b1f2 100644 --- a/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.ts +++ b/app/booking-ui/src/app/admin/layout/admin-layout/admin-layout.ts @@ -2,10 +2,11 @@ import { Component, inject } from '@angular/core'; import { AuthService } from '@core/services/auth/auth.service'; import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router'; import { CommonModule } from '@angular/common'; +import { MainLayout } from '@shared/components/main-layout/main-layout'; @Component({ selector: 'app-admin-layout', - imports: [CommonModule, RouterLink, RouterLinkActive, RouterOutlet], + imports: [CommonModule, RouterLink, RouterLinkActive, RouterOutlet, MainLayout], templateUrl: './admin-layout.html', styleUrl: './admin-layout.scss', }) diff --git a/app/booking-ui/src/app/app.routes.ts b/app/booking-ui/src/app/app.routes.ts index 2deb725..f3e0af3 100644 --- a/app/booking-ui/src/app/app.routes.ts +++ b/app/booking-ui/src/app/app.routes.ts @@ -11,6 +11,7 @@ import { DoctorLayout } from './doctor/layout/doctor-layout/doctor-layout'; import { SpecialtiesComponent } from './admin/pages/specialties/specialties'; import { AdminDashboard } from './admin/pages/admin-dashboard/admin-dashboard'; import { DoctorCalendarComponent } from './doctor/pages/doctor-calendar/doctor-calendar'; +import { PatientLayout } from './patient/layout/patient-layout/patient-layout'; export const routes: Routes = [ { path: '', component: Home }, @@ -45,5 +46,12 @@ export const routes: Routes = [ { path: 'doctors', component: DoctorsComponent }, ], }, + { + path: 'profile', + component: PatientLayout, + canActivate: [authGuard], + data: { roles: ['patient'] }, + children: [{ path: '', redirectTo: 'appointments', pathMatch: 'full' }], + }, { path: '**', redirectTo: '' }, ]; diff --git a/app/booking-ui/src/app/core/models/auth.model.ts b/app/booking-ui/src/app/core/models/auth.model.ts index 5d0681b..0671c9e 100644 --- a/app/booking-ui/src/app/core/models/auth.model.ts +++ b/app/booking-ui/src/app/core/models/auth.model.ts @@ -4,16 +4,24 @@ export enum UserRole { Patient = 'patient', } +export enum Gender { + Male = 'Male', + Female = 'Female', +} + export interface LoginRequest { email: string; password: string; } export interface RegisterRequest { - UserEmail: string; - UserPassword: string; - UserName: string; - UserSurname: string; + userName: string; + userSurname: string; + userEmail: string; + userPassword: string; + dateOfBirth?: string; + gender?: number; + phoneNumber?: string; } export interface AuthResponse { diff --git a/app/booking-ui/src/app/core/models/patient.models.ts b/app/booking-ui/src/app/core/models/patient.models.ts new file mode 100644 index 0000000..c759ad5 --- /dev/null +++ b/app/booking-ui/src/app/core/models/patient.models.ts @@ -0,0 +1,10 @@ +export interface PatientDto { + id: string; + fullName: string; + email: string; + photoUrl: string | null; + phoneNumber: string | null; + dateOfBirth: string; + gender: string; + address: string | null; +} diff --git a/app/booking-ui/src/app/core/services/patient/patient.service.spec.ts b/app/booking-ui/src/app/core/services/patient/patient.service.spec.ts new file mode 100644 index 0000000..d5d13b7 --- /dev/null +++ b/app/booking-ui/src/app/core/services/patient/patient.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { PatientService } from './patient.service'; + +describe('PatientService', () => { + let service: PatientService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(PatientService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/app/booking-ui/src/app/core/services/patient/patient.service.ts b/app/booking-ui/src/app/core/services/patient/patient.service.ts new file mode 100644 index 0000000..4f0a523 --- /dev/null +++ b/app/booking-ui/src/app/core/services/patient/patient.service.ts @@ -0,0 +1,28 @@ +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { PatientDto } from '@core/models/patient.models'; +import { environment } from '@env/environment'; +import { map } from 'rxjs/internal/operators/map'; + +@Injectable({ + providedIn: 'root', +}) +export class PatientService { + private http = inject(HttpClient); + private apiUrl = `${environment.apiUrl}/Patient`; + + getPatientProfile() { + return this.http.get(`${this.apiUrl}/profile`).pipe( + map((data) => { + return { + id: data.id, + fullName: data.fullName, + email: data.email, + photoUrl: data.photoUrl || 'assets/default-patient.png', + phoneNumber: data.phoneNumber, + dateOfBirth: data.dateOfBirth, + } as PatientDto; + }), + ); + } +} diff --git a/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.html b/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.html index 48a0474..da967ea 100644 --- a/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.html +++ b/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.html @@ -1,65 +1,48 @@ -
- - -
-
-

Overview

-
- - -
-
+ +
-
- +
+

Overview

+
+ +
- -
+ + +
+ +
+ diff --git a/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.scss b/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.scss index b921d64..a9b6009 100644 --- a/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.scss +++ b/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.scss @@ -1,4 +1,3 @@ -// --- VARIABLES & MIXINS --- $sidebar-width: 280px; $topbar-height: 64px; $transition: all 0.2s ease-in-out; @@ -11,131 +10,122 @@ $transition: all 0.2s ease-in-out; overflow: hidden; } -.sidebar { - width: $sidebar-width; - background-color: white; - border-right: 1px solid rgba(0, 0, 0, 0.08); +.brand-box { + padding: 24px; display: flex; - flex-direction: column; - flex-shrink: 0; - z-index: 10; - transition: $transition; + align-items: center; + gap: 12px; - .brand { - height: $topbar-height; - display: flex; - align-items: center; - padding: 0 24px; - gap: 12px; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); + .logo { + font-size: 32px; + color: #2563eb; + } + h2 { + margin: 0; + font-size: 1.25rem; + color: #1f2937; + } +} - .logo-icon { - font-size: 28px; - color: var(--mat-sys-primary, #005cbb); - } +.top-bar { + height: $topbar-height; + background-color: white; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 32px; + flex-shrink: 0; - h2 { - margin: 0; - font-size: 1.25rem; - font-weight: 600; - color: var(--mat-sys-on-surface, #333); - letter-spacing: -0.5px; - } + h3 { + margin: 0; + font-size: 1.25rem; + font-weight: 600; + color: var(--mat-sys-on-surface, #333); } - .menu { - flex: 1; - padding: 24px 16px; + .actions { display: flex; - flex-direction: column; - gap: 8px; - overflow-y: auto; + gap: 16px; - .menu-item { + .icon-btn { + background: none; + border: none; + cursor: pointer; + padding: 8px; + border-radius: 50%; + color: #666; + transition: $transition; display: flex; align-items: center; - gap: 12px; - padding: 12px 16px; - text-decoration: none; - color: var(--mat-sys-on-surface-variant, #666); - font-weight: 500; - border-radius: 12px; - transition: $transition; - font-size: 0.95rem; - - .material-icons { - font-size: 20px; - color: inherit; - } + justify-content: center; &:hover { - background-color: var(--mat-sys-surface-container-high, #f0f0f0); - color: var(--mat-sys-on-surface, #333); - } - - &.active { - background-color: var(--mat-sys-primary-container, #e3f2fd); - color: var(--mat-sys-on-primary-container, #005cbb); - - .material-icons { - color: var(--mat-sys-on-primary-container, #005cbb); - } + background-color: rgba(0, 0, 0, 0.05); + color: var(--mat-sys-primary, #005cbb); } } } - .user-profile { - padding: 20px 24px; - border-top: 1px solid rgba(0, 0, 0, 0.08); - display: flex; - align-items: center; - gap: 12px; - background-color: white; - - .avatar { - width: 40px; - height: 40px; - border-radius: 50%; - object-fit: cover; - border: 2px solid white; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); - } - - .info { - flex: 1; - overflow: hidden; - - .name { - margin: 0; - font-size: 0.9rem; - font-weight: 600; - color: var(--mat-sys-on-surface, #333); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } + @media (max-width: 768px) { + background-color: transparent; + } +} - .role { - margin: 0; - font-size: 0.75rem; - color: var(--mat-sys-primary, #005cbb); - font-weight: 500; - } +.menu-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + color: #4b5563; + text-decoration: none; + border-radius: 8px; + font-weight: 500; + transition: all 0.2s; + + &:hover { + background: #f3f4f6; + color: #111827; + } + &.active { + background: #eff6ff; + color: #2563eb; + .material-icons { + color: #2563eb; } + } +} - .logout-btn { - background: none; - border: none; - cursor: pointer; - padding: 8px; - border-radius: 50%; - color: #9ca3af; - transition: $transition; +.user-profile { + padding: 16px; + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 12px; + + .avatar { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; + } + .info p { + margin: 0; + font-size: 0.9rem; + line-height: 1.2; + } + .info .role { + font-size: 0.75rem; + color: #6b7280; + } - &:hover { - background-color: #fee2e2; - color: #ef4444; - } + .logout-icon-btn { + background: none; + border: none; + color: #9ca3af; + cursor: pointer; + &:hover { + color: #ef4444; } } } @@ -209,7 +199,6 @@ $transition: all 0.2s ease-in-out; } } -// --- RESPONSIVE (MOBILE) --- @media (max-width: 900px) { .layout-container { flex-direction: column; diff --git a/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.ts b/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.ts index 4e5a5fb..c74e62a 100644 --- a/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.ts +++ b/app/booking-ui/src/app/doctor/layout/doctor-layout/doctor-layout.ts @@ -3,10 +3,11 @@ import { DoctorDetailsDto } from '@core/models/doctor.model'; import { AuthService, DoctorService } from '@core/services/index'; import { Component, inject, OnInit, signal } from '@angular/core'; import { Router, RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router'; +import { MainLayout } from '@shared/components/main-layout/main-layout'; @Component({ selector: 'app-doctor-layout', - imports: [CommonModule, RouterLink, RouterLinkActive, RouterOutlet], + imports: [CommonModule, RouterLink, RouterLinkActive, RouterOutlet, MainLayout], templateUrl: './doctor-layout.html', styleUrl: './doctor-layout.scss', }) diff --git a/app/booking-ui/src/app/pages/doctor-details/components/doctor-reviews/doctor-reviews.ts b/app/booking-ui/src/app/pages/doctor-details/components/doctor-reviews/doctor-reviews.ts index 025f4da..df003c5 100644 --- a/app/booking-ui/src/app/pages/doctor-details/components/doctor-reviews/doctor-reviews.ts +++ b/app/booking-ui/src/app/pages/doctor-details/components/doctor-reviews/doctor-reviews.ts @@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common'; import { Component, EventEmitter, Input, Output, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; +import { CurrentUser } from '@core/models/auth.model'; import { ReviewDto } from '@core/models/doctor.model'; @Component({ @@ -12,7 +13,7 @@ import { ReviewDto } from '@core/models/doctor.model'; }) export class DoctorReviews { @Input({ required: true }) reviews: ReviewDto[] = []; - @Input() user!: { id: number; name: string }; + @Input() user: CurrentUser | null = null; @Input() isSubmitting = false; @Output() submitReview = new EventEmitter<{ rating: number; text: string }>(); diff --git a/app/booking-ui/src/app/pages/doctor-details/doctor-details.html b/app/booking-ui/src/app/pages/doctor-details/doctor-details.html index 3d55792..3b66b1e 100644 --- a/app/booking-ui/src/app/pages/doctor-details/doctor-details.html +++ b/app/booking-ui/src/app/pages/doctor-details/doctor-details.html @@ -11,6 +11,7 @@ diff --git a/app/booking-ui/src/app/pages/register/register.html b/app/booking-ui/src/app/pages/register/register.html index 5b0637b..f3f0db9 100644 --- a/app/booking-ui/src/app/pages/register/register.html +++ b/app/booking-ui/src/app/pages/register/register.html @@ -32,6 +32,51 @@

Create Account

+
+ + +
+ cake + + + + calendar_today +
+ +

Used to verify your identity and age.

+
+ +
+ + +
+ wc + + + + expand_more +
+
+
@@ -59,4 +104,9 @@

Create Account

Already have an account? Sign In

+
+

Форма валидна: {{ registerForm.valid }}

+

Ошибки даты: {{ registerForm.get('dateOfBirth')?.errors | json }}

+

Ошибки пола: {{ registerForm.get('gender')?.errors | json }}

+
diff --git a/app/booking-ui/src/app/pages/register/register.scss b/app/booking-ui/src/app/pages/register/register.scss index c98f620..dba7aba 100644 --- a/app/booking-ui/src/app/pages/register/register.scss +++ b/app/booking-ui/src/app/pages/register/register.scss @@ -36,12 +36,190 @@ .form-group { flex: 1; - margin-bottom: 0; // Сбрасываем отступ + margin-bottom: 0; + } + } + + .date-field-container { + .input-wrapper { + display: flex; + align-items: center; + position: relative; + border: 1.5px solid #d1d5db; + border-radius: 10px; + padding: 8px 12px; + transition: all 0.2s; + + &.focused { + border-color: #2563eb; + box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); + } + + .leading-icon { + margin-right: 10px; + color: #9ca3af; + } + + input[type='date'] { + border: none; + outline: none; + width: 100%; + background: transparent; + font-family: inherit; + cursor: pointer; + + &::-webkit-calendar-picker-indicator { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + cursor: pointer; + opacity: 0; + } + } + + .calendar-hint { + color: #2563eb; + font-size: 18px; + } + } + } + + .gender-group { + display: flex; + flex-direction: column; + gap: 8px; + + .field-label { + font-size: 0.875rem; + font-weight: 600; + color: #374151; + margin-left: 4px; + } + + .input-wrapper { + display: flex; + align-items: center; + position: relative; + background: #ffffff; + border: 1.5px solid #d1d5db; + border-radius: 12px; + padding: 0 12px; + height: 48px; + transition: all 0.2s ease; + + &.focused { + border-color: #2563eb; + box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1); + + .leading-icon { + color: #2563eb; + } + .arrow-icon { + color: #2563eb; + } + } + + .leading-icon { + color: #9ca3af; + margin-right: 12px; + font-size: 20px; + } + + .custom-select { + border: none; + outline: none; + background: transparent; + font-size: 1rem; + color: #111827; + width: 100%; + cursor: pointer; + appearance: none; + padding-right: 30px; + z-index: 2; + } + + .arrow-icon { + position: absolute; + right: 12px; + color: #9ca3af; + font-size: 24px; + pointer-events: none; + z-index: 1; + } + } + } + + .gender-group { + display: flex; + flex-direction: column; + gap: 8px; + + .field-label { + font-size: 0.875rem; + font-weight: 600; + color: #374151; + margin-left: 4px; + } + + .input-wrapper { + display: flex; + align-items: center; + position: relative; + background: #ffffff; + border: 1.5px solid #d1d5db; + border-radius: 12px; + padding: 0 12px; + height: 48px; + transition: all 0.2s ease; + + &.focused { + border-color: #2563eb; + box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1); + + .leading-icon { + color: #2563eb; + } + .arrow-icon { + color: #2563eb; + } + } + + .leading-icon { + color: #9ca3af; + margin-right: 12px; + font-size: 20px; + } + + .custom-select { + border: none; + outline: none; + background: transparent; + font-size: 1rem; + color: #111827; + width: 100%; + cursor: pointer; + appearance: none; + padding-right: 30px; + z-index: 2; + } + + .arrow-icon { + position: absolute; + right: 12px; + color: #9ca3af; + font-size: 24px; + pointer-events: none; + z-index: 1; + } } } .form-group { - margin-bottom: 20px; + margin: 20px 0; label { display: block; margin-bottom: 8px; @@ -68,7 +246,7 @@ .input-wrapper { position: relative; input { - padding-left: 40px; + padding-left: 15px; } .icon { position: absolute; diff --git a/app/booking-ui/src/app/pages/register/register.ts b/app/booking-ui/src/app/pages/register/register.ts index 8ca1214..5639cdd 100644 --- a/app/booking-ui/src/app/pages/register/register.ts +++ b/app/booking-ui/src/app/pages/register/register.ts @@ -17,12 +17,18 @@ export class RegisterComponent { private authService = inject(AuthService); private router = inject(Router); + birthDate = signal(''); + isFocused = signal(false); isLoading = signal(false); errorMessage = signal(null); + isGenderFocused = signal(false); + maxDate = new Date().toISOString().split('T')[0]; registerForm = this.fb.group({ firstName: ['', [Validators.required]], lastName: ['', [Validators.required]], + dateOfBirth: ['', [Validators.required, Validators.pattern(/^\d{4}-\d{2}-\d{2}$/)]], + gender: ['', [Validators.required]], email: ['', [Validators.required, Validators.email]], password: ['', [Validators.required, Validators.minLength(6)]], confirmPassword: ['', [Validators.required]], @@ -31,7 +37,8 @@ export class RegisterComponent { onSubmit() { if (this.registerForm.invalid) return; - const { firstName, lastName, email, password, confirmPassword } = this.registerForm.value; + const { firstName, lastName, email, password, confirmPassword, gender, dateOfBirth } = + this.registerForm.value; if (password !== confirmPassword) { this.errorMessage.set('Passwords do not match'); @@ -42,16 +49,18 @@ export class RegisterComponent { this.errorMessage.set(null); const request: RegisterRequest = { - UserEmail: email!, - UserPassword: password!, - UserName: firstName!, - UserSurname: lastName!, + userEmail: email!, + userPassword: password!, + dateOfBirth: dateOfBirth!, + userName: firstName!, + userSurname: lastName!, + gender: Number(gender), }; this.authService.register(request).subscribe({ next: () => { alert('Registration successful! Please login.'); - this.router.navigate(['/login']); + this.router.navigate(['/auth/login']); }, error: (err) => { console.error(err); @@ -62,4 +71,12 @@ export class RegisterComponent { }, }); } + + onDateChange(value: Event) { + this.birthDate.set((value.target as HTMLInputElement).value); + this.registerForm + .get('dateOfBirth') + ?.setValue((value.target as HTMLInputElement).value, { emitEvent: true }); + this.registerForm.get('dateOfBirth')?.markAsDirty(); + } } diff --git a/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.html b/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.html new file mode 100644 index 0000000..2eec5df --- /dev/null +++ b/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.html @@ -0,0 +1,32 @@ + +
+

My Health

+
+ + + + + +
+

+ Welcome back, +

{{ patientProfile()?.fullName }}

+

+
+ + +
+
+ + +
diff --git a/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.scss b/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.scss new file mode 100644 index 0000000..d31ea47 --- /dev/null +++ b/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.scss @@ -0,0 +1,121 @@ +$primary-color: #2563eb; +$sidebar-width: 280px; +$bg-color: #f3f4f6; +$transition-speed: 0.3s cubic-bezier(0.4, 0, 0.2, 1); + +.brand-box { + padding: 24px; + display: flex; + align-items: center; + gap: 12px; + + .logo { + font-size: 32px; + color: #2563eb; + } + h2 { + margin: 0; + font-size: 1.25rem; + color: #1f2937; + } +} + +.menu-item { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + color: #4b5563; + text-decoration: none; + border-radius: 8px; + font-weight: 500; + transition: all 0.2s; + + &:hover { + background: #f3f4f6; + color: #111827; + } + &.active { + background: #eff6ff; + color: #2563eb; + .material-icons { + color: #2563eb; + } + } +} + +.full-logout-btn { + width: 100%; + display: flex; + align-items: center; + gap: 12px; + padding: 16px 24px; + background: none; + border: none; + color: #ef4444; + font-weight: 500; + cursor: pointer; + &:hover { + background: #fef2f2; + } +} + +.content { + padding: 0; + + .page-wrapper { + padding: 32px; + max-width: 1200px; + margin: 0 auto; + } +} + +.top-bar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 32px; + background-color: transparent; + + h3 { + margin: 0; + font-size: 1.25rem; + font-weight: 600; + color: var(--mat-sys-on-surface, #333); + } + + .actions { + display: flex; + gap: 16px; + + .icon-btn { + background: none; + border: none; + cursor: pointer; + padding: 8px; + border-radius: 50%; + color: #666; + transition: $transition-speed; + display: flex; + align-items: center; + justify-content: center; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + color: var(--mat-sys-primary, #005cbb); + } + } + } + + .subtitle { + margin-top: 4px; + font-size: 0.875rem; + color: #6b7280; + } +} + +@media (max-width: 768px) { + .content .page-wrapper { + padding: 16px; + } +} diff --git a/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.spec.ts b/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.spec.ts new file mode 100644 index 0000000..ec3790d --- /dev/null +++ b/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PatientLayout } from './patient-layout'; + +describe('PatientLayout', () => { + let component: PatientLayout; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PatientLayout], + }).compileComponents(); + + fixture = TestBed.createComponent(PatientLayout); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.ts b/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.ts new file mode 100644 index 0000000..97b2224 --- /dev/null +++ b/app/booking-ui/src/app/patient/layout/patient-layout/patient-layout.ts @@ -0,0 +1,43 @@ +import { CommonModule } from '@angular/common'; +import { Component, inject, OnInit, signal } from '@angular/core'; +import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router'; +import { PatientDto } from '@core/models/patient.models'; +import { AuthService } from '@core/services/auth/auth.service'; +import { PatientService } from '@core/services/patient/patient.service'; +import { MainLayout } from '@shared/components/main-layout/main-layout'; + +@Component({ + selector: 'app-patient-layout', + imports: [CommonModule, RouterLink, RouterLinkActive, RouterOutlet, MainLayout], + templateUrl: './patient-layout.html', + styleUrl: './patient-layout.scss', +}) +export class PatientLayout implements OnInit { + private authService = inject(AuthService); + private patientService = inject(PatientService); + + currentUser = this.authService.currentUser; + patientProfile = signal(null); + isSidebarOpen = signal(false); + logout = signal(false); + + ngOnInit() { + const user = this.authService.currentUser(); + if (user && user.id) { + this.patientService.getPatientProfile().subscribe({ + next: (profile) => this.patientProfile.set(profile), + error: (err) => console.error('Failed to load patient profile', err), + }); + } else { + console.error('No user logged in'); + } + } + + toggleSidebar() { + this.isSidebarOpen.update((val) => !val); + } + + closeSidebar() { + this.isSidebarOpen.set(false); + } +} diff --git a/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.html b/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.html new file mode 100644 index 0000000..5cc8138 --- /dev/null +++ b/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.html @@ -0,0 +1 @@ +

patient-appointments works!

diff --git a/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.scss b/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.spec.ts b/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.spec.ts new file mode 100644 index 0000000..953d7f7 --- /dev/null +++ b/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PatientAppointments } from './patient-appointments'; + +describe('PatientAppointments', () => { + let component: PatientAppointments; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PatientAppointments], + }).compileComponents(); + + fixture = TestBed.createComponent(PatientAppointments); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.ts b/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.ts new file mode 100644 index 0000000..6e61e0e --- /dev/null +++ b/app/booking-ui/src/app/patient/pages/patient-appointments/patient-appointments.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-patient-appointments', + imports: [], + templateUrl: './patient-appointments.html', + styleUrl: './patient-appointments.scss', +}) +export class PatientAppointments {} diff --git a/app/booking-ui/src/app/shared/components/header/header.html b/app/booking-ui/src/app/shared/components/header/header.html index e1e5079..86d8bc1 100644 --- a/app/booking-ui/src/app/shared/components/header/header.html +++ b/app/booking-ui/src/app/shared/components/header/header.html @@ -96,7 +96,9 @@

All Specialties

} @else {