Skip to content

Commit 5f9b444

Browse files
committed
feat: add details page, add translations
1 parent eb0daa0 commit 5f9b444

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+718
-143
lines changed

apps/blog/src/assets/i18n/en.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,5 +314,19 @@
314314
"section1": "We’ll transfer your article to the CMS and schedule its publication.",
315315
"section2": "We’ll also promote it on social media to maximize its reach."
316316
}
317+
},
318+
"presentations": {
319+
"listTitle": "Meetup presentations",
320+
"watchRecording": "WATCH RECORDING",
321+
"authoredBy": "by {{author}}",
322+
"workshopsTitle": "Angular Workshops",
323+
"workshopsDescription": "Whether you want to level up your individual skills or train your entire team, our workshops are 100% hands-on practice with an expert, directly translating into the quality of your code and work speed with Mateusz Stefańczyk (Google Developer Expert).",
324+
"workshopsButton": "CHECK OPEN WORKSHOPS"
325+
},
326+
"ebook": {
327+
"title": "FREE EBOOK",
328+
"subTitle": "The Angular Evolution Guide: ng20+ Summary for CTOs",
329+
"description": "Download to discover how the latest Angular version improves performance, DX, UX and overall app efficiency, and how it affects your business.",
330+
"downloadButton": "DOWNLOAD NOW"
317331
}
318332
}

apps/blog/src/assets/i18n/pl.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,5 +317,19 @@
317317
"section1": "Teraz my zajmiemy się przeniesieniem twojego artykułu do CMS’a i zaplanujemy jego opublikowanie",
318318
"section2": "Zajmiemy się również jego promowaniem w mediach społecznościowych, aby jak najwięcej osób mogło skorzystać z wiedzy, którą w nim zawarłeś!"
319319
}
320+
},
321+
"presentations": {
322+
"listTitle": "Prezentacje Meetup",
323+
"watchRecording": "OBEJRZYJ NAGRYWANIE",
324+
"authoredBy": "autor: {{author}}",
325+
"workshopsTitle": "Warsztaty Angular",
326+
"workshopsDescription": "Niezależnie od tego, czy chcesz podnieść swoje umiejętności, czy przeszkolić cały zespół, nasze warsztaty to 100% praktyki z ekspertem, co bezpośrednio przekłada się na jakość twojego kodu i szybkość pracy z Mateuszem Stefańczykiem (Google Developer Expert).",
327+
"workshopsButton": "SPRAWDŹ OTWARTE WARSZTATY"
328+
},
329+
"ebook": {
330+
"title": "BEZPŁATNY EBOOK",
331+
"subTitle": "Przewodnik po ewolucji Angulara: Podsumowanie ng20+ dla CTO",
332+
"description": "Pobierz, aby odkryć, jak najnowsza wersja Angulara poprawia wydajność, DX, UX i ogólną efektywność aplikacji oraz jak wpływa na Twoją firmę.",
333+
"downloadButton": "POBIERZ TERAZ"
320334
}
321335
}

apps/blog/src/assets/icons/video-content.svg

Loading

libs/blog-contracts/presentations/src/lib/presentations.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ import { ArticlePreview } from '@angular-love/contracts/articles';
33
export type PresentationPreview = Omit<
44
ArticlePreview,
55
'readingTime' | 'difficulty'
6-
>;
6+
> & {
7+
eventName: string;
8+
presentationUrl: string;
9+
};

