Skip to content

Commit

Permalink
Merge pull request #75 from onesimus-wiafe/SCRUM-65-frontend-portfoli…
Browse files Browse the repository at this point in the history
…o-list-page

chore: Update frontend components and services for form submission ev…
  • Loading branch information
djangbahevans authored Aug 9, 2024
2 parents 1473c3b + 212acc0 commit ba1894d
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 40 deletions.
7 changes: 7 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@ng-icons/core": "^29.2.1",
"@ng-icons/heroicons": "^29.2.1",
"@ng-icons/remixicon": "^29.2.1",
"@stomp/stompjs": "^7.0.0",
"date-fns": "^3.6.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
Expand Down
14 changes: 11 additions & 3 deletions frontend/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { ToastComponent } from "./shared/components/toast/toast.component";
import { ToastComponent } from './shared/components/toast/toast.component';
import { WebsocketService } from './core/services/websocket.service';

@Component({
selector: 'app-root',
Expand All @@ -9,6 +10,13 @@ import { ToastComponent } from "./shared/components/toast/toast.component";
templateUrl: './app.component.html',
styleUrl: './app.component.css',
})
export class AppComponent {
export class AppComponent implements OnInit {
title = 'frontend';

constructor(private websocketService: WebsocketService) {}

ngOnInit(): void {
console.log('Connecting to websocket');
this.websocketService.connect();
}
}
5 changes: 3 additions & 2 deletions frontend/src/app/core/services/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class OrderService {
return this.httpClient.post<OrderResponse>("/orders", data);
}

cancelOrder
() {}
cancelOrder(orderId: number) {
return this.httpClient.delete(`/orders/${orderId}`);
}
}
54 changes: 54 additions & 0 deletions frontend/src/app/core/services/websocket.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Injectable, OnInit, signal } from '@angular/core';
import { Client } from '@stomp/stompjs';

@Injectable({
providedIn: 'root',
})
export class WebsocketService implements OnInit {
constructor() {}

ngOnInit(): void {
console.log('Setting up client');
this.client.set(this.setupClient());
}

client = signal<Client | null>(null);
connected = signal(false);

setupClient() {
console.log('Setting up client');
const client = new Client({
brokerURL: 'ws://localhost:5002/api/v1/report/ws',
});

client.onConnect = (frame) => {
this.connected.set(true);
console.log('Connected: ' + client.connected + ' : ' + frame);
client.subscribe('/topic/greetings', (greeting) => {
console.log(greeting);
});
};

client.onWebSocketError = (error) => {
console.error('Error with websocket', error);
};

client.onStompError = (frame) => {
console.error('Broker reported error: ' + frame.headers['message']);
console.error('Additional details: ' + frame.body);
};

return client;
}

connect() {
console.log('Connecting');
this.client()?.activate();
}

disconnect() {
this.client()?.deactivate();
this.connected.set(false);
console.log('Disconnected');
}
}
4 changes: 2 additions & 2 deletions frontend/src/app/features/dashboard/dashboard.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@
</div>

<div class="mt-4 flex space-x-5">
<div class="w-3/4">
<div [class]="isAdmin() ? 'w-full' : 'w-3/4'">
<canvasjs-chart
[options]="stockChartOptions()"
[styles]="{ width: '100%', height: '100%' }"
></canvasjs-chart>
</div>

<div class="w-1/4">
<div [class]="isAdmin() ? 'hidden' : 'w-1/4'">
<app-order-form (formSubmit)="placeOrder($event)" />
</div>
</div>
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/app/features/dashboard/dashboard.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { StockPriceCardComponent } from '../../shared/components/stock-price-car
import { ToastService, ToastVariant } from '../../core/services/toast.service';
import { Stock } from '../../shared/models/stock.model';
import { OrderRequest } from '../../shared/models/order.model';
import { AuthService } from '../../core/services/auth.service';
import { AccountType } from '../../shared/models/user.model';

