Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Angor UI #13

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion backend/src/repositories/AngorProjectRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ class AngorProjectRepository {
const [rows] = await DB.query(query);

let investments = rows as ProjectInvestment[];

if (investments.length === 0) {
return [];
}
investments = investments.map((investment) => ({
...investment,
// convert DB boolean representation (0 and 1) into JS boolean
Expand Down Expand Up @@ -260,7 +264,7 @@ class AngorProjectRepository {
limit = maxLimit;
}

const order = offset === undefined ? 'DESC' : 'ASC';
const order = 'DESC';

try {
const query = `SELECT id, founder_key, npub, created_on_block, txid
Expand Down
12 changes: 6 additions & 6 deletions frontend/proxy.conf.local.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';

let configContent;

// Read frontend config
// Read frontend config
try {
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
configContent = JSON.parse(rawConfig);
Expand Down Expand Up @@ -35,7 +35,7 @@ if (configContent && configContent.BASE_MODULE === 'liquid') {
},
{
context: ['/liquid/api/**'],
target: `http://localhost:8999`,
target: `http://localhost:8998`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
Expand Down Expand Up @@ -70,7 +70,7 @@ if (configContent && configContent.BASE_MODULE === 'liquid') {
PROXY_CONFIG.push(...[
{
context: ['/testnet/api/v1/lightning/**'],
target: `http://localhost:8999`,
target: `http://localhost:8998`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
Expand All @@ -88,15 +88,15 @@ PROXY_CONFIG.push(...[
},
{
context: ['/api/v1/**'],
target: `http://localhost:8999`,
target: `http://localhost:8998`,
secure: false,
ws: true,
changeOrigin: true,
proxyTimeout: 30000,
},
{
context: ['/api/**'],
target: `http://localhost:8999`,
target: `http://localhost:8998`,
secure: false,
changeOrigin: true,
proxyTimeout: 30000,
Expand All @@ -108,4 +108,4 @@ PROXY_CONFIG.push(...[

console.log(PROXY_CONFIG);

module.exports = PROXY_CONFIG;
module.exports = PROXY_CONFIG;
21 changes: 21 additions & 0 deletions frontend/src/app/angor/angor.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NgModule } from "@angular/core";
import { ProjectsListComponent } from "@app/angor/projects-list/projects-list.component";
import { ProjectComponent } from "./project/project.component";
import { CommonModule } from "@angular/common";
import { SharedModule } from "../shared/shared.module";
import { AngorRoutingModule } from "@app/angor/angor.routing.module";
import { ProjectsDashboardComponent } from "@app/angor/projects-dashboard/projects-dashboard.component";

@NgModule({
declarations: [
ProjectsListComponent,
ProjectsDashboardComponent,
ProjectComponent
],
imports: [
CommonModule,
SharedModule,
AngorRoutingModule
]
})
export class AngorModule{ }
44 changes: 44 additions & 0 deletions frontend/src/app/angor/angor.routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { RouterModule, Routes } from "@angular/router";
import { ProjectsListComponent } from "@app/angor/projects-list/projects-list.component";
import { ProjectComponent } from "./project/project.component";
import { NgModule } from "@angular/core";
import { StartComponent } from "@components/start/start.component";
import { ProjectsDashboardComponent } from "@app/angor/projects-dashboard/projects-dashboard.component";

const browserWindow = window || {};

const routes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'dashboard'
},
{
path: 'dashboard',
component: StartComponent,
children: [
{
path: '',
component: ProjectsDashboardComponent,
}
]
},
{
path: 'projects/list',
redirectTo: 'projects/list/1',
},
{
path: 'projects/list/:page',
component: ProjectsListComponent
},
{
path: 'projects/:projectId',
component: ProjectComponent
}
];

@NgModule({
imports: [RouterModule.forChild(routes)],
})
export class AngorRoutingModule { }

139 changes: 139 additions & 0 deletions frontend/src/app/angor/project/project.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<div class="container-xl">
<!-- Loading Spinner -->

<div *ngIf="isLoading" class="box">
<div class="row">
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
<tr>
<td><span class="skeleton-loader"></span></td>
<td><span class="skeleton-loader"></span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

<!-- Error Message -->
<div *ngIf="!isLoading && error">
<app-http-error [error]="error">
<span>Error loading transaction data.</span>
</app-http-error>
</div>

<!-- Main Content -->
<ng-container *ngIf="!isLoading && !error">
<div class="title-block">
<ng-container *ngIf="angorId$ | async as angorId">
<h1>Angor Project</h1>
<span class="angor-link">
<span class="angor-id">
<app-truncate
[text]="angorId"
[lastChars]="12"
[link]="['/projects/' | relativeUrl, angorId]"
>
<app-clipboard [text]="angorId"></app-clipboard>
</app-truncate>
</span>
</span>
</ng-container>
</div>

<!-- Project Stats -->
<ng-container *ngIf="projectStats$ | async as stats">
<div class="box">
<div class="row">
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td>Total Investment</td>
<td>{{ stats.amountInvested / 100000000 | number: '1.2-2' }} BTC</td>
</tr>
<tr>
<td>Total Spent By Founder</td>
<td>{{ stats.amountSpentSoFarByFounder / 100000000 | number: '1.2-2' }} BTC</td>
</tr>
<tr>
<td>Total Investors</td>
<td>{{ stats.investorCount }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-sm">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td>Withdrawn Amount</td>
<td>{{ stats.amountInPenalties / 100000000 | number: '1.2-2' }} BTC</td>
</tr>
<tr>
<td>Number of Rescinded Investments</td>
<td>{{ stats.countInPenalties }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</ng-container>

<!-- Investments List -->
<div class="investments-list" *ngIf="projectInvestments$ | async as investments">
<div *ngIf="investments.length > 0">
<table class="table table-borderless table-fixed">
<thead>
<th class="txid text-left">Investment Amount</th>
<th class="amount text-left">Transaction ID</th>
<th class="pubkey text-right">Investor Pubkey</th>
</thead>
<tbody>
<tr *ngFor="let investment of investments; let i = index;">
<td class="amount text-left">
{{ investment.totalAmount / 100000000 | number: '1.2-2' }} BTC
</td>
<td class="txid text-left">
<a [routerLink]="['/tx' | relativeUrl, investment.transactionId]">
<app-truncate [text]="investment.transactionId" [lastChars]="5"></app-truncate>
</a>
</td>
<td class="pubkey text-right">
<app-truncate [text]="investment.investorPublicKey" [lastChars]="5"></app-truncate>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</ng-container>
</div>
43 changes: 43 additions & 0 deletions frontend/src/app/angor/project/project.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.title-block {
flex-wrap: wrap;
align-items: baseline;
@media (min-width: 650px) {
flex-direction: row;
}
h1 {
margin: 0rem;
margin-right: 15px;
line-height: 1;
}
}

.angor-link {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: baseline;
width: 0;
max-width: 100%;
margin-right: 0px;
margin-bottom: 0px;
margin-top: 8px;
@media (min-width: 651px) {
flex-grow: 1;
margin-bottom: 0px;
margin-right: 1em;
top: 1px;
position: relative;
}
@media (max-width: 650px) {
width: 100%;
order: 3;

.angor-id {
width: 200px;
min-width: 200px;
flex-grow: 1;
}
}
}


66 changes: 66 additions & 0 deletions frontend/src/app/angor/project/project.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Component, OnInit } from "@angular/core";
import { finalize, Observable, of } from "rxjs";
import { AngorProjectInvestment, AngorProjectStats } from "@interfaces/angor.interface";
import { ActivatedRoute } from "@angular/router";
import { ApiService } from "@app/services/api.service";
import { catchError, map, switchMap } from "rxjs/operators";
import { HttpErrorResponse } from "@angular/common/http";

@Component({
selector: 'app-project',
templateUrl: './project.component.html',
styleUrls: ['project.component.scss']
})
export class ProjectComponent implements OnInit {
angorId$: Observable<string | null>;
projectStats$: Observable<AngorProjectStats> | null = null;
projectInvestments$: Observable<AngorProjectInvestment[]> | null = null;
isLoading = true;
error: HttpErrorResponse | null = null;

constructor(
private route: ActivatedRoute,
private apiService: ApiService,
) {
this.angorId$ = this.route.paramMap.pipe(
map(params => params.get('projectId'))
);

this.projectStats$ = of(null);
this.projectInvestments$ = of([]);
}

ngOnInit() {
this.angorId$.pipe(
switchMap(angorId => {
if (!angorId) {
throw new Error('Angor ID is missing.');
}

this.isLoading = true;

return this.apiService.getAngorProjectStats$(angorId).pipe(
switchMap(stats => {
this.projectStats$ = of(stats);
return this.apiService.getAngorProjectInvestments(angorId);
}),
map(investments => {
this.projectInvestments$ = of(investments);
return investments;
}),
catchError(err => {
this.error = new HttpErrorResponse({ error: 'Failed to load project data.' });
return of([]);
}),
finalize(() => this.isLoading = false),
);
}),
catchError(err => {
this.error = new HttpErrorResponse({ error: 'Invalid Angor ID.'});
this.isLoading = false;
return of([]);
})
)
.subscribe();
}
}
Loading
Loading