Browse Source

refactor(client): extract footer into standalone component

pull/5702/head
Aditya Pawar 4 weeks ago
parent
commit
d6d86d6227
  1. 239
      apps/client/src/app/app.component.html
  2. 18
      apps/client/src/app/app.component.scss
  3. 2
      apps/client/src/app/app.module.ts
  4. 155
      apps/client/src/app/components/footer/footer.component.html
  5. 16
      apps/client/src/app/components/footer/footer.component.scss
  6. 33
      apps/client/src/app/components/footer/footer.component.spec.ts
  7. 66
      apps/client/src/app/components/footer/footer.component.ts

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

@ -1,45 +1,30 @@
<header>
@if (canCreateAccount || user?.systemMessage) {
<div class="info-message-container">
<div class="info-message-inner-container position-fixed w-100">
<div class="align-items-center d-flex h-100 justify-content-center">
@if (canCreateAccount) {
<a class="text-center" [routerLink]="routerLinkRegister">
<div
class="cursor-pointer d-inline-block info-message"
(click)="onCreateAccount()"
>
<span i18n>You are using the Live Demo.</span>
<span class="a ml-2 p-1" i18n>Create Account</span>
</div></a
>
}
@if (!canCreateAccount && user?.systemMessage) {
<div
class="cursor-pointer d-inline-block info-message text-truncate"
(click)="onClickSystemMessage()"
>
{{ user.systemMessage.message }}
</div>
}
<div class="info-message-container">
<div class="info-message-inner-container position-fixed w-100">
<div class="align-items-center d-flex h-100 justify-content-center">
@if (canCreateAccount) {
<a class="text-center" [routerLink]="routerLinkRegister">
<div class="cursor-pointer d-inline-block info-message" (click)="onCreateAccount()">
<span i18n>You are using the Live Demo.</span>
<span class="a ml-2 p-1" i18n>Create Account</span>
</div>
</a>
}
@if (!canCreateAccount && user?.systemMessage) {
<div class="cursor-pointer d-inline-block info-message text-truncate" (click)="onClickSystemMessage()">
{{ user.systemMessage.message }}
</div>
}
</div>
</div>
</div>
}
<gf-header
class="position-fixed w-100"
[currentRoute]="currentRoute"
[deviceType]="deviceType"
<gf-header class="position-fixed w-100" [currentRoute]="currentRoute" [deviceType]="deviceType"
[hasPermissionToChangeDateRange]="hasPermissionToChangeDateRange"
[hasPermissionToChangeFilters]="hasPermissionToChangeFilters"
[hasPromotion]="hasPromotion"
[hasTabs]="hasTabs"
[info]="info"
[pageTitle]="pageTitle"
[user]="user"
(signOut)="onSignOut()"
/>
[hasPermissionToChangeFilters]="hasPermissionToChangeFilters" [hasPromotion]="hasPromotion" [hasTabs]="hasTabs"
[info]="info" [pageTitle]="pageTitle" [user]="user" (signOut)="onSignOut()" />
</header>
<main role="main">
@ -47,187 +32,5 @@
</main>
@if (showFooter) {
<footer class="justify-content-center overflow-hidden py-4 w-100">
<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>
</footer>
}
<gf-footer [info]="info" [user]="user" />
}

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 {
height: var(--mat-toolbar-standard-height);
}
@ -54,9 +42,3 @@
min-height: calc(100svh - var(--mat-toolbar-standard-height));
}
}
:host-context(.theme-dark) {
footer {
background-color: rgba(var(--palette-foreground-text-dark), 0.05);
}
}

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

@ -30,6 +30,7 @@ import { CustomDateAdapter } from './adapter/custom-date-adapter';
import { DateFormats } from './adapter/date-formats';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { GfFooterComponent } from './components/footer/footer.component';
import { GfHeaderComponent } from './components/header/header.component';
import { authInterceptorProviders } from './core/auth.interceptor';
import { httpResponseInterceptorProviders } from './core/http-response.interceptor';
@ -47,6 +48,7 @@ export function NgxStripeFactory(): string {
AppRoutingModule,
BrowserAnimationsModule,
BrowserModule,
GfFooterComponent,
GfHeaderComponent,
GfLogoComponent,
GfNotificationModule,

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

@ -0,0 +1,155 @@
<footer class="justify-content-center overflow-hidden py-4 w-100">
<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>
</footer>

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

@ -0,0 +1,16 @@
:host {
display: block;
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;
}
}
:host-context(.theme-dark) {
background-color: rgba(var(--palette-foreground-text-dark), 0.05);
}

33
apps/client/src/app/components/footer/footer.component.spec.ts

@ -0,0 +1,33 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { GfFooterComponent } from './footer.component';
describe('GfFooterComponent', () => {
let component: GfFooterComponent;
let fixture: ComponentFixture<GfFooterComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GfFooterComponent, RouterTestingModule]
}).compileComponents();
fixture = TestBed.createComponent(GfFooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have current year property', () => {
expect(component.currentYear).toBe(new Date().getFullYear());
});
it('should have router links defined', () => {
expect(component.routerLinkAbout).toBeDefined();
expect(component.routerLinkFeatures).toBeDefined();
expect(component.routerLinkResources).toBeDefined();
});
});

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

@ -0,0 +1,66 @@
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';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [CommonModule, GfLogoComponent, IonIcon, RouterModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class GfFooterComponent implements OnChanges {
@Input() info: InfoItem;
@Input() 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 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