Browse Source

Task/extract footer to component (#5702)

* Extract footer to component

* Update changelog
pull/5750/head
Aditya Pawar 3 weeks ago
committed by GitHub
parent
commit
e8366603ad
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 188
      apps/client/src/app/app.component.html
  3. 18
      apps/client/src/app/app.component.scss
  4. 35
      apps/client/src/app/app.component.ts
  5. 7
      apps/client/src/app/app.module.ts
  6. 181
      apps/client/src/app/components/footer/footer.component.html
  7. 16
      apps/client/src/app/components/footer/footer.component.scss
  8. 74
      apps/client/src/app/components/footer/footer.component.ts

1
CHANGELOG.md

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Disabled the zoom functionality in the _Progressive Web App_ (PWA) - Disabled the zoom functionality in the _Progressive Web App_ (PWA)
- Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service - Optimized the get quotes functionality by utilizing the asset profile resolutions in the _Financial Modeling Prep_ service
- Extracted the footer to a component
## 2.208.0 - 2025-10-11 ## 2.208.0 - 2025-10-11

188
apps/client/src/app/app.component.html

@ -11,8 +11,8 @@
> >
<span i18n>You are using the Live Demo.</span> <span i18n>You are using the Live Demo.</span>
<span class="a ml-2 p-1" i18n>Create Account</span> <span class="a ml-2 p-1" i18n>Create Account</span>
</div></a </div>
> </a>
} }
@if (!canCreateAccount && user?.systemMessage) { @if (!canCreateAccount && user?.systemMessage) {
<div <div
@ -47,187 +47,7 @@
</main> </main>
@if (showFooter) { @if (showFooter) {
<footer class="justify-content-center overflow-hidden py-4 w-100"> <footer class="justify-content-center overflow-hidden w-100">
<div class="container"> <gf-footer class="py-4" [info]="info" [user]="user" />
<div class="mb-3 row">
<div class="col-sm">
<a [routerLink]="['/']"><gf-logo /></a>
</div>
<div class="col-sm">
<div class="h6 mt-2" i18n>Personal Finance</div>
<ul class="list-unstyled">
@if (hasPermissionToAccessFearAndGreedIndex) {
<li>
<a i18n [routerLink]="routerLinkMarkets">Markets</a>
</li>
}
<li><a i18n [routerLink]="routerLinkResources">Resources</a></li>
</ul>
</div>
<div class="col-sm">
<div class="h6 mt-2">Ghostfolio</div>
<ul class="list-unstyled">
<li><a i18n [routerLink]="routerLinkAbout">About</a></li>
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkBlog">Blog</a>
</li>
}
<li>
<a i18n [routerLink]="routerLinkAboutChangelog">Changelog</a>
</li>
<li><a i18n [routerLink]="routerLinkFeatures">Features</a></li>
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkFaq"
>Frequently Asked Questions (FAQ)</a
>
</li>
}
@if (!hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutLicense">License</a>
</li>
}
@if (hasPermissionForStatistics) {
<li>
<a [routerLink]="routerLinkOpenStartup">Open Startup</a>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkPricing">Pricing</a>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutPrivacyPolicy"
>Privacy Policy</a
>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutTermsOfService"
>Terms of Service</a
>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a
class="align-items-baseline d-flex"
href="https://status.ghostfol.io"
target="_blank"
title="Ghostfolio Status"
>Status<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
}
</ul>
</div>
<div class="col-sm">
<div class="h6 mt-2" i18n>Community</div>
<ul class="list-unstyled">
<li>
<a
class="align-items-baseline d-flex"
href="https://github.com/ghostfolio/ghostfolio"
target="_blank"
title="Find Ghostfolio on GitHub"
>GitHub<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://linkedin.com/company/ghostfolio"
target="_blank"
title="Follow Ghostfolio on LinkedIn"
>LinkedIn<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
target="_blank"
title="Join the Ghostfolio Slack community"
>Slack<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://x.com/ghostfolio_"
target="_blank"
title="Follow Ghostfolio on X (formerly Twitter)"
>X (formerly Twitter)<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>&nbsp;</li>
<!--
<li>
<a href="../ca" title="Ghostfolio en català">Català</a>
</li>
-->
<li>
<a href="../zh" title="Ghostfolio in Chinese">Chinese</a>
</li>
<li>
<a href="../de" title="Ghostfolio in Deutsch">Deutsch</a>
</li>
<li>
<a href="../en" title="Ghostfolio in English">English</a>
</li>
<li>
<a href="../es" title="Ghostfolio in Español">Español</a>
</li>
<li>
<a href="../fr" title="Ghostfolio en Français">Français</a>
</li>
<li>
<a href="../it" title="Ghostfolio in Italiano">Italiano</a>
</li>
<li>
<a href="../nl" title="Ghostfolio in Nederlands">Nederlands</a>
</li>
<li>
<a href="../pl" title="Ghostfolio in Polski">Polski</a>
</li>
<li>
<a href="../pt" title="Ghostfolio in Português">Português</a>
</li>
<li>
<a href="../tr" title="Ghostfolio in Türkçe">Türkçe</a>
</li>
<!--
<li>
<a href="../uk" title="Ghostfolio in Українська">Українська</a>
</li>
-->
</ul>
</div>
</div>
<div class="mb-2 row text-center">
<div class="col">
© 2021 - {{ currentYear }}
<a href="https://ghostfol.io">Ghostfolio</a>
</div>
</div>
<div class="row text-center text-muted">
<div class="col">
<small class="d-block" i18n
>The risk of loss in trading can be substantial. It is not advisable
to invest money you may need in the short term.</small
>
</div>
</div>
</div>
<div class="container d-none d-md-block mt-5">
<div class="row justify-content-center">
<div class="font-weight-bold line-height-1 logotype">Ghostfolio</div>
</div>
</div>
</footer> </footer>
} }

