Skip to content

Commit

Permalink
Merge pull request #394 from Concordium/implement-sliders-for-commiss…
Browse files Browse the repository at this point in the history
…ion-fields

Add Sliders for commissionFields
  • Loading branch information
shjortConcordium authored Oct 11, 2023
2 parents b3ec062 + f2bdcb5 commit 78986cd
Show file tree
Hide file tree
Showing 16 changed files with 625 additions and 40 deletions.
6 changes: 6 additions & 0 deletions packages/browser-wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.1.9

### Added

- When creating/updating a baker, the commission rates can now be changed, if the ranges are not singletons.

## 1.1.8

### Added
Expand Down
3 changes: 2 additions & 1 deletion packages/browser-wallet/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@concordium/browser-wallet",
"version": "1.1.8",
"version": "1.1.9",
"description": "Browser extension wallet for the Concordium blockchain",
"author": "Concordium Software",
"license": "Apache-2.0",
Expand Down Expand Up @@ -38,6 +38,7 @@
"leb128": "^0.0.5",
"lodash.debounce": "^4.0.8",
"lodash.groupby": "^4.6.0",
"rc-slider": "9",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-hook-form": "^7.30.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
.account-transaction-flow {
padding: rem(10px) rem(15px);
padding: rem(10px) 0 rem(10px) rem(16px);
display: flex;
flex-direction: column;
width: 100%;
justify-content: space-between;
background-color: $color-bg;
min-height: 100%;
height: 100%;
white-space: break-spaces;
scrollbar-gutter: stable;
overflow: overlay;

&__header {
position: relative;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { CommonFieldProps, RequiredUncontrolledFieldProps } from '@popup/shared/Form/common/types';
import { makeUncontrolled } from '@popup/shared/Form/common/utils';
import { CommonFieldProps, RequiredControlledFieldProps } from '@popup/shared/Form/common/types';
import { makeControlled } from '@popup/shared/Form/common/utils';
import InlineNumber from '@popup/shared/Form/InlineNumber/InlineNumber';
import Slider from '@popup/shared/Form/Slider';
import clsx from 'clsx';
import React, { forwardRef, InputHTMLAttributes } from 'react';
import React, { InputHTMLAttributes } from 'react';
import { PropsOf } from 'wallet-common-helpers';

interface CommissionFieldProps {
label: string;
Expand All @@ -10,31 +13,69 @@ interface CommissionFieldProps {
min: number;
/** Decimal */
max: number;
/** Decimal */
existing?: number;
}

const commonSliderProps: Pick<PropsOf<typeof Slider>, 'step' | 'unit'> = {
step: 0.001,
unit: '%',
};

type Props = Pick<InputHTMLAttributes<HTMLInputElement>, 'type' | 'className' | 'autoFocus'> &
RequiredUncontrolledFieldProps<HTMLInputElement> &
RequiredControlledFieldProps &
CommonFieldProps &
CommissionFieldProps;

// TODO Implement sliders for commissions
export const CommissionInput = forwardRef<HTMLInputElement, Props>(
({ error, className, type, name, min, max, label, note, valid, ...props }, ref) => {
// Default to max percentage
const value = max * 100;
export function CommissionInput({
error,
className,
type,
name,
min,
max,
label,
note,
valid,
onChange,
onBlur,
value,
...props
}: Props) {
const minPercentage = min * 100;
const maxPercentage = max * 100;

if (min === max) {
return (
<>
<input type="hidden" name={name} ref={ref} value={value} {...props} />
<div className={clsx('baking__commissionField-slider', className)}>
{label && <div className="baking__commissionField-label">{label}</div>}
<div className="baking__commissionField-value">{value}%</div>
<div className={clsx('baking__commissionField-slider', className)}>
{label && <div className="baking__commissionField-label">{label}</div>}
<div className="baking__commissionField-value">
<InlineNumber
onChange={() => onChange?.(minPercentage)}
disabled
name={name}
{...props}
value={minPercentage.toString()}
/>
%
</div>
</>
</div>
);
}
);

const CommissionsField = makeUncontrolled(CommissionInput);
return (
<Slider
value={value}
label={label}
name={name}
min={minPercentage}
max={maxPercentage}
onChange={onChange}
onBlur={onBlur}
className={clsx('baking__commissionField-slider', className)}
isInvalid={error !== undefined}
{...commonSliderProps}
/>
);
}

const CommissionsField = makeControlled(CommissionInput);
export default CommissionsField;
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useContext, useMemo } from 'react';
import { CommissionRates, isChainParametersV0 } from '@concordium/web-sdk';
import { CommissionRates, isChainParametersV0, CommissionRange } from '@concordium/web-sdk';
import { useTranslation } from 'react-i18next';
import { isValidResolutionString } from 'wallet-common-helpers';

import Form from '@popup/shared/Form';
import { MultiStepFormPageProps } from '@popup/shared/MultiStepForm';
Expand All @@ -13,14 +14,29 @@ type CommissionsForm = CommissionRates;

type CommissionsProps = MultiStepFormPageProps<ConfigureBakerFlowState['commissionRates'], ConfigureBakerFlowState>;

const validationRules = (range: CommissionRange) => ({
min: range.min * 100,
max: range.max * 100,
// Note: The error is not actually displayed, so this doesn't need to be translated.
required: 'commission is required',
validate: (v?: number) => {
if (Number.isNaN(v)) {
return 'Must be a number';
}
if (v && !isValidResolutionString(1000, false, true, false)(v.toString())) {
return 'Must only have 3 decimals';
}
return undefined;
},
});

export default function CommissionsPage({ initial, onNext }: CommissionsProps) {
const { t: tShared } = useTranslation('shared');
const { t } = useTranslation('account', { keyPrefix: 'baking.configure' });
const { chainParameters } = useContext(earnPageContext);
const defaultValues: Partial<CommissionsForm> = useMemo(() => (initial === undefined ? {} : initial), [initial]);
const onSubmit = (vs: CommissionsForm) => onNext(vs);

const { chainParameters } = useContext(earnPageContext);

if (!chainParameters || isChainParametersV0(chainParameters)) {
return null;
}
Expand All @@ -30,30 +46,33 @@ export default function CommissionsPage({ initial, onNext }: CommissionsProps) {
{(f) => (
<>
<div>
<div className="m-t-0">{t('commission.description')}</div>
<div className="m-t-0 m-b-10">{t('commission.description')}</div>
<CommissionField
label={tShared('baking.transactionCommission')}
name="transactionCommission"
min={chainParameters.transactionCommissionRange.min}
max={chainParameters.transactionCommissionRange.max}
register={f.register}
rules={validationRules(chainParameters.transactionCommissionRange)}
control={f.control}
/>
<CommissionField
label={tShared('baking.bakingCommission')}
name="bakingCommission"
min={chainParameters.bakingCommissionRange.min}
register={f.register}
max={chainParameters.bakingCommissionRange.max}
rules={validationRules(chainParameters.bakingCommissionRange)}
control={f.control}
/>
<CommissionField
label={tShared('baking.finalizationCommission')}
name="finalizationCommission"
min={chainParameters.finalizationCommissionRange.min}
register={f.register}
max={chainParameters.finalizationCommissionRange.max}
rules={validationRules(chainParameters.finalizationCommissionRange)}
control={f.control}
/>
</div>
<Submit className="m-t-20" width="wide">
<Submit className="m-t-10" width="wide">
{tShared('continue')}
</Submit>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
OpenStatusText,
BakerKeysWithProofs,
} from '@concordium/web-sdk';
import { decimalToRewardFraction } from '@popup/shared/utils/baking-helpers';
import { decimalToRewardFraction, fractionToPercentage } from '@popup/shared/utils/baking-helpers';
import { getConfigureBakerEnergyCost } from '@shared/utils/energy-helpers';
import { not } from '@shared/utils/function-helpers';
import { ccdToMicroCcd, isDefined, isValidCcdString, microCcdToCcd, NotOptional } from 'wallet-common-helpers';
Expand Down Expand Up @@ -51,7 +51,11 @@ export const getExistingBakerValues = (accountInfo: AccountInfo): NotOptional<Co
restake: restakeEarnings,
openForDelegation: openStatusFromText(openStatus),
metadataUrl,
commissionRates,
commissionRates: {
transactionCommission: fractionToPercentage(commissionRates.transactionCommission),
bakingCommission: fractionToPercentage(commissionRates.bakingCommission),
finalizationCommission: fractionToPercentage(commissionRates.finalizationCommission),
},
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,11 @@
.baking {
&__commissionField-slider {
margin-top: rem(10px);
margin-bottom: rem(10px);
}

&__commissionField-label {
font-size: rem(8px);
color: $color-text;
font-weight: $font-weight-bold;
text-align: center;
}

Expand Down
68 changes: 68 additions & 0 deletions packages/browser-wallet/src/popup/shared/Form/Form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -401,3 +401,71 @@ $handle-scale: scale(1.02);
}
}
}

.form-slider {
display: block;
text-align: center;

&__slider {
height: 30px;
padding: 0;
z-index: 0;
}

.rc-slider-rail {
background-color: transparent;
border: 1px solid $color-grey;
height: 100%;
z-index: 1;

.invalid & {
border-color: $color-error;
border-width: 2px;
}
}

.rc-slider-track {
background-color: $color-cta;
height: 100%;
}

.rc-slider-handle {
top: 50%;
border-color: $color-grey;
margin-top: -7px;
z-index: 2;

&:hover,
&:active {
border-color: $color-cta;
}

.invalid & {
&,
&:hover,
&:active {
border-color: $color-error;
}
}
}

&__grid {
display: grid;
grid-template-columns: 60px auto 60px;
grid-gap: 5px;
align-items: center;
justify-items: center;
}

&__inputWrapper {
grid-column-start: 2;
border-bottom: 1px solid $color-text;
width: 100%;
max-width: 200px;

.invalid & {
color: $color-error;
border-color: $color-error;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ type Props = Pick<InputHTMLAttributes<HTMLInputElement>, 'type' | 'className' |
RequiredControlledFieldProps &
CommonFieldProps & {
fallbackValue?: string;
fallbackOnError?: boolean;
};

export function InlineInput({
className,
type = 'text',
value,
fallbackValue,
fallbackOnError = false,
onChange = noOp,
onBlur = noOp,
error,
Expand All @@ -34,7 +36,7 @@ export function InlineInput({

const handleBlur = useCallback(() => {
onBlur();
if (!value) {
if (!value || (fallbackOnError && error)) {
onChange(fallbackValue);
}
}, [onBlur, value, fallbackValue, onChange]);
Expand Down
Loading

0 comments on commit 78986cd

Please sign in to comment.