293 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			293 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import React, {Component} from "react";
 | |
| import {EventName, GetProfitLossMessage, NewTickMessage, PutProfitLossMessage} from "../types";
 | |
| import {socket} from "../index";
 | |
| import {DateTime} from "luxon";
 | |
| 
 | |
| type QuoteStatusProps = {
 | |
|     percentage?: boolean,
 | |
|     quote_symbol?: string,
 | |
|     amount: number,
 | |
|     subtitle: string,
 | |
|     sign?: boolean
 | |
| }
 | |
| 
 | |
| export class QuoteStatus extends Component<QuoteStatusProps> {
 | |
|     private whole: number;
 | |
|     private decimal: number;
 | |
|     private sign: string;
 | |
|     private signClass: string;
 | |
| 
 | |
|     constructor(props) {
 | |
|         super(props);
 | |
| 
 | |
|         this.deriveProps()
 | |
|     }
 | |
| 
 | |
|     deriveProps() {
 | |
|         this.whole = Math.abs(Math.trunc(this.props.amount))
 | |
|         this.decimal = Math.trunc(this.props.amount % 1 * 100)
 | |
|         this.sign = this.props.amount > 0 ? "+" : "-"
 | |
| 
 | |
|         this.signClass = this.props.amount > 0 ? "text-green-500" : "text-red-500"
 | |
|     }
 | |
| 
 | |
|     renderSign() {
 | |
|         if (this.props.sign) {
 | |
|             return (
 | |
|                 <span
 | |
|                     className={this.signClass}>{this.sign}</span>
 | |
|             )
 | |
|         }
 | |
|         return null
 | |
|     }
 | |
| 
 | |
|     symbolOrPercentageRender() {
 | |
|         if (this.props.percentage) {
 | |
|             return (
 | |
|                 <>
 | |
|                 <span className="text-4xl text-bold align-top">
 | |
|                     {this.renderSign()}</span>
 | |
|                     <span className="text-5xl">{Math.abs(this.props.amount).toFixed(2)}</span>
 | |
|                     <span className="text-3xl align-top">%</span>
 | |
|                 </>
 | |
|             )
 | |
|         } else {
 | |
|             return (
 | |
|                 <>
 | |
|                     <span className="text-4xl text-bold align-top">{this.renderSign()}{this.props.quote_symbol}</span>
 | |
|                     <span className="text-5xl">{this.whole.toLocaleString()}</span>
 | |
|                     <span className="text-3xl align-top">.{Math.abs(this.decimal)}</span>
 | |
|                 </>
 | |
|             )
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     render() {
 | |
|         return (
 | |
|             <>
 | |
|                 <div className="text-gray-700 mb-2">
 | |
|                     {this.symbolOrPercentageRender()}
 | |
|                 </div>
 | |
|                 <div className="text-sm uppercase text-gray-300 tracking-wide">
 | |
|                     {this.props.subtitle}
 | |
|                 </div>
 | |
|             </>
 | |
|         )
 | |
|     }
 | |
| }
 | |
| 
 | |
| type DateButtonProps = {
 | |
|     label: string,
 | |
|     onClick: any,
 | |
|     selected_default?: boolean
 | |
| }
 | |
| 
 | |
| type DateButtonState = {
 | |
|     selected: boolean,
 | |
| }
 | |
| 
 | |
| class DateButton extends Component<DateButtonProps, DateButtonState> {
 | |
|     private classSelected: string = "appearance-none py-4 text-blue-600 border-b border-blue-600 mr-3";
 | |
|     private classNotSelected: string = "appearance-none py-4 text-gray-600 border-b border-transparent hover:border-gray-800 mr-3";
 | |
|     private currentClass: string;
 | |
| 
 | |
|     state = {
 | |
|         selected: this.props.selected_default
 | |
|     }
 | |
| 
 | |
|     constructor(props) {
 | |
|         super(props);
 | |
| 
 | |
|         this.updateClass()
 | |
|     }
 | |
| 
 | |
|     onClick() {
 | |
|         this.setState({selected: !this.state.selected}, this.updateClass)
 | |
| 
 | |
|         this.props.onClick()
 | |
|     }
 | |
| 
 | |
|     updateClass() {
 | |
|         this.currentClass = this.state.selected ? this.classSelected : this.classNotSelected
 | |
|     }
 | |
| 
 | |
|     render() {
 | |
|         return (
 | |
|             <button key={this.props.label} type="button"
 | |
|                     className={this.currentClass} onClick={this.onClick.bind(this)}>
 | |
|                 {this.props.label}
 | |
|             </button>
 | |
|         )
 | |
|     }
 | |
| }
 | |
| 
 | |
| const PeriodUnit = {
 | |
|     SECOND: "second",
 | |
|     MINUTE: "minute",
 | |
|     HOUR: "hour",
 | |
|     DAY: "day",
 | |
|     WEEK: "week",
 | |
|     MONTH: "month",
 | |
|     YEAR: "year"
 | |
| }
 | |
| 
 | |
| type StatusBarState = {
 | |
|     pl_period_unit: string,
 | |
|     pl_period_amount: number,
 | |
|     pl: number,
 | |
|     pl_perc: number
 | |
| }
 | |
| 
 | |
| export class Statusbar extends Component<NewTickMessage, StatusBarState> {
 | |
| 
 | |
|     constructor(props) {
 | |
|         super(props);
 | |
| 
 | |
|         this.state = {
 | |
|             pl_period_unit: PeriodUnit.WEEK,
 | |
|             pl_period_amount: 1,
 | |
|             pl: 0.0,
 | |
|             pl_perc: 0.0
 | |
|         }
 | |
| 
 | |
|         this.emitGetProfitLoss = this.emitGetProfitLoss.bind(this)
 | |
|     }
 | |
| 
 | |
|     componentDidMount() {
 | |
|         socket.on(EventName.PutProfitLoss, (data: PutProfitLossMessage) => {
 | |
|             this.setState({
 | |
|                 pl: data.pl,
 | |
|                 pl_perc: data.pl_perc
 | |
|             })
 | |
|         })
 | |
| 
 | |
|         socket.on(EventName.FirstConnect, this.emitGetProfitLoss)
 | |
|         socket.on(EventName.NewTick, this.emitGetProfitLoss)
 | |
|     }
 | |
| 
 | |
| 
 | |
|     durationObjectfromStr(str: string) {
 | |
|         switch (str) {
 | |
|             case PeriodUnit.MINUTE:
 | |
|                 return {
 | |
|                     minutes: this.state.pl_period_amount
 | |
|                 }
 | |
|             case PeriodUnit.HOUR:
 | |
|                 return {
 | |
|                     hours: this.state.pl_period_amount
 | |
|                 }
 | |
|             case PeriodUnit.DAY:
 | |
|                 return {
 | |
|                     days: this.state.pl_period_amount
 | |
|                 }
 | |
|             case PeriodUnit.WEEK:
 | |
|                 return {
 | |
|                     weeks: this.state.pl_period_amount
 | |
|                 }
 | |
|             case PeriodUnit.MONTH:
 | |
|                 return {
 | |
|                     months: this.state.pl_period_amount
 | |
|                 }
 | |
|             case PeriodUnit.YEAR:
 | |
|                 return {
 | |
|                     years: this.state.pl_period_amount
 | |
|                 }
 | |
|             default:
 | |
|                 return {}
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     emitGetProfitLoss() {
 | |
|         const message: GetProfitLossMessage = {
 | |
|             start: DateTime.local().minus(this.durationObjectfromStr(this.state.pl_period_unit)).toMillis(),
 | |
|             end: DateTime.local().toMillis()
 | |
|         }
 | |
| 
 | |
|         socket.emit(EventName.GetProfitLoss, message)
 | |
|     }
 | |
| 
 | |
|     changeProfitLossPeriod(amount: number, unit: string) {
 | |
|         this.setState({
 | |
|             pl_period_amount: amount,
 | |
|             pl_period_unit: unit
 | |
|         }, this.emitGetProfitLoss)
 | |
|     }
 | |
| 
 | |
|     render() {
 | |
|         return (
 | |
|             <div className="bg-white border-t border-b sm:border-l sm:border-r sm:rounded-lg shadow flex-grow mb-6">
 | |
|                 <div className="bg-gray-50 rounded-tl-lg rounded-tr-lg border-b px-6">
 | |
|                     <div className="flex justify-between -mb-px">
 | |
|                         <div className="lg:hidden text-blue-600 py-4 text-lg">
 | |
|                             Price Charts
 | |
|                         </div>
 | |
|                         <div className="hidden lg:flex">
 | |
|                             <button type="button"
 | |
|                                     className="appearance-none py-4 text-blue-600 border-b border-blue-dark mr-6">
 | |
|                                 Bitcoin
 | |
|                             </button>
 | |
|                         </div>
 | |
|                         <div className="flex text-sm">
 | |
|                             <DateButton key={PeriodUnit.MINUTE} label={"1m"}
 | |
|                                         onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.MINUTE)}/>
 | |
|                             <DateButton key={PeriodUnit.DAY} label={"1D"}
 | |
|                                         onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.DAY)}/>
 | |
|                             <DateButton key={PeriodUnit.WEEK} label={"1W"} selected_default={true}
 | |
|                                         onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.WEEK)}/>
 | |
|                             <DateButton key={PeriodUnit.MONTH} label={"1M"}
 | |
|                                         onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.MONTH)}/>
 | |
