mirror of
https://github.com/Qortal/chrome-extension.git
synced 2025-03-14 11:52:33 +00:00
added payment notification
This commit is contained in:
parent
2f756f5c2d
commit
857f2980b7
@ -116,7 +116,7 @@ import { MainAvatar } from "./components/MainAvatar";
|
||||
import { useRetrieveDataLocalStorage } from "./useRetrieveDataLocalStorage";
|
||||
import { useQortalGetSaveSettings } from "./useQortalGetSaveSettings";
|
||||
import { useRecoilState, useResetRecoilState, useSetRecoilState } from "recoil";
|
||||
import { canSaveSettingToQdnAtom, fullScreenAtom, groupsPropertiesAtom, hasSettingsChangedAtom, isDisabledEditorEnterAtom, isUsingImportExportSettingsAtom, mailsAtom, oldPinnedAppsAtom, qMailLastEnteredTimestampAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom } from "./atoms/global";
|
||||
import { canSaveSettingToQdnAtom, fullScreenAtom, groupsPropertiesAtom, hasSettingsChangedAtom, isDisabledEditorEnterAtom, isUsingImportExportSettingsAtom, lastPaymentSeenTimestampAtom, mailsAtom, oldPinnedAppsAtom, qMailLastEnteredTimestampAtom, settingsLocalLastUpdatedAtom, settingsQDNLastUpdatedAtom, sortablePinnedAppsAtom } from "./atoms/global";
|
||||
import { useAppFullScreen } from "./useAppFullscreen";
|
||||
import { NotAuthenticated } from "./ExtStates/NotAuthenticated";
|
||||
import { useFetchResources } from "./common/useFetchResources";
|
||||
@ -137,6 +137,7 @@ import { BuyQortInformation } from "./components/BuyQortInformation";
|
||||
import { WalletIcon } from "./assets/Icons/WalletIcon";
|
||||
import { useBlockedAddresses } from "./components/Chat/useBlockUsers";
|
||||
import { QortPayment } from "./components/QortPayment";
|
||||
import { GeneralNotifications } from "./components/GeneralNotifications";
|
||||
|
||||
type extStates =
|
||||
| "not-authenticated"
|
||||
@ -436,6 +437,7 @@ function App() {
|
||||
const resetAtomQMailLastEnteredTimestampAtom = useResetRecoilState(qMailLastEnteredTimestampAtom)
|
||||
const resetAtomMailsAtom = useResetRecoilState(mailsAtom)
|
||||
const resetGroupPropertiesAtom = useResetRecoilState(groupsPropertiesAtom)
|
||||
const resetLastPaymentSeenTimestampAtom = useResetRecoilState(lastPaymentSeenTimestampAtom)
|
||||
const resetAllRecoil = () => {
|
||||
resetAtomSortablePinnedAppsAtom();
|
||||
resetAtomCanSaveSettingToQdnAtom();
|
||||
@ -446,6 +448,7 @@ function App() {
|
||||
resetAtomQMailLastEnteredTimestampAtom()
|
||||
resetAtomMailsAtom()
|
||||
resetGroupPropertiesAtom()
|
||||
resetLastPaymentSeenTimestampAtom()
|
||||
};
|
||||
useEffect(() => {
|
||||
if (!isMobile) return;
|
||||
@ -1860,6 +1863,10 @@ function App() {
|
||||
<CoreSyncStatus />
|
||||
<Spacer height="20px" />
|
||||
<QMailStatus />
|
||||
<Spacer height="20px"/>
|
||||
{extState === 'authenticated' && (
|
||||
<GeneralNotifications address={userInfo?.address} />
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -154,4 +154,9 @@ export const mailsAtom = atom({
|
||||
export const groupsPropertiesAtom = atom({
|
||||
key: 'groupsPropertiesAtom',
|
||||
default: {},
|
||||
});
|
||||
|
||||
export const lastPaymentSeenTimestampAtom = atom<null | number>({
|
||||
key: 'lastPaymentSeenTimestampAtom',
|
||||
default: null,
|
||||
});
|
@ -128,9 +128,9 @@ export const getForeignKey = async (foreignBlockchain)=> {
|
||||
|
||||
const pauseAllQueues = () => controlAllQueues("pause");
|
||||
const resumeAllQueues = () => controlAllQueues("resume");
|
||||
const checkDifference = (createdTimestamp) => {
|
||||
export const checkDifference = (createdTimestamp, diff = timeDifferenceForNotificationChatsBackground) => {
|
||||
return (
|
||||
Date.now() - createdTimestamp < timeDifferenceForNotificationChatsBackground
|
||||
Date.now() - createdTimestamp < diff
|
||||
);
|
||||
};
|
||||
export const getApiKeyFromStorage = async () => {
|
||||
@ -1747,6 +1747,40 @@ async function sendChat({ qortAddress, recipientPublicKey, message }) {
|
||||
return _response;
|
||||
}
|
||||
|
||||
export async function getTimestampLatestPayment() {
|
||||
const wallet = await getSaveWallet();
|
||||
const address = wallet.address0;
|
||||
const key = `latest-payment-${address}`;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.local.get([key], (result) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(new Error(chrome.runtime.lastError.message || "Error retrieving data"));
|
||||
} else {
|
||||
const timestamp = result[key];
|
||||
resolve(timestamp || 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function addTimestampLatestPayment(timestamp) {
|
||||
const wallet = await getSaveWallet();
|
||||
const address = wallet.address0;
|
||||
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.storage.local.set({ [`latest-payment-${address}`]: timestamp }, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(new Error(chrome.runtime.lastError.message || "Error saving data"));
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export async function addEnteredQmailTimestampFunc() {
|
||||
const wallet = await getSaveWallet();
|
||||
const address = wallet.address0;
|
||||
@ -5114,6 +5148,95 @@ chrome.notifications?.onClicked?.addListener((notificationId) => {
|
||||
);
|
||||
});
|
||||
|
||||
export const checkPaymentsForNotifications = async (address) => {
|
||||
try {
|
||||
const isDisableNotifications =
|
||||
(await getUserSettings({ key: "disable-push-notifications" })) || false;
|
||||
if(isDisableNotifications) return
|
||||
let latestPayment = null
|
||||
const savedtimestamp = await getTimestampLatestPayment();
|
||||
|
||||
const url = await createEndpoint(
|
||||
`/transactions/search?txType=PAYMENT&address=${address}&confirmationStatus=CONFIRMED&limit=5&reverse=true`
|
||||
);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const responseData = await response.json();
|
||||
|
||||
const latestTx = responseData.filter(
|
||||
(tx) => tx?.creatorAddress !== address && tx?.recipient === address
|
||||
)[0];
|
||||
if (!latestTx) {
|
||||
return; // continue to the next group
|
||||
}
|
||||
if (
|
||||
checkDifference(latestTx.timestamp, 86400000) &&
|
||||
(!savedtimestamp ||
|
||||
latestTx.timestamp >
|
||||
savedtimestamp)
|
||||
) {
|
||||
if(latestTx.timestamp){
|
||||
latestPayment = latestTx
|
||||
await addTimestampLatestPayment(latestTx.timestamp);
|
||||
}
|
||||
|
||||
// save new timestamp
|
||||
}
|
||||
|
||||
console.log('latestPayment', latestPayment)
|
||||
|
||||
if (
|
||||
latestPayment
|
||||
) {
|
||||
// Create a unique notification ID with type and group announcement details
|
||||
const notificationId =
|
||||
encodeURIComponent("payment_notification_" +
|
||||
Date.now() +
|
||||
"_type=payment-announcement");
|
||||
|
||||
const title = "New payment!";
|
||||
const body = `You have received a new payment of ${latestPayment?.amount} QORT`;
|
||||
|
||||
|
||||
chrome.notifications.create(notificationId, {
|
||||
type: "basic",
|
||||
iconUrl: "qort.png", // Add an appropriate icon for chat notifications
|
||||
title,
|
||||
message: body,
|
||||
priority: 2, // Use the maximum priority to ensure it's noticeable
|
||||
// buttons: [
|
||||
// { title: 'Go to group' }
|
||||
// ]
|
||||
});
|
||||
if (!isMobile) {
|
||||
setTimeout(() => {
|
||||
chrome.notifications.clear(notificationId);
|
||||
}, 7000);
|
||||
}
|
||||
|
||||
// Automatically close the notification after 5 seconds if it’s not clicked
|
||||
setTimeout(() => {
|
||||
notification.close();
|
||||
}, 10000); // Close after 5 seconds
|
||||
|
||||
chrome.runtime.sendMessage({
|
||||
action: "SET_PAYMENT_ANNOUNCEMENT",
|
||||
payload: latestPayment,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
};
|
||||
|
||||
// Reconnect when service worker wakes up
|
||||
chrome.runtime?.onStartup.addListener(() => {
|
||||
console.log("Service worker started up, reconnecting WebSocket...");
|
||||
@ -5151,6 +5274,13 @@ chrome.alarms?.get("checkForNotifications", (existingAlarm) => {
|
||||
}
|
||||
});
|
||||
|
||||
chrome.alarms?.get("checkForPayments", (existingAlarm) => {
|
||||
if (!existingAlarm) {
|
||||
// If the alarm does not exist, create it
|
||||
chrome.alarms.create("checkForPayments", { periodInMinutes: 3 });
|
||||
}
|
||||
});
|
||||
|
||||
chrome.alarms?.onAlarm.addListener(async (alarm) => {
|
||||
try {
|
||||
if (alarm.name === "checkForNotifications") {
|
||||
@ -5161,6 +5291,13 @@ chrome.alarms?.onAlarm.addListener(async (alarm) => {
|
||||
checkActiveChatsForNotifications();
|
||||
checkNewMessages();
|
||||
checkThreads();
|
||||
} else if (alarm.name === "checkForPayments") {
|
||||
|
||||
const wallet = await getSaveWallet();
|
||||
const address = wallet.address0;
|
||||
if (!address) return;
|
||||
|
||||
checkPaymentsForNotifications(address);
|
||||
}
|
||||
} catch (error) {}
|
||||
});
|
||||
|
132
src/components/GeneralNotifications.tsx
Normal file
132
src/components/GeneralNotifications.tsx
Normal file
@ -0,0 +1,132 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
Box,
|
||||
ButtonBase,
|
||||
Card,
|
||||
MenuItem,
|
||||
Popover,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
|
||||
import { formatDate } from "../utils/time";
|
||||
import { useHandlePaymentNotification } from "../hooks/useHandlePaymentNotification";
|
||||
|
||||
export const GeneralNotifications = ({ address }) => {
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const {latestTx,
|
||||
getNameOrAddressOfSenderMiddle,
|
||||
hasNewPayment, setLastEnteredTimestampPayment, nameAddressOfSender} = useHandlePaymentNotification(address)
|
||||
|
||||
const handlePopupClick = (event) => {
|
||||
event.stopPropagation(); // Prevent parent onClick from firing
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ButtonBase
|
||||
onClick={(e) => {
|
||||
handlePopupClick(e);
|
||||
|
||||
|
||||
}}
|
||||
style={{}}
|
||||
>
|
||||
<NotificationsIcon
|
||||
sx={{
|
||||
color: hasNewPayment ? "var(--unread)" : "rgba(255, 255, 255, 0.5)",
|
||||
}}
|
||||
/>
|
||||
</ButtonBase>
|
||||
|
||||
<Popover
|
||||
open={!!anchorEl}
|
||||
anchorEl={anchorEl}
|
||||
onClose={() => {
|
||||
if(hasNewPayment){
|
||||
setLastEnteredTimestampPayment(Date.now())
|
||||
}
|
||||
setAnchorEl(null)
|
||||
|
||||
}} // Close popover on click outside
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "300px",
|
||||
maxWidth: "100%",
|
||||
maxHeight: "60vh",
|
||||
overflow: "auto",
|
||||
padding: "5px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: hasNewPayment ? "flex-start" : "center",
|
||||
}}
|
||||
>
|
||||
{!hasNewPayment && <Typography sx={{
|
||||
userSelect: 'none'
|
||||
}}>No new notifications</Typography>}
|
||||
{hasNewPayment && (
|
||||
<MenuItem
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "5px",
|
||||
width: "100%",
|
||||
alignItems: "flex-start",
|
||||
textWrap: "auto",
|
||||
cursor: 'default'
|
||||
}}
|
||||
onClick={(e) => {
|
||||
// executeEvent("addTab", { data: { service: 'APP', name: 'q-mail' } });
|
||||
// executeEvent("open-apps-mode", { });
|
||||
}}
|
||||
>
|
||||
<Card sx={{
|
||||
padding: '10px',
|
||||
width: '100%',
|
||||
backgroundColor: "#1F2023",
|
||||
gap: '5px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<AccountBalanceWalletIcon
|
||||
sx={{
|
||||
color: "white",
|
||||
}}
|
||||
/>{" "}
|
||||
{formatDate(latestTx?.timestamp)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
|
||||
<Typography>{latestTx?.amount}</Typography>
|
||||
</Box>
|
||||
<Typography sx={{
|
||||
fontSize: '0.8rem'
|
||||
}}>{nameAddressOfSender.current[latestTx?.creatorAddress] || getNameOrAddressOfSenderMiddle(latestTx?.creatorAddress)}</Typography>
|
||||
|
||||
</Card>
|
||||
</MenuItem>
|
||||
)}
|
||||
</Box>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
108
src/hooks/useHandlePaymentNotification.tsx
Normal file
108
src/hooks/useHandlePaymentNotification.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { getBaseApiReact } from '../App';
|
||||
import { addTimestampLatestPayment, checkDifference, getNameInfoForOthers, getTimestampLatestPayment } from '../background';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { lastPaymentSeenTimestampAtom } from '../atoms/global';
|
||||
|
||||
export const useHandlePaymentNotification = (address) => {
|
||||
const [latestTx, setLatestTx] = useState(null);
|
||||
|
||||
const nameAddressOfSender = useRef({})
|
||||
const isFetchingName = useRef({})
|
||||
|
||||
|
||||
const [lastEnteredTimestampPayment, setLastEnteredTimestampPayment] =
|
||||
useRecoilState(lastPaymentSeenTimestampAtom);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastEnteredTimestampPayment && address) {
|
||||
addTimestampLatestPayment(Date.now()).catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}, [lastEnteredTimestampPayment, address]);
|
||||
|
||||
const getNameOrAddressOfSender = useCallback(async(senderAddress)=> {
|
||||
if(isFetchingName.current[senderAddress]) return senderAddress
|
||||
try {
|
||||
isFetchingName.current[senderAddress] = true
|
||||
const res = await getNameInfoForOthers(senderAddress)
|
||||
nameAddressOfSender.current[senderAddress] = res || senderAddress
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
isFetchingName.current[senderAddress] = false
|
||||
}
|
||||
|
||||
}, [])
|
||||
|
||||
const getNameOrAddressOfSenderMiddle = useCallback(async(senderAddress)=> {
|
||||
getNameOrAddressOfSender(senderAddress)
|
||||
return senderAddress
|
||||
|
||||
}, [getNameOrAddressOfSender])
|
||||
|
||||
const hasNewPayment = useMemo(() => {
|
||||
if (!latestTx) return false;
|
||||
if (!checkDifference(latestTx?.timestamp, 86400000)) return false;
|
||||
if (
|
||||
!lastEnteredTimestampPayment ||
|
||||
lastEnteredTimestampPayment < latestTx?.timestamp
|
||||
)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}, [lastEnteredTimestampPayment, latestTx]);
|
||||
|
||||
console.log('hasNewPayment', hasNewPayment)
|
||||
|
||||
const getLastSeenData = useCallback(async () => {
|
||||
try {
|
||||
if (!address) return;
|
||||
console.log('address', address)
|
||||
const key = `last-seen-payment-${address}`;
|
||||
|
||||
const res = await getTimestampLatestPayment<any>().catch(() => null);
|
||||
console.log('res', res)
|
||||
if (res) {
|
||||
setLastEnteredTimestampPayment(res);
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`${getBaseApiReact()}/transactions/search?txType=PAYMENT&address=${address}&confirmationStatus=CONFIRMED&limit=5&reverse=true`
|
||||
);
|
||||
|
||||
const responseData = await response.json();
|
||||
console.log('responseData', responseData)
|
||||
const latestTx = responseData.filter(
|
||||
(tx) => tx?.creatorAddress !== address && tx?.recipient === address
|
||||
)[0];
|
||||
if (!latestTx) {
|
||||
return; // continue to the next group
|
||||
}
|
||||
|
||||
setLatestTx(latestTx);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}, [address, setLastEnteredTimestampPayment]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
getLastSeenData();
|
||||
|
||||
chrome?.runtime?.onMessage.addListener((message, sender, sendResponse) => {
|
||||
console.log('message', message)
|
||||
if (message?.action === "SET_PAYMENT_ANNOUNCEMENT" && message?.payload) {
|
||||
setLatestTx(message.payload);
|
||||
}
|
||||
});
|
||||
}, [getLastSeenData]);
|
||||
return {
|
||||
latestTx,
|
||||
getNameOrAddressOfSenderMiddle,
|
||||
hasNewPayment,
|
||||
setLastEnteredTimestampPayment,
|
||||
nameAddressOfSender
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user