Skip to content

Commit

Permalink
Selectable DCA frequency unit via dropdown
Browse files Browse the repository at this point in the history
  • Loading branch information
jvonasek committed Sep 26, 2024
1 parent b9be8e7 commit 39e22f8
Show file tree
Hide file tree
Showing 12 changed files with 245 additions and 37 deletions.
8 changes: 8 additions & 0 deletions packages/apps/src/app/dca/Form.css
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@
width: 100%;
}

.frequency-trigger {
display: inline-flex;
margin-right: -8px;
height: 18px;
font-size: 14px;
cursor: pointer;
}

.adornment {
white-space: nowrap;
font-weight: 500;
Expand Down
118 changes: 101 additions & 17 deletions packages/apps/src/app/dca/Form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ import { baseStyles, formStyles } from 'styles';
import { formatAmount, humanizeAmount } from 'utils/amount';
import { MINUTE_MS } from 'utils/time';

import { DcaOrder, INTERVAL_DCA, IntervalDca } from './types';
import { DcaOrder, FrequencyUnit, INTERVAL_DCA, IntervalDca } from './types';

import { Amount, Asset } from '@galacticcouncil/sdk';

import styles from './Form.css';

const HOUR_MIN = 60;
const DAY_MIN = 24 * HOUR_MIN;

@customElement('gc-dca-form')
export class DcaForm extends BaseElement {
private account = new DatabaseController<Account>(this, AccountCursor);
Expand All @@ -36,6 +39,7 @@ export class DcaForm extends BaseElement {
@property({ type: Object }) assetIn: Asset = null;
@property({ type: Object }) assetOut: Asset = null;
@property({ type: String }) interval: IntervalDca = 'hour';

@property({ type: Number }) intervalMultiplier: number = 1;
@property({ type: Number }) frequency: number = null;
@property({ type: String }) amountIn = null;
Expand All @@ -44,6 +48,7 @@ export class DcaForm extends BaseElement {
@property({ attribute: false }) error = {};

@state() advanced: boolean = false;
@state() frequencyUnit: FrequencyUnit = 'min';

static styles = [baseStyles, formStyles, styles];

Expand Down Expand Up @@ -83,6 +88,18 @@ export class DcaForm extends BaseElement {
this.advanced = !this.advanced;
}

get minInterval() {
return this.order
? Math.min(this.order.frequencyMin, this.order.frequencyOpt)
: 0;
}

get maxInterval() {
return Number.isFinite(this.order?.frequencyOpt)
? Math.max(this.minInterval, this.order.frequencyOpt)
: 0;
}

onScheduleClick(e: any) {
const options = {
bubbles: true,
Expand All @@ -92,11 +109,20 @@ export class DcaForm extends BaseElement {
}

onIntervalChange(e: any) {
const interval = e.detail.value as IntervalDca;
const options = {
bubbles: true,
composed: true,
detail: { value: e.detail.value },
detail: { value: interval },
};

const freqUnitByInterval: Record<IntervalDca, FrequencyUnit> = {
hour: 'min',
day: 'min',
week: 'hour',
};

this.setFrequencyUnit(this.maxInterval, freqUnitByInterval[interval]);
this.dispatchEvent(new CustomEvent('interval-change', options));
}

Expand All @@ -109,15 +135,28 @@ export class DcaForm extends BaseElement {
this.dispatchEvent(new CustomEvent('interval-mul-change', options));
}

onFrequencyChange(e: any) {
convertFrequencyValue(value: number, unit: FrequencyUnit = 'min') {
return unit === 'min'
? value
: unit === 'hour'
? value * HOUR_MIN
: value * DAY_MIN;
}

onFrequencyChange(value: number, unit: FrequencyUnit = 'min') {
const options = {
bubbles: true,
composed: true,
detail: { value: e.detail.value },
detail: { value: this.convertFrequencyValue(value, unit) },
};
this.dispatchEvent(new CustomEvent('frequency-change', options));
}

setFrequencyUnit(value: number, unit: FrequencyUnit) {
this.frequencyUnit = unit;
this.onFrequencyChange(value, unit);
}

infoSummaryTemplate() {
if (this.inProgress) {
return html`
Expand Down Expand Up @@ -327,14 +366,8 @@ export class DcaForm extends BaseElement {
}

formFrequencyTemplate() {
const min = this.order
? Math.min(this.order.frequencyMin, this.order.frequencyOpt)
: 0;

const max = Number.isFinite(this.order?.frequencyOpt)
? Math.max(min, this.order.frequencyOpt)
: 0;

const min = this.minInterval;
const max = this.maxInterval;
const value = this.frequency ?? max;

const valueMsec = value * 60 * 1000;
Expand All @@ -348,18 +381,69 @@ export class DcaForm extends BaseElement {
})
: undefined;

const range = max - min;
const rangeInHours = Math.floor(range / HOUR_MIN);
const rangeInDays = Math.floor(range / DAY_MIN);

const minValues: Record<FrequencyUnit, number> = {
min: min,
hour: Math.ceil(min / HOUR_MIN),
day: Math.ceil(min / DAY_MIN),
};

const maxValues: Record<FrequencyUnit, number> = {
min: max,
hour: Math.floor(max / HOUR_MIN),
day: Math.floor(max / DAY_MIN),
};

const values: Record<FrequencyUnit, number> = {
min: value,
hour: Math.floor(value / HOUR_MIN),
day: Math.floor(value / DAY_MIN),
};

const units = [
'min',
rangeInHours > 0 && 'hour',
rangeInDays > 0 && 'day',
].filter((u): u is FrequencyUnit => !!u);

return html`
<div>
<uigc-slider
label=${i18n.t('form.advanced.interval')}
unit="min"
unit=${i18n.t(`form.frequency.${this.frequencyUnit}`)}
hint=${blockHint}
.min=${min}
.max=${max}
.value=${value}
.min=${minValues[this.frequencyUnit]}
.max=${maxValues[this.frequencyUnit]}
.value=${values[this.frequencyUnit]}
.disabled=${!this.order}
@input-change=${(e: CustomEvent) => this.onFrequencyChange(e)}>
@input-change=${(e: CustomEvent) =>
this.onFrequencyChange(
parseFloat(e.detail.value),
this.frequencyUnit,
)}>
>
<div slot="value">
${units.length > 1
? html`
<uigc-dropdown
triggerMethod="click"
placement="bottom-end"
.items=${units.map((u) => ({
active: this.frequencyUnit === u,
text: i18n.t(`form.frequency.${u}`),
onClick: () => this.setFrequencyUnit(values[u], u),
}))}>
<div class="frequency-trigger">
${i18n.t(`form.frequency.${this.frequencyUnit}`)}
<uigc-icon-dropdown></uigc-icon-dropdown>
</div>
</uigc-dropdown>
`
: i18n.t(`form.frequency.${this.frequencyUnit}`)}
</div>
</uigc-slider>
</div>
`;
Expand Down
4 changes: 4 additions & 0 deletions packages/apps/src/app/dca/translation.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"form.info.estSchedule": "Schedule end (est.)",
"form.info.slippage": "Slippage protection",

"form.frequency.min": "minutes",
"form.frequency.hour": "hours",
"form.frequency.day": "days",

"error.insufficientBalance": "Your trade is bigger than your balance.",
"error.minBudgetTooLow": "The minimum budget is {{amount}} {{asset}}.",
"error.frequencyOutOfRange": "The valid frequency is between {{min}} and {{max}} minutes.",
Expand Down
1 change: 1 addition & 0 deletions packages/apps/src/app/dca/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const INTERVAL_DCA_MS: Record<IntervalDca, number> = {
};

export type IntervalDca = (typeof INTERVAL_DCA)[number];
export type FrequencyUnit = 'min' | 'hour' | 'day';

export interface DcaOrder extends Humanizer {
amountIn: BigNumber;
Expand Down
41 changes: 41 additions & 0 deletions packages/ui/src/component/Dropdown.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.tooltip {
display: none;
width: max-content;
min-width: 120px;
text-align: left;
position: fixed;
top: 0;
left: 0;
background: var(--hex-dark-blue-700);
border: 1px solid var(--hex-dark-blue-400);
color: white;
padding: 10px;
border-radius: 8px;
z-index: 1000;
box-shadow: 0px 40px 40px 0px rgba(0, 0, 0, 0.8);
}

.tooltip.show {
display: block;
}

.tooltip > button {
display: block;
width: 100%;
text-align: left;
border: none;
padding: 4px 8px;
background: transparent;
border-radius: 4px;
cursor: pointer;
color: var(--hex-basic-500);
}

.tooltip > button.active {
background: rgba(255, 255, 255, 0.06);
color: white;
}

.tooltip > button:hover {
color: white;
}
47 changes: 47 additions & 0 deletions packages/ui/src/component/Dropdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { UIGCElement } from 'component/base/UIGCElement';

import { Popper } from 'component/Popper';

import styles from './Dropdown.css';

type ItemProps = {
text: string;
active?: boolean;
onClick: () => void;
};

@customElement('uigc-dropdown')
export class Dropdown extends Popper {
static styles = [UIGCElement.styles, styles];

get buttonElement() {
return this.shadowRoot.querySelector('.tooltip > button');
}

@property({ type: String }) text = null;
@property({ type: Array }) items: ItemProps[] = [];

handleItemClick(item: ItemProps) {
this.tooltipElement.classList.remove('show');
item.onClick();
}

render() {
return html`
<slot></slot>
<div class="tooltip">
${this.items.map(
(item) => html`
<button
class=${item.active ? 'active' : ''}
@click=${() => this.handleItemClick(item)}>
${item.text}
</button>
`,
)}
</div>
`;
}
}
4 changes: 4 additions & 0 deletions packages/ui/src/component/Popper.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@
font-size: 90%;
z-index: 1000;
}

.tooltip.show {
display: block;
}
Loading

0 comments on commit 39e22f8

Please sign in to comment.