Skip to content

Commit f891dc9

Browse files
authored
Merge pull request #125 from soft-eng-practicum/feat/email-confirm-page
feat: Add email confirmation page
2 parents 68a57e9 + 0652acb commit f891dc9

6 files changed

Lines changed: 279 additions & 83 deletions

File tree

src/Analysim.Web/ClientApp/src/app/email-confirmation/email-confirmation.component.css

Whitespace-only changes.
Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,47 @@
1-
<p>{{result}}</p>
1+
<div class="ec-page">
2+
<div class="ec-card surface-soft">
3+
<h4 class="card-title text-center mb-2 mt-1">Email Confirmation</h4>
4+
<hr />
5+
6+
<p class="ec-message">{{ message }}</p>
7+
8+
<!-- Missing params: send to login -->
9+
<div *ngIf="!userId || !token" class="ec-block ec-actions">
10+
<a class="btn btn-primary nav-cta nav-cta--lg" routerLink="/login">
11+
Go to Login
12+
</a>
13+
</div>
14+
15+
<!-- User not registered: send to register -->
16+
<div *ngIf="showRegister" class="ec-block ec-actions">
17+
<a class="btn btn-primary nav-cta nav-cta--lg" routerLink="/register">
18+
Register Account
19+
</a>
20+
</div>
21+
22+
<!-- Valid flow -->
23+
<div
24+
*ngIf="userId && token && !linkInvalid && !showRegister"
25+
class="ec-block"
26+
>
27+
<!-- Before confirmation -->
28+
<div *ngIf="!confirmed" class="ec-actions">
29+
<button
30+
type="button"
31+
class="btn btn-primary nav-cta nav-cta--lg"
32+
(click)="onConfirmClick()"
33+
[disabled]="confirming"
34+
>
35+
{{ confirming ? "Confirming..." : "Confirm Email" }}
36+
</button>
37+
</div>
38+
39+
<!-- After confirmation -->
40+
<div *ngIf="confirmed" class="ec-actions">
41+
<a class="btn btn-primary nav-cta nav-cta--lg" routerLink="/login">
42+
Go to Login
43+
</a>
44+
</div>
45+
</div>
46+
</div>
47+
</div>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
.ec-page {
2+
min-height: 75vh;
3+
display: flex;
4+
align-items: center;
5+
justify-content: center;
6+
padding: var(--space-6);
7+
color: var(--text-on-light);
8+
}
9+
10+
.ec-card {
11+
width: 100%;
12+
max-width: 520px;
13+
border: var(--border-w-1) var(--border-style) var(--border-color);
14+
border-radius: var(--radius-1);
15+
padding: var(--space-6);
16+
text-align: center;
17+
}
18+
19+
/* Typography */
20+
.ec-title {
21+
margin: 0 0 var(--space-7);
22+
}
23+
24+
.ec-message {
25+
color: var(--text-muted);
26+
margin: 0 0 var(--space-7);
27+
}
28+
29+
/* Layout helpers */
30+
.ec-block {
31+
display: flex;
32+
flex-direction: column;
33+
align-items: center;
34+
gap: var(--space-3);
35+
}
36+
37+
.ec-actions {
38+
display: flex;
39+
justify-content: center;
40+
}
Lines changed: 97 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,108 @@
1-
import { Component, Input, OnInit } from '@angular/core';
2-
import { ActivatedRoute, Router } from '@angular/router';
3-
import { AccountService } from 'src/app/services/account.service';
4-
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
5-
import { User } from 'src/app/interfaces/user';
6-
import { NotificationService } from '../services/notification.service';
1+
import { Component, OnInit } from '@angular/core';
2+
import { ActivatedRoute } from '@angular/router';
3+
import { HttpClient } from '@angular/common/http';
74