libs/blog/articles/feature-shell/src/lib/routes.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ export const articleRoutes: Routes = [
2424
loadComponent: async () =>
2525
(await import('@angular-love/blog/presentations/presentations-page'))
2626
.PresentationsPageComponent,
27-
data: {
28-
seo: { title: 'Presentations', autoHrefLang: true },
29-
category: 'presentations',
30-
title: 'Angular Presentations',
31-
id: 'angular-presentations',
32-
},
27+
},
28+
{
29+
path: 'presentation/:presentationSlug',
30+
loadComponent: async () =>
31+
(
32+
await import(
33+
'@angular-love/blog/presentations/presentation-details-page'
34+
)
35+
).PresentationDetailsPageComponent,
3336
},
3437
{
3538
path: 'guides',

libs/blog/layouts/ui-layouts/src/lib/layout/layout.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<main
2-
class="mx-auto h-full w-full px-2 py-4 md:px-4 xl:px-0"
2+
class="relative mx-auto h-full w-full px-2 py-4 md:px-4 xl:px-0"
33
[class]="{
44
'max-w-screen-xl': !layoutConfig()?.roadmap,
55
'overflow-hidden': layoutConfig()?.roadmap,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './lib/state/presentations-list.store';
2+
export * from './lib/state/presentation-detail.store';

libs/blog/presentations/data-access/src/lib/infrastructure/presentations.service.ts

Lines changed: 111 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,99 @@ import { ConfigService } from '@angular-love/shared/config';
88

99
import { PresentationsQuery } from '../dto/presentations.query';
1010

11+
const MOCK_PRESENTATIONS = [
12+
{
13+
slug: 'from-chaos-to-clarity-mastering-state-management',
14+
title: 'From Chaos to Clarity: Mastering State Management in Angular',
15+
excerpt:
16+
'Discover practical patterns for managing complex application state without losing your mind.',
17+
featuredImageUrl: null,
18+
eventName: 'Angular Camp',
19+
presentationUrl: 'https://www.youtube.com/@angularlove/videos',
20+
publishDate: '2025-03-15',
21+
author: {
22+
name: 'Sarah Chen',
23+
slug: 'sarah-chen',
24+
avatarUrl: 'assets/mock-avatar.png',
25+
},
26+
},
27+
{
28+
slug: 'zero-to-hero-building-accessible-angular-apps',
29+
title: 'Zero to Hero: Building Accessible Angular Applications',
30+
excerpt:
31+
'Transform your Angular apps into inclusive experiences that everyone can use and enjoy.',
32+
featuredImageUrl: null,
33+
eventName: 'Angular Camp',
34+
presentationUrl: 'https://www.youtube.com/@angularlove/videos',
35+
publishDate: '2025-03-14',
36+
author: {
37+
name: 'Marcus Thompson',
38+
slug: 'marcus-thompson',
39+
avatarUrl: 'assets/mock-avatar.png',
40+
},
41+
},
42+
{
43+
slug: 'performance-wizardry-making-angular-blazingly-fast',
44+
title: 'Performance Wizardry: Making Angular Blazingly Fast',
45+
excerpt:
46+
'Learn the secrets behind lightning-fast Angular applications that delight users.',
47+
featuredImageUrl: null,
48+
eventName: 'Angular Camp',
49+
presentationUrl: 'https://www.youtube.com/@angularlove/videos',
50+
publishDate: '2025-03-13',
51+
author: {
52+
name: 'Priya Sharma',
53+
slug: 'priya-sharma',
54+
avatarUrl: 'assets/mock-avatar.png',
55+
},
56+
},
57+
{
58+
slug: 'reactive-revolution-rxjs-patterns-that-actually-work',
59+
title: 'Reactive Revolution: RxJS Patterns That Actually Work',
60+
excerpt:
61+
'Cut through the complexity and master RxJS with real-world patterns you can use today.',
62+
featuredImageUrl: null,
63+
eventName: 'Angular Camp',
64+
presentationUrl: 'https://www.youtube.com/@angularlove/videos',
65+
publishDate: '2025-03-12',
66+
author: {
67+
name: 'Lukas Müller',
68+
slug: 'lukas-muller',
69+
avatarUrl: 'assets/mock-avatar.png',
70+
},
71+
},
72+
{
73+
slug: 'component-architecture-building-scalable-design-systems',
74+
title: 'Component Architecture: Building Scalable Design Systems',
75+
excerpt:
76+
'Create maintainable, reusable components that grow with your application and team.',
77+
featuredImageUrl: null,
78+
eventName: 'Angular Camp',
79+
presentationUrl: 'https://www.youtube.com/@angularlove/videos',
80+
publishDate: '2025-03-11',
81+
author: {
82+
name: 'Elena Rodriguez',
83+
slug: 'elena-rodriguez',
84+
avatarUrl: 'assets/mock-avatar.png',
85+
},
86+
},
87+
{
88+
slug: 'debugging-like-a-detective-advanced-angular-techniques',
89+
title: 'Debugging Like a Detective: Advanced Angular Techniques',
90+
excerpt:
91+
'Uncover hidden bugs and solve mysterious issues with professional debugging strategies.',
92+
featuredImageUrl: null,
93+
eventName: 'Angular Camp',
94+
presentationUrl: 'https://www.youtube.com/@angularlove/videos',
95+
publishDate: '2025-03-10',
96+
author: {
97+
name: 'Takeshi Nakamura',
98+
slug: 'takeshi-nakamura',
99+
avatarUrl: 'assets/mock-avatar.png',
100+
},
101+
},
102+
];
103+
11104
@Injectable({ providedIn: 'root' })
12105
export class PresentationsService {
13106
private readonly _apiBaseUrl = inject(ConfigService).get('apiBaseUrl');
@@ -17,92 +110,29 @@ export class PresentationsService {
17110
query: PresentationsQuery,
18111
): Observable<ArrayResponse<PresentationPreview>> {
19112
return of({
20-
data: [
21-
{
22-
slug: 'from-chaos-to-clarity-mastering-state-management',
23-
title: 'From Chaos to Clarity: Mastering State Management in Angular',
24-
excerpt:
25-
'Discover practical patterns for managing complex application state without losing your mind.',
26-
featuredImageUrl: null,
27-
publishDate: '2025-03-15',
28-
author: {
29-
name: 'Sarah Chen',
30-
slug: 'sarah-chen',
31-
avatarUrl: 'assets/mock-avatar.png',
32-
},
33-
},
34-
{
35-
slug: 'zero-to-hero-building-accessible-angular-apps',
36-
title: 'Zero to Hero: Building Accessible Angular Applications',
37-
excerpt:
38-
'Transform your Angular apps into inclusive experiences that everyone can use and enjoy.',
39-
featuredImageUrl: null,
40-
publishDate: '2025-03-14',
41-
author: {
42-
name: 'Marcus Thompson',
43-
slug: 'marcus-thompson',
44-
avatarUrl: 'assets/mock-avatar.png',
45-
},
46-
},
47-
{
48-
slug: 'performance-wizardry-making-angular-blazingly-fast',
49-
title: 'Performance Wizardry: Making Angular Blazingly Fast',
50-
excerpt:
51-
'Learn the secrets behind lightning-fast Angular applications that delight users.',
52-
featuredImageUrl: null,
53-
publishDate: '2025-03-13',
54-
author: {
55-
name: 'Priya Sharma',
56-
slug: 'priya-sharma',
57-
avatarUrl: 'assets/mock-avatar.png',
58-
},
59-
},
60-
{
61-
slug: 'reactive-revolution-rxjs-patterns-that-actually-work',
62-
title: 'Reactive Revolution: RxJS Patterns That Actually Work',
63-
excerpt:
64-
'Cut through the complexity and master RxJS with real-world patterns you can use today.',
65-
featuredImageUrl: null,
66-
publishDate: '2025-03-12',
67-
author: {
68-
name: 'Lukas Müller',
69-
slug: 'lukas-muller',
70-
avatarUrl: 'assets/mock-avatar.png',
71-
},
72-
},
73-
{
74-
slug: 'component-architecture-building-scalable-design-systems',
75-
title: 'Component Architecture: Building Scalable Design Systems',
76-
excerpt:
77-
'Create maintainable, reusable components that grow with your application and team.',
78-
featuredImageUrl: null,
79-
publishDate: '2025-03-11',
80-
author: {
81-
name: 'Elena Rodriguez',
82-
slug: 'elena-rodriguez',
83-
avatarUrl: 'assets/mock-avatar.png',
84-
},
85-
},
86-
{
87-
slug: 'debugging-like-a-detective-advanced-angular-techniques',
88-
title: 'Debugging Like a Detective: Advanced Angular Techniques',
89-
excerpt:
90-
'Uncover hidden bugs and solve mysterious issues with professional debugging strategies.',
91-
featuredImageUrl: null,
92-
publishDate: '2025-03-10',
93-
author: {
94-
name: 'Takeshi Nakamura',
95-
slug: 'takeshi-nakamura',
96-
avatarUrl: 'assets/mock-avatar.png',
97-
},
98-
},
99-
],
100-
total: 6,
113+
data: MOCK_PRESENTATIONS,
114+
total: MOCK_PRESENTATIONS.length,
101115
});
102116
// TODO - add real API
103117
// return this._http.get<ArrayResponse<PresentationPreview>>(
104118
// `${this._apiBaseUrl}/presentations`,
105119
// { params: query || {} },
106120
// );
107121
}
122+
123+
getPresentationDetail(slug: string): Observable<PresentationPreview> {
124+
// Find the presentation preview by slug
125+
// This is a mock implementation; replace with real API call
126+
const preview = MOCK_PRESENTATIONS.find((p) => p.slug === slug);
127+
128+
if (!preview) {
129+
throw new Error(`Presentation with slug "${slug}" not found`);
130+
}
131+
132+
return of(preview);
133+
// TODO - add real API
134+
// return this._http.get<PresentationDetails>(
135+
// `${this._apiBaseUrl}/presentations/${slug}`,
136+
// );
137+
}
108138
}

libs/blog/presentations/data-access/src/lib/infrastructure/videos.service.ts

Whitespace-only changes.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { inject } from '@angular/core';
2+
import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';
3+
import { rxMethod } from '@ngrx/signals/rxjs-interop';
4+
import { concatMap, pipe, tap } from 'rxjs';
5+
6+
import { PresentationPreview } from '@angular-love/blog-contracts/presentations';
7+
import { withLangState } from '@angular-love/blog/i18n/data-access';
8+
import {
9+
LoadingState,
10+
withCallState,
11+
} from '@angular-love/shared/utils-signal-store';
12+
13+
import { PresentationsService } from '../infrastructure/presentations.service';
14+
15+
type PresentationDetailState = {
16+
presentation: PresentationPreview | null;
17+
slug: string | null;
18+
};
19+
20+
const initialState: PresentationDetailState = {
21+
presentation: null,
22+
slug: null,
23+
};
24+
25+
export const PresentationDetailStore = signalStore(
26+
{ providedIn: 'root' },
27+
withState(initialState),
28+
withLangState(),
29+
withCallState('fetch presentation detail'),
30+
withMethods((store) => {
31+
const presentationsService = inject(PresentationsService);
32+
return {
33+
fetchPresentationDetail: rxMethod<string>(
34+
pipe(
35+
tap((slug) =>
36+
patchState(store, {
37+
slug: slug,
38+
fetchPresentationDetailCallState: LoadingState.LOADING,
39+
}),
40+
),
41+
concatMap((slug) =>
42+
presentationsService.getPresentationDetail(slug).pipe(
43+
tap({
44+
next: (data) =>
45+
patchState(store, {
46+
presentation: data,
47+
fetchPresentationDetailCallState: LoadingState.LOADED,
48+
}),
49+
error: (error) =>
50+
patchState(store, {
51+
fetchPresentationDetailCallState: { error },
52+
}),
53+
}),
54+
),
55+
),
56+
),
57+
),
58+
};
59+
}),
60+
);

0 commit comments

Comments
 (0)