diff --git a/app/admin/page.tsx b/app/admin/page.tsx index 3dd6a43..7285a8f 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -79,6 +79,22 @@ const Admin = () => { console.log(data); }; + const setRecentActivity = async () => { + const res = await fetch( + `/api/vercel/set-recent-activity?key=${setterKey}&value=${setterValue}` + ); + const data = await res.json(); + console.log(data); + }; + + const getAllRecentActivity = async () => { + const res = await fetch( + `/api/vercel/get-all-recent-activity?key=${inputValue}` + ); + const data = await res.json(); + console.log(data); + }; + const buttons = [ { name: "flushall", @@ -109,6 +125,15 @@ const Admin = () => { name: "setData", method: () => setData(), }, + { + name: "setRecentActivity", + method: () => setRecentActivity(), + }, + + { + name: "getAllRecentActivity - by parent wallet", + method: () => getAllRecentActivity(), + }, ]; if (!isUnlocked) { diff --git a/app/parent-dashboard/page.tsx b/app/parent-dashboard/page.tsx index ebfcd30..a8439c2 100644 --- a/app/parent-dashboard/page.tsx +++ b/app/parent-dashboard/page.tsx @@ -1,5 +1,4 @@ "use client"; -/* eslint-disable react/no-children-prop */ import { Box, @@ -40,8 +39,8 @@ const Parent: React.FC = () => { //============================================================================= const [childKey, setChildKey] = useState(0); - const [childrenLoading, setChildrenLoading] = useState(false); - const [children, setChildren] = useState([]); + const [membersLoading, setMembersLoading] = useState(false); + const [members, setMembers] = useState([]); // const [stakeContract, setStakeContract] = useState(); const [familyDetails, setFamilyDetails] = useState({} as User); @@ -84,12 +83,6 @@ const Parent: React.FC = () => { onClose: onCloseEtherScan, } = useDisclosure(); - const { - isOpen: isOpenChildDetails, - onOpen: onOpenChildDetails, - onClose: onCloseChildDetails, - } = useDisclosure(); - const { isOpen: isChangeUsernameOpen, onOpen: onChangeUsernameOpen, @@ -134,16 +127,9 @@ const Parent: React.FC = () => { useEffect(() => { fetchFamilyDetails(); - fetchChildren(); + fetchMembers(); }, []); - // useEffect(() => { - // if (!stakeContract || !children.length) { - // setChildrenStakes({}); - // return; - // } - // }, [stakeContract, children]); - //============================================================================= // FUNCTIONS //============================================================================= @@ -159,30 +145,30 @@ const Parent: React.FC = () => { setFamilyDetails(user); }, [userDetails?.wallet]); - const fetchChildren = useCallback(async () => { - const getChildren = async () => { + const fetchMembers = useCallback(async () => { + const getMembers = async () => { if (!userDetails?.wallet) return; - const children = [] as User[]; + const members = [] as User[]; familyDetails.children?.forEach(async (walletAddress) => { const { data } = await axios.get( `/api/vercel/get-json?key=${walletAddress}` ); - children.push(data as User); + members.push(data as User); }); - if (children.length) { - const childrenWalletBalances = await axios.post( + if (members.length) { + const membersWalletBalances = await axios.post( `/api/etherscan/balancemulti`, { - addresses: children.map((c) => c.wallet), + addresses: members.map((c) => c.wallet), } ); - const childrenWithBalances = children.map((c) => { - const balance = childrenWalletBalances.data.find( + const membersWithBalances = members.map((c) => { + const balance = membersWalletBalances.data.find( (b) => b.account === c.wallet ); return { @@ -191,17 +177,17 @@ const Parent: React.FC = () => { }; }); - // setChildren(childrenWithBalances); + setMembers(membersWithBalances); } else { - // setChildren(children); + setMembers(members); } - setChildrenLoading(false); + setMembersLoading(false); }; - await getChildren(); + await getMembers(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [children.length, userDetails?.wallet]); + }, [members.length, userDetails?.wallet]); const closeTab = () => { setSelectedTab(ParentDashboardTabs.DASHBOARD); @@ -213,7 +199,7 @@ const Parent: React.FC = () => { { bg={useColorModeValue("gray.100", "gray.900")} borderRadius={isMobileSize ? "0" : "10px"} > - + { isOpen={isChangeUsernameOpen} onClose={onChangeUsernameClose} childKey={childKey} - children={children} + members={members} familyId={familyDetails.familyId} - fetchChildren={fetchChildren} + fetchChildren={fetchMembers} fetchFamilyDetails={fetchFamilyDetails} /> diff --git a/pages/api/vercel/get-all-recent-activity.ts b/pages/api/vercel/get-all-recent-activity.ts new file mode 100644 index 0000000..cfe069d --- /dev/null +++ b/pages/api/vercel/get-all-recent-activity.ts @@ -0,0 +1,16 @@ +import { kv } from "@vercel/kv"; +import { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + try { + const { key } = req.query as { key: string }; + const data = await kv.hgetall(`activity::${key}`); + return res.status(200).json(data); + } catch (error) { + console.error("Error:", error); + return res.status(500).json({ error: "An error occurred" }); + } +} diff --git a/pages/api/vercel/set-recent-activity.ts b/pages/api/vercel/set-recent-activity.ts new file mode 100644 index 0000000..4a8cab6 --- /dev/null +++ b/pages/api/vercel/set-recent-activity.ts @@ -0,0 +1,27 @@ +import { kv } from "@vercel/kv"; +import { NextApiRequest, NextApiResponse } from "next"; + +//https://redis.io/commands/hset/ + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + try { + const { key, value } = req.query as unknown as { + key: string; + value: string; + }; + + // value = "::" + + const data = await kv.hset(`activity::${key}`, { + [Math.floor(Date.now() / 1000)]: value, + }); + + return res.status(200).json(data); + } catch (error) { + console.error("Error:", error); + return res.status(500).json({ error: "An error occurred" }); + } +} diff --git a/src/components/AvatarSelection.tsx b/src/components/AvatarSelection.tsx index 89c3426..04117b9 100644 --- a/src/components/AvatarSelection.tsx +++ b/src/components/AvatarSelection.tsx @@ -159,7 +159,7 @@ export const AvatarSelection = ({ diff --git a/src/components/CollapsedDashboardMenu.tsx b/src/components/CollapsedDashboardMenu.tsx index 361f38e..1910acd 100644 --- a/src/components/CollapsedDashboardMenu.tsx +++ b/src/components/CollapsedDashboardMenu.tsx @@ -66,11 +66,8 @@ export const CollapsedDashboardMenu = ({ name={userDetails?.username} sx={{ fontFamily: "Slackey", - bgColor: `${ - userDetails?.avatarURI ? "transparent" : "purple.500" - }`, }} - src={userDetails?.avatarURI || "/images/placeholder-avatar.jpeg"} + src={userDetails?.avatarURI} /> {userDetails?.username} diff --git a/src/components/ExpandedDashboardMenu.tsx b/src/components/ExpandedDashboardMenu.tsx index 6a9c849..c12ea45 100644 --- a/src/components/ExpandedDashboardMenu.tsx +++ b/src/components/ExpandedDashboardMenu.tsx @@ -29,7 +29,7 @@ import { useNetwork } from "wagmi"; export const ExpandedDashboardMenu = ({ familyDetails, - children, + members, onAddChildOpen, setSelectedTab, onToggleCollapsedMenu, @@ -44,7 +44,7 @@ export const ExpandedDashboardMenu = ({ onOpenMembersTableModal, }: { familyDetails: User; - children: User[]; + members: User[]; onAddChildOpen: () => void; setSelectedTab: (tab: ParentDashboardTabs) => void; onToggleCollapsedMenu: () => void; @@ -133,7 +133,7 @@ export const ExpandedDashboardMenu = ({ onOpenSendFundsModal={onOpenSendFundsModal} onOpenNetworkModal={onOpenNetworkModal} onOpenMembersTableModal={onOpenMembersTableModal} - children={children} + members={members} /> diff --git a/src/components/LoggedInNavbar.tsx b/src/components/LoggedInNavbar.tsx index cdae809..7e12988 100644 --- a/src/components/LoggedInNavbar.tsx +++ b/src/components/LoggedInNavbar.tsx @@ -52,11 +52,7 @@ export default function LoggedInNavBar() { { setMobileMenuOpen(!mobileMenuOpen); }} diff --git a/src/components/forms/RegisterParentForm.tsx b/src/components/forms/RegisterParentForm.tsx index 43afbb3..f7bb1e6 100644 --- a/src/components/forms/RegisterParentForm.tsx +++ b/src/components/forms/RegisterParentForm.tsx @@ -33,6 +33,7 @@ import shallow from "zustand/shallow"; import { useRouter } from "next/navigation"; import { useAccount } from "wagmi"; import { TestnetNetworks, NetworkType } from "@/data-schema/enums"; +import { registerActivityEvent } from "@/utils/recentActivity"; export const RegisterParentForm = ({ onClose }: { onClose: () => void }) => { //============================================================================= @@ -144,6 +145,12 @@ export const RegisterParentForm = ({ onClose }: { onClose: () => void }) => { setUserDetails(body); setIsLoggedIn(true); + registerActivityEvent( + String(address), + String(address), + "Registered as parent" + ); + const emailSent = await sendEmailConfirmation(); if (!emailSent) { return; diff --git a/src/components/modals/UsernameModal.tsx b/src/components/modals/UsernameModal.tsx index fc5c276..28ea2a5 100644 --- a/src/components/modals/UsernameModal.tsx +++ b/src/components/modals/UsernameModal.tsx @@ -33,7 +33,7 @@ export const UsernameModal = ({ isOpen, onClose, childKey, - children, + members, familyId, fetchChildren, fetchFamilyDetails, @@ -43,7 +43,7 @@ export const UsernameModal = ({ isOpen: boolean; onClose: () => void; childKey?: number; - children?: any; + members?: any; familyId?: string; fetchChildren?: () => void; fetchFamilyDetails?: () => void; diff --git a/src/components/parentDashboard/Avatar.tsx b/src/components/parentDashboard/Avatar.tsx index 5ebadc7..303606e 100644 --- a/src/components/parentDashboard/Avatar.tsx +++ b/src/components/parentDashboard/Avatar.tsx @@ -20,7 +20,7 @@ const ParentAvatar = () => { sx={{ bgColor: `${!userDetails?.avatarURI && "purple.500"}`, }} - src={userDetails?.avatarURI || "/images/placeholder-avatar.jpeg"} + src={userDetails?.avatarURI} /> ); diff --git a/src/components/parentDashboard/ButtonMenu.tsx b/src/components/parentDashboard/ButtonMenu.tsx index 5cf5eb9..e7e8844 100644 --- a/src/components/parentDashboard/ButtonMenu.tsx +++ b/src/components/parentDashboard/ButtonMenu.tsx @@ -16,7 +16,7 @@ const ButtonMenu = ({ onOpenSendFundsModal, onOpenNetworkModal, onOpenMembersTableModal, - children, + members, }: { onAddChildOpen: () => void; setSelectedTab: (tab: ParentDashboardTabs) => void; @@ -26,7 +26,7 @@ const ButtonMenu = ({ onOpenSendFundsModal: () => void; onOpenNetworkModal: () => void; onOpenMembersTableModal: () => void; - children?: User[]; + members?: User[]; }) => { const { userDetails } = useAuthStore( (state) => ({ diff --git a/src/components/parentDashboard/RecentMemberActivity.tsx b/src/components/parentDashboard/RecentMemberActivity.tsx index a0cd205..8415061 100644 --- a/src/components/parentDashboard/RecentMemberActivity.tsx +++ b/src/components/parentDashboard/RecentMemberActivity.tsx @@ -1,4 +1,4 @@ -import { Fragment } from "react"; +import { Fragment, useEffect, useState } from "react"; import { Container, Flex, @@ -11,57 +11,42 @@ import { Heading, Button, } from "@chakra-ui/react"; +import { useAuthStore } from "@/store/auth/authStore"; +import shallow from "zustand/shallow"; +import { User } from "@/data-schema/types"; +import { dateInSecondsToLongDate } from "@/utils/dateTime"; -interface Activity { - activity: string; - dateTime: string; - userName: string; - userAvatar: string; -} +const RecentMemberActivity = ({ members }: { members: User[] }) => { + const [activity, setActivity] = useState([]); -const memberActivity: Activity[] = [ - { - activity: `Dan Abrahmov Updated Avatar.`, - dateTime: "September 10th at 9:10 AM", - userName: "Dan Abrahmov", - userAvatar: "https://bit.ly/dan-abramov", - }, - { - activity: `Kent Dodds Staked 1.5 ETH.`, - dateTime: "yesterday", - userName: "Kent Dodds", - userAvatar: "https://bit.ly/kent-c-dodds", - }, - { - activity: `Jena Karlis Timelocked 5 ETH.`, - dateTime: "4 days ago", - userName: "Jena Karlis", - userAvatar: - "https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=334&q=80", - }, - { - activity: `Jena Karlis Staked 2 ETH.`, - dateTime: "4 days ago", - userName: "Jena Karlis", - userAvatar: - "https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=334&q=80", - }, - { - activity: `Jena Karlis Updated Avatar.`, - dateTime: "4 days ago", - userName: "Jena Karlis", - userAvatar: - "https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-1.2.1&ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&auto=format&fit=crop&w=334&q=80", - }, - { - activity: `Kent Dodds Claimed 1.5 ETH in staking rewards.`, - dateTime: "5 days ago", - userName: "Kent Dodds", - userAvatar: "https://bit.ly/kent-c-dodds", - }, -]; + const { userDetails } = useAuthStore( + (state) => ({ + userDetails: state.userDetails, + }), + shallow + ); + + useEffect(() => { + const getData = async () => { + const res = await fetch( + `/api/vercel/get-all-recent-activity?key=${userDetails.wallet}` + ); + let data = await res.json(); + data = Object.entries(data).reverse().slice(0, 6); + setActivity(data); + }; + + if (!userDetails.wallet) return; + getData(); + }, [userDetails.wallet]); + + const parseUser = (wallet: string) => { + const member = members.find((m) => m.wallet === wallet); + const username = member ? member.username : userDetails.username; + const avatar = member ? member.avatarURI : userDetails.avatarURI; + return { username, avatar }; + }; -const RecentMemberActivity = () => { return ( @@ -82,39 +67,39 @@ const RecentMemberActivity = () => { rounded="md" overflow="hidden" spacing={0} - my="2.5rem" + mb="2.5rem" > - {memberActivity.map((activity, index) => ( + {activity.map(([timestamp, record]: [string, string], index) => ( - + + - {activity.dateTime} + {dateInSecondsToLongDate(timestamp)} - {memberActivity.length - 1 !== index && } + ))} diff --git a/src/utils/dateTime.ts b/src/utils/dateTime.ts index 8a88c65..92b4f20 100644 --- a/src/utils/dateTime.ts +++ b/src/utils/dateTime.ts @@ -6,3 +6,10 @@ export const formatDateToIsoString = (timestamp: number) => { export const timestampInSeconds = (timestampInMilliseconds: number) => Math.floor(timestampInMilliseconds / 1000); + +export const dateInSecondsToLongDate = (date: string) => { + const dateInSeconds = parseInt(date); + const dateInMilliseconds = dateInSeconds * 1000; + const dateObject = new Date(dateInMilliseconds); + return dateObject.toLocaleDateString(); +}; diff --git a/src/utils/recentActivity.ts b/src/utils/recentActivity.ts new file mode 100644 index 0000000..b5d5b58 --- /dev/null +++ b/src/utils/recentActivity.ts @@ -0,0 +1,18 @@ +export const registerActivityEvent = async ( + parentAddress: string, + memberAddress: string, + eventDescription: string +) => { + const key = parentAddress; + const value = `${memberAddress}::${eventDescription}`; + + try { + const res = await fetch( + `/api/vercel/set-recent-activity?key=${key}&value=${value}` + ); + const data = await res.json(); + return data; + } catch (e) { + console.error(e); + } +};