Skip to content

Commit 900b5d5

Browse files
feat: add About page with contact form and Auth page with sign-in/sign-up functionality.
1 parent 2e4dec4 commit 900b5d5

4 files changed

Lines changed: 131 additions & 107 deletions

File tree

frontend/src/pages/about/about.component.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,20 +95,20 @@ <h3 class="text-white text-xs font-bold uppercase tracking-[0.2em] mb-6 border-l
9595
<div class="absolute top-0 right-0 w-64 h-64 bg-primary/5 rounded-full blur-[80px] pointer-events-none"></div>
9696
<h3 class="font-serif text-2xl text-white mb-2" i18n="@@aboutVIPInquiry">VIP Inquiry</h3>
9797
<p class="text-gray-400 text-sm mb-8" i18n="@@aboutVIPDesc">Request a private consultation. Our team will contact you within 24 hours.</p>
98-
<form [formGroup]="contactForm" (ngSubmit)="onSubmit()" class="space-y-6 relative z-10">
98+
<form (ngSubmit)="onSubmit()" class="space-y-6 relative z-10">
9999
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
100100
<div class="space-y-2">
101101
<label class="text-xs uppercase tracking-wider text-gray-500 font-bold" i18n="@@formFullName">Full Name</label>
102-
<input formControlName="fullName" class="w-full bg-[#151515] border-0 border-b border-[#333] focus:border-primary focus:ring-0 text-white px-0 py-3 transition-colors placeholder:text-gray-600" placeholder="e.g. Malika Karimova" i18n-placeholder="@@formFullNamePlaceholder" type="text"/>
102+
<input [formField]="contactForm.fullName" class="w-full bg-[#151515] border-0 border-b border-[#333] focus:border-primary focus:ring-0 text-white px-0 py-3 transition-colors placeholder:text-gray-600" placeholder="e.g. Malika Karimova" i18n-placeholder="@@formFullNamePlaceholder" type="text"/>
103103
</div>
104104
<div class="space-y-2">
105105
<label class="text-xs uppercase tracking-wider text-gray-500 font-bold" i18n="@@formPhone">Phone Number</label>
106-
<input formControlName="phoneNumber" class="w-full bg-[#151515] border-0 border-b border-[#333] focus:border-primary focus:ring-0 text-white px-0 py-3 transition-colors placeholder:text-gray-600" placeholder="+992 ..." i18n-placeholder="@@formPhonePlaceholder" type="tel"/>
106+
<input [formField]="contactForm.phoneNumber" class="w-full bg-[#151515] border-0 border-b border-[#333] focus:border-primary focus:ring-0 text-white px-0 py-3 transition-colors placeholder:text-gray-600" placeholder="+992 ..." i18n-placeholder="@@formPhonePlaceholder" type="tel"/>
107107
</div>
108108
</div>
109109
<div class="space-y-2">
110110
<label class="text-xs uppercase tracking-wider text-gray-500 font-bold" i18n="@@formServiceInterest">Service of Interest</label>
111-
<select formControlName="serviceOfInterest" class="w-full bg-[#151515] border-0 border-b border-[#333] focus:border-primary focus:ring-0 text-white px-0 py-3 transition-colors">
111+
<select [formField]="contactForm.serviceOfInterest" class="w-full bg-[#151515] border-0 border-b border-[#333] focus:border-primary focus:ring-0 text-white px-0 py-3 transition-colors">
112112
<option value="" disabled selected i18n="@@formSelectTreatment">Select a treatment...</option>
113113
<option i18n="@@formMedicalFacial">Medical Facial</option>
114114
<option i18n="@@formLaserTreatment">Laser Treatment</option>
@@ -118,7 +118,7 @@ <h3 class="font-serif text-2xl text-white mb-2" i18n="@@aboutVIPInquiry">VIP Inq
118118
</div>
119119
<div class="space-y-2">
120120
<label class="text-xs uppercase tracking-wider text-gray-500 font-bold" i18n="@@formMessage">Message</label>
121-
<textarea formControlName="message" class="w-full bg-[#151515] border-0 border-b border-[#333] focus:border-primary focus:ring-0 text-white px-0 py-3 transition-colors placeholder:text-gray-600 resize-none" placeholder="Tell us about your aesthetic goals..." i18n-placeholder="@@formMessagePlaceholder" rows="3"></textarea>
121+
<textarea [formField]="contactForm.message" class="w-full bg-[#151515] border-0 border-b border-[#333] focus:border-primary focus:ring-0 text-white px-0 py-3 transition-colors placeholder:text-gray-600 resize-none" placeholder="Tell us about your aesthetic goals..." i18n-placeholder="@@formMessagePlaceholder" rows="3"></textarea>
122122
</div>
123123
<div class="pt-4">
124124
<button class="w-full h-14 bg-primary hover:bg-primary-hover text-background-dark font-bold uppercase tracking-wider rounded transition-all shadow-gold flex items-center justify-center gap-2 group btn-primary-shimmer active:scale-[0.98]" type="submit">

