Browse Source

Feature/add Black Weeks 2026 blog post (#8354)

* Add blog post

* Update changelog
pull/8370/head
Thomas Kaul 4 days ago
committed by GitHub
parent
commit
cb200acf95
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 4
      apps/api/src/app/endpoints/sitemap/sitemap.service.ts
  3. 16
      apps/api/src/middlewares/html-template.middleware.ts
  4. 7
      apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html
  5. 7
      apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html
  6. 76
      apps/client/src/app/pages/blog/2026/11/black-weeks-2026/black-weeks-2026-page.component.ts
  7. 201
      apps/client/src/app/pages/blog/2026/11/black-weeks-2026/black-weeks-2026-page.html
  8. 26
      apps/client/src/app/pages/blog/blog-page.html
  9. 9
      apps/client/src/app/pages/blog/blog-page.routes.ts
  10. BIN
      apps/client/src/assets/images/blog/black-weeks-2026.jpg

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Added a blog post: _Black Weeks 2026_
### Changed
- Extended the sectors mapping in the data enhancer for asset profile data via _Trackinsight_

4
apps/api/src/app/endpoints/sitemap/sitemap.service.ts

@ -124,6 +124,10 @@ export class SitemapService {
{
languageCode: 'en',
routerLink: ['2026', '04', 'ghostfolio-3']
},
{
languageCode: 'en',
routerLink: ['2026', '11', 'black-weeks-2026']
}
]
.map(({ languageCode, routerLink }) => {

16
apps/api/src/middlewares/html-template.middleware.ts

@ -87,6 +87,12 @@ const locales = {
'/en/blog/2026/04/ghostfolio-3': {
featureGraphicPath: 'assets/images/blog/ghostfolio-3.jpg',
title: `Announcing Ghostfolio 3.0 - ${title}`
},
'/en/blog/2026/11/black-weeks-2026': {
description:
'Get 15 months of Ghostfolio Premium for the price of 12. Track stocks, ETFs and crypto with the open source portfolio tracker. Offer valid until the end of November.',
featureGraphicPath: 'assets/images/blog/black-weeks-2026.jpg',
title: `Black Weeks 2026: 15 Months of Premium - ${title}`
}
};
@ -156,10 +162,12 @@ export class HtmlTemplateMiddleware implements NestMiddleware {
languageCode,
path,
rootUrl,
description: this.i18nService.getTranslation({
languageCode,
id: 'metaDescription'
}),
description:
locales[path]?.description ??
this.i18nService.getTranslation({
languageCode,
id: 'metaDescription'
}),
featureGraphicPath:
locales[path]?.featureGraphicPath ?? 'assets/cover.png',
keywords: this.i18nService.getTranslation({

7
apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html

@ -12,6 +12,13 @@
title="Black Weeks 2024"
/>
</div>
<section class="mb-4">
<p>
<strong>This offer has ended.</strong> See
<a href="../en/blog/2026/11/black-weeks-2026">Black Weeks 2026</a>
for our latest offer.
</p>
</section>
<section class="mb-4">
<p>
Take advantage of our exclusive <strong>Black Weeks</strong> offer

7
apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html

@ -12,6 +12,13 @@
title="Black Weeks 2025"
/>
</div>
<section class="mb-4">
<p>
<strong>This offer has ended.</strong> See
<a href="../en/blog/2026/11/black-weeks-2026">Black Weeks 2026</a>
for our latest offer.
</p>
</section>
<section class="mb-4">
<p>
Save <strong>25%</strong> on the

76
apps/client/src/app/pages/blog/2026/11/black-weeks-2026/black-weeks-2026-page.component.ts

@ -0,0 +1,76 @@
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { SubscriptionType } from '@ghostfolio/common/enums';
import { SubscriptionOffer } from '@ghostfolio/common/interfaces';
import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { DataService } from '@ghostfolio/ui/services';
import { DecimalPipe } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
inject,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' },
imports: [
DecimalPipe,
GfPremiumIndicatorComponent,
MatButtonModule,
RouterModule
],
selector: 'gf-black-weeks-2026-page',
templateUrl: './black-weeks-2026-page.html'
})
export class BlackWeeks2026PageComponent implements OnInit {
public baseCurrency: string;
public price: number | undefined;
public routerLinkBlog = publicRoutes.blog.routerLink;
public routerLinkFeatures = publicRoutes.features.routerLink;
public routerLinkPricing = publicRoutes.pricing.routerLink;
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
private readonly userService = inject(UserService);
public ngOnInit() {
const { baseCurrency, subscriptionOffer } = this.dataService.fetchInfo();
this.baseCurrency = baseCurrency;
this.setPrice(subscriptionOffer);
this.userService.stateChanged
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((state) => {
if (state?.user) {
this.setPrice(
state.user.subscription?.offer,
state.user.subscription?.type
);
}
this.changeDetectorRef.markForCheck();
});
}
private setPrice(
offer: SubscriptionOffer | undefined,
subscriptionType?: SubscriptionType
) {
this.price =
offer?.durationExtension &&
offer.price &&
subscriptionType !== SubscriptionType.Premium
? offer.price - (offer.coupon ?? 0)
: undefined;
}
}

201
apps/client/src/app/pages/blog/2026/11/black-weeks-2026/black-weeks-2026-page.html

@ -0,0 +1,201 @@
<div class="blog container">
<div class="row">
<div class="col-md-8 offset-md-2">
<article>
<div class="mb-4 text-center">
<h1 class="mb-1">
Black Weeks 2026: 15 Months of Premium for the Price of 12
</h1>
<div class="mb-3 text-body-secondary"><small>2026-11-01</small></div>
<img
alt="Black Weeks 2026: 15 months of Ghostfolio Premium for the price of 12"
class="rounded w-100"
src="../assets/images/blog/black-weeks-2026.jpg"
title="Black Weeks 2026"
/>
</div>
<section class="mb-4">
<p>
Get <strong>15 months</strong> of
<span class="align-items-center d-inline-flex"
>Ghostfolio Premium
<gf-premium-indicator
class="d-inline-block ms-1"
[enableLink]="false"
/>
</span>
for the price of 12. Our <strong>Black Weeks</strong> offer adds
<strong>3 extra months</strong> to the annual plan of the open
source portfolio tracker, which gives you 25% more time at the same
price.
</p>
</section>
<section class="mb-4">
<h2 class="h4">What you get with Ghostfolio Premium</h2>
<p>
<a
href="https://ghostfol.io"
title="Open Source Wealth Management Software"
>Ghostfolio</a
>
unifies your finances in one place and gives you a clear overview of
your portfolio across stocks, ETFs, cryptocurrencies or other
assets. With Ghostfolio Premium, you can:
</p>
<ul class="list-unstyled">
<li>
<strong>See the full picture</strong>: Understand your portfolio
at a glance with the summary and allocations.
</li>
<li>
<strong>Measure your progress</strong>: Compare your performance
against benchmarks.
</li>
<li>
<strong>Access professional data</strong>: Get 80’000+ tickers
from over 50 exchanges.
</li>
<li>
<strong>Plan your financial independence</strong>: Calculate your
path to retirement with the FIRE calculator.
</li>
<li>
<strong>Connect your AI client</strong>: Analyze your portfolio
and import activities with the new Ghostfolio MCP (enable
<i>Experimental Features</i> in your account settings).
</li>
</ul>
<p>
Explore all the
<a [routerLink]="routerLinkFeatures">features</a> of Ghostfolio.
</p>
</section>
<section class="mb-4">
<h2 class="h4">How the offer works</h2>
@if (price) {
<p>
You pay {{ baseCurrency }}&nbsp;<strong>{{ price }}</strong> for
15 months, which is {{ baseCurrency }}&nbsp;{{
price / 15 | number: '1.2-2'
}}
per month.
</p>
}
<p>
It is a one-time payment with no auto-renewal. The offer is valid
until the <strong>end of November</strong>.
</p>
@if (price) {
<p class="text-center">
<a
color="primary"
mat-flat-button
[routerLink]="routerLinkPricing"
>Get 3 extra months</a
>
</p>
<p class="mt-5">
More details are available on the
<a [routerLink]="routerLinkPricing">pricing page</a>.
</p>
}
</section>
<section class="mb-4">
<ul class="list-inline">
<li class="list-inline-item">
<span class="badge bg-light text-dark">2026</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Black Friday</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Black Weeks</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Cryptocurrency</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Deal</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">ETF</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Finance</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Fintech</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Ghostfolio</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Ghostfolio MCP</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Ghostfolio Premium</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Investment</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Open Source</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">OSS</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Personal Finance</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Portfolio</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Portfolio Tracker</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Pricing</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Promotion</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">SaaS</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Sale</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Software</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Stock</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Subscription</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Wealth</span>
</li>
<li class="list-inline-item">
<span class="badge bg-light text-dark">Wealth Management</span>
</li>
</ul>
</section>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a i18n [routerLink]="routerLinkBlog">Blog</a>
</li>
<li
aria-current="page"
class="active breadcrumb-item text-truncate"
>
Black Weeks 2026
</li>
</ol>
</nav>
</article>
</div>
</div>
</div>

26
apps/client/src/app/pages/blog/blog-page.html

@ -8,6 +8,32 @@
finance</small
>
</h1>
@if (hasPermissionForSubscription) {
<mat-card appearance="outlined" class="mb-3">
<mat-card-content class="p-0">
<div class="container p-0">
<div class="flex-nowrap g-0 row">
<a
class="d-flex overflow-hidden p-3 w-100"
href="../en/blog/2026/11/black-weeks-2026"
>
<div class="flex-grow-1 overflow-hidden">
<div class="h6 m-0 text-truncate">Black Weeks 2026</div>
<div class="d-flex text-body-secondary">2026-11-01</div>
</div>
<div class="align-items-center d-flex">
<ion-icon
class="text-body-secondary"
name="chevron-forward-outline"
size="small"
/>
</div>
</a>
</div>
</div>
</mat-card-content>
</mat-card>
}
<mat-card appearance="outlined" class="mb-3">
<mat-card-content class="p-0">
<div class="container p-0">

9
apps/client/src/app/pages/blog/blog-page.routes.ts

@ -227,5 +227,14 @@ export const routes: Routes = [
(c) => c.Ghostfolio3PageComponent
),
title: 'Ghostfolio 3.0'
},
{
canActivate: [AuthGuard],
path: '2026/11/black-weeks-2026',
loadComponent: () =>
import('./2026/11/black-weeks-2026/black-weeks-2026-page.component').then(
(c) => c.BlackWeeks2026PageComponent
),
title: 'Black Weeks 2026: 15 Months of Premium'
}
];

BIN
apps/client/src/assets/images/blog/black-weeks-2026.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Loading…
Cancel
Save