-
- circle
- {{ row.publicKey | truncateMid: (desktop ? 15 : 6): 6 }}
-
-
- {{ row.uptimePercentage }}%
- bookmark_check
-
-
{{ row.blocksProduced ?? 0 }}
+
+ @if (!isLoading) {
+ @for (row of rows; track $index) {
+
+
+
+ circle
+ {{ row.publicKey | truncateMid: (desktop ? 15 : 6): 6 }}
+
+
+ {{ row.uptimePercentage }}%
+ @if (row.uptimePercentage > 33.33) {
+ bookmark_check
+ }
+ @if (row.uptimePrize) {
+
+ }
+
+
+ {{ row.blocksProduced ?? 0 }}
+ @if (row.blocksPrize) {
+
+ }
+
+
+ }
+ } @else {
+
}
+
+
+
+
diff --git a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.scss b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.scss
index 81c62669a9..a4868d0b6c 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.scss
+++ b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.scss
@@ -11,7 +11,6 @@
}
.row-wrap {
-
&.head {
max-width: unset;
height: 56px;
@@ -22,7 +21,7 @@
}
}
- &:nth-child(odd):not(.head) {
+ &.odd:not(.head) {
background-color: $mina-base-container;
}
}
@@ -55,7 +54,10 @@
}
.perc {
- width: 50px;
+ width: 37px;
+ @media (max-width: 480px) {
+ width: 26px;
+ }
}
.circle.active {
@@ -77,3 +79,17 @@
padding-left: 2px;
}
}
+
+:host ::ng-deep mina-loading-spinner .loading {
+ border: 1px solid $mina-base-primary !important;
+ border-top-color: transparent !important;
+}
+
+.p-absolute {
+ background-color: $mina-cta-primary;
+}
+
+mina-loading-spinner + div {
+ font-family: "IBM Plex Sans", sans-serif;
+ color: $mina-base-primary;
+}
diff --git a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.ts b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.ts
index e8c77a914c..a36b6fcc7a 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.ts
+++ b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.ts
@@ -3,6 +3,7 @@ import { LeaderboardSelectors } from '@leaderboard/leaderboard.state';
import { HeartbeatSummary } from '@shared/types/leaderboard/heartbeat-summary.type';
import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class';
import { isDesktop } from '@openmina/shared';
+import { animate, style, transition, trigger } from '@angular/animations';
@Component({
selector: 'mina-leaderboard-table',
@@ -10,10 +11,21 @@ import { isDesktop } from '@openmina/shared';
styleUrl: './leaderboard-table.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'flex-column w-100 h-100' },
+ animations: [
+ trigger('fadeInOut', [
+ transition(':enter', [
+ style({ opacity: 0 }),
+ animate('400ms linear', style({ opacity: 1 })),
+ ]),
+ transition(':leave', [
+ animate('400ms linear', style({ opacity: 0 })),
+ ]),
+ ]),
+ ],
})
export class LeaderboardTableComponent extends StoreDispatcher implements OnInit {
- isLoading: boolean;
+ isLoading: boolean = true;
rows: HeartbeatSummary[] = [];
desktop: boolean = isDesktop();
diff --git a/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.html b/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.html
new file mode 100644
index 0000000000..2c5e2dcd0c
--- /dev/null
+++ b/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+ Mina Web Node Testing Program
+ Terms and Conditions
+ TBD
+
+
+
diff --git a/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.scss b/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.scss
new file mode 100644
index 0000000000..b5c5a9483d
--- /dev/null
+++ b/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.scss
@@ -0,0 +1,41 @@
+@import 'leaderboard-variables';
+
+:host {
+ display: block;
+ height: 100%;
+ padding-top: 52px;
+ background-color: $mina-cta-primary;
+ color: $mina-base-primary;
+ font-family: "IBM Plex Sans", sans-serif;
+}
+
+main,
+mina-leaderboard-header,
+mina-leaderboard-footer {
+ max-width: 1200px;
+ width: 100%;
+ padding: 0 48px;
+ margin: 0 auto;
+
+ @media (max-width: 1023px) {
+ padding: 0;
+ }
+}
+
+h2 {
+ margin-top: 72px;
+ margin-bottom: 16px;
+ color: $mina-base-secondary;
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 28px;
+}
+
+h1 {
+ margin-top: 0;
+ margin-bottom: 80px;
+ color: $mina-base-primary;
+ font-size: 80px;
+ font-weight: 400;
+ line-height: 80px;
+}
diff --git a/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.ts b/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.ts
new file mode 100644
index 0000000000..ebb2ba6d5c
--- /dev/null
+++ b/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.ts
@@ -0,0 +1,11 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+
+@Component({
+ selector: 'mina-leaderboard-terms-and-conditions',
+ templateUrl: './leaderboard-terms-and-conditions.component.html',
+ styleUrl: './leaderboard-terms-and-conditions.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class LeaderboardTermsAndConditionsComponent {
+
+}
diff --git a/frontend/src/app/features/leaderboard/leaderboard.module.ts b/frontend/src/app/features/leaderboard/leaderboard.module.ts
index b44db7f664..44c318a2f0 100644
--- a/frontend/src/app/features/leaderboard/leaderboard.module.ts
+++ b/frontend/src/app/features/leaderboard/leaderboard.module.ts
@@ -13,6 +13,11 @@ import { EffectsModule } from '@ngrx/effects';
import { LeaderboardEffects } from '@leaderboard/leaderboard.effects';
import { LeaderboardFooterComponent } from '@leaderboard/leaderboard-footer/leaderboard-footer.component';
import { LeaderboardLandingPageComponent } from '@leaderboard/leaderboard-landing-page/leaderboard-landing-page.component';
+import { LeaderboardDetailsComponent } from '@leaderboard/leaderboard-details/leaderboard-details.component';
+import { LeaderboardTermsAndConditionsComponent } from '@leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component';
+import { LeaderboardImpressumComponent } from '@leaderboard/leaderboard-impressum/leaderboard-impressum.component';
+import { LeaderboardPrivacyPolicyComponent } from '@leaderboard/leaderboard-privacy-policy/leaderboard-privacy-policy.component';
+import { LeaderboardApplyComponent } from '@leaderboard/leaderboard-apply/leaderboard-apply.component';
@NgModule({
@@ -24,6 +29,11 @@ import { LeaderboardLandingPageComponent } from '@leaderboard/leaderboard-landin
LeaderboardTitleComponent,
LeaderboardFooterComponent,
LeaderboardLandingPageComponent,
+ LeaderboardDetailsComponent,
+ LeaderboardTermsAndConditionsComponent,
+ LeaderboardImpressumComponent,
+ LeaderboardPrivacyPolicyComponent,
+ LeaderboardApplyComponent,
],
imports: [
CommonModule,
diff --git a/frontend/src/app/features/leaderboard/leaderboard.routing.ts b/frontend/src/app/features/leaderboard/leaderboard.routing.ts
index 32e605a291..69b819e42c 100644
--- a/frontend/src/app/features/leaderboard/leaderboard.routing.ts
+++ b/frontend/src/app/features/leaderboard/leaderboard.routing.ts
@@ -1,12 +1,32 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LeaderboardPageComponent } from '@leaderboard/leaderboard-page/leaderboard-page.component';
+import { LeaderboardDetailsComponent } from '@leaderboard/leaderboard-details/leaderboard-details.component';
+import { LeaderboardPrivacyPolicyComponent } from '@leaderboard/leaderboard-privacy-policy/leaderboard-privacy-policy.component';
+import { LeaderboardTermsAndConditionsComponent } from '@leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component';
+import { LeaderboardImpressumComponent } from '@leaderboard/leaderboard-impressum/leaderboard-impressum.component';
const routes: Routes = [
{
path: 'leaderboard',
component: LeaderboardPageComponent,
},
+ {
+ path: 'leaderboard/details',
+ component: LeaderboardDetailsComponent,
+ },
+ {
+ path: 'leaderboard/impressum',
+ component: LeaderboardImpressumComponent,
+ },
+ {
+ path: 'leaderboard/privacy-policy',
+ component: LeaderboardPrivacyPolicyComponent,
+ },
+ {
+ path: 'leaderboard/terms-and-conditions',
+ component: LeaderboardTermsAndConditionsComponent,
+ },
{
path: '**',
redirectTo: '',
diff --git a/frontend/src/app/features/leaderboard/leaderboard.service.ts b/frontend/src/app/features/leaderboard/leaderboard.service.ts
index e18eb7efbe..6c19937356 100644
--- a/frontend/src/app/features/leaderboard/leaderboard.service.ts
+++ b/frontend/src/app/features/leaderboard/leaderboard.service.ts
@@ -1,258 +1,47 @@
import { Injectable } from '@angular/core';
-import { Observable, of } from 'rxjs';
+import { combineLatest, map, Observable } from 'rxjs';
import { HeartbeatSummary } from '@shared/types/leaderboard/heartbeat-summary.type';
+import { collection, collectionData, CollectionReference, Firestore } from '@angular/fire/firestore';
@Injectable({
providedIn: 'root',
})
export class LeaderboardService {
- constructor() { }
+ private scoresCollection: CollectionReference;
+ private maxScoreCollection: CollectionReference;
+
+ constructor(private firestore: Firestore) {
+ this.scoresCollection = collection(this.firestore, 'scores');
+ this.maxScoreCollection = collection(this.firestore, 'maxScore');
+ }
getHeartbeatsSummaries(): Observable
{
- const mockData: HeartbeatSummary[] = [
- {
- publicKey: '0x7a23c98f21345dc98e23a5b1c98d23b1c98d23b1c',
- isActive: true,
- uptimePercentage: 99.8,
- blocksProduced: 1243,
- },
- {
- publicKey: '0x8b34d09e32456ed09f34b6c2d09f34b6c2d09f34',
- isActive: true,
- uptimePercentage: 98.2,
- blocksProduced: 982,
- },
- {
- publicKey: '0x9c45e10f43567fe10a45c7d3e10a45c7d3e10a45',
- isActive: false,
- uptimePercentage: 45.6,
- blocksProduced: 234,
- },
- {
- publicKey: '0x0d56f21g54678gf21b56d8e4f21b56d8e4f21b56',
- isActive: true,
- uptimePercentage: 56.9,
- blocksProduced: 876,
- },
- {
- publicKey: '0x1e67g32h65789hg32c67e9f5g32c67e9f5g32c67',
- isActive: true,
- uptimePercentage: 23.1,
- blocksProduced: 1102,
- },
- {
- publicKey: '0x7a23c98f21345dc98e23a5b1c98d23b1c98d23b1c',
- isActive: true,
- uptimePercentage: 99.8,
- blocksProduced: 1243,
- },
- {
- publicKey: '0x8b34d09e32456ed09f34b6c2d09f34b6c2d09f34',
- isActive: false,
- uptimePercentage: 98.2,
- blocksProduced: 982,
- },
- {
- publicKey: '0x9c45e10f43567fe10a45c7d3e10a45c7d3e10a45',
- isActive: false,
- uptimePercentage: 45.6,
- blocksProduced: 234,
- },
- {
- publicKey: '0x0d56f21g54678gf21b56d8e4f21b56d8e4f21b56',
- isActive: true,
- uptimePercentage: 56.9,
- blocksProduced: 876,
- },
- {
- publicKey: '0x1e67g32h65789hg32c67e9f5g32c67e9f5g32c67',
- isActive: false,
- uptimePercentage: 23.1,
- blocksProduced: 1102,
- },
- {
- publicKey: '0x7a23c98f21345dc98e23a5b1c98d23b1c98d23b1c',
- isActive: false,
- uptimePercentage: 99.8,
- blocksProduced: 1243,
- },
- {
- publicKey: '0x8b34d09e32456ed09f34b6c2d09f34b6c2d09f34',
- isActive: false,
- uptimePercentage: 98.2,
- blocksProduced: 982,
- },
- {
- publicKey: '0x9c45e10f43567fe10a45c7d3e10a45c7d3e10a45',
- isActive: false,
- uptimePercentage: 45.6,
- blocksProduced: 234,
- },
- {
- publicKey: '0x0d56f21g54678gf21b56d8e4f21b56d8e4f21b56',
- isActive: true,
- uptimePercentage: 56.9,
- blocksProduced: 876,
- },
- {
- publicKey: '0x1e67g32h65789hg32c67e9f5g32c67e9f5g32c67',
- isActive: true,
- uptimePercentage: 23.1,
- blocksProduced: 1102,
- },
- {
- publicKey: '0x7a23c98f21345dc98e23a5b1c98d23b1c98d23b1c',
- isActive: true,
- uptimePercentage: 99.8,
- blocksProduced: 1243,
- },
- {
- publicKey: '0x8b34d09e32456ed09f34b6c2d09f34b6c2d09f34',
- isActive: true,
- uptimePercentage: 98.2,
- blocksProduced: 982,
- },
- {
- publicKey: '0x9c45e10f43567fe10a45c7d3e10a45c7d3e10a45',
- isActive: false,
- uptimePercentage: 45.6,
- blocksProduced: 234,
- },
- {
- publicKey: '0x0d56f21g54678gf21b56d8e4f21b56d8e4f21b56',
- isActive: true,
- uptimePercentage: 56.9,
- blocksProduced: 876,
- },
- {
- publicKey: '0x1e67g32h65789hg32c67e9f5g32c67e9f5g32c67',
- isActive: true,
- uptimePercentage: 23.1,
- blocksProduced: 1102,
- },
- {
- publicKey: '0x7a23c98f21345dc98e23a5b1c98d23b1c98d23b1c',
- isActive: true,
- uptimePercentage: 99.8,
- blocksProduced: 1243,
- },
- {
- publicKey: '0x8b34d09e32456ed09f34b6c2d09f34b6c2d09f34',
- isActive: true,
- uptimePercentage: 98.2,
- blocksProduced: 982,
- },
- {
- publicKey: '0x9c45e10f43567fe10a45c7d3e10a45c7d3e10a45',
- isActive: false,
- uptimePercentage: 45.6,
- blocksProduced: 234,
- },
- {
- publicKey: '0x0d56f21g54678gf21b56d8e4f21b56d8e4f21b56',
- isActive: true,
- uptimePercentage: 56.9,
- blocksProduced: 876,
- },
- {
- publicKey: '0x1e67g32h65789hg32c67e9f5g32c67e9f5g32c67',
- isActive: true,
- uptimePercentage: 23.1,
- blocksProduced: 1102,
- },
- {
- publicKey: '0x7a23c98f21345dc98e23a5b1c98d23b1c98d23b1c',
- isActive: true,
- uptimePercentage: 99.8,
- blocksProduced: 1243,
- },
- {
- publicKey: '0x8b34d09e32456ed09f34b6c2d09f34b6c2d09f34',
- isActive: false,
- uptimePercentage: 98.2,
- blocksProduced: 982,
- },
- {
- publicKey: '0x9c45e10f43567fe10a45c7d3e10a45c7d3e10a45',
- isActive: false,
- uptimePercentage: 45.6,
- blocksProduced: 234,
- },
- {
- publicKey: '0x0d56f21g54678gf21b56d8e4f21b56d8e4f21b56',
- isActive: true,
- uptimePercentage: 56.9,
- blocksProduced: 876,
- },
- {
- publicKey: '0x1e67g32h65789hg32c67e9f5g32c67e9f5g32c67',
- isActive: false,
- uptimePercentage: 23.1,
- blocksProduced: 1102,
- },
- {
- publicKey: '0x7a23c98f21345dc98e23a5b1c98d23b1c98d23b1c',
- isActive: false,
- uptimePercentage: 99.8,
- blocksProduced: 1243,
- },
- {
- publicKey: '0x8b34d09e32456ed09f34b6c2d09f34b6c2d09f34',
- isActive: false,
- uptimePercentage: 98.2,
- blocksProduced: 982,
- },
- {
- publicKey: '0x9c45e10f43567fe10a45c7d3e10a45c7d3e10a45',
- isActive: false,
- uptimePercentage: 45.6,
- blocksProduced: 234,
- },
- {
- publicKey: '0x0d56f21g54678gf21b56d8e4f21b56d8e4f21b56',
- isActive: true,
- uptimePercentage: 56.9,
- blocksProduced: 876,
- },
- {
- publicKey: '0x1e67g32h65789hg32c67e9f5g32c67e9f5g32c67',
- isActive: true,
- uptimePercentage: 23.1,
- blocksProduced: 1102,
- },
- {
- publicKey: '0x7a23c98f21345dc98e23a5b1c98d23b1c98d23b1c',
- isActive: true,
- uptimePercentage: 99.8,
- blocksProduced: 1243,
- },
- {
- publicKey: '0x8b34d09e32456ed09f34b6c2d09f34b6c2d09f34',
- isActive: true,
- uptimePercentage: 98.2,
- blocksProduced: 982,
- },
- {
- publicKey: '0x9c45e10f43567fe10a45c7d3e10a45c7d3e10a45',
- isActive: false,
- uptimePercentage: 45.6,
- blocksProduced: 234,
- },
- {
- publicKey: '0x0d56f21g54678gf21b56d8e4f21b56d8e4f21b56',
- isActive: true,
- uptimePercentage: 56.9,
- blocksProduced: 876,
- },
- {
- publicKey: '0x1e67g32h65789hg32c67e9f5g32c67e9f5g32c67',
- isActive: true,
- uptimePercentage: 23.1,
- blocksProduced: 1102,
- },
- ];
+ return combineLatest([
+ collectionData(this.scoresCollection, { idField: 'id' }),
+ collectionData(this.maxScoreCollection, { idField: 'id' }),
+ ]).pipe(
+ map(([scores, maxScore]) => {
+ const maxScoreRightNow = maxScore.find(c => c.id === 'current')['value'];
+
+ const items = scores.map(score => ({
+ publicKey: score['publicKey'],
+ blocksProduced: score['blocksProduced'],
+ isActive: score['lastUpdated'] > Date.now() - 120000,
+ uptimePercentage: Math.floor((score['score'] / maxScoreRightNow) * 100),
+ uptimePrize: false,
+ blocksPrize: false,
+ } as HeartbeatSummary));
- return of(mockData);
+ const sortedItemsByUptime = [...items].sort((a, b) => b.uptimePercentage - a.uptimePercentage);
+ const fifthPlacePercentageByUptime = sortedItemsByUptime[4]?.uptimePercentage ?? 0;
+ const highestProducedBlocks = Math.max(...items.map(item => item.blocksProduced));
+ return items.map(item => ({
+ ...item,
+ uptimePrize: item.uptimePercentage >= fifthPlacePercentageByUptime,
+ blocksPrize: item.blocksProduced === highestProducedBlocks,
+ }));
+ }),
+ );
}
}
diff --git a/frontend/src/app/features/web-node/web-node.component.ts b/frontend/src/app/features/web-node/web-node.component.ts
index 68de34ddd1..81f919da09 100644
--- a/frontend/src/app/features/web-node/web-node.component.ts
+++ b/frontend/src/app/features/web-node/web-node.component.ts
@@ -31,6 +31,7 @@ export class WebNodeComponent extends StoreDispatcher implements OnInit {
private webNodeService: WebNodeService) { super(); }
ngOnInit(): void {
+ document.body.style.backgroundColor = 'var(--base-background)';
this.listenToFileUploadingEvents();
this.checkIfDeviceIsSupported();
this.listenToRoute();
diff --git a/frontend/src/app/shared/types/leaderboard/heartbeat-summary.type.ts b/frontend/src/app/shared/types/leaderboard/heartbeat-summary.type.ts
index db5e249ab2..a134981c2f 100644
--- a/frontend/src/app/shared/types/leaderboard/heartbeat-summary.type.ts
+++ b/frontend/src/app/shared/types/leaderboard/heartbeat-summary.type.ts
@@ -3,4 +3,6 @@ export interface HeartbeatSummary {
isActive: boolean;
uptimePercentage: number;
blocksProduced: number;
+ uptimePrize: boolean;
+ blocksPrize: boolean;
}
diff --git a/frontend/src/index.html b/frontend/src/index.html
index 874924d364..e627af2266 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -49,13 +49,18 @@