frontend/src/pages/about/about.component.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,38 @@
1-
2-
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
1+
import { Component, ChangeDetectionStrategy, inject, signal } from '@angular/core';
32
import { CommonModule } from '@angular/common';
4-
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
3+
import { form, FormField, required } from '@angular/forms/signals';
54

65
@Component({
76
selector: 'app-about',
87
standalone: true,
9-
imports: [CommonModule, ReactiveFormsModule],
8+
imports: [CommonModule, FormField],
109
changeDetection: ChangeDetectionStrategy.OnPush,
1110
templateUrl: './about.component.html',
1211
styleUrls: ['./about.component.scss']
1312
})
1413
export class AboutComponent {
15-
// Fix: Explicitly type injected FormBuilder to resolve type inference issue.
16-
private fb: FormBuilder = inject(FormBuilder);
14+
15+
contactModel = signal({
16+
fullName: '',
17+
phoneNumber: '',
18+
serviceOfInterest: '',
19+
message: ''
20+
});
1721

18-
contactForm = this.fb.group({
19-
fullName: ['', Validators.required],
20-
phoneNumber: ['', Validators.required],
21-
serviceOfInterest: ['', Validators.required],
22-
message: ['']
22+
contactForm = form(this.contactModel, (schema) => {
23+
required(schema.fullName);
24+
required(schema.phoneNumber);
25+
required(schema.serviceOfInterest);
2326
});
2427

2528
onSubmit() {
26-
if (this.contactForm.valid) {
27-
console.log('Form Submitted', this.contactForm.value);
29+
// Basic validation check
30+
const isNameValid = this.contactForm.fullName().valid();
31+
const isPhoneValid = this.contactForm.phoneNumber().valid();
32+
const isServiceValid = this.contactForm.serviceOfInterest().valid();
33+
34+
if (isNameValid && isPhoneValid && isServiceValid) {
35+
console.log('Form Submitted', this.contactModel());
2836
// Here you would typically send the data to a service
2937
} else {
3038
console.log('Form is invalid');

frontend/src/pages/auth/auth.component.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ <h1 class="font-display text-4xl md:text-5xl text-gray-900 dark:text-white mb-2
3333
</button>
3434
</div>
3535
<div class="p-8">
36-
<form class="space-y-6" [formGroup]="loginForm" (ngSubmit)="onSubmit()">
36+
<form class="space-y-6" (ngSubmit)="onSubmit()">
3737

3838
@if (authMode() === 'signup') {
3939
<div class="grid grid-cols-2 gap-4 animate-slide-up">
@@ -45,7 +45,7 @@ <h1 class="font-display text-4xl md:text-5xl text-gray-900 dark:text-white mb-2
4545
id="firstName"
4646
placeholder="Jane"
4747
type="text"
48-
formControlName="firstName"/>
48+
[formField]="loginForm.firstName"/>
4949
</div>
5050
</div>
5151
<div class="space-y-2">
@@ -56,7 +56,7 @@ <h1 class="font-display text-4xl md:text-5xl text-gray-900 dark:text-white mb-2
5656
id="lastName"
5757
placeholder="Doe"
5858
type="text"
59-
formControlName="lastName"/>
59+
[formField]="loginForm.lastName"/>
6060
</div>
6161
</div>
6262
</div>
@@ -69,7 +69,7 @@ <h1 class="font-display text-4xl md:text-5xl text-gray-900 dark:text-white mb-2
6969
id="phone"
7070
placeholder="+992 00 000 0000"
7171
type="tel"
72-
formControlName="phone"/>
72+
[formField]="loginForm.phone"/>
7373
</div>
7474
</div>
7575
}
@@ -82,7 +82,7 @@ <h1 class="font-display text-4xl md:text-5xl text-gray-900 dark:text-white mb-2
8282
id="email"
8383
placeholder="admin@mavluda.beauty"
8484
type="email"
85-
formControlName="email"/>
85+
[formField]="loginForm.email"/>
8686
</div>
8787
</div>
8888

@@ -93,7 +93,7 @@ <h1 class="font-display text-4xl md:text-5xl text-gray-900 dark:text-white mb-2
9393
<input class="w-full pl-12 pr-12 py-3 bg-gray-50 dark:bg-[#1C1C1C] border border-gray-200 dark:border-[#333] rounded-lg text-gray-900 dark:text-white placeholder:text-gray-500 transition-all"
9494
id="password"
9595
[type]="showPassword() ? 'text' : 'password'"
96-
formControlName="password"/>
96+
[formField]="loginForm.password"/>
9797
<button class="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-primary transition-colors" type="button" (click)="togglePassword()">
9898
<span class="material-symbols-outlined text-[20px]">{{ showPassword() ? 'visibility_off' : 'visibility' }}</span>
9999
</button>
@@ -102,7 +102,7 @@ <h1 class="font-display text-4xl md:text-5xl text-gray-900 dark:text-white mb-2
102102

103103
<div class="flex items-center justify-between text-sm">
104104
<label class="flex items-center space-x-2 cursor-pointer group">
105-
<input class="w-4 h-4 rounded border-gray-300 dark:border-white/30 text-primary focus:ring-primary bg-transparent" type="checkbox" formControlName="rememberMe"/>
105+
<input class="w-4 h-4 rounded border-gray-300 dark:border-white/30 text-primary focus:ring-primary bg-transparent" type="checkbox" [formField]="loginForm.rememberMe"/>
106106
<span class="text-gray-600 dark:text-gray-400 group-hover:text-gray-800 dark:group-hover:text-gray-200 transition-colors">Remember me</span>
107107
</label>
108108
@if (authMode() === 'signin') {
Lines changed: 98 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1-
import { Component, ChangeDetectionStrategy, inject, signal } from '@angular/core';
1+
import { Component, ChangeDetectionStrategy, inject, signal, effect } from '@angular/core';
22
import { CommonModule, DOCUMENT } from '@angular/common';
3-
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
43
import { Router } from '@angular/router';
4+
import { form, FormField, required, email, minLength } from '@angular/forms/signals';
55
import { AuthService } from '@entities/user/auth.service';
66
import { LanguageSwitcherComponent } from '../../features/language-selection/language-switcher.component';
77

88
@Component({
99
selector: 'app-auth',
1010
standalone: true,
11-
imports: [CommonModule, ReactiveFormsModule, LanguageSwitcherComponent],
11+
imports: [CommonModule, FormField, LanguageSwitcherComponent],
1212
changeDetection: ChangeDetectionStrategy.OnPush,
1313
templateUrl: './auth.component.html',
1414
styleUrls: ['./auth.component.scss']
1515
})
1616
export class AuthComponent {
17-
private fb: FormBuilder = inject(FormBuilder);
1817
private router: Router = inject(Router);
1918
public authService = inject(AuthService);
2019
private document: Document = inject(DOCUMENT);
@@ -27,46 +26,46 @@ export class AuthComponent {
2726
// 'signin' or 'signup' mode
2827
authMode = signal<'signin' | 'signup'>('signin');
2928

30-
loginForm = this.fb.group({
31-
firstName: [''],
32-
lastName: [''],
33-
email: ['admin@mavluda.beauty', [Validators.required, Validators.email]],
34-
password: ['password123', [Validators.required, Validators.minLength(6)]]
29+
loginModel = signal({
30+
firstName: '',
31+
lastName: '',
32+
phone: '',
33+
email: 'admin@mavluda.beauty',
34+
password: 'password123',
35+
rememberMe: false
3536
});
3637

37-
setAuthMode(mode: 'signin' | 'signup') {
38-
this.authMode.set(mode);
39-
this.errorMessage.set(null); // Clear errors
40-
const firstNameControl = this.loginForm.get('firstName');
41-
const lastNameControl = this.loginForm.get('lastName');
42-
43-
if (mode === 'signup') {
44-
firstNameControl?.setValidators([Validators.required]);
45-
lastNameControl?.setValidators([Validators.required]);
38+
loginForm = form(this.loginModel, (schema) => {
39+
required(schema.email);
40+
email(schema.email);
41+
required(schema.password);
42+
minLength(schema.password, 6);
43+
});
4644

47-
// Clear defaults for signup
48-
if (this.loginForm.get('email')?.value === 'admin@mavluda.beauty') {
49-
this.loginForm.patchValue({
50-
firstName: '',
51-
lastName: '',
52-
email: '',
53-
password: ''
54-
});
45+
constructor() {
46+
// Effect to clear/restore defaults when switching modes
47+
effect(() => {
48+
const mode = this.authMode();
49+
this.errorMessage.set(null);
50+
51+
const current = this.loginModel();
52+
53+
if (mode === 'signup') {
54+
// Clear defaults if switching to signup and generic admin email is present
55+
if (current.email === 'admin@mavluda.beauty') {
56+
this.loginModel.update(v => ({...v, firstName: '', lastName: '', email: '', password: ''}));
57+
}
58+
} else {
59+
// Restore default admin credentials for demo convenience if empty
60+
if (!current.email) {
61+
this.loginModel.update(v => ({...v, email: 'admin@mavluda.beauty', password: 'password123'}));
62+
}
5563
}
56-
} else {
57-
firstNameControl?.clearValidators();
58-
lastNameControl?.clearValidators();
64+
});
65+
}
5966

60-
// Restore default admin credentials for demo convenience if empty
61-
if (!this.loginForm.get('email')?.value) {
62-
this.loginForm.patchValue({
63-
email: 'admin@mavluda.beauty',
64-
password: 'password123'
65-
});
66-
}
67-
}
68-
firstNameControl?.updateValueAndValidity();
69-
lastNameControl?.updateValueAndValidity();
67+
setAuthMode(mode: 'signin' | 'signup') {
68+
this.authMode.set(mode);
7069
}
7170

7271
togglePassword() {
@@ -83,53 +82,70 @@ export class AuthComponent {
8382
}
8483

8584
onSubmit() {
86-
if (this.loginForm.valid) {
87-
this.isLoading.set(true);
88-
this.errorMessage.set(null);
85+
// Manual validation check
86+
const emailValid = this.loginForm.email().valid();
87+
const passValid = this.loginForm.password().valid();
88+
89+
if (!emailValid || !passValid) {
90+
// Since we don't have markAllAsTouched, we rely on user having interacted or just return
91+
// If fields are untouched, valid() might be false but errors not shown?
92+
// Usually required() makes it invalid immediately but touched is false.
93+
return;
94+
}
95+
96+
// Manual validation for signup fields
97+
if (this.authMode() === 'signup') {
98+
const firstName = this.loginModel().firstName;
99+
const lastName = this.loginModel().lastName;
89100

90-
const formValue = this.loginForm.value;
91-
const mode = this.authMode();
101+
if (!firstName || !lastName) {
102+
return;
103+
}
104+
}
92105

93-
if (mode === 'signin') {
94-
this.authService.login({
95-
email: formValue.email!,
96-
password: formValue.password!
97-
}).subscribe({
98-
next: () => {
99-
this.isLoading.set(false);
100-
// Navigation handled in guard or explicit check
101-
if (this.authService.isAdmin()) {
102-
this.router.navigate(['/admin/dashboard']);
103-
} else {
104-
this.router.navigate(['/user/home']);
105-
}
106-
},
107-
error: (err) => {
108-
this.isLoading.set(false);
109-
this.errorMessage.set('Login failed. Please check credentials.');
110-
console.error(err);
111-
}
112-
});
113-
} else {
114-
this.authService.register({
115-
firstName: formValue.firstName!,
116-
lastName: formValue.lastName || undefined,
117-
email: formValue.email!,
118-
password: formValue.password!
119-
}).subscribe({
120-
next: () => {
121-
this.isLoading.set(false);
106+
this.isLoading.set(true);
107+
this.errorMessage.set(null);
108+
109+
// Get values directly from model signal
110+
const formValue = this.loginModel();
111+
const mode = this.authMode();
112+
113+
if (mode === 'signin') {
114+
this.authService.login({
115+
email: formValue.email,
116+
password: formValue.password
117+
}).subscribe({
118+
next: () => {
119+
this.isLoading.set(false);
120+
if (this.authService.isAdmin()) {
121+
this.router.navigate(['/admin/dashboard']);
122+
} else {
122123
this.router.navigate(['/user/home']);
123-
},
124-
error: (err) => {
125-
this.isLoading.set(false);
126-
this.errorMessage.set('Registration failed. Email might be taken.');
127-
console.error(err);
128-
}
129-
});
130-
}
124+
}
125+
},
126+
error: (err) => {
127+
this.isLoading.set(false);
128+
this.errorMessage.set('Login failed. Please check credentials.');
129+
console.error(err);
130+
}
131+
});
131132
} else {
132-
this.loginForm.markAllAsTouched();
133+
this.authService.register({
134+
firstName: formValue.firstName,
135+
lastName: formValue.lastName || undefined,
136+
email: formValue.email,
137+
password: formValue.password
138+
}).subscribe({
139+
next: () => {
140+
this.isLoading.set(false);
141+
this.router.navigate(['/user/home']);
142+
},
143+
error: (err) => {
144+
this.isLoading.set(false);
145+
this.errorMessage.set('Registration failed. Email might be taken.');
146+
console.error(err);
147+
}
148+
});
133149
}
134150
}
135151
}

0 commit comments

Comments
 (0)