Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 035d32bf94 | |||
| 348e8af8ab | |||
| f00d6e10ce |
@@ -316,5 +316,104 @@
|
||||
"change_parent_issue": "Change parent issue",
|
||||
"remove_parent_issue": "Remove parent issue",
|
||||
"add_parent": "Add parent",
|
||||
"loading_members": "Loading members..."
|
||||
"loading_members": "Loading members...",
|
||||
|
||||
"date_time" : {
|
||||
"units_short": {
|
||||
"s": "s",
|
||||
"m": "min",
|
||||
"h": "h",
|
||||
"d": "j",
|
||||
"w": "sem",
|
||||
"mo": "mois",
|
||||
"y": "an"
|
||||
},
|
||||
|
||||
"date": {
|
||||
"days": {
|
||||
"Monday": "Monday",
|
||||
"Tuesday": "Tuesday",
|
||||
"Wednesday": "Wednesday",
|
||||
"Thursday": "Thursday",
|
||||
"Friday": "Friday",
|
||||
"Saturday": "Saturday",
|
||||
"Sunday": "Sunday"
|
||||
},
|
||||
"months_short": {
|
||||
"Jan": "Jan",
|
||||
"Feb": "Feb",
|
||||
"Mar": "Mar",
|
||||
"Apr": "Apr",
|
||||
"May": "May",
|
||||
"Jun": "Jun",
|
||||
"Jul": "Jul",
|
||||
"Aug": "Aug",
|
||||
"Sep": "Sep",
|
||||
"Oct": "Oct",
|
||||
"Nov": "Nov",
|
||||
"Dec": "Dec"
|
||||
},
|
||||
"format": {
|
||||
"full": "{day}, {month} {date} {time}",
|
||||
"shortDate": "{month} {date}",
|
||||
"timeOnly": "{hours}:{minutes}"
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"less_than_x_seconds": {
|
||||
"past": "{time, plural, one{less than # second} other{less than # seconds}} ago",
|
||||
"future": "in {time, plural, one{less than # second} other{less than # seconds}}"
|
||||
},
|
||||
"less_than_x_minutes": {
|
||||
"past": "less than a minute ago",
|
||||
"future": "in less than a minute"
|
||||
},
|
||||
"x_minutes": {
|
||||
"past": "{time, plural, one{# minute} other{# minutes}} ago",
|
||||
"future": "in {time, plural, one{# minute} other{# minutes}}"
|
||||
},
|
||||
"about_x_hours": {
|
||||
"past": "{time, plural, one{about # hour} other{about # hours}} ago",
|
||||
"future": "in {time, plural, one{about # hour} other{about # hours}}"
|
||||
},
|
||||
"x_days": {
|
||||
"past": "{time, plural, one{# day} other{# days}} ago",
|
||||
"future": "in {time, plural, one{# day} other{# days}}"
|
||||
},
|
||||
"about_x_months": {
|
||||
"past": "{time, plural, one{about # month} other{about # months}} ago",
|
||||
"future": "in {time, plural, one{about # month} other{about # months}}"
|
||||
},
|
||||
"about_x_years": {
|
||||
"past": "{time, plural, one{about # year} other{about # years}} ago",
|
||||
"future": "in {time, plural, one{about # year} other{about # years}}"
|
||||
},
|
||||
"over_x_years": {
|
||||
"past": "{time, plural, one{over # year} other{over # years}} ago",
|
||||
"future": "in {time, plural, one{over # year} other{over # years}}"
|
||||
},
|
||||
"almost_x_years": {
|
||||
"past": "{time, plural, one{almost # year} other{almost # years}} ago",
|
||||
"future": "in {time, plural, one{almost # year} other{almost # years}}"
|
||||
}
|
||||
},
|
||||
"include_seconds": {
|
||||
"less_than_5_seconds": {
|
||||
"past": "less than 5 seconds ago",
|
||||
"future": "in less than 5 seconds"
|
||||
},
|
||||
"less_than_10_seconds": {
|
||||
"past": "less than 10 seconds ago",
|
||||
"future": "in less than 10 seconds"
|
||||
},
|
||||
"less_than_20_seconds": {
|
||||
"past": "less than 20 seconds ago",
|
||||
"future": "in less than 20 seconds"
|
||||
},
|
||||
"half_a_minute": {
|
||||
"past": "half a minute ago",
|
||||
"future": "in half a minute"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,5 +316,102 @@
|
||||
"remove_parent_issue": "Eliminar problema padre",
|
||||
"add_parent": "Agregar padre",
|
||||
"loading_members": "Cargando miembros...",
|
||||
"inbox": "bandeja de entrada"
|
||||
"inbox": "bandeja de entrada",
|
||||
"date_time": {
|
||||
"units_short": {
|
||||
"s": "s",
|
||||
"m": "min",
|
||||
"h": "h",
|
||||
"d": "d",
|
||||
"w": "sem",
|
||||
"mo": "mes",
|
||||
"y": "año"
|
||||
},
|
||||
"date": {
|
||||
"days": {
|
||||
"Monday": "lunes",
|
||||
"Tuesday": "martes",
|
||||
"Wednesday": "miércoles",
|
||||
"Thursday": "jueves",
|
||||
"Friday": "viernes",
|
||||
"Saturday": "sábado",
|
||||
"Sunday": "domingo"
|
||||
},
|
||||
"months_short": {
|
||||
"Jan": "ene.",
|
||||
"Feb": "feb.",
|
||||
"Mar": "mar.",
|
||||
"Apr": "abr.",
|
||||
"May": "may.",
|
||||
"Jun": "jun.",
|
||||
"Jul": "jul.",
|
||||
"Aug": "ago.",
|
||||
"Sep": "sept.",
|
||||
"Oct": "oct.",
|
||||
"Nov": "nov.",
|
||||
"Dec": "dic."
|
||||
},
|
||||
"format": {
|
||||
"full": "{day} {date} de {month} {time}",
|
||||
"shortDate": "{date} {month}",
|
||||
"timeOnly": "{hours}:{minutes}"
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"less_than_x_seconds": {
|
||||
"past": "hace {time, plural, one{menos de un segundo} other{menos de # segundos}}",
|
||||
"future": "en {time, plural, one{menos de un segundo} other{menos de # segundos}}"
|
||||
},
|
||||
"less_than_x_minutes": {
|
||||
"past": "hace menos de un minuto",
|
||||
"future": "en menos de un minuto"
|
||||
},
|
||||
"x_minutes": {
|
||||
"past": "hace {time, plural, one{# minuto} other{# minutos}}",
|
||||
"future": "en {time, plural, one{# minuto} other{# minutos}}"
|
||||
},
|
||||
"about_x_hours": {
|
||||
"past": "hace {time, plural, one{aproximadamente # hora} other{aproximadamente # horas}}",
|
||||
"future": "en {time, plural, one{aproximadamente # hora} other{aproximadamente # horas}}"
|
||||
},
|
||||
"x_days": {
|
||||
"past": "hace {time, plural, one{# día} other{# días}}",
|
||||
"future": "en {time, plural, one{# día} other{# días}}"
|
||||
},
|
||||
"about_x_months": {
|
||||
"past": "hace {time, plural, one{aproximadamente # mes} other{aproximadamente # meses}}",
|
||||
"future": "en {time, plural, one{aproximadamente # mes} other{aproximadamente # meses}}"
|
||||
},
|
||||
"about_x_years": {
|
||||
"past": "hace {time, plural, one{aproximadamente # año} other{aproximadamente # años}}",
|
||||
"future": "en {time, plural, one{aproximadamente # año} other{aproximadamente # años}}"
|
||||
},
|
||||
"over_x_years": {
|
||||
"past": "hace {time, plural, one{más de # año} other{más de # años}}",
|
||||
"future": "en {time, plural, one{más de # año} other{más de # años}}"
|
||||
},
|
||||
"almost_x_years": {
|
||||
"past": "hace {time, plural, one{casi # año} other{casi # años}}",
|
||||
"future": "en {time, plural, one{casi # año} other{casi # años}}"
|
||||
}
|
||||
},
|
||||
"include_seconds": {
|
||||
"less_than_5_seconds": {
|
||||
"past": "hace menos de 5 segundos",
|
||||
"future": "en menos de 5 segundos"
|
||||
},
|
||||
"less_than_10_seconds": {
|
||||
"past": "hace menos de 10 segundos",
|
||||
"future": "en menos de 10 segundos"
|
||||
},
|
||||
"less_than_20_seconds": {
|
||||
"past": "hace menos de 20 segundos",
|
||||
"future": "en menos de 20 segundos"
|
||||
},
|
||||
"half_a_minute": {
|
||||
"past": "hace medio minuto",
|
||||
"future": "en medio minuto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,5 +316,102 @@
|
||||
"remove_parent_issue": "Supprimer le problème parent",
|
||||
"add_parent": "Ajouter un parent",
|
||||
"loading_members": "Chargement des membres...",
|
||||
"inbox": "Boîte de réception"
|
||||
"inbox": "Boîte de réception",
|
||||
"date_time": {
|
||||
"units_short": {
|
||||
"s": "s",
|
||||
"m": "min",
|
||||
"h": "h",
|
||||
"d": "j",
|
||||
"w": "sem",
|
||||
"mo": "mois",
|
||||
"y": "an"
|
||||
},
|
||||
"date": {
|
||||
"days": {
|
||||
"Monday": "lundi",
|
||||
"Tuesday": "mardi",
|
||||
"Wednesday": "mercredi",
|
||||
"Thursday": "jeudi",
|
||||
"Friday": "vendredi",
|
||||
"Saturday": "samedi",
|
||||
"Sunday": "dimanche"
|
||||
},
|
||||
"months_short": {
|
||||
"Jan": "janv.",
|
||||
"Feb": "févr.",
|
||||
"Mar": "mars",
|
||||
"Apr": "avr.",
|
||||
"May": "mai",
|
||||
"Jun": "juin",
|
||||
"Jul": "juil.",
|
||||
"Aug": "août",
|
||||
"Sep": "sept.",
|
||||
"Oct": "oct.",
|
||||
"Nov": "nov.",
|
||||
"Dec": "déc."
|
||||
},
|
||||
"format": {
|
||||
"full": "{day} {date} {month} {time}",
|
||||
"shortDate": "{date} {month}",
|
||||
"timeOnly": "{hours}:{minutes}"
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"less_than_x_seconds": {
|
||||
"past": "il y a {time, plural, one{moins d'une seconde} other{moins de # secondes}}",
|
||||
"future": "dans {time, plural, one{moins d'une seconde} other{moins de # secondes}}"
|
||||
},
|
||||
"less_than_x_minutes": {
|
||||
"past": "il y a moins d'une minute",
|
||||
"future": "dans moins d'une minute"
|
||||
},
|
||||
"x_minutes": {
|
||||
"past": "il y a {time, plural, one{# minute} other{# minutes}}",
|
||||
"future": "dans {time, plural, one{# minute} other{# minutes}}"
|
||||
},
|
||||
"about_x_hours": {
|
||||
"past": "il y a {time, plural, one{environ # heure} other{environ # heures}}",
|
||||
"future": "dans {time, plural, one{environ # heure} other{environ # heures}}"
|
||||
},
|
||||
"x_days": {
|
||||
"past": "il y a {time, plural, one{# jour} other{# jours}}",
|
||||
"future": "dans {time, plural, one{# jour} other{# jours}}"
|
||||
},
|
||||
"about_x_months": {
|
||||
"past": "il y a {time, plural, one{environ # mois} other{environ # mois}}",
|
||||
"future": "dans {time, plural, one{environ # mois} other{environ # mois}}"
|
||||
},
|
||||
"about_x_years": {
|
||||
"past": "il y a {time, plural, one{environ # an} other{environ # ans}}",
|
||||
"future": "dans {time, plural, one{environ # an} other{environ # ans}}"
|
||||
},
|
||||
"over_x_years": {
|
||||
"past": "il y a {time, plural, one{plus d'un an} other{plus de # ans}}",
|
||||
"future": "dans {time, plural, one{plus d'un an} other{plus de # ans}}"
|
||||
},
|
||||
"almost_x_years": {
|
||||
"past": "il y a {time, plural, one{presque # an} other{presque # ans}}",
|
||||
"future": "dans {time, plural, one{presque # an} other{presque # ans}}"
|
||||
}
|
||||
},
|
||||
"include_seconds": {
|
||||
"less_than_5_seconds": {
|
||||
"past": "il y a moins de 5 secondes",
|
||||
"future": "dans moins de 5 secondes"
|
||||
},
|
||||
"less_than_10_seconds": {
|
||||
"past": "il y a moins de 10 secondes",
|
||||
"future": "dans moins de 10 secondes"
|
||||
},
|
||||
"less_than_20_seconds": {
|
||||
"past": "il y a moins de 20 secondes",
|
||||
"future": "dans moins de 20 secondes"
|
||||
},
|
||||
"half_a_minute": {
|
||||
"past": "il y a 30 secondes",
|
||||
"future": "dans 30 secondes"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,5 +316,102 @@
|
||||
"remove_parent_issue": "親問題を削除",
|
||||
"add_parent": "親問題を追加",
|
||||
"loading_members": "メンバーを読み込んでいます...",
|
||||
"inbox": "受信箱"
|
||||
"inbox": "受信箱",
|
||||
"date_time": {
|
||||
"units_short": {
|
||||
"s": "秒",
|
||||
"m": "分",
|
||||
"h": "時間",
|
||||
"d": "日",
|
||||
"w": "週間",
|
||||
"mo": "ヶ月",
|
||||
"y": "年"
|
||||
},
|
||||
"date": {
|
||||
"days": {
|
||||
"Monday": "月曜日",
|
||||
"Tuesday": "火曜日",
|
||||
"Wednesday": "水曜日",
|
||||
"Thursday": "木曜日",
|
||||
"Friday": "金曜日",
|
||||
"Saturday": "土曜日",
|
||||
"Sunday": "日曜日"
|
||||
},
|
||||
"months_short": {
|
||||
"Jan": "1月",
|
||||
"Feb": "2月",
|
||||
"Mar": "3月",
|
||||
"Apr": "4月",
|
||||
"May": "5月",
|
||||
"Jun": "6月",
|
||||
"Jul": "7月",
|
||||
"Aug": "8月",
|
||||
"Sep": "9月",
|
||||
"Oct": "10月",
|
||||
"Nov": "11月",
|
||||
"Dec": "12月"
|
||||
},
|
||||
"format": {
|
||||
"full": "{month}{date}日 ({day}) {time}",
|
||||
"shortDate": "{month}{date}日",
|
||||
"timeOnly": "{hours}:{minutes}"
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"less_than_x_seconds": {
|
||||
"past": "{time}秒未満前",
|
||||
"future": "{time}秒未満後"
|
||||
},
|
||||
"less_than_x_minutes": {
|
||||
"past": "1分未満前",
|
||||
"future": "1分未満後"
|
||||
},
|
||||
"x_minutes": {
|
||||
"past": "{time}分前",
|
||||
"future": "{time}分後"
|
||||
},
|
||||
"about_x_hours": {
|
||||
"past": "約{time}時間前",
|
||||
"future": "約{time}時間後"
|
||||
},
|
||||
"x_days": {
|
||||
"past": "{time}日前",
|
||||
"future": "{time}日後"
|
||||
},
|
||||
"about_x_months": {
|
||||
"past": "約{time}ヶ月前",
|
||||
"future": "約{time}ヶ月後"
|
||||
},
|
||||
"about_x_years": {
|
||||
"past": "約{time}年前",
|
||||
"future": "約{time}年後"
|
||||
},
|
||||
"over_x_years": {
|
||||
"past": "{time}年以上前",
|
||||
"future": "{time}年以上後"
|
||||
},
|
||||
"almost_x_years": {
|
||||
"past": "ほぼ{time}年前",
|
||||
"future": "ほぼ{time}年後"
|
||||
}
|
||||
},
|
||||
"include_seconds": {
|
||||
"less_than_5_seconds": {
|
||||
"past": "5秒未満前",
|
||||
"future": "5秒未満後"
|
||||
},
|
||||
"less_than_10_seconds": {
|
||||
"past": "10秒未満前",
|
||||
"future": "10秒未満後"
|
||||
},
|
||||
"less_than_20_seconds": {
|
||||
"past": "20秒未満前",
|
||||
"future": "20秒未満後"
|
||||
},
|
||||
"half_a_minute": {
|
||||
"past": "30秒前",
|
||||
"future": "30秒後"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,5 +315,102 @@
|
||||
"remove_parent_issue": "移除父问题",
|
||||
"add_parent": "添加父问题",
|
||||
"loading_members": "正在加载成员...",
|
||||
"inbox": "收件箱"
|
||||
"inbox": "收件箱",
|
||||
"date_time": {
|
||||
"units_short": {
|
||||
"s": "秒",
|
||||
"m": "分",
|
||||
"h": "小时",
|
||||
"d": "天",
|
||||
"w": "周",
|
||||
"mo": "月",
|
||||
"y": "年"
|
||||
},
|
||||
"date": {
|
||||
"days": {
|
||||
"Monday": "星期一",
|
||||
"Tuesday": "星期二",
|
||||
"Wednesday": "星期三",
|
||||
"Thursday": "星期四",
|
||||
"Friday": "星期五",
|
||||
"Saturday": "星期六",
|
||||
"Sunday": "星期日"
|
||||
},
|
||||
"months_short": {
|
||||
"Jan": "1月",
|
||||
"Feb": "2月",
|
||||
"Mar": "3月",
|
||||
"Apr": "4月",
|
||||
"May": "5月",
|
||||
"Jun": "6月",
|
||||
"Jul": "7月",
|
||||
"Aug": "8月",
|
||||
"Sep": "9月",
|
||||
"Oct": "10月",
|
||||
"Nov": "11月",
|
||||
"Dec": "12月"
|
||||
},
|
||||
"format": {
|
||||
"full": "{month}{date}日 {day} {time}",
|
||||
"shortDate": "{month}{date}日",
|
||||
"timeOnly": "{hours}:{minutes}"
|
||||
}
|
||||
},
|
||||
"standard": {
|
||||
"less_than_x_seconds": {
|
||||
"past": "{time, plural, one{不到1秒} other{不到#秒}}前",
|
||||
"future": "{time, plural, one{不到1秒} other{不到#秒}}后"
|
||||
},
|
||||
"less_than_x_minutes": {
|
||||
"past": "不到1分钟前",
|
||||
"future": "不到1分钟后"
|
||||
},
|
||||
"x_minutes": {
|
||||
"past": "{time, plural, one{#分钟} other{#分钟}}前",
|
||||
"future": "{time, plural, one{#分钟} other{#分钟}}后"
|
||||
},
|
||||
"about_x_hours": {
|
||||
"past": "大约{time, plural, one{#小时} other{#小时}}前",
|
||||
"future": "大约{time, plural, one{#小时} other{#小时}}后"
|
||||
},
|
||||
"x_days": {
|
||||
"past": "{time, plural, one{#天} other{#天}}前",
|
||||
"future": "{time, plural, one{#天} other{#天}}后"
|
||||
},
|
||||
"about_x_months": {
|
||||
"past": "大约{time, plural, one{#个月} other{#个月}}前",
|
||||
"future": "大约{time, plural, one{#个月} other{#个月}}后"
|
||||
},
|
||||
"about_x_years": {
|
||||
"past": "大约{time, plural, one{#年} other{#年}}前",
|
||||
"future": "大约{time, plural, one{#年} other{#年}}后"
|
||||
},
|
||||
"over_x_years": {
|
||||
"past": "超过{time, plural, one{#年} other{#年}}前",
|
||||
"future": "超过{time, plural, one{#年} other{#年}}后"
|
||||
},
|
||||
"almost_x_years": {
|
||||
"past": "接近{time, plural, one{#年} other{#年}}前",
|
||||
"future": "接近{time, plural, one{#年} other{#年}}后"
|
||||
}
|
||||
},
|
||||
"include_seconds": {
|
||||
"less_than_5_seconds": {
|
||||
"past": "不到5秒前",
|
||||
"future": "不到5秒后"
|
||||
},
|
||||
"less_than_10_seconds": {
|
||||
"past": "不到10秒前",
|
||||
"future": "不到10秒后"
|
||||
},
|
||||
"less_than_20_seconds": {
|
||||
"past": "不到20秒前",
|
||||
"future": "不到20秒后"
|
||||
},
|
||||
"half_a_minute": {
|
||||
"past": "半分钟前",
|
||||
"future": "半分钟后"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { XCircle } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IApiToken } from "@plane/types";
|
||||
// components
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { DeleteApiTokenModal } from "@/components/api-token";
|
||||
import { renderFormattedDate, calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { renderFormattedDate, calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// ui
|
||||
// helpers
|
||||
@@ -22,6 +23,9 @@ export const ApiTokenListItem: React.FC<Props> = (props) => {
|
||||
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(token.expired_at);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -54,7 +58,7 @@ export const ApiTokenListItem: React.FC<Props> = (props) => {
|
||||
? token.expired_at
|
||||
? `Expires ${renderFormattedDate(token.expired_at!)}`
|
||||
: "Never expires"
|
||||
: `Expired ${calculateTimeAgo(token.expired_at)}`}
|
||||
: `Expired ${t(i18n_time_ago, { time })}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import { Network } from "lucide-react";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TWorkspaceBaseActivity } from "@plane/types";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
import { renderFormattedTime, renderFormattedDate, calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { renderFormattedTime, renderFormattedDate, calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
// hooks
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// local components
|
||||
@@ -25,8 +26,9 @@ export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => {
|
||||
const { icon, activity, ends, children, customUserName } = props;
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const { t } = useTranslation();
|
||||
if (!activity) return <></>;
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(activity.created_at);
|
||||
return (
|
||||
<div
|
||||
className={`relative flex items-center gap-2 text-xs ${
|
||||
@@ -44,7 +46,7 @@ export const ActivityBlockComponent: FC<TActivityBlockComponent> = (props) => {
|
||||
tooltipContent={`${renderFormattedDate(activity.created_at)}, ${renderFormattedTime(activity.created_at)}`}
|
||||
>
|
||||
<span className="whitespace-nowrap text-custom-text-350 font-medium cursor-help">
|
||||
{calculateTimeAgo(activity.created_at)}
|
||||
{t(i18n_time_ago, { time })}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import { History } from "lucide-react";
|
||||
// types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TRecentActivityWidgetResponse } from "@plane/types";
|
||||
// components
|
||||
import { Card, Avatar, getButtonStyling } from "@plane/ui";
|
||||
@@ -12,7 +13,7 @@ import { ActivityIcon, ActivityMessage, IssueLink } from "@/components/core";
|
||||
import { RecentActivityEmptyState, WidgetLoader, WidgetProps } from "@/components/dashboard/widgets";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useDashboard, useUser } from "@/hooks/store";
|
||||
@@ -23,6 +24,7 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
|
||||
const { dashboardId, workspaceSlug } = props;
|
||||
// store hooks
|
||||
const { data: currentUser } = useUser();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const { fetchWidgetStats, getWidgetStats } = useDashboard();
|
||||
const widgetStats = getWidgetStats<TRecentActivityWidgetResponse[]>(workspaceSlug, dashboardId, WIDGET_KEY);
|
||||
@@ -44,51 +46,52 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
|
||||
</Link>
|
||||
{widgetStats.length > 0 ? (
|
||||
<div className="mt-4 space-y-6">
|
||||
{widgetStats.map((activity) => (
|
||||
<div key={activity.id} className="flex gap-5">
|
||||
<div className="flex-shrink-0">
|
||||
{activity.field ? (
|
||||
activity.new_value === "restore" ? (
|
||||
<History className="h-3.5 w-3.5 text-custom-text-200" />
|
||||
) : (
|
||||
<div className="flex h-6 w-6 justify-center">
|
||||
<ActivityIcon activity={activity} />
|
||||
</div>
|
||||
)
|
||||
) : activity.actor_detail.avatar_url && activity.actor_detail.avatar_url !== "" ? (
|
||||
<Avatar
|
||||
src={getFileURL(activity.actor_detail.avatar_url)}
|
||||
name={activity.actor_detail.display_name}
|
||||
size={24}
|
||||
className="h-full w-full rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-700 text-xs text-white">
|
||||
{activity.actor_detail.is_bot
|
||||
? activity.actor_detail.first_name.charAt(0)
|
||||
: activity.actor_detail.display_name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="-mt-2 break-words">
|
||||
<p className="inline text-sm text-custom-text-200">
|
||||
<span className="font-medium text-custom-text-100">
|
||||
{currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail?.display_name}{" "}
|
||||
</span>
|
||||
{widgetStats.map((activity) => {
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(activity.created_at);
|
||||
return (
|
||||
<div key={activity.id} className="flex gap-5">
|
||||
<div className="flex-shrink-0">
|
||||
{activity.field ? (
|
||||
<ActivityMessage activity={activity} showIssue />
|
||||
activity.new_value === "restore" ? (
|
||||
<History className="h-3.5 w-3.5 text-custom-text-200" />
|
||||
) : (
|
||||
<div className="flex h-6 w-6 justify-center">
|
||||
<ActivityIcon activity={activity} />
|
||||
</div>
|
||||
)
|
||||
) : activity.actor_detail.avatar_url && activity.actor_detail.avatar_url !== "" ? (
|
||||
<Avatar
|
||||
src={getFileURL(activity.actor_detail.avatar_url)}
|
||||
name={activity.actor_detail.display_name}
|
||||
size={24}
|
||||
className="h-full w-full rounded-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<span>
|
||||
created <IssueLink activity={activity} />
|
||||
</span>
|
||||
<div className="grid h-7 w-7 place-items-center rounded-full border-2 border-white bg-gray-700 text-xs text-white">
|
||||
{activity.actor_detail.is_bot
|
||||
? activity.actor_detail.first_name.charAt(0)
|
||||
: activity.actor_detail.display_name.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-custom-text-200 whitespace-nowrap">
|
||||
{calculateTimeAgo(activity.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="-mt-2 break-words">
|
||||
<p className="inline text-sm text-custom-text-200">
|
||||
<span className="font-medium text-custom-text-100">
|
||||
{currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail?.display_name}{" "}
|
||||
</span>
|
||||
{activity.field ? (
|
||||
<ActivityMessage activity={activity} showIssue />
|
||||
) : (
|
||||
<span>
|
||||
created <IssueLink activity={activity} />
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-custom-text-200 whitespace-nowrap">{t(i18n_time_ago, { time })}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
<Link
|
||||
href={redirectionLink}
|
||||
className={cn(
|
||||
|
||||
@@ -6,6 +6,7 @@ import { IUser } from "@plane/types";
|
||||
import { Button } from "@plane/ui";
|
||||
// hooks
|
||||
import { useCurrentTime } from "@/hooks/use-current-time";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
||||
export interface IUserGreetingsView {
|
||||
user: IUser;
|
||||
@@ -16,6 +17,7 @@ export const UserGreetingsView: FC<IUserGreetingsView> = (props) => {
|
||||
const { user, handleWidgetModal } = props;
|
||||
// current time hook
|
||||
const { currentTime } = useCurrentTime();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const hour = new Intl.DateTimeFormat("en-US", {
|
||||
hour12: false,
|
||||
@@ -23,10 +25,13 @@ export const UserGreetingsView: FC<IUserGreetingsView> = (props) => {
|
||||
}).format(currentTime);
|
||||
|
||||
const date = new Intl.DateTimeFormat("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
}).format(currentTime);
|
||||
|
||||
const month = new Intl.DateTimeFormat("en-US", {
|
||||
month: "short",
|
||||
}).format(currentTime);
|
||||
|
||||
const weekDay = new Intl.DateTimeFormat("en-US", {
|
||||
weekday: "long",
|
||||
}).format(currentTime);
|
||||
@@ -39,6 +44,12 @@ export const UserGreetingsView: FC<IUserGreetingsView> = (props) => {
|
||||
}).format(currentTime);
|
||||
|
||||
const greeting = parseInt(hour, 10) < 12 ? "morning" : parseInt(hour, 10) < 18 ? "afternoon" : "evening";
|
||||
const translatedTime = t(`date_time.date.format.full`, {
|
||||
day: t(`date_time.date.days.${weekDay}`),
|
||||
date,
|
||||
month: t(`date_time.date.months_short.${month}`),
|
||||
time: timeString,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex justify-between">
|
||||
@@ -48,9 +59,7 @@ export const UserGreetingsView: FC<IUserGreetingsView> = (props) => {
|
||||
</h3>
|
||||
<h6 className="flex items-center gap-2 font-medium text-custom-text-400">
|
||||
<div>{greeting === "morning" ? "🌤️" : greeting === "afternoon" ? "🌥️" : "🌙️"}</div>
|
||||
<div>
|
||||
{weekDay}, {date} {timeString}
|
||||
</div>
|
||||
<div>{translatedTime}</div>
|
||||
</h6>
|
||||
</div>
|
||||
<Button variant="neutral-primary" size="sm" onClick={handleWidgetModal} className="my-auto mb-0">
|
||||
|
||||
@@ -5,10 +5,11 @@ import { FC } from "react";
|
||||
// ui
|
||||
import { observer } from "mobx-react";
|
||||
import { Pencil, Trash2, ExternalLink, EllipsisVertical, Link2, Link } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TOAST_TYPE, setToast, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
// helpers
|
||||
import { cn, copyTextToClipboard } from "@plane/utils";
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo, calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { useHome } from "@/hooks/store/use-home";
|
||||
import { TLinkOperations } from "./use-links";
|
||||
|
||||
@@ -24,6 +25,7 @@ export const ProjectLinkDetail: FC<TProjectLinkDetail> = observer((props) => {
|
||||
const {
|
||||
quickLinks: { getLinkById, toggleLinkModal, setLinkData },
|
||||
} = useHome();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const linkDetail = getLinkById(linkId);
|
||||
if (!linkDetail) return <></>;
|
||||
@@ -71,6 +73,7 @@ export const ProjectLinkDetail: FC<TProjectLinkDetail> = observer((props) => {
|
||||
icon: Trash2,
|
||||
},
|
||||
];
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(linkDetail.created_at);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -82,7 +85,7 @@ export const ProjectLinkDetail: FC<TProjectLinkDetail> = observer((props) => {
|
||||
</div>
|
||||
<div className="my-auto flex-1">
|
||||
<div className="text-sm font-medium truncate">{linkDetail.title || linkDetail.url}</div>
|
||||
<div className="text-xs font-medium text-custom-text-400">{calculateTimeAgo(linkDetail.created_at)}</div>
|
||||
<div className="text-xs font-medium text-custom-text-400">{t(i18n_time_ago, { time })}</div>
|
||||
</div>
|
||||
|
||||
<CustomMenu
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TActivityEntityData, TIssueEntityData } from "@plane/types";
|
||||
import { LayersIcon, PriorityIcon, StateGroupIcon, Tooltip } from "@plane/ui";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
import { MemberDropdown } from "@/components/dropdowns";
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { useIssueDetail, useProjectState } from "@/hooks/store";
|
||||
import { IssueIdentifier } from "@/plane-web/components/issues";
|
||||
|
||||
@@ -16,9 +17,11 @@ export const RecentIssue = (props: BlockProps) => {
|
||||
// hooks
|
||||
const { getStateById } = useProjectState();
|
||||
const { setPeekIssue } = useIssueDetail();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const issueDetails: TIssueEntityData = activity.entity_data as TIssueEntityData;
|
||||
const state = getStateById(issueDetails?.state);
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(activity.visited_at);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
@@ -47,7 +50,7 @@ export const RecentIssue = (props: BlockProps) => {
|
||||
</div>
|
||||
)}
|
||||
<div className="text-custom-text-200 font-medium text-sm whitespace-nowrap">{issueDetails?.name}</div>
|
||||
<div className="font-medium text-xs text-custom-text-400">{calculateTimeAgo(activity.visited_at)}</div>
|
||||
<div className="font-medium text-xs text-custom-text-400">{t(i18n_time_ago, { time })} </div>
|
||||
</div>
|
||||
}
|
||||
quickActionElement={
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { FileText } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TActivityEntityData, TPageEntityData } from "@plane/types";
|
||||
import { Avatar, Logo } from "@plane/ui";
|
||||
import { getFileURL } from "@plane/utils";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { useMember } from "@/hooks/store";
|
||||
|
||||
type BlockProps = {
|
||||
@@ -18,9 +19,11 @@ export const RecentPage = (props: BlockProps) => {
|
||||
const router = useRouter();
|
||||
// hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const pageDetails: TPageEntityData = activity.entity_data as TPageEntityData;
|
||||
const ownerDetails = getUserDetails(pageDetails?.owned_by);
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(activity.visited_at);
|
||||
return (
|
||||
<ListItem
|
||||
key={activity.id}
|
||||
@@ -43,7 +46,7 @@ export const RecentPage = (props: BlockProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-custom-text-200 font-medium text-sm whitespace-nowrap">{pageDetails?.name}</div>
|
||||
<div className="font-medium text-xs text-custom-text-400">{calculateTimeAgo(activity.visited_at)}</div>
|
||||
<div className="font-medium text-xs text-custom-text-400">{t(i18n_time_ago, { time })}</div>
|
||||
</div>
|
||||
}
|
||||
quickActionElement={
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { TActivityEntityData, TProjectEntityData } from "@plane/types";
|
||||
import { Logo } from "@plane/ui";
|
||||
import { ListItem } from "@/components/core/list";
|
||||
import { MemberDropdown } from "@/components/dropdowns";
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
|
||||
type BlockProps = {
|
||||
activity: TActivityEntityData;
|
||||
@@ -14,8 +15,11 @@ export const RecentProject = (props: BlockProps) => {
|
||||
const { activity, ref, workspaceSlug } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
// hooks
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const projectDetails: TProjectEntityData = activity.entity_data as TProjectEntityData;
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(activity.visited_at);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
@@ -33,7 +37,7 @@ export const RecentProject = (props: BlockProps) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-custom-text-200 font-medium text-sm whitespace-nowrap">{projectDetails?.name}</div>
|
||||
<div className="font-medium text-xs text-custom-text-400">{calculateTimeAgo(activity.visited_at)}</div>
|
||||
<div className="font-medium text-xs text-custom-text-400">{t(i18n_time_ago, { time })} </div>
|
||||
</div>
|
||||
}
|
||||
quickActionElement={
|
||||
|
||||
+5
-2
@@ -3,8 +3,9 @@
|
||||
import { FC, ReactNode } from "react";
|
||||
import { Network } from "lucide-react";
|
||||
// hooks
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { renderFormattedTime, renderFormattedDate, calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { renderFormattedTime, renderFormattedDate, calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// ui
|
||||
@@ -26,10 +27,12 @@ export const IssueActivityBlockComponent: FC<TIssueActivityBlockComponent> = (pr
|
||||
const {
|
||||
activity: { getActivityById },
|
||||
} = useIssueDetail();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const activity = getActivityById(activityId);
|
||||
const { isMobile } = usePlatformOS();
|
||||
if (!activity) return <></>;
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(activity.created_at);
|
||||
return (
|
||||
<div
|
||||
className={`relative flex items-center gap-3 text-xs ${
|
||||
@@ -48,7 +51,7 @@ export const IssueActivityBlockComponent: FC<TIssueActivityBlockComponent> = (pr
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`${renderFormattedDate(activity.created_at)}, ${renderFormattedTime(activity.created_at)}`}
|
||||
>
|
||||
<span className="whitespace-nowrap"> {calculateTimeAgo(activity.created_at)}</span>
|
||||
<span className="whitespace-nowrap"> {t(i18n_time_ago, { time })}</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,8 @@ import { FC, ReactNode } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { MessageCircle } from "lucide-react";
|
||||
// helpers
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
@@ -20,10 +21,12 @@ export const IssueCommentBlock: FC<TIssueCommentBlock> = observer((props) => {
|
||||
const {
|
||||
comment: { getCommentById },
|
||||
} = useIssueDetail();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const comment = getCommentById(commentId);
|
||||
|
||||
if (!comment) return <></>;
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(comment.created_at);
|
||||
return (
|
||||
<div className={`relative flex gap-3 ${ends === "top" ? `pb-2` : ends === "bottom" ? `pt-2` : `py-2`}`}>
|
||||
<div className="absolute left-[13px] top-0 bottom-0 w-0.5 bg-custom-background-80" aria-hidden />
|
||||
@@ -59,7 +62,7 @@ export const IssueCommentBlock: FC<TIssueCommentBlock> = observer((props) => {
|
||||
? comment.actor_detail?.first_name + " Bot"
|
||||
: comment.actor_detail?.display_name}
|
||||
</div>
|
||||
<div className="text-xs text-custom-text-200">commented {calculateTimeAgo(comment.created_at)}</div>
|
||||
<div className="text-xs text-custom-text-200">commented {t(i18n_time_ago, { time })}</div>
|
||||
</div>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
|
||||
@@ -4,11 +4,12 @@ import { FC } from "react";
|
||||
// hooks
|
||||
// ui
|
||||
import { Pencil, Trash2, LinkIcon, ExternalLink } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
// icons
|
||||
// types
|
||||
// helpers
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
import { useIssueDetail, useMember } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
@@ -31,6 +32,7 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
|
||||
} = useIssueDetail();
|
||||
const { getUserDetails } = useMember();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { t } = useTranslation();
|
||||
const linkDetail = getLinkById(linkId);
|
||||
if (!linkDetail) return <></>;
|
||||
|
||||
@@ -40,7 +42,7 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
|
||||
};
|
||||
|
||||
const createdByDetails = getUserDetails(linkDetail.created_by_id);
|
||||
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(linkDetail.created_at);
|
||||
return (
|
||||
<div key={linkId}>
|
||||
<div className="relative flex flex-col rounded-md bg-custom-background-90 p-2.5">
|
||||
@@ -107,7 +109,7 @@ export const IssueLinkDetail: FC<TIssueLinkDetail> = (props) => {
|
||||
|
||||
<div className="px-5">
|
||||
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
||||
Added {calculateTimeAgo(linkDetail.created_at)}
|
||||
Added {t(i18n_time_ago, { time })}
|
||||
<br />
|
||||
{createdByDetails && (
|
||||
<>
|
||||
|
||||
@@ -8,12 +8,13 @@ import { TIssueServiceType } from "@plane/types";
|
||||
// ui
|
||||
import { Tooltip, TOAST_TYPE, setToast, CustomMenu } from "@plane/ui";
|
||||
// helpers
|
||||
import { calculateTimeAgoShort } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgoShort } from "@/helpers/date-time.helper";
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useIssueDetail } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import { TLinkOperationsModal } from "./create-update-link-modal";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
||||
type TIssueLinkItem = {
|
||||
linkId: string;
|
||||
@@ -32,6 +33,7 @@ export const IssueLinkItem: FC<TIssueLinkItem> = observer((props) => {
|
||||
link: { getLinkById },
|
||||
} = useIssueDetail(issueServiceType);
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { t } = useTranslation();
|
||||
const linkDetail = getLinkById(linkId);
|
||||
if (!linkDetail) return <></>;
|
||||
|
||||
@@ -39,6 +41,7 @@ export const IssueLinkItem: FC<TIssueLinkItem> = observer((props) => {
|
||||
toggleIssueLinkModalStore(modalToggle);
|
||||
setIssueLinkData(linkDetail);
|
||||
};
|
||||
const { i18n_key, time } = calculateI18nTimeAgoShort(linkDetail.created_at);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -60,7 +63,8 @@ export const IssueLinkItem: FC<TIssueLinkItem> = observer((props) => {
|
||||
</div>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
<p className="p-1 text-xs align-bottom leading-5 text-custom-text-400 group-hover-text-custom-text-200">
|
||||
{calculateTimeAgoShort(linkDetail.created_at)}
|
||||
{time}
|
||||
{t(i18n_key)}
|
||||
</p>
|
||||
<span
|
||||
onClick={() => {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { Copy, LinkIcon, Pencil, Trash2 } from "lucide-react";
|
||||
// plane types
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ILinkDetails } from "@plane/types";
|
||||
// plane ui
|
||||
import { setToast, TOAST_TYPE, Tooltip } from "@plane/ui";
|
||||
// helpers
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useMember } from "@/hooks/store";
|
||||
@@ -22,6 +23,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
||||
const { handleDeleteLink, handleEditLink, isEditingAllowed, link } = props;
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const { t } = useTranslation();
|
||||
// derived values
|
||||
const createdByDetails = getUserDetails(link.created_by);
|
||||
// platform os
|
||||
@@ -36,6 +38,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
||||
})
|
||||
);
|
||||
};
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(link.created_at);
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col rounded-md bg-custom-background-90 p-2.5">
|
||||
@@ -88,7 +91,7 @@ export const ModulesLinksListItem: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
<div className="px-5">
|
||||
<p className="mt-0.5 stroke-[1.5] text-xs text-custom-text-300">
|
||||
Added {calculateTimeAgo(link.created_at)}
|
||||
Added {t(i18n_time_ago, { time })}
|
||||
<br />
|
||||
{createdByDetails && (
|
||||
<>by {createdByDetails?.is_bot ? createdByDetails?.first_name + " Bot" : createdByDetails?.display_name}</>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { observer } from "mobx-react";
|
||||
// helpers
|
||||
import { calculateTimeAgoShort } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgoShort } from "@/helpers/date-time.helper";
|
||||
// store types
|
||||
import { TPageInstance } from "@/store/pages/base-page";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
|
||||
type Props = {
|
||||
page: TPageInstance;
|
||||
@@ -10,10 +11,15 @@ type Props = {
|
||||
|
||||
export const PageEditInformationPopover: React.FC<Props> = observer((props) => {
|
||||
const { page } = props;
|
||||
const { t } = useTranslation();
|
||||
const { i18n_key, time } = page.updated_at ? calculateI18nTimeAgoShort(page.updated_at) : { i18n_key: "", time: 0 };
|
||||
|
||||
return (
|
||||
<div className="flex-shrink-0 whitespace-nowrap">
|
||||
<span className="text-sm text-custom-text-300">Edited {calculateTimeAgoShort(page.updated_at ?? "")} ago</span>
|
||||
<span className="text-sm text-custom-text-300">
|
||||
Edited {time}
|
||||
{t(i18n_key)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ import Link from "next/link";
|
||||
import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { History, MessageSquare } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { IUserActivityResponse } from "@plane/types";
|
||||
// hooks
|
||||
// components
|
||||
@@ -12,7 +13,7 @@ import { RichTextReadOnlyEditor } from "@/components/editor/rich-text-editor/ric
|
||||
// ui
|
||||
import { ActivitySettingsLoader } from "@/components/ui";
|
||||
// helpers
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
@@ -27,6 +28,7 @@ export const ActivityList: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { data: currentUser } = useUser();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// TODO: refactor this component
|
||||
return (
|
||||
@@ -34,6 +36,7 @@ export const ActivityList: React.FC<Props> = observer((props) => {
|
||||
{activity ? (
|
||||
<ul role="list">
|
||||
{activity.results.map((activityItem) => {
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(activityItem.created_at);
|
||||
if (activityItem.field === "comment")
|
||||
return (
|
||||
<div key={activityItem.id} className="mt-2">
|
||||
@@ -66,9 +69,7 @@ export const ActivityList: React.FC<Props> = observer((props) => {
|
||||
? activityItem.actor_detail.first_name + " Bot"
|
||||
: activityItem.actor_detail.display_name}
|
||||
</div>
|
||||
<p className="mt-0.5 text-xs text-custom-text-200">
|
||||
Commented {calculateTimeAgo(activityItem.created_at)}
|
||||
</p>
|
||||
<p className="mt-0.5 text-xs text-custom-text-200">Commented {t(i18n_time_ago, { time })}</p>
|
||||
</div>
|
||||
<div className="issue-comments-section p-0">
|
||||
<RichTextReadOnlyEditor
|
||||
@@ -155,9 +156,7 @@ export const ActivityList: React.FC<Props> = observer((props) => {
|
||||
)}{" "}
|
||||
<div className="inline gap-1">
|
||||
{message}{" "}
|
||||
<span className="flex-shrink-0 whitespace-nowrap">
|
||||
{calculateTimeAgo(activityItem.created_at)}
|
||||
</span>
|
||||
<span className="flex-shrink-0 whitespace-nowrap">{t(i18n_time_ago, { time })}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,13 +5,14 @@ import useSWR from "swr";
|
||||
// icons
|
||||
import { History, MessageSquare } from "lucide-react";
|
||||
// hooks
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { ActivityIcon, ActivityMessage, IssueLink } from "@/components/core";
|
||||
import { RichTextReadOnlyEditor } from "@/components/editor/rich-text-editor/rich-text-read-only-editor";
|
||||
import { ActivitySettingsLoader } from "@/components/ui";
|
||||
// constants
|
||||
import { USER_ACTIVITY } from "@/constants/fetch-keys";
|
||||
// helpers
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
@@ -32,7 +33,7 @@ export const ProfileActivityListPage: React.FC<Props> = observer((props) => {
|
||||
const { cursor, perPage, updateResultsCount, updateTotalPages, updateEmptyState } = props;
|
||||
// store hooks
|
||||
const { data: currentUser } = useUser();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { data: userProfileActivity } = useSWR(
|
||||
USER_ACTIVITY({
|
||||
cursor,
|
||||
@@ -60,6 +61,7 @@ export const ProfileActivityListPage: React.FC<Props> = observer((props) => {
|
||||
{userProfileActivity ? (
|
||||
<ul role="list">
|
||||
{userProfileActivity.results.map((activityItem: any) => {
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(activityItem.created_at);
|
||||
if (activityItem.field === "comment")
|
||||
return (
|
||||
<div key={activityItem.id} className="mt-2">
|
||||
@@ -92,9 +94,7 @@ export const ProfileActivityListPage: React.FC<Props> = observer((props) => {
|
||||
? activityItem.actor_detail.first_name + " Bot"
|
||||
: activityItem.actor_detail.display_name}
|
||||
</div>
|
||||
<p className="mt-0.5 text-xs text-custom-text-200">
|
||||
Commented {calculateTimeAgo(activityItem.created_at)}
|
||||
</p>
|
||||
<p className="mt-0.5 text-xs text-custom-text-200">Commented {t(i18n_time_ago, { time })}</p>
|
||||
</div>
|
||||
<div className="issue-comments-section p-0">
|
||||
<RichTextReadOnlyEditor
|
||||
@@ -177,9 +177,7 @@ export const ProfileActivityListPage: React.FC<Props> = observer((props) => {
|
||||
)}{" "}
|
||||
<div className="inline gap-1">
|
||||
{message}{" "}
|
||||
<span className="flex-shrink-0 whitespace-nowrap">
|
||||
{calculateTimeAgo(activityItem.created_at)}
|
||||
</span>
|
||||
<span className="flex-shrink-0 whitespace-nowrap">{t(i18n_time_ago, { time })}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Loader, Card } from "@plane/ui";
|
||||
// components
|
||||
import { ActivityMessage, IssueLink } from "@/components/core";
|
||||
@@ -11,7 +12,7 @@ import { ProfileEmptyState } from "@/components/ui";
|
||||
// constants
|
||||
import { USER_PROFILE_ACTIVITY } from "@/constants/fetch-keys";
|
||||
// helpers
|
||||
import { calculateTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo } from "@/helpers/date-time.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useUser } from "@/hooks/store";
|
||||
@@ -25,6 +26,7 @@ const userService = new UserService();
|
||||
export const ProfileActivity = observer(() => {
|
||||
const { workspaceSlug, userId } = useParams();
|
||||
// store hooks
|
||||
const { t } = useTranslation();
|
||||
const { data: currentUser } = useUser();
|
||||
|
||||
const { data: userProfileActivity } = useSWR(
|
||||
@@ -44,40 +46,45 @@ export const ProfileActivity = observer(() => {
|
||||
{userProfileActivity ? (
|
||||
userProfileActivity.results.length > 0 ? (
|
||||
<div className="space-y-5">
|
||||
{userProfileActivity.results.map((activity) => (
|
||||
<div key={activity.id} className="flex gap-3">
|
||||
<div className="flex-shrink-0 grid place-items-center overflow-hidden rounded h-6 w-6">
|
||||
{activity.actor_detail?.avatar_url && activity.actor_detail?.avatar_url !== "" ? (
|
||||
<img
|
||||
src={getFileURL(activity.actor_detail?.avatar_url)}
|
||||
alt={activity.actor_detail?.display_name}
|
||||
className="rounded"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-6 w-6 place-items-center rounded border-2 bg-gray-700 text-xs text-white">
|
||||
{activity.actor_detail?.display_name?.charAt(0)}
|
||||
{userProfileActivity.results.map((activity) => {
|
||||
{
|
||||
const { i18n_time_ago, time } = calculateI18nTimeAgo(activity.created_at);
|
||||
return (
|
||||
<div key={activity.id} className="flex gap-3">
|
||||
<div className="flex-shrink-0 grid place-items-center overflow-hidden rounded h-6 w-6">
|
||||
{activity.actor_detail?.avatar_url && activity.actor_detail?.avatar_url !== "" ? (
|
||||
<img
|
||||
src={getFileURL(activity.actor_detail?.avatar_url)}
|
||||
alt={activity.actor_detail?.display_name}
|
||||
className="rounded"
|
||||
/>
|
||||
) : (
|
||||
<div className="grid h-6 w-6 place-items-center rounded border-2 bg-gray-700 text-xs text-white">
|
||||
{activity.actor_detail?.display_name?.charAt(0)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="-mt-1 w-4/5 break-words">
|
||||
<p className="inline text-sm text-custom-text-200">
|
||||
<span className="font-medium text-custom-text-100">
|
||||
{currentUser?.id === activity.actor_detail?.id ? "You" : activity.actor_detail?.display_name}{" "}
|
||||
</span>
|
||||
{activity.field ? (
|
||||
<ActivityMessage activity={activity} showIssue />
|
||||
) : (
|
||||
<span>
|
||||
created <IssueLink activity={activity} />
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-custom-text-200 whitespace-nowrap ">
|
||||
{calculateTimeAgo(activity.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="-mt-1 w-4/5 break-words">
|
||||
<p className="inline text-sm text-custom-text-200">
|
||||
<span className="font-medium text-custom-text-100">
|
||||
{currentUser?.id === activity.actor_detail?.id
|
||||
? "You"
|
||||
: activity.actor_detail?.display_name}{" "}
|
||||
</span>
|
||||
{activity.field ? (
|
||||
<ActivityMessage activity={activity} showIssue />
|
||||
) : (
|
||||
<span>
|
||||
created <IssueLink activity={activity} />
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-custom-text-200 whitespace-nowrap ">{t(i18n_time_ago, { time })}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<ProfileEmptyState
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
import { FC, useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { Clock } from "lucide-react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Avatar, Row } from "@plane/ui";
|
||||
// components
|
||||
import { NotificationOption } from "@/components/workspace-notifications";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { calculateTimeAgo, renderFormattedDate, renderFormattedTime } from "@/helpers/date-time.helper";
|
||||
import { calculateI18nTimeAgo, renderFormattedDate, renderFormattedTime } from "@/helpers/date-time.helper";
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
import { sanitizeCommentForNotification } from "@/helpers/notification.helper";
|
||||
import { replaceUnderscoreIfSnakeCase, stripAndTruncateHTML } from "@/helpers/string.helper";
|
||||
@@ -26,6 +27,7 @@ export const NotificationItem: FC<TNotificationItem> = observer((props) => {
|
||||
const { currentSelectedNotificationId, setCurrentSelectedNotificationId } = useWorkspaceNotifications();
|
||||
const { asJson: notification, markNotificationAsRead } = useNotification(notificationId);
|
||||
const { getIsIssuePeeked, setPeekIssue } = useIssueDetail();
|
||||
const { t } = useTranslation();
|
||||
// states
|
||||
const [isSnoozeStateModalOpen, setIsSnoozeStateModalOpen] = useState(false);
|
||||
const [customSnoozeModal, setCustomSnoozeModal] = useState(false);
|
||||
@@ -56,6 +58,9 @@ export const NotificationItem: FC<TNotificationItem> = observer((props) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
const { i18n_time_ago, time } = notification?.created_at
|
||||
? calculateI18nTimeAgo(notification?.created_at)
|
||||
: { i18n_time_ago: "", time: "" };
|
||||
|
||||
if (!workspaceSlug || !notificationId || !notification?.id || !notificationField) return <></>;
|
||||
|
||||
@@ -165,7 +170,7 @@ export const NotificationItem: FC<TNotificationItem> = observer((props) => {
|
||||
</p>
|
||||
) : (
|
||||
<p className="mt-auto flex-shrink-0 text-custom-text-300">
|
||||
{notification.created_at && calculateTimeAgo(notification.created_at)}
|
||||
{notification.created_at && t(i18n_time_ago, { time })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { EUserPermissions } from "ee/constants/user-permissions";
|
||||
import { Plus, Shapes } from "lucide-react";
|
||||
import { EUserPermissions } from "ee/constants/user-permissions";
|
||||
|
||||
export interface EmptyStateDetails {
|
||||
key: EmptyStateType;
|
||||
|
||||
@@ -156,12 +156,14 @@ export const findHowManyDaysLeft = (
|
||||
};
|
||||
|
||||
// Time Difference Helpers
|
||||
|
||||
/**
|
||||
* @returns {string} formatted date in the form of amount of time passed since the event happened
|
||||
* @description Returns time passed since the event happened
|
||||
* @param {string | Date} time
|
||||
* @example calculateTimeAgo("2023-01-01") // 1 year ago
|
||||
*/
|
||||
|
||||
export const calculateTimeAgo = (time: string | number | Date | null): string => {
|
||||
if (!time) return "";
|
||||
// Parse the time to check if it is valid
|
||||
@@ -173,6 +175,143 @@ export const calculateTimeAgo = (time: string | number | Date | null): string =>
|
||||
return distance;
|
||||
};
|
||||
|
||||
export const calculateI18nTimeAgo = (time: string | number | Date | null): TimeDistanceResult => {
|
||||
if (!time) return { i18n_time_ago: "", time: null };
|
||||
|
||||
const parsedTime = typeof time === "string" || typeof time === "number" ? parseISO(String(time)) : time;
|
||||
|
||||
if (!parsedTime) return { i18n_time_ago: "", time: null };
|
||||
|
||||
const diffInSeconds = (Date.now() - parsedTime.getTime()) / 1000;
|
||||
const distance = getTimeDistance(Math.abs(diffInSeconds));
|
||||
|
||||
return distance;
|
||||
};
|
||||
interface TimeDistanceResult {
|
||||
i18n_time_ago: string;
|
||||
time: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates relative time distance and returns appropriate translation keys
|
||||
*/
|
||||
function getTimeDistance(diffInSeconds: number): TimeDistanceResult {
|
||||
const absSeconds = Math.abs(diffInSeconds);
|
||||
const direction = diffInSeconds > 0 ? "past" : "future";
|
||||
|
||||
// Convert to larger time units
|
||||
const minutes = Math.round(absSeconds / 60);
|
||||
const hours = Math.round(absSeconds / 3600);
|
||||
const days = Math.round(absSeconds / 86400);
|
||||
const months = Math.round(absSeconds / 2592000);
|
||||
const years = Math.round(absSeconds / 31536000);
|
||||
|
||||
// Seconds-based intervals (<1 minute)
|
||||
if (absSeconds < 30)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.less_than_x_seconds.${direction}`,
|
||||
time: 30,
|
||||
};
|
||||
if (absSeconds < 5)
|
||||
return {
|
||||
i18n_time_ago: `date_time.include_seconds.less_than_5_seconds.${direction}`,
|
||||
time: null,
|
||||
};
|
||||
if (absSeconds < 10)
|
||||
return {
|
||||
i18n_time_ago: `date_time.include_seconds.less_than_10_seconds.${direction}`,
|
||||
time: null,
|
||||
};
|
||||
if (absSeconds < 20)
|
||||
return {
|
||||
i18n_time_ago: `date_time.include_seconds.less_than_20_seconds.${direction}`,
|
||||
time: null,
|
||||
};
|
||||
if (absSeconds < 40)
|
||||
return {
|
||||
i18n_time_ago: `date_time.include_seconds.half_a_minute.${direction}`,
|
||||
time: null,
|
||||
};
|
||||
if (absSeconds < 60)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.less_than_x_minutes.${direction}`,
|
||||
time: null,
|
||||
};
|
||||
|
||||
// Minutes to Years
|
||||
if (absSeconds < 90)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.x_minutes.${direction}`,
|
||||
time: 1,
|
||||
};
|
||||
if (absSeconds < 2670)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.x_minutes.${direction}`,
|
||||
time: minutes,
|
||||
};
|
||||
if (absSeconds < 5370)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.about_x_hours.${direction}`,
|
||||
time: 1,
|
||||
};
|
||||
if (absSeconds < 86370)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.about_x_hours.${direction}`,
|
||||
time: hours,
|
||||
};
|
||||
if (absSeconds < 151200)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.x_days.${direction}`,
|
||||
time: 1,
|
||||
};
|
||||
if (absSeconds < 2592000)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.x_days.${direction}`,
|
||||
time: days,
|
||||
};
|
||||
if (absSeconds < 3888000)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.about_x_months.${direction}`,
|
||||
time: 1,
|
||||
};
|
||||
if (absSeconds < 31536000)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.about_x_months.${direction}`,
|
||||
time: months,
|
||||
};
|
||||
if (absSeconds < 47304000)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.about_x_years.${direction}`,
|
||||
time: 1,
|
||||
};
|
||||
if (absSeconds < 56160000)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.over_x_years.${direction}`,
|
||||
time: 1,
|
||||
};
|
||||
if (absSeconds < 63072000)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.almost_x_years.${direction}`,
|
||||
time: 2,
|
||||
};
|
||||
|
||||
// Multiple years
|
||||
if (months % 12 < 3)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.about_x_years.${direction}`,
|
||||
time: years,
|
||||
};
|
||||
if (months % 12 < 9)
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.over_x_years.${direction}`,
|
||||
time: years,
|
||||
};
|
||||
return {
|
||||
i18n_time_ago: `date_time.standard.almost_x_years.${direction}`,
|
||||
time: years + 1,
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateTimeAgoShort(date: string | number | Date | null): string {
|
||||
if (!date) {
|
||||
return "";
|
||||
@@ -210,6 +349,67 @@ export function calculateTimeAgoShort(date: string | number | Date | null): stri
|
||||
return `${Math.floor(diffInYears)}y`;
|
||||
}
|
||||
|
||||
export function calculateI18nTimeAgoShort(date: string | number | Date | null): {
|
||||
i18n_key: string;
|
||||
time: number;
|
||||
} {
|
||||
if (!date) {
|
||||
return {
|
||||
i18n_key: "",
|
||||
time: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const parsedDate = typeof date === "string" ? parseISO(date) : new Date(date);
|
||||
const now = new Date();
|
||||
const diffInSeconds = (now.getTime() - parsedDate.getTime()) / 1000;
|
||||
|
||||
if (diffInSeconds < 60) {
|
||||
return {
|
||||
i18n_key: "date_time.units_short.s",
|
||||
time: Math.floor(diffInSeconds),
|
||||
};
|
||||
}
|
||||
|
||||
const diffInMinutes = diffInSeconds / 60;
|
||||
if (diffInMinutes < 60) {
|
||||
return {
|
||||
i18n_key: "date_time.units_short.m",
|
||||
time: Math.floor(diffInMinutes),
|
||||
};
|
||||
}
|
||||
|
||||
const diffInHours = diffInMinutes / 60;
|
||||
if (diffInHours < 24) {
|
||||
return {
|
||||
i18n_key: "date_time.units_short.h",
|
||||
time: Math.floor(diffInHours),
|
||||
};
|
||||
}
|
||||
|
||||
const diffInDays = diffInHours / 24;
|
||||
if (diffInDays < 30) {
|
||||
return {
|
||||
i18n_key: "date_time.units_short.d",
|
||||
time: Math.floor(diffInDays),
|
||||
};
|
||||
}
|
||||
|
||||
const diffInMonths = diffInDays / 30;
|
||||
if (diffInMonths < 12) {
|
||||
return {
|
||||
i18n_key: "date_time.units_short.mo",
|
||||
time: Math.floor(diffInMonths),
|
||||
};
|
||||
}
|
||||
|
||||
const diffInYears = diffInMonths / 12;
|
||||
return {
|
||||
i18n_key: "date_time.units_short.y",
|
||||
time: Math.floor(diffInYears),
|
||||
};
|
||||
}
|
||||
|
||||
// Date Validation Helpers
|
||||
/**
|
||||
* @returns {string} boolean value depending on whether the date is greater than today
|
||||
|
||||
Reference in New Issue
Block a user