Skip to content

Commit 50ca3f9

Browse files
authored
Fix mobile UX issues and blog meta tags (#16)
1 parent 560c2ca commit 50ca3f9

File tree

11 files changed

+121
-43
lines changed

11 files changed

+121
-43
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "personal-website-ui",
3-
"version": "1.8.0",
3+
"version": "1.8.1",
44
"type": "module",
55
"scripts": {
66
"ng": "ng",

src/app/pages/blog-detail/blog-detail.component.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ErrorStateComponent } from '../../shared/components/error-state/error-s
1313
import { SeoService } from '../../shared/services/seo.service';
1414
import { PortfolioService } from '../../services/portfolio.service';
1515
import { BlogDetailResult } from '../../models/blog.interface';
16+
import { EnvironmentService } from '../../shared/services/environment.service';
1617

1718
@Component({
1819
selector: 'app-blog-detail',
@@ -39,6 +40,7 @@ export class BlogDetailComponent implements OnInit, OnDestroy {
3940
private blogService = inject(BlogService);
4041
private portfolioService = inject(PortfolioService);
4142
private categoryConfigService = inject(CategoryConfigService);
43+
private environmentService = inject(EnvironmentService);
4244

4345
private apiErrorSignal = signal<boolean>(false);
4446
private currentBlogSignal = signal<Blog | null>(null);
@@ -74,21 +76,28 @@ export class BlogDetailComponent implements OnInit, OnDestroy {
7476

7577
ngOnInit(): void {
7678
this.route.data.pipe(takeUntil(this.destroy$)).subscribe((data) => {
77-
const result = data['blog'] as BlogDetailResult;
78-
if (result?.blog) {
79+
const result = data['blog'] as BlogDetailResult | undefined;
80+
if (!result) {
81+
this.apiErrorSignal.set(true);
82+
this.currentBlogSignal.set(null);
83+
this.environmentService.warn('BlogDetail: Route resolver data missing - routing configuration issue');
84+
return;
85+
}
86+
if (result.blog) {
7987
this.currentBlogSignal.set(result.blog);
8088
this.apiErrorSignal.set(false);
8189
if (isPlatformBrowser(this.platformId)) {
8290
this.incrementViewCount(result.blog.id);
8391
}
8492
this.scrollToTop();
85-
} else if (result?.error === 'not_found') {
93+
} else if (result.error === 'not_found') {
8694
this.router.navigate(['/blogs']);
87-
} else if (result?.error === 'api_error') {
95+
} else if (result.error === 'api_error') {
8896
this.apiErrorSignal.set(true);
8997
this.currentBlogSignal.set(null);
9098
this.scrollToTop();
9199
} else {
100+
this.environmentService.warn('BlogDetail: Unexpected resolver state - blog and error both null');
92101
this.router.navigate(['/blogs']);
93102
}
94103
});

src/app/pages/blogs/blogs.component.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,10 +557,10 @@
557557

558558
.blogs-sidebar {
559559
position: static;
560-
order: 2;
560+
order: 1;
561561
}
562562

563563
.blogs-main {
564-
order: 1;
564+
order: 2;
565565
}
566566
}

src/app/pages/home/home.component.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,8 @@
431431
}
432432

433433
.social-links {
434-
margin-top: var(--space-4);
435-
gap: var(--space-3);
434+
margin-top: var(--space-10);
435+
gap: var(--space-4);
436436
}
437437

438438
.social-link {

src/app/shared/navigation/navigation.component.html

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,21 @@
6565
</nav>
6666

6767
<!-- Mobile Navigation Menu -->
68-
<div class="mobile-nav-menu" [class.active]="isMobileMenuOpen()">
68+
@if (isMobileMenuOpen()) {
69+
<div
70+
class="mobile-nav-menu-backdrop"
71+
tabindex="0"
72+
role="button"
73+
aria-label="Close mobile menu"
74+
(click)="closeMobileMenu()"
75+
(keydown)="onBackdropKeyDown($event)"
76+
></div>
77+
}
78+
<div
79+
class="mobile-nav-menu"
80+
[class.active]="isMobileMenuOpen()"
81+
(click)="$event.stopPropagation()"
82+
>
6983
<div class="nav-tabs">
7084
@for (item of navigationItems(); track item.id) {
7185
<div class="nav-tab" [class.active]="isCurrentRoute(item.route)">

src/app/shared/navigation/navigation.component.scss

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -427,21 +427,19 @@
427427
}
428428
}
429429

430-
.mobile-nav-menu::before {
431-
content: "";
430+
.mobile-nav-menu-backdrop {
432431
position: fixed;
433432
top: 0;
434433
left: 0;
435434
width: 100vw;
436435
height: 100vh;
437436
background: rgba(0, 0, 0, 0.4);
438-
z-index: -1;
439-
opacity: 0;
437+
z-index: 998;
438+
opacity: 1;
439+
pointer-events: auto;
440440
transition: opacity var(--duration-normal) var(--ease-standard);
441-
}
442-
443-
.mobile-nav-menu.active::before {
444-
opacity: 0;
441+
backdrop-filter: blur(2px);
442+
-webkit-backdrop-filter: blur(2px);
445443
}
446444

447445
.hamburger {

src/app/shared/navigation/navigation.component.ts

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ export class NavigationComponent implements OnDestroy {
4242
}
4343
}
4444

45+
onBackdropKeyDown(event: KeyboardEvent): void {
46+
if (event.key === 'Enter' || event.key === ' ') {
47+
event.preventDefault();
48+
this.closeMobileMenu();
49+
}
50+
}
51+
4552
@HostListener('window:scroll')
4653
onWindowScroll(): void {
4754
if (!isPlatformBrowser(this.platformId)) {
@@ -52,6 +59,16 @@ export class NavigationComponent implements OnDestroy {
5259
this.isScrolled.set(scrollY > 10);
5360
}
5461

62+
@HostListener('window:resize')
63+
onWindowResize(): void {
64+
if (!isPlatformBrowser(this.platformId)) {
65+
return;
66+
}
67+
if (window.innerWidth >= 1024 && this.isMobileMenuOpen()) {
68+
this.closeMobileMenu();
69+
}
70+
}
71+
5572
private setupRouteTracking(): void {
5673
this.router.events
5774
.pipe(
@@ -60,6 +77,9 @@ export class NavigationComponent implements OnDestroy {
6077
)
6178
.subscribe(() => {
6279
this.currentRoute.set(this.router.url);
80+
if (this.isMobileMenuOpen()) {
81+
this.closeMobileMenu();
82+
}
6383
});
6484
}
6585

@@ -76,11 +96,25 @@ export class NavigationComponent implements OnDestroy {
7696
}
7797

7898
toggleMobileMenu(): void {
79-
this.isMobileMenuOpen.update((open) => !open);
99+
if (this.isMobileMenuOpen()) {
100+
this.closeMobileMenu();
101+
} else {
102+
this.openMobileMenu();
103+
}
104+
}
105+
106+
openMobileMenu(): void {
107+
if (this.isMobileMenuOpen()) {
108+
return;
109+
}
110+
this.isMobileMenuOpen.set(true);
80111
this.updateBodyScroll();
81112
}
82113

83114
closeMobileMenu(): void {
115+
if (!this.isMobileMenuOpen()) {
116+
return;
117+
}
84118
this.isMobileMenuOpen.set(false);
85119
this.updateBodyScroll();
86120
}
@@ -92,32 +126,42 @@ export class NavigationComponent implements OnDestroy {
92126

93127
const body = document.body;
94128
if (this.isMobileMenuOpen()) {
129+
const scrollY = window.scrollY;
95130
body.style.overflow = 'hidden';
96131
body.style.position = 'fixed';
97132
body.style.width = '100%';
98-
body.style.top = `-${window.scrollY}px`;
133+
body.style.top = `-${scrollY}px`;
134+
body.setAttribute('data-scroll-y', scrollY.toString());
99135
} else {
100-
const scrollY = body.style.top;
101-
body.style.overflow = '';
102-
body.style.position = '';
103-
body.style.width = '';
104-
body.style.top = '';
105-
if (scrollY) {
106-
window.scrollTo(0, parseInt(scrollY || '0') * -1);
136+
this.restoreBodyScroll();
137+
}
138+
}
139+
140+
private restoreBodyScroll(): void {
141+
if (!isPlatformBrowser(this.platformId)) {
142+
return;
143+
}
144+
const body = document.body;
145+
const scrollY = body.getAttribute('data-scroll-y');
146+
body.style.overflow = '';
147+
body.style.position = '';
148+
body.style.width = '';
149+
body.style.top = '';
150+
body.removeAttribute('data-scroll-y');
151+
if (scrollY) {
152+
const scrollPosition = parseInt(scrollY, 10);
153+
if (!isNaN(scrollPosition) && scrollPosition >= 0) {
154+
window.scrollTo(0, scrollPosition);
107155
}
108156
}
109157
}
110158

111159
ngOnDestroy(): void {
112160
this.destroy$.next();
113161
this.destroy$.complete();
114-
115162
if (isPlatformBrowser(this.platformId) && this.isMobileMenuOpen()) {
116-
const body = document.body;
117-
body.style.overflow = '';
118-
body.style.position = '';
119-
body.style.width = '';
120-
body.style.top = '';
163+
this.isMobileMenuOpen.set(false);
164+
this.restoreBodyScroll();
121165
}
122166
}
123167

src/app/shared/services/seo.service.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,11 @@ export class SeoService {
2424
const url = `${this.baseUrl}/blogs/${blog.slug}`;
2525
const description = blog.excerpt || this.defaultDescription;
2626

27-
let image = blog.featuredImage || this.defaultImage;
28-
if (image && !image.startsWith('http://') && !image.startsWith('https://')) {
29-
image = image.startsWith('/')
30-
? `${this.baseUrl}${image}`
31-
: `${this.baseUrl}/${image}`;
32-
}
27+
// Prioritize featured image, only fallback to default if truly missing
28+
const featuredImage = blog.featuredImage?.trim();
29+
const image = featuredImage
30+
? this.normalizeImageUrl(featuredImage)
31+
: this.defaultImage;
3332

3433
const modifiedTime = blog.updatedAt;
3534
const publishedTime = blog.publishedAt || blog.createdAt;
@@ -297,4 +296,18 @@ export class SeoService {
297296
return typeMap[extension] || 'image/png';
298297
}
299298

299+
/**
300+
* Normalizes image URL to ensure it's always absolute for social media crawlers.
301+
* Handles relative paths, absolute paths, and already absolute URLs.
302+
*/
303+
private normalizeImageUrl(imageUrl: string): string {
304+
if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
305+
return imageUrl;
306+
}
307+
if (imageUrl.startsWith('/')) {
308+
return `${this.baseUrl}${imageUrl}`;
309+
}
310+
return `${this.baseUrl}/${imageUrl}`;
311+
}
312+
300313
}

src/environments/environment.prod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const environment = {
22
production: true,
33
apiUrl: 'https://api.kappilgarg.dev/api',
44
appName: 'Personal Website',
5-
version: '1.8.0',
5+
version: '1.8.1',
66
features: {
77
enableAnalytics: true,
88
enableDebugMode: false,

0 commit comments

Comments
 (0)