From 8af397717898b76eadd1bde7123de12ed0405759 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 28 Feb 2025 16:31:08 +0200 Subject: [PATCH] user lookup feature --- src/App.tsx | 50 +- src/atoms/global.ts | 5 + src/background.ts | 2 +- src/components/Apps/AppsPrivate.tsx | 20 +- src/components/Chat/GroupAnnouncements.tsx | 2 +- src/components/Drawer/DrawerUserLookup.tsx | 22 + src/components/Group/Group.tsx | 6 +- src/components/Home/QortPrice.tsx | 420 +++++++++------- src/components/UserLookup.tsx/UserLookup.tsx | 495 +++++++++++++++++++ src/components/WrapperUserAction.tsx | 36 ++ src/utils/time.ts | 2 +- 11 files changed, 858 insertions(+), 202 deletions(-) create mode 100644 src/components/Drawer/DrawerUserLookup.tsx create mode 100644 src/components/UserLookup.tsx/UserLookup.tsx diff --git a/src/App.tsx b/src/App.tsx index 9e14601..7df8043 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -35,6 +35,7 @@ import RefreshIcon from "@mui/icons-material/Refresh"; import Logo2 from "./assets/svgs/Logo2.svg"; import Copy from "./assets/svgs/Copy.svg"; import ltcLogo from "./assets/ltc.png"; +import PersonSearchIcon from '@mui/icons-material/PersonSearch'; import qortLogo from "./assets/qort.png"; import { CopyToClipboard } from "react-copy-to-clipboard"; import Download from "./assets/svgs/Download.svg"; @@ -113,6 +114,7 @@ import { canSaveSettingToQdnAtom, enabledDevModeAtom, fullScreenAtom, + groupsPropertiesAtom, hasSettingsChangedAtom, isDisabledEditorEnterAtom, isUsingImportExportSettingsAtom, @@ -145,6 +147,8 @@ import { QMailStatus } from "./components/QMailStatus"; import { GlobalActions } from "./components/GlobalActions/GlobalActions"; import { useBlockedAddresses } from "./components/Group/useBlockUsers"; import { WalletIcon } from "./assets/Icons/WalletIcon"; +import { DrawerUserLookup } from "./components/Drawer/DrawerUserLookup"; +import { UserLookup } from "./components/UserLookup.tsx/UserLookup"; type extStates = | "not-authenticated" @@ -400,6 +404,7 @@ function App() { const [openSnack, setOpenSnack] = useState(false); const [hasLocalNode, setHasLocalNode] = useState(false); const [isOpenDrawerProfile, setIsOpenDrawerProfile] = useState(false); + const [isOpenDrawerLookup, setIsOpenDrawerLookup] = useState(false) const [apiKey, setApiKey] = useState(""); const [isOpenSendQort, setIsOpenSendQort] = useState(false); const [isOpenSendQortSuccess, setIsOpenSendQortSuccess] = useState(false); @@ -488,7 +493,7 @@ function App() { const resetAtomOldPinnedAppsAtom = useResetRecoilState(oldPinnedAppsAtom); const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(qMailLastEnteredTimestampAtom) const resetAtomMailsAtom = useResetRecoilState(mailsAtom) - + const resetGroupPropertiesAtom = useResetRecoilState(groupsPropertiesAtom) const resetAllRecoil = () => { resetAtomSortablePinnedAppsAtom(); resetAtomCanSaveSettingToQdnAtom(); @@ -498,6 +503,8 @@ function App() { resetAtomIsUsingImportExportSettingsAtom() resetAtomQMailLastEnteredTimestampAtom() resetAtomMailsAtom() + resetGroupPropertiesAtom() + }; useEffect(() => { if (!isMobile) return; @@ -1696,6 +1703,38 @@ function App() { /> + + { + setIsOpenDrawerLookup(true); + }} + > + USER LOOKUP} + placement="left" + arrow + sx={{ fontSize: "24" }} + slotProps={{ + tooltip: { + sx: { + color: "#ffffff", + backgroundColor: "#444444", + }, + }, + arrow: { + sx: { + color: "#444444", + }, + }, + }} + > + + + {desktopViewMode !== 'home' && ( <> @@ -2055,7 +2094,7 @@ function App() { display: "flex", flexDirection: "column", alignItems: "center", - zIndex: 6, + zIndex: 10000, }} > @@ -3110,7 +3149,7 @@ function App() { display: "flex", flexDirection: "column", alignItems: "center", - zIndex: 6, + zIndex: 10000, }} > @@ -3210,6 +3249,9 @@ function App() { open={isShow} aria-labelledby="alert-dialog-title" aria-describedby="alert-dialog-description" + sx={{ + zIndex: 10001 + }} > {message.paymentFee ? "Payment" : "Publish"} @@ -3635,7 +3677,7 @@ function App() { > {renderProfileLeft()} - + {extState === "create-wallet" && walletToBeDownloaded && ( { diff --git a/src/atoms/global.ts b/src/atoms/global.ts index b385f00..cb49bf7 100644 --- a/src/atoms/global.ts +++ b/src/atoms/global.ts @@ -169,4 +169,9 @@ export const qMailLastEnteredTimestampAtom = atom({ export const mailsAtom = atom({ key: 'mailsAtom', default: [], +}); + +export const groupsPropertiesAtom = atom({ + key: 'groupsPropertiesAtom', + default: {}, }); \ No newline at end of file diff --git a/src/background.ts b/src/background.ts index 91dc597..ad8ddf3 100644 --- a/src/background.ts +++ b/src/background.ts @@ -845,7 +845,7 @@ export async function getNameInfoForOthers(address) { return ""; } } -async function getAddressInfo(address) { +export async function getAddressInfo(address) { const validApi = await getBaseApi(); const response = await fetch(validApi + "/addresses/" + address); const data = await response.json(); diff --git a/src/components/Apps/AppsPrivate.tsx b/src/components/Apps/AppsPrivate.tsx index b014cd2..d18fcad 100644 --- a/src/components/Apps/AppsPrivate.tsx +++ b/src/components/Apps/AppsPrivate.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from "react"; +import React, { useContext, useMemo, useState } from "react"; import { Avatar, Box, @@ -18,7 +18,7 @@ import { import { useDropzone } from "react-dropzone"; import { useHandlePrivateApps } from "./useHandlePrivateApps"; import { useRecoilState, useSetRecoilState } from "recoil"; -import { myGroupsWhereIAmAdminAtom } from "../../atoms/global"; +import { groupsPropertiesAtom, myGroupsWhereIAmAdminAtom } from "../../atoms/global"; import { Label } from "../Group/AddGroup"; import { Spacer } from "../../common/Spacer"; import { @@ -43,14 +43,22 @@ export const AppsPrivate = ({myName}) => { const [logo, setLogo] = useState(null); const [qortalUrl, setQortalUrl] = useState(""); const [selectedGroup, setSelectedGroup] = useState(0); - + const [groupsProperties] = useRecoilState(groupsPropertiesAtom) const [valueTabPrivateApp, setValueTabPrivateApp] = useState(0); - const [myGroupsWhereIAmAdmin, setMyGroupsWhereIAmAdmin] = useRecoilState( + const [myGroupsWhereIAmAdminFromGlobal] = useRecoilState( myGroupsWhereIAmAdminAtom ); + + const myGroupsWhereIAmAdmin = useMemo(()=> { + return myGroupsWhereIAmAdminFromGlobal?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false) + }, [myGroupsWhereIAmAdminFromGlobal, groupsProperties]) const [isOpenPrivateModal, setIsOpenPrivateModal] = useState(false); const { show, setInfoSnackCustom, setOpenSnackGlobal, memberGroups } = useContext(MyContext); + + const myGroupsPrivate = useMemo(()=> { + return memberGroups?.filter((group)=> groupsProperties[group?.groupId]?.isOpen === false) + }, [memberGroups, groupsProperties]) const [privateAppValues, setPrivateAppValues] = useState({ name: "", service: "DOCUMENT", @@ -95,7 +103,7 @@ export const AppsPrivate = ({myName}) => { await openApp(privateAppValues, true); } catch (error) { - console.log('error', error?.message) + console.error(error) } }; @@ -311,7 +319,7 @@ export const AppsPrivate = ({myName}) => { > No group selected - {memberGroups + {myGroupsPrivate ?.filter((item) => !item?.isOpen) .map((group) => { return ( diff --git a/src/components/Chat/GroupAnnouncements.tsx b/src/components/Chat/GroupAnnouncements.tsx index cdcd5db..9ba7668 100644 --- a/src/components/Chat/GroupAnnouncements.tsx +++ b/src/components/Chat/GroupAnnouncements.tsx @@ -194,7 +194,7 @@ export const GroupAnnouncements = ({ }; }); } catch (error) { - console.log("error", error); + console.error("error", error); } }; diff --git a/src/components/Drawer/DrawerUserLookup.tsx b/src/components/Drawer/DrawerUserLookup.tsx new file mode 100644 index 0000000..f64e096 --- /dev/null +++ b/src/components/Drawer/DrawerUserLookup.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import Box from '@mui/material/Box'; +import Drawer from '@mui/material/Drawer'; + +export const DrawerUserLookup = ({open, setOpen, children}) => { + + const toggleDrawer = (newOpen: boolean) => () => { + setOpen(newOpen); + }; + + + return ( +
+ + + + {children} + + +
+ ); +} diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 08f75e9..c4c7622 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -74,8 +74,8 @@ import { HubsIcon } from "../../assets/Icons/HubsIcon"; import { MessagingIcon } from "../../assets/Icons/MessagingIcon"; import { formatEmailDate } from "./QMailMessages"; import { AdminSpace } from "../Chat/AdminSpace"; -import { useSetRecoilState } from "recoil"; -import { addressInfoControllerAtom, selectedGroupIdAtom } from "../../atoms/global"; +import { useRecoilState, useSetRecoilState } from "recoil"; +import { addressInfoControllerAtom, groupsPropertiesAtom, selectedGroupIdAtom } from "../../atoms/global"; import { sortArrayByTimestampAndGroupName } from "../../utils/time"; import BlockIcon from '@mui/icons-material/Block'; import LockIcon from '@mui/icons-material/Lock'; @@ -446,7 +446,7 @@ export const Group = ({ const [isOpenSideViewGroups, setIsOpenSideViewGroups] = useState(false) const [isForceShowCreationKeyPopup, setIsForceShowCreationKeyPopup] = useState(false) - const [groupsProperties, setGroupsProperties] = useState({}) + const [groupsProperties, setGroupsProperties] = useRecoilState(groupsPropertiesAtom) const setUserInfoForLevels = useSetRecoilState(addressInfoControllerAtom); const isPrivate = useMemo(()=> { diff --git a/src/components/Home/QortPrice.tsx b/src/components/Home/QortPrice.tsx index 3d54a04..f4586fb 100644 --- a/src/components/Home/QortPrice.tsx +++ b/src/components/Home/QortPrice.tsx @@ -1,209 +1,257 @@ -import React, { useCallback, useEffect, useState } from 'react' -import { getBaseApiReact } from '../../App'; -import { Box, Tooltip, Typography } from '@mui/material'; -import { BarSpinner } from '../../common/Spinners/BarSpinner/BarSpinner'; +import React, { useCallback, useEffect, useState } from "react"; +import { getBaseApiReact } from "../../App"; +import { Box, Tooltip, Typography } from "@mui/material"; +import { BarSpinner } from "../../common/Spinners/BarSpinner/BarSpinner"; +import { formatDate } from "../../utils/time"; function getAverageLtcPerQort(trades) { - let totalQort = 0; - let totalLtc = 0; - - trades.forEach((trade) => { - const qort = parseFloat(trade.qortAmount); - const ltc = parseFloat(trade.foreignAmount); - - totalQort += qort; - totalLtc += ltc; - }); - - // Avoid division by zero - if (totalQort === 0) return 0; - - // Weighted average price - return parseFloat((totalLtc / totalQort).toFixed(8)); - } + let totalQort = 0; + let totalLtc = 0; - function getTwoWeeksAgoTimestamp() { - const now = new Date(); - now.setDate(now.getDate() - 14); // Subtract 14 days - return now.getTime(); // Get timestamp in milliseconds - } - - function formatWithCommasAndDecimals(number) { + trades.forEach((trade) => { + const qort = parseFloat(trade.qortAmount); + const ltc = parseFloat(trade.foreignAmount); - return Number(number).toLocaleString(); - } + totalQort += qort; + totalLtc += ltc; + }); + + // Avoid division by zero + if (totalQort === 0) return 0; + + // Weighted average price + return parseFloat((totalLtc / totalQort).toFixed(8)); +} + +function getTwoWeeksAgoTimestamp() { + const now = new Date(); + now.setDate(now.getDate() - 14); // Subtract 14 days + return now.getTime(); // Get timestamp in milliseconds +} + +function formatWithCommasAndDecimals(number) { + return Number(number).toLocaleString(); +} - export const QortPrice = () => { - const [ltcPerQort, setLtcPerQort] = useState(null) - const [supply, setSupply] = useState(null) - const [lastBlock, setLastBlock] = useState(null) - const [loading, setLoading] = useState(true) - - const getPrice = useCallback(async () => { - try { - setLoading(true) - - const response = await fetch(`${getBaseApiReact()}/crosschain/trades?foreignBlockchain=LITECOIN&minimumTimestamp=${getTwoWeeksAgoTimestamp()}&limit=20&reverse=true`); - const data = await response.json(); - - - setLtcPerQort(getAverageLtcPerQort(data)); - } catch (error) { - console.error(error); - } finally { - setLoading(false) - - } - }, []) + const [ltcPerQort, setLtcPerQort] = useState(null); + const [supply, setSupply] = useState(null); + const [lastBlock, setLastBlock] = useState(null); + const [loading, setLoading] = useState(true); - const getLastBlock = useCallback(async () => { - try { - setLoading(true) - - const response = await fetch(`${getBaseApiReact()}/blocks/last`); - const data = await response.json(); + const getPrice = useCallback(async () => { + try { + setLoading(true); - setLastBlock(data); - } catch (error) { - console.error(error); - } finally { - setLoading(false) - - } - }, []) + const response = await fetch( + `${getBaseApiReact()}/crosschain/trades?foreignBlockchain=LITECOIN&minimumTimestamp=${getTwoWeeksAgoTimestamp()}&limit=20&reverse=true` + ); + const data = await response.json(); - const getSupplyInCirculation = useCallback(async () => { - try { - setLoading(true) - - const response = await fetch(`${getBaseApiReact()}/stats/supply/circulating`); - const data = await response.text(); - formatWithCommasAndDecimals(data) - setSupply(formatWithCommasAndDecimals(data)); - } catch (error) { - console.error(error); - } finally { - setLoading(false) - - } - }, []) - - - + setLtcPerQort(getAverageLtcPerQort(data)); + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + }, []); + + const getLastBlock = useCallback(async () => { + try { + setLoading(true); + + const response = await fetch(`${getBaseApiReact()}/blocks/last`); + const data = await response.json(); + + setLastBlock(data); + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + }, []); + + const getSupplyInCirculation = useCallback(async () => { + try { + setLoading(true); + + const response = await fetch( + `${getBaseApiReact()}/stats/supply/circulating` + ); + const data = await response.text(); + formatWithCommasAndDecimals(data); + setSupply(formatWithCommasAndDecimals(data)); + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + getPrice(); + getSupplyInCirculation(); + getLastBlock(); + const interval = setInterval(() => { + getPrice(); + getSupplyInCirculation(); + getLastBlock(); + }, 900000); + + return () => clearInterval(interval); + }, [getPrice]); - useEffect(() => { - - getPrice(); - getSupplyInCirculation() - getLastBlock() - const interval = setInterval(() => { - getPrice(); - getSupplyInCirculation() - getLastBlock() - }, 900000); - - return () => clearInterval(interval); - - }, [getPrice]); - console.log('supply', supply) - return ( - Based on the latest 20 trades} - placement="bottom" - arrow - sx={{ fontSize: "24" }} - slotProps={{ - tooltip: { - sx: { - color: "#ffffff", - backgroundColor: "#444444", - }, - }, - arrow: { - sx: { - color: "#444444", - }, - }, - }} - > - + + Based on the latest 20 trades + + } + placement="bottom" + arrow + sx={{ fontSize: "24" }} + slotProps={{ + tooltip: { + sx: { + color: "#ffffff", + backgroundColor: "#444444", + }, + }, + arrow: { + sx: { + color: "#444444", + }, + }, + }} + > + - Price - {!ltcPerQort ? ( - - ): ( - + {ltcPerQort} LTC/QORT + fontWeight: "bold", + }} + > + Price + + {!ltcPerQort ? ( + + ) : ( + + {ltcPerQort} LTC/QORT + )} + + + + + Supply + + {!supply ? ( + + ) : ( + + {supply} QORT + + )} + + + {lastBlock?.timestamp && formatDate(lastBlock?.timestamp)} + + } + placement="bottom" + arrow + sx={{ fontSize: "24" }} + slotProps={{ + tooltip: { + sx: { + color: "#ffffff", + backgroundColor: "#444444", + }, + }, + arrow: { + sx: { + color: "#444444", + }, + }, + }} + > + - - - - - Supply - {!supply ? ( - - ): ( - {supply} QORT - )} - - - - Last height - {!lastBlock?.height ? ( + fontWeight: "bold", + }} + > + Last height + + {!lastBlock?.height ? ( - ): ( - {lastBlock?.height} - + ) : ( + + {lastBlock?.height} + )} - + + - ) -} + ); +}; diff --git a/src/components/UserLookup.tsx/UserLookup.tsx b/src/components/UserLookup.tsx/UserLookup.tsx new file mode 100644 index 0000000..d8bb0ea --- /dev/null +++ b/src/components/UserLookup.tsx/UserLookup.tsx @@ -0,0 +1,495 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { DrawerUserLookup } from "../Drawer/DrawerUserLookup"; +import { + Avatar, + Box, + Button, + ButtonBase, + Card, + Divider, + TableBody, + TableCell, + TableHead, + TableRow, + TextField, + Tooltip, + Typography, + Table, + CircularProgress, +} from "@mui/material"; +import { getAddressInfo, getNameOrAddress } from "../../background"; +import { getBaseApiReact } from "../../App"; +import { getNameInfo } from "../Group/Group"; +import AccountCircleIcon from "@mui/icons-material/AccountCircle"; +import { Spacer } from "../../common/Spacer"; +import { formatTimestamp } from "../../utils/time"; +import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen'; +import SearchIcon from '@mui/icons-material/Search'; +import { executeEvent, subscribeToEvent, unsubscribeFromEvent } from "../../utils/events"; + +function formatAddress(str) { + if (str.length <= 12) return str; + + const first6 = str.slice(0, 6); + const last6 = str.slice(-6); + + return `${first6}....${last6}`; +} + +export const UserLookup = ({ isOpenDrawerLookup, setIsOpenDrawerLookup }) => { + const [nameOrAddress, setNameOrAddress] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); + const [addressInfo, setAddressInfo] = useState(null); + const [isLoadingUser, setIsLoadingUser] = useState(false); + const [isLoadingPayments, setIsLoadingPayments] = useState(false); + const [payments, setPayments] = useState([]); + const lookupFunc = useCallback(async (messageAddressOrName) => { + try { + setErrorMessage('') + setIsLoadingUser(true) + setPayments([]) + setAddressInfo(null) + const inputAddressOrName = messageAddressOrName || nameOrAddress + if (!inputAddressOrName?.trim()) + throw new Error("Please insert a name or address"); + const owner = await getNameOrAddress(inputAddressOrName); + if (!owner) throw new Error("Name does not exist"); + const addressInfoRes = await getAddressInfo(owner); + if (!addressInfoRes?.publicKey) { + throw new Error("Address does not exist on blockchain"); + } + const name = await getNameInfo(owner); + const balanceRes = await fetch( + `${getBaseApiReact()}/addresses/balance/${owner}` + ); + const balanceData = await balanceRes.json(); + setAddressInfo({ + ...addressInfoRes, + balance: balanceData, + name, + }); + setIsLoadingUser(false) + setIsLoadingPayments(true) + + const getPayments = await fetch( + `${getBaseApiReact()}/transactions/search?txType=PAYMENT&address=${owner}&confirmationStatus=CONFIRMED&limit=20&reverse=true` + ); + const paymentsData = await getPayments.json(); + setPayments(paymentsData); + + } catch (error) { + setErrorMessage(error?.message) + console.error(error); + } finally { + setIsLoadingUser(false) + setIsLoadingPayments(false) + } + }, [nameOrAddress]); + + const openUserLookupDrawerFunc = useCallback((e) => { + setIsOpenDrawerLookup(true) + const message = e.detail?.addressOrName; + if(message){ + lookupFunc(message) + } + }, [lookupFunc, setIsOpenDrawerLookup]); + + useEffect(() => { + subscribeToEvent("openUserLookupDrawer", openUserLookupDrawerFunc); + + return () => { + unsubscribeFromEvent("openUserLookupDrawer", openUserLookupDrawerFunc); + }; + }, [openUserLookupDrawerFunc]); + + + + return ( + + + + + + setNameOrAddress(e.target.value)} + size="small" + placeholder="Address or Name" + autoComplete="off" + onKeyDown={(e) => { + if (e.key === "Enter" && nameOrAddress) { + lookupFunc(); + } + }} + /> + + + + { + setIsOpenDrawerLookup(false) + }}> + + + + + {!isLoadingUser && errorMessage && ( + + {errorMessage} + + )} + {isLoadingUser && ( + + + + )} + {!isLoadingUser && addressInfo && ( + <> + + + + + + {addressInfo?.name ?? "Name not registered"} + + + + {addressInfo?.name ? ( + + + + ) : ( + + )} + + + + Level {addressInfo?.level} + + + + + + Address + + + copy address + + } + placement="bottom" + arrow + sx={{ fontSize: "24" }} + slotProps={{ + tooltip: { + sx: { + color: "#ffffff", + backgroundColor: "#444444", + }, + }, + arrow: { + sx: { + color: "#444444", + }, + }, + }} + > + { + navigator.clipboard.writeText(addressInfo?.address); + }} + > + + {addressInfo?.address} + + + + + + Balance + {addressInfo?.balance} + + + + + + + + + )} + + {isLoadingPayments && ( + + + + )} + {!isLoadingPayments && addressInfo && ( + + 20 most recent payments + + {!isLoadingPayments && payments?.length === 0 && ( + + No payments + + )} + + + + Sender + Reciver + Amount + Time + + + + {payments.map((payment, index) => ( + + + + copy address + + } + placement="bottom" + arrow + sx={{ fontSize: "24" }} + slotProps={{ + tooltip: { + sx: { + color: "#ffffff", + backgroundColor: "#444444", + }, + }, + arrow: { + sx: { + color: "#444444", + }, + }, + }} + > + { + navigator.clipboard.writeText( + payment?.creatorAddress + ); + }} + > + {formatAddress(payment?.creatorAddress)} + + + + + + copy address + + } + placement="bottom" + arrow + sx={{ fontSize: "24" }} + slotProps={{ + tooltip: { + sx: { + color: "#ffffff", + backgroundColor: "#444444", + }, + }, + arrow: { + sx: { + color: "#444444", + }, + }, + }} + > + { + navigator.clipboard.writeText(payment?.recipient); + }} + > + {formatAddress(payment?.recipient)} + + + + + + {payment?.amount} + + {formatTimestamp(payment?.timestamp)} + + ))} + +
+
+ )} + +
+
+
+ ); +}; diff --git a/src/components/WrapperUserAction.tsx b/src/components/WrapperUserAction.tsx index 8bcc03a..3bcb143 100644 --- a/src/components/WrapperUserAction.tsx +++ b/src/components/WrapperUserAction.tsx @@ -121,6 +121,42 @@ export const WrapperUserAction = ({ children, address, name, disabled }) => { > Copy address + + + + + diff --git a/src/utils/time.ts b/src/utils/time.ts index b0a27cf..c89c1cd 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -12,7 +12,7 @@ export function formatTimestamp(timestamp: number): string { } else if (elapsedTime < 1440) { return `${Math.floor(elapsedTime / 60)}h ago` } else { - return timestampMoment.format('MMM D') + return timestampMoment.format('MMM D, YYYY') } } export function formatTimestampForum(timestamp: number): string {