18
apps/client/src/app/app.component.scss

@ -34,18 +34,6 @@
} }
} }
footer {
background-color: rgba(var(--palette-foreground-text), 0.05);
font-size: 90%;
.logotype {
font-size: 13vw;
letter-spacing: -0.03em;
margin-bottom: -5svw;
opacity: 0.05;
}
}
header { header {
height: var(--mat-toolbar-standard-height); height: var(--mat-toolbar-standard-height);
} }
@ -54,9 +42,3 @@
min-height: calc(100svh - var(--mat-toolbar-standard-height)); min-height: calc(100svh - var(--mat-toolbar-standard-height));
} }
} }
:host-context(.theme-dark) {
footer {
background-color: rgba(var(--palette-foreground-text-dark), 0.05);
}
}

35
apps/client/src/app/app.component.ts

@ -52,36 +52,16 @@ export class AppComponent implements OnDestroy, OnInit {
public canCreateAccount: boolean; public canCreateAccount: boolean;
public currentRoute: string; public currentRoute: string;
public currentSubRoute: string; public currentSubRoute: string;
public currentYear = new Date().getFullYear();
public deviceType: string; public deviceType: string;
public hasImpersonationId: boolean; public hasImpersonationId: boolean;
public hasInfoMessage: boolean; public hasInfoMessage: boolean;
public hasPermissionForStatistics: boolean;
public hasPermissionForSubscription: boolean;
public hasPermissionToAccessFearAndGreedIndex: boolean;
public hasPermissionToChangeDateRange: boolean; public hasPermissionToChangeDateRange: boolean;
public hasPermissionToChangeFilters: boolean; public hasPermissionToChangeFilters: boolean;
public hasPromotion = false; public hasPromotion = false;
public hasTabs = false; public hasTabs = false;
public info: InfoItem; public info: InfoItem;
public pageTitle: string; public pageTitle: string;
public routerLinkAbout = publicRoutes.about.routerLink;
public routerLinkAboutChangelog =
publicRoutes.about.subRoutes.changelog.routerLink;
public routerLinkAboutLicense =
publicRoutes.about.subRoutes.license.routerLink;
public routerLinkAboutPrivacyPolicy =
publicRoutes.about.subRoutes.privacyPolicy.routerLink;
public routerLinkAboutTermsOfService =
publicRoutes.about.subRoutes.termsOfService.routerLink;
public routerLinkBlog = publicRoutes.blog.routerLink;
public routerLinkFaq = publicRoutes.faq.routerLink;
public routerLinkFeatures = publicRoutes.features.routerLink;
public routerLinkMarkets = publicRoutes.markets.routerLink;
public routerLinkOpenStartup = publicRoutes.openStartup.routerLink;
public routerLinkPricing = publicRoutes.pricing.routerLink;
public routerLinkRegister = publicRoutes.register.routerLink; public routerLinkRegister = publicRoutes.register.routerLink;
public routerLinkResources = publicRoutes.resources.routerLink;
public showFooter = false; public showFooter = false;
public user: User; public user: User;
@ -126,21 +106,6 @@ export class AppComponent implements OnDestroy, OnInit {
this.deviceType = this.deviceService.getDeviceInfo().deviceType; this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.info = this.dataService.fetchInfo(); this.info = this.dataService.fetchInfo();
this.hasPermissionForSubscription = hasPermission(
this.info?.globalPermissions,
permissions.enableSubscription
);
this.hasPermissionForStatistics = hasPermission(
this.info?.globalPermissions,
permissions.enableStatistics
);
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.info?.globalPermissions,
permissions.enableFearAndGreedIndex
);
this.hasPromotion = this.hasPromotion =
!!this.info?.subscriptionOffer?.coupon || !!this.info?.subscriptionOffer?.coupon ||
!!this.info?.subscriptionOffer?.durationExtension; !!this.info?.subscriptionOffer?.durationExtension;

7
apps/client/src/app/app.module.ts

@ -1,5 +1,3 @@
import { GfLogoComponent } from '@ghostfolio/ui/logo';
import { Platform } from '@angular/cdk/platform'; import { Platform } from '@angular/cdk/platform';
import { import {
provideHttpClient, provideHttpClient,
@ -20,7 +18,6 @@ import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ServiceWorkerModule } from '@angular/service-worker'; import { ServiceWorkerModule } from '@angular/service-worker';
import { provideIonicAngular } from '@ionic/angular/standalone'; import { provideIonicAngular } from '@ionic/angular/standalone';
import { IonIcon } from '@ionic/angular/standalone';
import { provideMarkdown } from 'ngx-markdown'; import { provideMarkdown } from 'ngx-markdown';
import { provideNgxSkeletonLoader } from 'ngx-skeleton-loader'; import { provideNgxSkeletonLoader } from 'ngx-skeleton-loader';
import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe'; import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe';
@ -30,6 +27,7 @@ import { CustomDateAdapter } from './adapter/custom-date-adapter';
import { DateFormats } from './adapter/date-formats'; import { DateFormats } from './adapter/date-formats';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { GfFooterComponent } from './components/footer/footer.component';
import { GfHeaderComponent } from './components/header/header.component'; import { GfHeaderComponent } from './components/header/header.component';
import { authInterceptorProviders } from './core/auth.interceptor'; import { authInterceptorProviders } from './core/auth.interceptor';
import { httpResponseInterceptorProviders } from './core/http-response.interceptor'; import { httpResponseInterceptorProviders } from './core/http-response.interceptor';
@ -47,10 +45,9 @@ export function NgxStripeFactory(): string {
AppRoutingModule, AppRoutingModule,
BrowserAnimationsModule, BrowserAnimationsModule,
BrowserModule, BrowserModule,
GfFooterComponent,
GfHeaderComponent, GfHeaderComponent,
GfLogoComponent,
GfNotificationModule, GfNotificationModule,
IonIcon,
MatAutocompleteModule, MatAutocompleteModule,
MatChipsModule, MatChipsModule,
MatNativeDateModule, MatNativeDateModule,

181
apps/client/src/app/components/footer/footer.component.html

@ -0,0 +1,181 @@
<div class="container">
<div class="mb-3 row">
<div class="col-sm">
<a [routerLink]="['/']"><gf-logo /></a>
</div>
<div class="col-sm">
<div class="h6 mt-2" i18n>Personal Finance</div>
<ul class="list-unstyled">
@if (hasPermissionToAccessFearAndGreedIndex) {
<li>
<a i18n [routerLink]="routerLinkMarkets">Markets</a>
</li>
}
<li><a i18n [routerLink]="routerLinkResources">Resources</a></li>
</ul>
</div>
<div class="col-sm">
<div class="h6 mt-2">Ghostfolio</div>
<ul class="list-unstyled">
<li><a i18n [routerLink]="routerLinkAbout">About</a></li>
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkBlog">Blog</a>
</li>
}
<li>
<a i18n [routerLink]="routerLinkAboutChangelog">Changelog</a>
</li>
<li><a i18n [routerLink]="routerLinkFeatures">Features</a></li>
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkFaq"
>Frequently Asked Questions (FAQ)</a
>
</li>
}
@if (!hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutLicense">License</a>
</li>
}
@if (hasPermissionForStatistics) {
<li>
<a [routerLink]="routerLinkOpenStartup">Open Startup</a>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkPricing">Pricing</a>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutPrivacyPolicy"
>Privacy Policy</a
>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a i18n [routerLink]="routerLinkAboutTermsOfService"
>Terms of Service</a
>
</li>
}
@if (hasPermissionForSubscription) {
<li>
<a
class="align-items-baseline d-flex"
href="https://status.ghostfol.io"
target="_blank"
title="Ghostfolio Status"
>Status<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
}
</ul>
</div>
<div class="col-sm">
<div class="h6 mt-2" i18n>Community</div>
<ul class="list-unstyled">
<li>
<a
class="align-items-baseline d-flex"
href="https://github.com/ghostfolio/ghostfolio"
target="_blank"
title="Find Ghostfolio on GitHub"
>GitHub<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://linkedin.com/company/ghostfolio"
target="_blank"
title="Follow Ghostfolio on LinkedIn"
>LinkedIn<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg"
target="_blank"
title="Join the Ghostfolio Slack community"
>Slack<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>
<a
class="align-items-baseline d-flex"
href="https://x.com/ghostfolio_"
target="_blank"
title="Follow Ghostfolio on X (formerly Twitter)"
>X (formerly Twitter)<ion-icon class="ml-1" name="open-outline"
/></a>
</li>
<li>&nbsp;</li>
<!--
<li>
<a href="../ca" title="Ghostfolio en català">Català</a>
</li>
-->
<li>
<a href="../zh" title="Ghostfolio in Chinese">Chinese</a>
</li>
<li>
<a href="../de" title="Ghostfolio in Deutsch">Deutsch</a>
</li>
<li>
<a href="../en" title="Ghostfolio in English">English</a>
</li>
<li>
<a href="../es" title="Ghostfolio in Español">Español</a>
</li>
<li>
<a href="../fr" title="Ghostfolio en Français">Français</a>
</li>
<li>
<a href="../it" title="Ghostfolio in Italiano">Italiano</a>
</li>
<li>
<a href="../nl" title="Ghostfolio in Nederlands">Nederlands</a>
</li>
<li>
<a href="../pl" title="Ghostfolio in Polski">Polski</a>
</li>
<li>
<a href="../pt" title="Ghostfolio in Português">Português</a>
</li>
<li>
<a href="../tr" title="Ghostfolio in Türkçe">Türkçe</a>
</li>
<!--
<li>
<a href="../uk" title="Ghostfolio in Українська">Українська</a>
</li>
-->
</ul>
</div>
</div>
<div class="mb-2 row text-center">
<div class="col">
© 2021 - {{ currentYear }}
<a href="https://ghostfol.io">Ghostfolio</a>
</div>
</div>
<div class="row text-center text-muted">
<div class="col">
<small class="d-block" i18n
>The risk of loss in trading can be substantial. It is not advisable to
invest money you may need in the short term.</small
>
</div>
</div>
</div>
<div class="container d-none d-md-block mt-5">
<div class="row justify-content-center">
<div class="font-weight-bold line-height-1 logotype">Ghostfolio</div>
</div>
</div>

16
apps/client/src/app/components/footer/footer.component.scss

@ -0,0 +1,16 @@
:host {
background-color: rgba(var(--palette-foreground-text), 0.05);
display: block;
font-size: 90%;
.logotype {
font-size: 13vw;
letter-spacing: -0.03em;
margin-bottom: -5svw;
opacity: 0.05;
}
}
:host-context(.theme-dark) {
background-color: rgba(var(--palette-foreground-text-dark), 0.05);
}

74
apps/client/src/app/components/footer/footer.component.ts

@ -0,0 +1,74 @@
import { InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfLogoComponent } from '@ghostfolio/ui/logo';
import { CommonModule } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
CUSTOM_ELEMENTS_SCHEMA,
Input,
OnChanges
} from '@angular/core';
import { RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { openOutline } from 'ionicons/icons';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, GfLogoComponent, IonIcon, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-footer',
styleUrls: ['./footer.component.scss'],
templateUrl: './footer.component.html'
})
export class GfFooterComponent implements OnChanges {
@Input() public info: InfoItem;
@Input() public user: User;
public currentYear = new Date().getFullYear();
public hasPermissionForStatistics: boolean;
public hasPermissionForSubscription: boolean;
public hasPermissionToAccessFearAndGreedIndex: boolean;
public routerLinkAbout = publicRoutes.about.routerLink;
public routerLinkAboutChangelog =
publicRoutes.about.subRoutes.changelog.routerLink;
public routerLinkAboutLicense =
publicRoutes.about.subRoutes.license.routerLink;
public routerLinkAboutPrivacyPolicy =
publicRoutes.about.subRoutes.privacyPolicy.routerLink;
public routerLinkAboutTermsOfService =
publicRoutes.about.subRoutes.termsOfService.routerLink;
public routerLinkBlog = publicRoutes.blog.routerLink;
public routerLinkFaq = publicRoutes.faq.routerLink;
public routerLinkFeatures = publicRoutes.features.routerLink;
public routerLinkMarkets = publicRoutes.markets.routerLink;
public routerLinkOpenStartup = publicRoutes.openStartup.routerLink;
public routerLinkPricing = publicRoutes.pricing.routerLink;
public routerLinkResources = publicRoutes.resources.routerLink;
public constructor() {
addIcons({
openOutline
});
}
public ngOnChanges() {
this.hasPermissionForStatistics = hasPermission(
this.info?.globalPermissions,
permissions.enableStatistics
);
this.hasPermissionForSubscription = hasPermission(
this.info?.globalPermissions,
permissions.enableSubscription
);
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.info?.globalPermissions,
permissions.enableFearAndGreedIndex
);
}
}
Loading…
Cancel
Save