From 2e0aa864db05cebb1a2f378df08ddedde091ccd4 Mon Sep 17 00:00:00 2001 From: Dominik Piatek Date: Tue, 8 Aug 2023 08:45:02 +0200 Subject: [PATCH] Add last seen label to avatars --- demo/package-lock.json | 59 +++++++++++++++++++++++++++++- demo/package.json | 3 +- demo/src/components/AvatarInfo.tsx | 39 +++++++++++++++++++- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/demo/package-lock.json b/demo/package-lock.json index e6e5d807..dd755286 100644 --- a/demo/package-lock.json +++ b/demo/package-lock.json @@ -11,7 +11,7 @@ "@ably-labs/spaces": "^0.0.12-alpha", "ably": "^1.2.41", "classnames": "^2.3.2", - "esbuild": "^0.19.0", + "dayjs": "^1.11.9", "lodash.assign": "^4.2.0", "lodash.find": "^4.6.0", "lodash.omit": "^4.5.0", @@ -31,6 +31,7 @@ "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react": "^4.0.3", "autoprefixer": "^10.4.14", + "esbuild": "^0.19.0", "eslint": "^8.45.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", @@ -445,6 +446,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "android" @@ -460,6 +462,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "android" @@ -475,6 +478,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "android" @@ -490,6 +494,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -505,6 +510,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "darwin" @@ -520,6 +526,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -535,6 +542,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "freebsd" @@ -550,6 +558,7 @@ "cpu": [ "arm" ], + "dev": true, "optional": true, "os": [ "linux" @@ -565,6 +574,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -580,6 +590,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "linux" @@ -595,6 +606,7 @@ "cpu": [ "loong64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -610,6 +622,7 @@ "cpu": [ "mips64el" ], + "dev": true, "optional": true, "os": [ "linux" @@ -625,6 +638,7 @@ "cpu": [ "ppc64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -640,6 +654,7 @@ "cpu": [ "riscv64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -655,6 +670,7 @@ "cpu": [ "s390x" ], + "dev": true, "optional": true, "os": [ "linux" @@ -670,6 +686,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "linux" @@ -685,6 +702,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "netbsd" @@ -700,6 +718,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "openbsd" @@ -715,6 +734,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "sunos" @@ -730,6 +750,7 @@ "cpu": [ "arm64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -745,6 +766,7 @@ "cpu": [ "ia32" ], + "dev": true, "optional": true, "os": [ "win32" @@ -760,6 +782,7 @@ "cpu": [ "x64" ], + "dev": true, "optional": true, "os": [ "win32" @@ -1749,6 +1772,11 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", "dev": true }, + "node_modules/dayjs": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1859,6 +1887,7 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.0.tgz", "integrity": "sha512-i7i8TP4vuG55bKeLyqqk5sTPu1ZjPH3wkcLvAj/0X/222iWFo3AJUYRKjbOoY6BWFMH3teizxHEdV9Su5ESl0w==", + "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -20533,132 +20562,154 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.0.tgz", "integrity": "sha512-GAkjUyHgWTYuex3evPd5V7uV/XS4LMKr1PWHRPW1xNyy/Jx08x3uTrDFRefBYLKT/KpaWM8/YMQcwbp5a3yIDA==", + "dev": true, "optional": true }, "@esbuild/android-arm64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.0.tgz", "integrity": "sha512-AzsozJnB+RNaDncBCs3Ys5g3kqhPFUueItfEaCpp89JH2naFNX2mYDIvUgPYMqqjm8hiFoo+jklb3QHZyR3ubw==", + "dev": true, "optional": true }, "@esbuild/android-x64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.0.tgz", "integrity": "sha512-SUG8/qiVhljBDpdkHQ9DvOWbp7hFFIP0OzxOTptbmVsgBgzY6JWowmMd6yJuOhapfxmj/DrvwKmjRLvVSIAKZg==", + "dev": true, "optional": true }, "@esbuild/darwin-arm64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.0.tgz", "integrity": "sha512-HkxZ8k3Jvcw0FORPNTavA8BMgQjLOB6AajT+iXmil7BwY3gU1hWvJJAyWyEogCmA4LdbGvKF8vEykdmJ4xNJJQ==", + "dev": true, "optional": true }, "@esbuild/darwin-x64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.0.tgz", "integrity": "sha512-9IRWJjqpWFHM9a5Qs3r3bK834NCFuDY5ZaLrmTjqE+10B6w65UMQzeZjh794JcxpHolsAHqwsN/33crUXNCM2Q==", + "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.0.tgz", "integrity": "sha512-s7i2WcXcK0V1PJHVBe7NsGddsL62a9Vhpz2U7zapPrwKoFuxPP9jybwX8SXnropR/AOj3ppt2ern4ItblU6UQQ==", + "dev": true, "optional": true }, "@esbuild/freebsd-x64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.0.tgz", "integrity": "sha512-NMdBSSdgwHCqCsucU5k1xflIIRU0qi1QZnM6+vdGy5fvxm1c8rKh50VzsWsIVTFUG3l91AtRxVwoz3Lcvy3I5w==", + "dev": true, "optional": true }, "@esbuild/linux-arm": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.0.tgz", "integrity": "sha512-2F1+lH7ZBcCcgxiSs8EXQV0PPJJdTNiNcXxDb61vzxTRJJkXX1I/ye9mAhfHyScXzHaEibEXg1Jq9SW586zz7w==", + "dev": true, "optional": true }, "@esbuild/linux-arm64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.0.tgz", "integrity": "sha512-I4zvE2srSZxRPapFnNqj+NL3sDJ1wkvEZqt903OZUlBBgigrQMvzUowvP/TTTu2OGYe1oweg5MFilfyrElIFag==", + "dev": true, "optional": true }, "@esbuild/linux-ia32": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.0.tgz", "integrity": "sha512-dz2Q7+P92r1Evc8kEN+cQnB3qqPjmCrOZ+EdBTn8lEc1yN8WDgaDORQQiX+mxaijbH8npXBT9GxUqE52Gt6Y+g==", + "dev": true, "optional": true }, "@esbuild/linux-loong64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.0.tgz", "integrity": "sha512-IcVJovJVflih4oFahhUw+N7YgNbuMSVFNr38awb0LNzfaiIfdqIh518nOfYaNQU3aVfiJnOIRVJDSAP4k35WxA==", + "dev": true, "optional": true }, "@esbuild/linux-mips64el": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.0.tgz", "integrity": "sha512-bZGRAGySMquWsKw0gIdsClwfvgbsSq/7oq5KVu1H1r9Il+WzOcfkV1hguntIuBjRVL8agI95i4AukjdAV2YpUw==", + "dev": true, "optional": true }, "@esbuild/linux-ppc64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.0.tgz", "integrity": "sha512-3LC6H5/gCDorxoRBUdpLV/m7UthYSdar0XcCu+ypycQxMS08MabZ06y1D1yZlDzL/BvOYliRNRWVG/YJJvQdbg==", + "dev": true, "optional": true }, "@esbuild/linux-riscv64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.0.tgz", "integrity": "sha512-jfvdKjWk+Cp2sgLtEEdSHXO7qckrw2B2eFBaoRdmfhThqZs29GMMg7q/LsQpybA7BxCLLEs4di5ucsWzZC5XPA==", + "dev": true, "optional": true }, "@esbuild/linux-s390x": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.0.tgz", "integrity": "sha512-ofcucfNLkoXmcnJaw9ugdEOf40AWKGt09WBFCkpor+vFJVvmk/8OPjl/qRtks2Z7BuZbG3ztJuK1zS9z5Cgx9A==", + "dev": true, "optional": true }, "@esbuild/linux-x64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.0.tgz", "integrity": "sha512-Fpf7zNDBti3xrQKQKLdXT0hTyOxgFdRJIMtNy8x1az9ATR9/GJ1brYbB/GLWoXhKiHsoWs+2DLkFVNNMTCLEwA==", + "dev": true, "optional": true }, "@esbuild/netbsd-x64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.0.tgz", "integrity": "sha512-AMQAp/5oENgDOvVhvOlbhVe1pWii7oFAMRHlmTjSEMcpjTpIHtFXhv9uAFgUERHm3eYtNvS9Vf+gT55cwuI6Aw==", + "dev": true, "optional": true }, "@esbuild/openbsd-x64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.0.tgz", "integrity": "sha512-fDztEve1QUs3h/Dw2AUmBlWGkNQbhDoD05ppm5jKvzQv+HVuV13so7m5RYeiSMIC2XQy7PAjZh+afkxAnCRZxA==", + "dev": true, "optional": true }, "@esbuild/sunos-x64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.0.tgz", "integrity": "sha512-bKZzJ2/rvUjDzA5Ddyva2tMk89WzNJEibZEaq+wY6SiqPlwgFbqyQLimouxLHiHh1itb5P3SNCIF1bc2bw5H9w==", + "dev": true, "optional": true }, "@esbuild/win32-arm64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.0.tgz", "integrity": "sha512-NQJ+4jmnA79saI+sE+QzcEls19uZkoEmdxo7r//PDOjIpX8pmoWtTnWg6XcbnO7o4fieyAwb5U2LvgWynF4diA==", + "dev": true, "optional": true }, "@esbuild/win32-ia32": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.0.tgz", "integrity": "sha512-uyxiZAnsfu9diHm9/rIH2soecF/HWLXYUhJKW4q1+/LLmNQ+55lRjvSUDhUmsgJtSUscRJB/3S4RNiTb9o9mCg==", + "dev": true, "optional": true }, "@esbuild/win32-x64": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.0.tgz", "integrity": "sha512-jl+NXUjK2StMgqnZnqgNjZuerFG8zQqWXMBZdMMv4W/aO1ZKQaYWZBxTrtWKphkCBVEMh0wMVfGgOd2BjOZqUQ==", + "dev": true, "optional": true }, "@eslint-community/eslint-utils": { @@ -21359,6 +21410,11 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", "dev": true }, + "dayjs": { + "version": "1.11.9", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", + "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -21442,6 +21498,7 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.0.tgz", "integrity": "sha512-i7i8TP4vuG55bKeLyqqk5sTPu1ZjPH3wkcLvAj/0X/222iWFo3AJUYRKjbOoY6BWFMH3teizxHEdV9Su5ESl0w==", + "dev": true, "requires": { "@esbuild/android-arm": "0.19.0", "@esbuild/android-arm64": "0.19.0", diff --git a/demo/package.json b/demo/package.json index a7a5ac39..880cf545 100644 --- a/demo/package.json +++ b/demo/package.json @@ -15,7 +15,7 @@ "@ably-labs/spaces": "^0.0.12-alpha", "ably": "^1.2.41", "classnames": "^2.3.2", - "esbuild": "^0.19.0", + "dayjs": "^1.11.9", "lodash.assign": "^4.2.0", "lodash.find": "^4.6.0", "lodash.omit": "^4.5.0", @@ -35,6 +35,7 @@ "@typescript-eslint/parser": "^6.0.0", "@vitejs/plugin-react": "^4.0.3", "autoprefixer": "^10.4.14", + "esbuild": "^0.19.0", "eslint": "^8.45.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", diff --git a/demo/src/components/AvatarInfo.tsx b/demo/src/components/AvatarInfo.tsx index 00a05b85..ec6dd6fa 100644 --- a/demo/src/components/AvatarInfo.tsx +++ b/demo/src/components/AvatarInfo.tsx @@ -1,4 +1,9 @@ +import { useEffect, useState } from 'react'; + import cn from 'classnames'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + import { type SpaceMember } from '../../../src/types'; import { type ProfileData } from '../utils/types'; @@ -8,7 +13,37 @@ type Props = Omit & { profileData: ProfileData; }; -export const AvatarInfo = ({ isSelf, isConnected, profileData, isList = false }: Props) => { +dayjs.extend(relativeTime); + +export const AvatarInfo = ({ isSelf, isConnected, profileData, isList = false, lastEvent }: Props) => { + const [currentTime, setCurrentTime] = useState(dayjs()); + + const lastSeen = (timestamp: number) => { + const diffInSeconds = currentTime.diff(timestamp, 'seconds') + 1; + + if (diffInSeconds === 0) { + return `Last seen a moment ago`; + } else { + return `Last seen ${diffInSeconds} second${diffInSeconds === 1 ? '' : 's'} ago`; + } + }; + + useEffect(() => { + let intervalId: ReturnType; + + if (isSelf) return; + + if (!isConnected) { + intervalId = setInterval(() => { + setCurrentTime(dayjs()); + }, 1000); + } + + return () => { + clearInterval(intervalId); + }; + }, [isConnected]); + return isSelf ? (
- {isConnected ? 'Online now' : 'Left a few minutes ago'} + {isConnected ? 'Online now' : lastSeen(lastEvent.timestamp)}