@Component({
selector: 'app-dashboard',
Expand All @@ -27,9 +29,12 @@ export class DashboardComponent {
constructor(
private themeService: ThemeService,
private orderService: OrderService,
private toastService: ToastService
private toastService: ToastService,
private authService: AuthService
) {}

isAdmin = computed(() => this.authService.role() == AccountType.ADMIN);

stocks: Stock[] = [
{
name: 'Amazon',
Expand Down Expand Up @@ -90,7 +95,7 @@ export class DashboardComponent {
];

placeOrder($event: OrderRequest) {
console.log("placing order", $event);
console.log('placing order', $event);
this.orderService.createOrder($event).subscribe({
next: () => {
this.toastService.initiate({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ <h2 class="card-title">Place Order</h2>
<option [value]="portfolio.id">{{ portfolio.name }}</option>
}
</select>
@if (orderForm.get('portfolio')?.touched && orderForm.get('portfolio')?.invalid) {
@if (orderForm.get('portfolioId')?.touched && orderForm.get('portfolioId')?.invalid) {
<small class="text-error">Portfolio is required</small>
}
@if (orderForm.get('portfolioId')?.touched && orderForm.get('portfolioId')?.invalid) {
<small class="text-error">
{{ orderForm.get('portfolioId')?.errors?.['message'] }}
</small>
}
</div>
<div>
<label for="stock" class="label text-sm">Stock</label>
Expand All @@ -36,6 +41,11 @@ <h2 class="card-title">Place Order</h2>
<option value="ORCL">Oracle</option>
<option value="AMZN">Amazon</option>
</select>
@if (orderForm.get('ticker')?.touched && orderForm.get('ticker')?.invalid) {
<small class="text-error">
{{ orderForm.get('ticker')?.errors?.['message'] }}
</small>
}
</div>
<div>
<label for="tradeType" class="label text-sm">Order Type</label>
Expand All @@ -47,6 +57,11 @@ <h2 class="card-title">Place Order</h2>
<option value="MARKET">Market</option>
<option value="LIMIT">Limit</option>
</select>
@if (orderForm.get('orderType')?.touched && orderForm.get('orderType')?.invalid) {
<small class="text-error">
{{ orderForm.get('orderType')?.errors?.['message'] }}
</small>
}
</div>
<div>
<label for="side" class="label text-sm">Side</label>
Expand All @@ -58,6 +73,11 @@ <h2 class="card-title">Place Order</h2>
<option value="BUY">Buy</option>
<option value="SELL">Sell</option>
</select>
@if (orderForm.get('side')?.touched && orderForm.get('side')?.invalid) {
<small class="text-error">
{{ orderForm.get('side')?.errors?.['message'] }}
</small>
}
</div>
<div>
<label for="price" class="label text-sm">Price</label>
Expand All @@ -70,9 +90,7 @@ <h2 class="card-title">Place Order</h2>
/>
@if (orderForm.get('unitPrice')?.touched && orderForm.get('unitPrice')?.invalid) {
<small class="text-error">
{{ orderForm.get('unitPrice')?.errors?.['required']
? 'Price is required'
: 'Price must be a number' }}
{{ orderForm.get('unitPrice')?.errors?.['message'] }}
</small>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import {
import * as v from 'valibot';
import { PortfolioService } from '../../../core/services/portfolio.service';
import { PortfolioListResponse } from '../../models/portfolio.model';
import { OrderRequest, OrderRequestSchema, OrderType } from '../../models/order.model';
import {
OrderRequest,
OrderRequestSchema,
OrderType,
} from '../../models/order.model';
import { validateField } from '../../../core/validators/validate';

@Component({
selector: 'app-order-form',
Expand All @@ -40,41 +45,56 @@ export class OrderFormComponent implements OnInit {
defaultside = input<'BUY' | 'SELL' | undefined | null>('BUY');

orderForm: FormGroup = new FormGroup({
portfolioId: new FormControl('', Validators.required),
ticker: new FormControl('', Validators.required),
orderType: new FormControl('MARKET', Validators.required),
side: new FormControl('', Validators.required),
unitPrice: new FormControl('', [
Validators.required,
Validators.pattern(/^[0-9]+(\.[0-9]{1,2})?$/),
]),
quantity: new FormControl('', [
Validators.required,
Validators.pattern(/^[0-9]+$/),
]),
portfolioId: new FormControl(
'',
validateField(OrderRequestSchema.entries.portfolioId)
),
ticker: new FormControl(
'',
validateField(OrderRequestSchema.entries.ticker)
),
orderType: new FormControl(
'MARKET',
validateField(OrderRequestSchema.entries.orderType)
),
side: new FormControl('', validateField(OrderRequestSchema.entries.side)),
unitPrice: new FormControl(
'',
validateField(OrderRequestSchema.entries.unitPrice)
),
quantity: new FormControl(
'',
validateField(OrderRequestSchema.entries.quantity)
),
});

constructor(private portfolioService: PortfolioService) {
effect(() => {
this.orderForm = new FormGroup({
portfolioId: new FormControl(
this.defaultportfolio() || undefined,
Validators.required
validateField(OrderRequestSchema.entries.portfolioId)
),
ticker: new FormControl(
this.defaultstock() || 'MSFT',
Validators.required
validateField(OrderRequestSchema.entries.ticker)
),
orderType: new FormControl(
'MARKET',
validateField(OrderRequestSchema.entries.orderType)
),
side: new FormControl(
this.defaultside() || 'BUY',
validateField(OrderRequestSchema.entries.side)
),
unitPrice: new FormControl(
'',
validateField(OrderRequestSchema.entries.unitPrice)
),
quantity: new FormControl(
'',
validateField(OrderRequestSchema.entries.quantity)
),
orderType: new FormControl('MARKET', Validators.required),
side: new FormControl(this.defaultside() || 'BUY', Validators.required),
unitPrice: new FormControl('', [
Validators.required,
Validators.pattern(/^[0-9]+(\.[0-9]{1,2})?$/),
]),
quantity: new FormControl('', [
Validators.required,
Validators.pattern(/^[0-9]+$/),
]),
});
});
}
Expand Down Expand Up @@ -106,6 +126,7 @@ export class OrderFormComponent implements OnInit {
this.orderForm.reset();
} else {
console.error(result.issues);
console.log(this.orderForm.value);
}
}
}
7 changes: 5 additions & 2 deletions frontend/src/app/shared/models/order.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,18 @@ export const TradeSchema = v.object({
export type Trade = v.InferOutput<typeof TradeSchema>;

export const OrderRequestSchema = v.object({
portfolioId: v.pipe(v.string(), v.transform(Number.parseInt)),
portfolioId: v.pipe(
v.union([v.number(), v.string()]),
v.transform(Number.parseInt)
),
ticker: v.enum(StockSymbol),
quantity: v.pipe(
v.union([v.number(), v.string()]),
v.transform(Number.parseInt)
),
orderType: v.enum(OrderType),
side: v.enum(OrderSide),
unitPrice: v.pipe(v.string(), v.transform(parseFloat)),
unitPrice: v.pipe(v.string(), v.nonEmpty(), v.transform(parseFloat)),
});

export type OrderRequest = v.InferOutput<typeof OrderRequestSchema>;
Expand Down

0 comments on commit ba1894d

Please sign in to comment.