Skip to content

Commit 3fd94d7

Browse files
SOV-4921: Refactors stake form validation logic (#1081)
* SOV-4921: Refactors stake form validation logic and improves date handling in the StakeDatePicker comp to utilize UTC consistently * Create wise-gifts-count.md
1 parent 3c71678 commit 3fd94d7

3 files changed

Lines changed: 54 additions & 60 deletions

File tree

.changeset/wise-gifts-count.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"frontend": patch
3+
---
4+
5+
SOV-4921: Refactors stake form validation logic and improves date han…

apps/frontend/src/app/3_organisms/StakeForm/components/AddStakeForm/AddStakeForm.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,15 @@ export const AddStakeForm: FC<StakeFormProps> = ({
8787
[amount, balance, stakingLocked, unlockDate],
8888
);
8989

90+
const isRenderValid = useMemo(
91+
() =>
92+
decimalic(amount).gt(0) && isValidAmount && unlockDate > 0 && weight > 0,
93+
[amount, isValidAmount, unlockDate, weight],
94+
);
95+
9096
const renderNewStakedAmount = useCallback(
9197
() =>
92-
decimalic(amount).isZero() || !isValidAmount ? (
98+
!isRenderValid ? (
9399
t(translations.common.na)
94100
) : (
95101
<AmountRenderer
@@ -98,12 +104,12 @@ export const AddStakeForm: FC<StakeFormProps> = ({
98104
precision={TOKEN_RENDER_PRECISION}
99105
/>
100106
),
101-
[amount, isValidAmount, totalStakedValue],
107+
[amount, totalStakedValue, isRenderValid],
102108
);
103109

104110
const renderVotingPowerReceived = useCallback(
105111
() =>
106-
decimalic(amount).isZero() || !isValidAmount ? (
112+
!isRenderValid ? (
107113
t(translations.common.na)
108114
) : (
109115
<AmountRenderer
@@ -112,12 +118,12 @@ export const AddStakeForm: FC<StakeFormProps> = ({
112118
precision={TOKEN_RENDER_PRECISION}
113119
/>
114120
),
115-
[amount, votingPowerReceived, isValidAmount],
121+
[votingPowerReceived, isRenderValid],
116122
);
117123

118124
const renderNewVotingPowerIncrease = useCallback(
119125
() =>
120-
decimalic(amount).isZero() || !isValidAmount ? (
126+
!isRenderValid ? (
121127
t(translations.common.na)
122128
) : (
123129
<AmountRenderer
@@ -126,7 +132,7 @@ export const AddStakeForm: FC<StakeFormProps> = ({
126132
precision={TOKEN_RENDER_PRECISION}
127133
/>
128134
),
129-
[amount, votingPowerReceived, votingPower, isValidAmount],
135+
[votingPower, votingPowerReceived, isRenderValid],
130136
);
131137

132138
const onTransactionSuccess = useCallback(() => {
@@ -148,13 +154,17 @@ export const AddStakeForm: FC<StakeFormProps> = ({
148154
}, [isSubmitDisabled, handleSubmitStake]);
149155

150156
useEffect(() => {
151-
if (unlockDate === 0 || !isValidAmount) {
157+
if (!isRenderValid) {
152158
setVotingPowerReceived(0);
159+
return;
153160
}
154-
if (weight !== 0) {
155-
setVotingPowerReceived((Number(amount) * weight) / WEIGHT_FACTOR);
161+
162+
const newVotingPower = (Number(amount) * weight) / WEIGHT_FACTOR;
163+
164+
if (!isNaN(newVotingPower) && weight > 0) {
165+
setVotingPowerReceived(newVotingPower);
156166
}
157-
}, [amount, weight, unlockDate, isValidAmount]);
167+
}, [amount, weight, isRenderValid]);
158168

159169
return (
160170
<div>

apps/frontend/src/app/3_organisms/StakeForm/components/StakeDatePicker/StakeDatePicker.tsx

Lines changed: 29 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
1+
import React, { FC, useCallback, useEffect, useState } from 'react';
22

33
import classNames from 'classnames';
44
import dayjs from 'dayjs';
5+
import utc from 'dayjs/plugin/utc';
56
import { t } from 'i18next';
67

78
import { ErrorBadge, ErrorLevel, Select } from '@sovryn/ui';
@@ -15,6 +16,8 @@ import styles from './StakeDatePicker.module.css';
1516
import { Month, StakeDatePickerProps } from './StakeDatePicker.types';
1617
import { getAvailableDates } from './StakeDatePicker.utils';
1718

19+
dayjs.extend(utc);
20+
1821
const MAX_PERIODS = 78;
1922

2023
export const StakeDatePicker: FC<StakeDatePickerProps> = ({
@@ -28,11 +31,6 @@ export const StakeDatePicker: FC<StakeDatePickerProps> = ({
2831
const [filteredDates, setFilteredDates] = useState<FilteredDate[]>([]);
2932

3033
const { kickoffTs } = useGetKickoffTs();
31-
const currentDate = useMemo(() => new Date(), []);
32-
const currentUserOffset = useMemo(
33-
() => currentDate.getTimezoneOffset() / 60,
34-
[currentDate],
35-
);
3634

3735
const { availableYears, availableMonths, availableDays } = getAvailableDates(
3836
filteredDates,
@@ -47,27 +45,22 @@ export const StakeDatePicker: FC<StakeDatePickerProps> = ({
4745
setSelectedDay(day);
4846

4947
if (selectedYear !== '' && selectedMonth !== '') {
50-
const monthIndex = Month[selectedMonth];
5148
const selectedDate = new Date(
5249
Number(selectedYear),
53-
monthIndex,
50+
Month[selectedMonth],
5451
Number(day),
5552
);
56-
const selectedDateString = selectedDate.toLocaleDateString();
57-
const date = filteredDates.find(
58-
date => date.date.toLocaleDateString() === selectedDateString,
59-
)?.key;
6053

61-
const timestamp = date
62-
? Number(
63-
dayjs(date).subtract(currentUserOffset, 'hour').add(1, 'hour'),
64-
) / MS
65-
: 0;
54+
const match = filteredDates.find(d =>
55+
dayjs(d.date).isSame(selectedDate, 'day'),
56+
);
57+
58+
const timestamp = match ? dayjs.utc(match.key).unix() : 0;
6659

6760
onChange(timestamp);
6861
}
6962
},
70-
[selectedYear, selectedMonth, filteredDates, onChange, currentUserOffset],
63+
[selectedYear, selectedMonth, filteredDates, onChange],
7164
);
7265

7366
const handleSelectYear = useCallback(
@@ -92,16 +85,13 @@ export const StakeDatePicker: FC<StakeDatePickerProps> = ({
9285
const onMaxDurationClick = useCallback(() => {
9386
const maxDate = filteredDates[filteredDates.length - 1];
9487
if (maxDate) {
95-
const timestamp =
96-
Number(
97-
dayjs(maxDate.key).subtract(currentUserOffset, 'hour').add(1, 'hour'),
98-
) / MS;
99-
setSelectedYear(timestamp ? maxDate.date.getFullYear().toString() : '');
100-
setSelectedMonth(timestamp ? Month[maxDate.date.getMonth()] : '');
101-
setSelectedDay(timestamp ? maxDate.date.getDate().toString() : '');
88+
const timestamp = dayjs.utc(maxDate.key).unix();
89+
setSelectedYear(maxDate.date.getFullYear().toString());
90+
setSelectedMonth(Month[maxDate.date.getMonth()]);
91+
setSelectedDay(maxDate.date.getDate().toString());
10292
onChange(timestamp);
10393
}
104-
}, [currentUserOffset, filteredDates, onChange]);
94+
}, [filteredDates, onChange]);
10595

10696
useEffect(() => {
10797
setFilteredDates(
@@ -116,49 +106,40 @@ export const StakeDatePicker: FC<StakeDatePickerProps> = ({
116106
useEffect(() => {
117107
if (!kickoffTs) return;
118108

119-
const contractDate = dayjs(kickoffTs * MS).toDate();
120-
const contractOffset = contractDate.getTimezoneOffset() / 60;
121-
const contractDateDeployed = dayjs(kickoffTs * MS).add(
122-
contractOffset,
123-
'hour',
124-
);
125-
const userDateUTC = dayjs(currentDate).add(currentUserOffset, 'hour');
109+
const kickoffDate = dayjs.utc(kickoffTs * MS);
110+
const now = dayjs.utc();
126111

127112
const dates: Date[] = [];
128113
const datesFutures: Date[] = [];
129114

130-
let intervalDate = contractDateDeployed;
115+
let interval = kickoffDate;
131116

132-
while (intervalDate.unix() < userDateUTC.unix()) {
133-
intervalDate = intervalDate.add(2, 'week');
117+
while (interval.unix() < now.unix()) {
118+
interval = interval.add(2, 'week');
134119
}
135120

136121
for (let i = 1; i < MAX_PERIODS; i++) {
137-
if (intervalDate.unix() > userDateUTC.unix()) {
138-
const date = intervalDate.add(2, 'week');
139-
intervalDate = date;
122+
if (interval.unix() > now.unix()) {
123+
interval = interval.add(2, 'week');
124+
125+
const nextDate = interval.toDate();
140126

141127
if (!previouslySelectedDate) {
142-
dates.push(date.toDate());
128+
dates.push(nextDate);
143129
}
144130

145131
if (
146132
previouslySelectedDate &&
147-
dayjs(previouslySelectedDate * MS)
148-
.add(contractOffset, 'hour')
149-
.toDate()
150-
.getTime() /
151-
MS <
152-
date.unix()
133+
dayjs.utc(previouslySelectedDate * MS).unix() < interval.unix()
153134
) {
154-
datesFutures.push(date.toDate());
135+
datesFutures.push(nextDate);
155136
}
156137
}
157138
}
158139

159140
setDates(datesFutures.length ? datesFutures : dates);
160141
setIsDateAvailable(datesFutures.length > 0 || !previouslySelectedDate);
161-
}, [kickoffTs, currentDate, currentUserOffset, previouslySelectedDate]);
142+
}, [kickoffTs, previouslySelectedDate]);
162143

163144
return (
164145
<div className="relative w-full flex lg:flex-row flex-col justify-between items-center gap-3 mt-3.5 mb-6">
@@ -180,7 +161,6 @@ export const StakeDatePicker: FC<StakeDatePickerProps> = ({
180161
: selectedYear
181162
}
182163
/>
183-
184164
<Select
185165
value={selectedMonth}
186166
onChange={handleSelectMonth}
@@ -197,7 +177,6 @@ export const StakeDatePicker: FC<StakeDatePickerProps> = ({
197177
: selectedMonth
198178
}
199179
/>
200-
201180
<Select
202181
value={selectedDay}
203182
onChange={handleSelectDay}

0 commit comments

Comments
 (0)