Skip to content

Commit

Permalink
🎨 Style: update ui for login page with google sign-in and forgot pass…
Browse files Browse the repository at this point in the history
…word implementation
  • Loading branch information
Sye0w committed Oct 3, 2024
1 parent 7c04348 commit 475991c
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 26 deletions.
1 change: 1 addition & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
],
"styles": [
"src/styles.scss",
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"node_modules/primeng/resources/themes/lara-light-blue/theme.css",
"node_modules/primeng/resources/primeng.min.css",
"node_modules/primeicons/primeicons.css"
Expand Down
80 changes: 77 additions & 3 deletions src/app/services/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Injectable } from '@angular/core';
import { Auth, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, GoogleAuthProvider, signInWithPopup, User, AuthError } from '@angular/fire/auth';
import { Auth, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, GoogleAuthProvider, signInWithPopup, User, AuthError, updateProfile, updateEmail, sendPasswordResetEmail } from '@angular/fire/auth';
import { BehaviorSubject, Observable } from 'rxjs';
import { IUser } from '../blog.interface';
import { Firestore, doc, getDoc, setDoc, updateDoc } from '@angular/fire/firestore';

export enum AuthErrorType {
EmailAlreadyInUse = 'auth/email-already-in-use',
Expand All @@ -20,7 +21,7 @@ export class AuthService {
private userSubject: BehaviorSubject<User | null> = new BehaviorSubject<User | null>(null);
user$: Observable<User | null> = this.userSubject.asObservable();

constructor(private auth: Auth) {
constructor(private auth: Auth, private firestore: Firestore) {
this.auth.onAuthStateChanged(user => {
this.userSubject.next(user);
});
Expand All @@ -43,15 +44,43 @@ export class AuthService {
}
}

async forgotPassword(email: string): Promise<void> {
try {
await sendPasswordResetEmail(this.auth, email);
} catch (error) {
throw this.handleAuthError(error as AuthError);
}
}

async googleSignIn(): Promise<void> {
const provider = new GoogleAuthProvider();
try {
await signInWithPopup(this.auth, provider);
const result = await signInWithPopup(this.auth, provider);
const user = result.user;
await this.createOrUpdateUserInFirestore(user);
} catch (error) {
throw this.handleAuthError(error as AuthError);
}
}

private async createOrUpdateUserInFirestore(user: User): Promise<void> {
const userDocRef = doc(this.firestore, 'users', user.uid);
const userDocSnap = await getDoc(userDocRef);

const userData = {
username: user.displayName || '',
email: user.email || '',
image: user.photoURL || '',
uid: user.uid
};

if (userDocSnap.exists()) {
await updateDoc(userDocRef, userData);
} else {
await setDoc(userDocRef, userData);
}
}

async logout(): Promise<void> {
try {
await signOut(this.auth);
Expand All @@ -60,6 +89,51 @@ export class AuthService {
}
}

async updateUserProfile(user: IUser): Promise<void> {
const currentUser = this.auth.currentUser;
if (!currentUser) {
throw new Error('No authenticated user found');
}
try {
// Update Auth profile
await updateProfile(currentUser, {
displayName: user.username,
photoURL: user.image
});

// Update email if changed
if (currentUser.email !== user.email) {
await updateEmail(currentUser, user.email);
}

// Check if user document exists in Firestore
const userDocRef = doc(this.firestore, 'users', currentUser.uid);
const userDocSnap = await getDoc(userDocRef);

if (userDocSnap.exists()) {
// Update existing document
await updateDoc(userDocRef, {
username: user.username,
email: user.email,
image: user.image
});
} else {
// Create new document
await setDoc(userDocRef, {
username: user.username,
email: user.email,
image: user.image,
uid: currentUser.uid
});
}

this.userSubject.next(currentUser);
} catch (error) {
console.error('Error updating user profile:', error);
throw this.handleAuthError(error as AuthError);
}
}

private handleAuthError(error: AuthError): { type: AuthErrorType, message: string } {
let errorType: AuthErrorType;
switch (error.code) {
Expand Down
29 changes: 23 additions & 6 deletions src/app/views/auth/login/login.component.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
<p-toast></p-toast>
<section>
<h4>Login</h4>
<h4>{{ isForgotPasswordMode ? 'Forgot Password' : 'Login' }}</h4>
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<div class="form-grp">
<input formControlName="email" type="email" placeholder="Email">
</div>
<div class="form-grp">
<div class="form-grp" *ngIf="!isForgotPasswordMode">
<input formControlName="password" type="password" placeholder="Password">
</div>
<button type="submit" mat-button [disabled]="isLoading">
<p>
{{ isLoading ? 'Logging in...' : 'Login' }}
</p>
<button type="submit" mat-raised-button color="primary" [disabled]="isLoading">
{{ isLoading ? 'Processing...' : (isForgotPasswordMode ? 'Reset Password' : 'Login') }}
</button>
</form>

<div>
<p class="forgot-password" (click)="toggleForgotPasswordMode()">
{{ isForgotPasswordMode ? 'Back to Login' : 'Forgot Password?' }}
</p>

<div class="divider">
<mat-divider></mat-divider>
<span>OR</span>
<mat-divider></mat-divider>
</div>

<button (click)="googleSignIn()" mat-raised-button class="google-btn" [disabled]="isLoading">
<img src="../../../../assets/images/google.png" alt="Google logo" class="google-logo">
{{ isLoading ? 'Processing...' : 'Sign in with Google' }}
</button>

</div>

<footer>
<span>Don't have an account?</span>
<a routerLink="/auth/register/" class="link-effect">
Expand Down
52 changes: 48 additions & 4 deletions src/app/views/auth/login/login.component.scss
Original file line number Diff line number Diff line change
@@ -1,22 +1,66 @@
@use '../../../../partials/variables' as *;
@use '../../../../partials/mixins' as *;

section{
section {
@include formCard-style;
}

.forgot-password {
cursor: pointer;
color: $burntsienna;
text-decoration: underline;
margin-top: 1rem;
text-align: right;
}

button[mat-raised-button] {
width: 100%;
margin-top: 1rem;
}

.divider {
display: flex;
align-items: center;
margin: 1rem 0;

mat-divider {
flex-grow: 1;
}

span {
padding: 0 1rem;
color: rgba(0, 0, 0, 0.54);
font-size: 0.875rem;
}
}

.google-btn {
display: flex;
align-items: center !important;
justify-content: center;
background-color: #fff !important;
color: rgba(0, 0, 0, 0.54);
border: 1px solid #ccc;

&:hover {
background-color: #f8f8f8;
}

.google-logo {
width: 18px;
height: 18px;
margin-right: 8px;
}
}

::ng-deep .p-toast {
.p-toast-message {
background-color: transparent;


.p-toast-message-content {
background-color: transparent;
padding: 1rem;



.p-toast-message-text {
margin-left: 1rem;
}
Expand Down
59 changes: 46 additions & 13 deletions src/app/views/auth/login/login.component.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import { Component, OnInit } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { Router, RouterModule } from '@angular/router';
import { AuthErrorType, AuthService, } from '../../../services/auth/auth.service';
import { AuthErrorType, AuthService } from '../../../services/auth/auth.service';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';
import { MatDividerModule } from '@angular/material/divider';

@Component({
selector: 'app-login',
standalone: true,
imports: [RouterModule, MatButtonModule, ReactiveFormsModule, CommonModule, ToastModule],
imports: [RouterModule,MatDividerModule, MatButtonModule, ReactiveFormsModule, CommonModule, ToastModule],
templateUrl: './login.component.html',
styleUrl: './login.component.scss',
providers: [MessageService]
})
export class LoginComponent implements OnInit {
loginForm!: FormGroup;
isLoading: boolean = false;
isForgotPasswordMode: boolean = false;

constructor(
private authService: AuthService,
Expand All @@ -42,13 +44,19 @@ export class LoginComponent implements OnInit {
this.isLoading = true;
const { email, password } = this.loginForm.value;
try {
await this.authService.login(email, password);
this.messageService.add({severity:'success', summary: 'Success', detail: 'Login successful!',life: 3000});
setTimeout(() => {
this.router.navigate(['/fireblog/posts'])
}, 3000);
if (this.isForgotPasswordMode) {
await this.authService.forgotPassword(email);
this.messageService.add({severity:'success', summary: 'Success', detail: 'Password reset email sent!', life: 3000});
this.isForgotPasswordMode = false;
} else {
await this.authService.login(email, password);
this.messageService.add({severity:'success', summary: 'Success', detail: 'Login successful!', life: 3000});
setTimeout(() => {
this.router.navigate(['/fireblog/posts']);
}, 3000);
}
} catch (error: any) {
this.handleRegistrationError(error);
this.handleAuthError(error);
} finally {
this.isLoading = false;
}
Expand All @@ -57,14 +65,39 @@ export class LoginComponent implements OnInit {
}
}

private handleRegistrationError(error: { type: AuthErrorType, message: string }) {
async googleSignIn() {
this.isLoading = true;
try {
await this.authService.googleSignIn();
this.messageService.add({severity:'success', summary: 'Success', detail: 'Google sign-in successful!', life: 3000});
setTimeout(() => {
this.router.navigate(['/fireblog/posts']);
}, 3000);
} catch (error: any) {
this.handleAuthError(error);
} finally {
this.isLoading = false;
}
}

toggleForgotPasswordMode() {
this.isForgotPasswordMode = !this.isForgotPasswordMode;
if (this.isForgotPasswordMode) {
this.loginForm.get('password')?.clearValidators();
} else {
this.loginForm.get('password')?.setValidators([Validators.required]);
}
this.loginForm.get('password')?.updateValueAndValidity();
}

private handleAuthError(error: { type: AuthErrorType, message: string }) {
let errorMessage: string;
switch (error.type) {
case AuthErrorType.EmailAlreadyInUse:
errorMessage = 'This email is already in use. Please try a different email.';
case AuthErrorType.UserNotFound:
errorMessage = 'User not found. Please check your email or sign up.';
break;
case AuthErrorType.WeakPassword:
errorMessage = 'The password is too weak. Please choose a stronger password.';
case AuthErrorType.WrongPassword:
errorMessage = 'Incorrect password. Please try again.';
break;
case AuthErrorType.InvalidEmail:
errorMessage = 'The email address is invalid. Please enter a valid email.';
Expand Down
Binary file added src/assets/images/google.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 475991c

Please sign in to comment.