Skip to content

Commit 403bfe8

Browse files
authored
feat: unstaking time format improved (#496)
* feat: unstaking time format improved * fix: locale distance, past date unstaking
1 parent fe51dfc commit 403bfe8

9 files changed

Lines changed: 95 additions & 27 deletions

File tree

.changeset/seven-feet-bathe.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@stakekit/widget": patch
3+
---
4+
5+
feat: unstaking time format improved

packages/widget/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
"chartjs-plugin-annotation": "^3.1.0",
116116
"clsx": "^2.1.1",
117117
"cosmjs-types": "^0.9.0",
118+
"date-fns": "^4.1.0",
118119
"eventemitter3": "^5.0.1",
119120
"i18next": "^25.6.2",
120121
"i18next-browser-languagedetector": "^8.2.0",

packages/widget/src/pages/position-details/components/position-balances.tsx

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type { YieldBalanceDto, YieldDto } from "@stakekit/api-hooks";
22
import BigNumber from "bignumber.js";
3+
import { isPast } from "date-fns";
34
import { useMemo } from "react";
45
import { useTranslation } from "react-i18next";
56
import { Box } from "../../../components/atoms/box";
67
import { TokenIcon } from "../../../components/atoms/token-icon";
78
import { Text } from "../../../components/atoms/typography/text";
89
import { defaultFormattedNumber } from "../../../utils";
9-
import { daysUntilDate } from "../../../utils/date";
10+
import { formatDurationUntilDate } from "../../../utils/date";
1011

1112
export const PositionBalances = ({
1213
yieldBalance,
@@ -17,14 +18,30 @@ export const PositionBalances = ({
1718
}) => {
1819
const { t } = useTranslation();
1920

20-
const daysRemaining = useMemo(() => {
21-
return (yieldBalance.type === "unstaking" ||
22-
yieldBalance.type === "unlocking" ||
23-
yieldBalance.type === "preparing") &&
24-
yieldBalance.date
25-
? daysUntilDate(new Date(yieldBalance.date))
26-
: null;
27-
}, [yieldBalance.date, yieldBalance.type]);
21+
const durationUntilDate = useMemo(() => {
22+
if (
23+
!yieldBalance.date ||
24+
(yieldBalance.type !== "unstaking" &&
25+
yieldBalance.type !== "unlocking" &&
26+
yieldBalance.type !== "preparing")
27+
) {
28+
return null;
29+
}
30+
31+
const date = new Date(yieldBalance.date);
32+
33+
if (isPast(date)) {
34+
return t("position_details.unstaking_imminent");
35+
}
36+
37+
const duration = formatDurationUntilDate(date);
38+
39+
if (!duration) {
40+
return null;
41+
}
42+
43+
return t("position_details.unstaking_duration", { duration });
44+
}, [yieldBalance.date, yieldBalance.type, t]);
2845

2946
const yieldType = integrationData.metadata.type;
3047

@@ -68,11 +85,9 @@ export const PositionBalances = ({
6885
</Text>
6986
</Box>
7087

71-
{typeof daysRemaining === "number" && (
88+
{!!durationUntilDate && (
7289
<Text variant={{ type: "muted", weight: "normal" }}>
73-
{t("position_details.unstaking_days", {
74-
count: daysRemaining,
75-
})}
90+
{durationUntilDate}
7691
</Text>
7792
)}
7893
</Box>

packages/widget/src/translation/English/translations.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,8 @@
475475
"locked": "Locked",
476476
"unlocking": "Unlocking"
477477
},
478-
"unstaking_days_one": "{{count}} day remaining",
479-
"unstaking_days_other": "{{count}} days remaining",
478+
"unstaking_duration": "{{duration}} remaining",
479+
"unstaking_imminent": "Imminent",
480480
"pending_action": {
481481
"stake": "Stake",
482482
"unstake": "Unstake",

packages/widget/src/translation/French/translations.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,8 @@
425425
"locked": "Bloqué",
426426
"unlocking": "En cours de déblocage"
427427
},
428-
"unstaking_days_one": "{{count}} jour restant",
429-
"unstaking_days_other": "{{count}} jours restants",
428+
"unstaking_duration": "{{duration}} restant",
429+
"unstaking_imminent": "Prochain",
430430
"pending_action": {
431431
"stake": "Staker",
432432
"unstake": "Déstaker",

packages/widget/src/translation/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { useQuery } from "@tanstack/react-query";
2+
import { setDefaultOptions } from "date-fns";
3+
import { enUS as dateFnsEN, fr as dateFnsFR } from "date-fns/locale";
24
import { createInstance } from "i18next";
35
import LanguageDetector from "i18next-browser-languagedetector";
46
import { EitherAsync } from "purify-ts";
@@ -25,8 +27,17 @@ i18nInstance
2527
fallbackLng: "en",
2628
interpolation: { escapeValue: false },
2729
detection: { order: ["navigator", "localStorage"] },
30+
})
31+
.then(() => {
32+
setDefaultOptions({
33+
locale: i18nInstance.language === "fr" ? dateFnsFR : dateFnsEN,
34+
});
2835
});
2936

37+
i18nInstance.on("languageChanged", (lng) => {
38+
setDefaultOptions({ locale: lng === "fr" ? dateFnsFR : dateFnsEN });
39+
});
40+
3041
i18nInstance.services.formatter?.add("lowercase", (value, _, __) =>
3142
value.toLowerCase()
3243
);

packages/widget/src/utils/date.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,42 @@
1-
export const daysUntilDate = (futureDate: Date) => {
2-
const now = new Date();
3-
const _MS_PER_DAY = 1000 * 60 * 60 * 24;
1+
import {
2+
type FormatDurationOptions,
3+
formatDuration,
4+
intervalToDuration,
5+
} from "date-fns";
46

5-
const utc1 = Date.UTC(now.getFullYear(), now.getMonth(), now.getDate());
6-
const utc2 = Date.UTC(
7-
futureDate.getFullYear(),
8-
futureDate.getMonth(),
9-
futureDate.getDate()
10-
);
7+
const getFormat = ({
8+
days,
9+
hours,
10+
minutes,
11+
}: {
12+
days: number;
13+
hours: number;
14+
minutes: number;
15+
}): FormatDurationOptions["format"] => {
16+
if (days >= 1) {
17+
return ["days"];
18+
}
19+
if (hours >= 1) {
20+
return ["hours"];
21+
}
22+
if (minutes >= 1) {
23+
return ["minutes"];
24+
}
25+
return ["seconds"];
26+
};
1127

12-
return Math.floor((utc2 - utc1) / _MS_PER_DAY);
28+
export const formatDurationUntilDate = (futureDate: Date) => {
29+
const {
30+
days = 0,
31+
hours = 0,
32+
minutes = 0,
33+
seconds = 0,
34+
} = intervalToDuration({ start: new Date(), end: futureDate });
35+
36+
return formatDuration(
37+
{ days, hours, minutes, seconds },
38+
{ format: getFormat({ days, hours, minutes }) }
39+
);
1340
};
1441

1542
export const dateOlderThen7Days = (date: string): boolean => {

packages/widget/vite/vite.config.base.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const getConfig = (overides?: Partial<UserConfig>): UserConfigFnObject =>
2828
"vite-plugin-node-polyfills/shims/process",
2929
"@vanilla-extract/recipes/createRuntimeFn",
3030
"@vanilla-extract/sprinkles/createRuntimeSprinkles",
31+
"date-fns/locale",
3132
],
3233
},
3334
test: {

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)