85
@Component({
96
selector: 'app-email-confirmation',
107
templateUrl: './email-confirmation.component.html',
11-
styleUrls: ['./email-confirmation.component.css']
8+
styleUrls: ['./email-confirmation.component.scss']
129
})
1310
export class EmailConfirmationComponent implements OnInit {
11+
12+
userId: string | null = null;
13+
token: string | null = null;
1414

15-
result: string = "";
16-
17-
constructor(
18-
private router: Router,
19-
private route: ActivatedRoute,
20-
private accountService: AccountService,
21-
private modalService: BsModalService,
22-
public notfi: NotificationService
23-
) {
24-
25-
}
26-
27-
async ngOnInit(): Promise<void> {
28-
this.route.queryParams.subscribe(params=>{
29-
const res = params['result'];
30-
const checkIfSuccess = res === "true";
31-
if(checkIfSuccess)
32-
{
33-
this.router.navigate(['/login']);
34-
this.notfi.showInfo('Email Confirmation Successful','You can now login!');
35-
CheckEmailToken: Boolean = null;
15+
confirming = false;
16+
confirmed = false;
17+
error = false;
18+
linkInvalid = false;
19+
showRegister = false;
20+
21+
message: string = "Loading confirmation details...";
22+
23+
constructor(private route: ActivatedRoute, private http: HttpClient) {}
24+
25+
ngOnInit(): void {
26+
this.route.queryParams.subscribe(params => {
27+
const uid = params['userid'];
28+
const tok = params['token'];
29+
30+
this.userId = uid ?? null;
31+
this.token = tok ?? null;
32+
33+
if (!this.userId || !this.token) {
34+
this.message = "Invalid confirmation link. Missing user id or token.";
35+
return;
3636
}
37-
else{
38-
this.result = res;
37+
38+
this.message = "Click below to confirm your email.";
39+
});
40+
}
41+
42+
onConfirmClick(): void {
43+
if (!this.userId || !this.token) {
44+
this.message = "Invalid confirmation link. Missing user id or token.";
45+
this.error = true;
46+
return;
47+
}
48+
49+
this.confirming = true;
50+
this.error = false;
51+
52+
const url = `/api/Account/ConfirmEmailPost?userID=${encodeURIComponent(this.userId)}&token=${encodeURIComponent(this.token)}`;
53+
54+
this.http.post<any>(url, {}).subscribe({
55+
next: (res) => {
56+
this.confirming = false;
57+
58+
const msg = res?.message ?? "Email confirmation complete.";
59+
this.message = msg;
60+
61+
// Treat already verified as confirmed state
62+
if (msg.toLowerCase().includes("already")) {
63+
this.confirmed = true;
64+
} else if (res?.success) {
65+
this.confirmed = true;
66+
}
67+
},
68+
error: (err) => {
69+
this.confirming = false;
70+
this.error = true;
71+
72+
// Default message
73+
let msg = "Account confirmation failed. Please try again later.";
74+
75+
// If server returns JSON message
76+
const serverMsg = err?.error?.message;
77+
if (typeof serverMsg === "string" && serverMsg.trim().length > 0) {
78+
msg = serverMsg;
79+
}
80+
81+
// If server returns 500+
82+
if (err?.status >= 500) {
83+
msg = "Something went wrong while confirming your email. Please try again later.";
84+
}
85+
86+
this.message = msg;
87+
88+
// Detect invalid link / user not found cases
89+
const msgLower = serverMsg.toLowerCase();
90+
91+
// User does not exist
92+
if (msgLower.includes("not been registered")) {
93+
this.linkInvalid = true;
94+
this.showRegister = true;
95+
return;
96+
}
97+
98+
// Invalid / expired token
99+
if (
100+
msgLower.includes("invalid") ||
101+
msgLower.includes("expired")
102+
) {
103+
this.linkInvalid = true;
104+
}
39105
}
40-
})
106+
});
41107
}
42108
}

src/Analysim.Web/ClientApp/src/assets/styles/abstracts/_tokens.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
--space-4: 16px;
4242
--space-5: 20px;
4343
--space-6: 24px;
44+
--space-7: 36px;
45+
--space-8: 48px;
4446

4547
/* Borders and radius */
4648
--border-w-1: 1px;

0 commit comments

Comments
 (0)