|                             <DateButton key={PeriodUnit.YEAR} label={"1Y"}
 | |
|                                         onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.YEAR)}/>
 | |
|                             {/*<DateButton label={"ALL"} onClick={() => this.changeProfitLossPeriod(1, PeriodUnit.MINUTE)}/>*/}
 | |
|                         </div>
 | |
|                     </div>
 | |
|                 </div>
 | |
|                 <div className="flex items-center px-6 lg:hidden">
 | |
|                     <div className="flex-grow flex-no-shrink py-6">
 | |
|                         <div className="text-gray-700 mb-2">
 | |
|                             <span className="text-3xl align-top">CA$</span>
 | |
|                             <span className="text-5xl">21,404</span>
 | |
|                             <span className="text-3xl align-top">.74</span>
 | |
|                         </div>
 | |
|                         <div className="text-green-300 text-sm">
 | |
|                             ↑ CA$12,955.35 (154.16%)
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     <div className="flex-shrink w-32 inline-block relative">
 | |
|                         <select
 | |
|                             className="block appearance-none w-full bg-white border border-grey-light px-4 py-2 pr-8 rounded">
 | |
|                             <option>BTC</option>
 | |
|                         </select>
 | |
|                         <div className="pointer-events-none absolute pin-y pin-r flex items-center px-2 text-gray-300">
 | |
