mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-25 14:26:39 +00:00
💄 show product updates list
This commit is contained in:
@@ -1282,6 +1282,8 @@
|
|||||||
"productUpdateMoreInfo": "{noOfUpdates} more updates",
|
"productUpdateMoreInfo": "{noOfUpdates} more updates",
|
||||||
"productUpdateInfo": "{noOfUpdates} updates",
|
"productUpdateInfo": "{noOfUpdates} updates",
|
||||||
"productUpdateWhatsNew": "What's New",
|
"productUpdateWhatsNew": "What's New",
|
||||||
|
"productUpdateTitle": "Product Updates",
|
||||||
|
"dismissAll": "Dismiss all",
|
||||||
"pangolinUpdateAvailable": "New version available",
|
"pangolinUpdateAvailable": "New version available",
|
||||||
"pangolinUpdateAvailableInfo": "Version {version} is ready to install",
|
"pangolinUpdateAvailableInfo": "Version {version} is ready to install",
|
||||||
"pangolinUpdateAvailableReleaseNotes": "View release notes",
|
"pangolinUpdateAvailableReleaseNotes": "View release notes",
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import { useTranslations } from "next-intl";
|
|||||||
import { Transition } from "@headlessui/react";
|
import { Transition } from "@headlessui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Badge } from "./ui/badge";
|
||||||
|
import { timeAgoFormatter } from "@app/lib/timeAgoFormatter";
|
||||||
|
|
||||||
export default function ProductUpdates({
|
export default function ProductUpdates({
|
||||||
isCollapsed
|
isCollapsed
|
||||||
@@ -158,8 +161,40 @@ function ProductUpdatesListPopup({
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent side="right" align="end">
|
<PopoverContent
|
||||||
Hello
|
side="right"
|
||||||
|
align="end"
|
||||||
|
className="p-0 flex flex-col w-80"
|
||||||
|
>
|
||||||
|
<div className="p-3 flex justify-between border-b items-center">
|
||||||
|
<span className="text-sm inline-flex gap-2 items-center font-medium">
|
||||||
|
{t("productUpdateTitle")}
|
||||||
|
<Badge variant="secondary">{updates.length}</Badge>
|
||||||
|
</span>
|
||||||
|
<Button variant="ghost">{t("dismissAll")}</Button>
|
||||||
|
</div>
|
||||||
|
<ol className="p-3 flex flex-col gap-1 max-h-112 overflow-y-auto">
|
||||||
|
{updates.map((update) => (
|
||||||
|
<li
|
||||||
|
key={update.id}
|
||||||
|
className="border rounded-md flex flex-col p-4 gap-2"
|
||||||
|
>
|
||||||
|
<h4 className="text-sm font-medium inline-flex items-start gap-1">
|
||||||
|
<span>{update.title}</span>
|
||||||
|
<Badge variant="secondary">New</Badge>
|
||||||
|
</h4>
|
||||||
|
<small className="text-muted-foreground">
|
||||||
|
{update.contents}
|
||||||
|
</small>
|
||||||
|
<time
|
||||||
|
dateTime={update.publishedAt.toLocaleString()}
|
||||||
|
className="text-xs text-muted-foreground"
|
||||||
|
>
|
||||||
|
{timeAgoFormatter(update.publishedAt)}
|
||||||
|
</time>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Transition>
|
</Transition>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
48
src/lib/timeAgoFormatter.ts
Normal file
48
src/lib/timeAgoFormatter.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
export function timeAgoFormatter(
|
||||||
|
dateInput: string | Date,
|
||||||
|
short: boolean = false
|
||||||
|
): string {
|
||||||
|
const date = new Date(dateInput);
|
||||||
|
const now = new Date();
|
||||||
|
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
||||||
|
|
||||||
|
const secondsInMinute = 60;
|
||||||
|
const secondsInHour = 60 * secondsInMinute;
|
||||||
|
const secondsInDay = 24 * secondsInHour;
|
||||||
|
const secondsInWeek = 7 * secondsInDay;
|
||||||
|
const secondsInMonth = 30 * secondsInDay;
|
||||||
|
const secondsInYear = 365 * secondsInDay;
|
||||||
|
|
||||||
|
let value: number;
|
||||||
|
let unit: Intl.RelativeTimeFormatUnit;
|
||||||
|
|
||||||
|
if (diffInSeconds < secondsInMinute) {
|
||||||
|
value = diffInSeconds;
|
||||||
|
unit = "second";
|
||||||
|
} else if (diffInSeconds < secondsInHour) {
|
||||||
|
value = Math.floor(diffInSeconds / secondsInMinute);
|
||||||
|
unit = "minute";
|
||||||
|
} else if (diffInSeconds < secondsInDay) {
|
||||||
|
value = Math.floor(diffInSeconds / secondsInHour);
|
||||||
|
unit = "hour";
|
||||||
|
} else if (diffInSeconds < secondsInWeek) {
|
||||||
|
value = Math.floor(diffInSeconds / secondsInDay);
|
||||||
|
unit = "day";
|
||||||
|
} else if (diffInSeconds < secondsInMonth) {
|
||||||
|
value = Math.floor(diffInSeconds / secondsInWeek);
|
||||||
|
unit = "week";
|
||||||
|
} else if (diffInSeconds < secondsInYear) {
|
||||||
|
value = Math.floor(diffInSeconds / secondsInMonth);
|
||||||
|
unit = "month";
|
||||||
|
} else {
|
||||||
|
value = Math.floor(diffInSeconds / secondsInYear);
|
||||||
|
unit = "year";
|
||||||
|
}
|
||||||
|
|
||||||
|
const rtf = new Intl.RelativeTimeFormat("en", {
|
||||||
|
numeric: "auto",
|
||||||
|
style: short ? "narrow" : "long"
|
||||||
|
});
|
||||||
|
const formatedValue = rtf.format(-value, unit);
|
||||||
|
return formatedValue === "now" ? "Just now" : formatedValue;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user