|                             <svg className="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg"
 | |
|                                  viewBox="0 0 20 20">
 | |
|                                 <path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
 | |
|                             </svg>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                 </div>
 | |
|                 <div className="hidden lg:flex">
 | |
|                     <div className="w-1/3 text-center py-8">
 | |
|                         <div className="border-r">
 | |
|                             <QuoteStatus key={this.props.price} quote_symbol={"USD"} amount={this.props.price}
 | |
|                                          subtitle={"Bitcoin price"}/>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     <div className="w-1/3 text-center py-8">
 | |
|                         <div className="border-r">
 | |
|                             <QuoteStatus key={this.state.pl} quote_symbol={"USD"} sign={true} amount={this.state.pl}
 | |
|                                          subtitle={"since last ".concat(this.state.pl_period_unit)}/>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                     <div className="w-1/3 text-center py-8">
 | |
|                         <div>
 | |
|                             <QuoteStatus key={this.state.pl_perc} percentage={true} sign={true}
 | |
|                                          amount={this.state.pl_perc}
 | |
|                                          subtitle={"since last ".concat(this.state.pl_period_unit)}/>
 | |
|                         </div>
 | |
|                     </div>
 | |
|                 </div>
 | |
|             </div>
 | |
|         )
 | |
|     }
 | |
| } |