diff --git a/src/blocks/dropdown/Dropdown.tsx b/src/blocks/dropdown/Dropdown.tsx index 39e0bf51e5..1c9a85c6f3 100644 --- a/src/blocks/dropdown/Dropdown.tsx +++ b/src/blocks/dropdown/Dropdown.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components'; import * as RadixDropdown from '@radix-ui/react-dropdown-menu'; import { DropdownProps } from './Dropdown.types'; -const RadixDropdownContent = styled(RadixDropdown.Content)` +const RadixDropdownContent = styled(RadixDropdown.Content) ` /* Extra CSS props */ ${(props) => props.css || ''} `; @@ -40,7 +40,8 @@ const Dropdown: FC = forwardRef onPointerDownOutside={() => hideDropdown()} {...props} > - {overlay} + {typeof overlay === 'function' ? overlay(setIsOpen) : overlay} + diff --git a/src/blocks/dropdown/Dropdown.types.ts b/src/blocks/dropdown/Dropdown.types.ts index d468bf9cf5..3e84b5d3fe 100644 --- a/src/blocks/dropdown/Dropdown.types.ts +++ b/src/blocks/dropdown/Dropdown.types.ts @@ -12,7 +12,7 @@ export type DropdownComponentProps = { // This is used for custom css instead of style prop, check Box/Text component css?: FlattenSimpleInterpolation; // This will be the contents of the dropdown overlay - overlay?: ReactNode; + overlay?: ReactNode | ((setIsOpen: (isOpen: boolean) => void) => ReactNode); }; export type DropdownProps = DropdownComponentProps & DropdownMenuContentProps; diff --git a/src/common/components/ManageSettingsDropdown.tsx b/src/common/components/ManageSettingsDropdown.tsx new file mode 100644 index 0000000000..cf5fd58fed --- /dev/null +++ b/src/common/components/ManageSettingsDropdown.tsx @@ -0,0 +1,187 @@ +import { FC, useState } from 'react'; + +import { Box, Button, Separator, Text, ToggleSwitch } from 'blocks'; + +import InputSlider from 'components/reusables/sliders/InputSlider'; +import RangeSlider from 'components/reusables/sliders/RangeSlider'; + +import { UserSetting } from 'helpers/channel/types'; + +type NotificationSettingsDropdownProps = { + userSetting: UserSetting[]; + updateNotificationSettings: (setting: UserSetting[]) => Promise; + updatingNotificationSettings: boolean; + unsubscribing: boolean; + unsubscribe: () => void; +}; + +const ManageSettingsDropdown: FC = ({ + userSetting, + updateNotificationSettings, + updatingNotificationSettings, + unsubscribing, + unsubscribe +}) => { + const [modifiedSettings, setModifiedSettings] = useState([...userSetting]); + + const handleSliderChange = (index: number, value: number | { lower: number; upper: number }) => { + const updatedSettings = [...modifiedSettings]; + updatedSettings[index].user = value; + setModifiedSettings(updatedSettings); + }; + + const handleSwitchChange = (index: number) => { + const updatedSettings = [...modifiedSettings]; + if (updatedSettings[index].type === 1) { + // Type 1 + // Use a type guard to narrow the type to UserSetting of type 1 + const setting = updatedSettings[index] as UserSetting & { type: 1 }; + setting.user = !setting.user; + } else if (updatedSettings[index].type === 2) { + // Type 2 + // Use a type guard to narrow the type to UserSetting of type 2 + const setting = updatedSettings[index] as UserSetting & { type: 2 }; + setting.enabled = !setting.enabled; + } else { + // Type 3 + // Use a type guard to narrow the type to UserSetting of type 2 + const setting = updatedSettings[index] as UserSetting & { type: 3 }; + setting.enabled = !setting.enabled; + } + setModifiedSettings(updatedSettings); + }; + + const handleUpdateNotificationSettings = () => { + updateNotificationSettings(modifiedSettings); + }; + + const handleOptOut = async () => { + unsubscribe(); + }; + + return ( + + + + {modifiedSettings.map((setting, index) => { + return ( + + + + + {setting.description} + + handleSwitchChange(index)} + /> + + {setting.type === 2 && setting.enabled === true && ( + + + {setting.user || setting.default} + + handleSliderChange(index, x)} + /> + + )} + + {setting.type === 3 && setting.enabled === true && ( + + + {setting.user.lower || setting.default.lower} - {setting.user.upper || setting.default.upper} + + + handleSliderChange(index, { lower: startVal, upper: endVal }) + } + /> + + )} + + + + ); + })} + + + + + You will receive all important updates from this channel. + + + + + + {unsubscribing ? 'Unsubscribing' : 'Unsubscribe'} + + + + + + + ); +}; + +export { ManageSettingsDropdown }; diff --git a/src/common/components/NotificationSettingsDropdown.tsx b/src/common/components/NotificationSettingsDropdown.tsx new file mode 100644 index 0000000000..ff85ca9d24 --- /dev/null +++ b/src/common/components/NotificationSettingsDropdown.tsx @@ -0,0 +1,172 @@ +import { FC, useState } from 'react'; + +import { Box, Button, Separator, Text, ToggleSwitch } from 'blocks'; + +import InputSlider from 'components/reusables/sliders/InputSlider'; +import RangeSlider from 'components/reusables/sliders/RangeSlider'; + +import { ChannelSetting } from 'helpers/channel/types'; + +type NotificationSettingsDropdownProps = { + // TODO: Change this to the new channel Settings type + channelSettings: ChannelSetting[]; + optInHandler: (channelSettings: ChannelSetting[]) => Promise; + loading: boolean; + onClose: () => void; +}; + +const NotificationSettingsDropdown: FC = ({ + optInHandler, + channelSettings, + loading, + onClose +}) => { + const [modifiedSettings, setModifiedSettings] = useState([...channelSettings]); + + const handleSliderChange = (index: number, value: number | { lower: number; upper: number }) => { + const updatedSettings = [...modifiedSettings]; + updatedSettings[index].default = value; + setModifiedSettings(updatedSettings); + }; + + const handleSwitchChange = (index: number) => { + const updatedSettings = [...modifiedSettings]; + if (updatedSettings[index].type === 1) { + // Type 1 + // Use a type guard to narrow the type to ChannelSetting of type 1 + const setting = updatedSettings[index] as ChannelSetting & { type: 1 }; + setting.default = !setting.default; + } else { + // Type 2 + // Use a type guard to narrow the type to ChannelSetting of type 2 + const setting = updatedSettings[index] as ChannelSetting & { type: 2 }; + setting.enabled = !setting.enabled; + } + setModifiedSettings(updatedSettings); + }; + + const handleOptIn = async () => { + await optInHandler(modifiedSettings); + }; + + return ( + + + + {modifiedSettings.map((setting, index) => { + return ( + + + + + {setting.description} + + handleSwitchChange(index)} + /> + + {setting.type === 2 && setting.enabled === true && ( + + + {setting.default} + + handleSliderChange(index, x)} + /> + + )} + + {setting.type === 3 && setting.enabled === true && ( + + + {setting.default.lower} - {setting.default.upper} + + + handleSliderChange(index, { lower: startVal, upper: endVal }) + } + /> + + )} + + + + ); + })} + + + + + You will receive all important updates from this channel. + + + + + + Cancel + + + + + + + ); +}; + +export { NotificationSettingsDropdown }; diff --git a/src/common/components/SubscribeChannelDropdown.tsx b/src/common/components/SubscribeChannelDropdown.tsx index a20dd6e5e0..1394c7737c 100644 --- a/src/common/components/SubscribeChannelDropdown.tsx +++ b/src/common/components/SubscribeChannelDropdown.tsx @@ -1,11 +1,23 @@ // React and other libraries import { FC, ReactNode } from 'react'; +import { MdCheckCircle, MdError } from 'react-icons/md'; -// Utility functions -import { ChannelDetailsResponse } from 'queries'; +import { ethers } from 'ethers'; -// Components -import OptinNotifSettingDropdown from 'components/dropdowns/OptinNotifSettingDropdown'; +import { Box, Dropdown } from 'blocks'; +import { appConfig } from 'config'; +import { useAppContext } from 'contexts/AppContext'; + +import { useAccount } from 'hooks'; + +import { ChannelSetting } from 'helpers/channel/types'; +import { getMinimalUserSetting, notifChannelSettingFormatString } from 'helpers/channel/notifSetting'; +import { convertAddressToAddrCaip } from 'helpers/CaipHelper'; +import useToast from 'hooks/useToast'; + +import { ChannelDetailsResponse, useSubscribeChannel } from 'queries'; + +import { NotificationSettingsDropdown } from './NotificationSettingsDropdown'; export type SubscribeChannelDropdownProps = { children: ReactNode; @@ -15,17 +27,94 @@ export type SubscribeChannelDropdownProps = { const SubscribeChannelDropdown: FC = (options) => { const { children, channelDetails, onSuccess } = options; + const { account, provider, wallet, chainId } = useAccount(); + + const { connectWallet } = useAppContext(); + + const channelSettings = + channelDetails && channelDetails?.channel_settings ? JSON.parse(channelDetails?.channel_settings) : null; + + const { mutate: subscribeChannel, isPending } = useSubscribeChannel(); + const subscribeToast = useToast(); + + const optInHandler = async (channelSetting?: ChannelSetting[]) => { + const hasAccount = wallet?.accounts?.length > 0; + + const connectedWallet = !hasAccount ? await connectWallet() : null; + + const walletAddress = hasAccount ? account : connectedWallet.accounts[0].address; + const web3Provider = hasAccount ? provider : new ethers.providers.Web3Provider(connectedWallet.provider, 'any'); + const onCoreNetwork = chainId === appConfig.coreContractChain; + + const channelAddress = !onCoreNetwork ? (channelDetails.alias_address as string) : channelDetails.channel; + + const _signer = await web3Provider?.getSigner(walletAddress); + + const minimalNotifSettings = channelSetting + ? getMinimalUserSetting(notifChannelSettingFormatString({ settings: channelSetting })) + : null; + + subscribeChannel( + { + signer: _signer, + channelAddress: convertAddressToAddrCaip(channelAddress, chainId), + userAddress: convertAddressToAddrCaip(walletAddress, chainId), + settings: minimalNotifSettings, + env: appConfig.pushNodesEnv + }, + { + onSuccess: (response) => { + console.log('Response on the channels apge', response); + if (response.status == '204') { + onSuccess(); + subscribeToast.showMessageToast({ + toastTitle: 'Success', + toastMessage: 'Successfully opted into channel !', + toastType: 'SUCCESS', + getToastIcon: (size) => + }); + } else { + console.log('Error in the response >>', response); + subscribeToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: `There was an error opting into channel`, + toastType: 'ERROR', + getToastIcon: (size) => + }); + } + }, + onError: (error) => { + console.log('Error in the schnnale', error); + } + } + ); + }; return ( - { - //empty function - }} - onSuccessOptin={onSuccess} - > - {children} - + <> + {channelSettings && channelSettings.length ? ( + ( + setIsOpen(false)} + /> + )} + > + {children} + + ) : ( + { + optInHandler(); + }} + > + {children} + + )} + ); }; diff --git a/src/common/components/UnsubscribeChannelDropdown.tsx b/src/common/components/UnsubscribeChannelDropdown.tsx index caccb8810c..50441cf136 100644 --- a/src/common/components/UnsubscribeChannelDropdown.tsx +++ b/src/common/components/UnsubscribeChannelDropdown.tsx @@ -1,37 +1,166 @@ // React and other libraries import { FC, ReactNode } from 'react'; +import { MdCheckCircle, MdError } from 'react-icons/md'; + +import { useSelector } from 'react-redux'; + +import { Dropdown, Menu, MenuItem, OptOut } from 'blocks'; + +import { appConfig } from 'config'; +import { useAppContext } from 'contexts/AppContext'; + +import { useAccount } from 'hooks'; +import useToast from 'hooks/useToast'; + +import { UserSetting } from 'helpers/channel/types'; +import { convertAddressToAddrCaip } from 'helpers/CaipHelper'; +import { notifUserSettingFormatString, userSettingsFromDefaultChannelSetting } from 'helpers/channel/notifSetting'; // Utility functions -import { ChannelDetailsResponse } from 'queries'; +import { ChannelDetailsResponse, useUnsubscribeChannel, useUpdateNotificationSettings } from 'queries'; // Components -import ManageNotifSettingDropdown from 'components/dropdowns/ManageNotifSettingDropdown'; -import { UserSetting } from 'helpers/channel/types'; +import { ManageSettingsDropdown } from './ManageSettingsDropdown'; +import { UserStoreType } from 'types'; export type UnsubscribeChannelDropdownProps = { children: ReactNode; channelDetail: ChannelDetailsResponse; - centeronMobile?: boolean; onSuccess: () => void; userSetting?: UserSetting[] | undefined; }; const UnsubscribeChannelDropdown: FC = ({ children, - centeronMobile = false, channelDetail, onSuccess, - userSetting, + userSetting }) => { + const { account, chainId, provider, wallet } = useAccount(); + + const { handleConnectWalletAndEnableProfile } = useAppContext(); + + const { userPushSDKInstance } = useSelector((state: UserStoreType) => state.user); + + const channelSetting = + channelDetail && channelDetail?.channel_settings ? JSON.parse(channelDetail?.channel_settings) : null; + + const { mutate: saveNotificationSettings, isPending: updatingNotificationSettings } = useUpdateNotificationSettings(); + const { mutate: unsubscribeChannel, isPending: unsubscribing } = useUnsubscribeChannel(); + + // This will get changed when new toast is made + const unsubscribeToast = useToast(); + + const handleSaveNotificationSettings = async (settings: UserSetting[]) => { + const onCoreNetwork = chainId === appConfig.coreContractChain; + + const channelAddress = !onCoreNetwork ? (channelDetail.alias_address as string) : channelDetail.channel; + + const sdkInstance = !userPushSDKInstance.signer + ? (await handleConnectWalletAndEnableProfile({ wallet })) ?? undefined + : userPushSDKInstance; + + saveNotificationSettings( + { + userPushSDKInstance: sdkInstance, + channelAddress: convertAddressToAddrCaip(channelAddress, chainId), + settings: notifUserSettingFormatString({ settings: settings }) + }, + { + onSuccess: (response) => { + if (response.status === 204) { + onSuccess(); + unsubscribeToast.showMessageToast({ + toastTitle: 'Success', + toastMessage: 'Successfully saved the user settings!', + toastType: 'SUCCESS', + getToastIcon: (size) => + }); + } else { + console.log('Error in Saving notification settings', response); + unsubscribeToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: `There was an error in saving the settings`, + toastType: 'ERROR', + getToastIcon: (size) => + }); + } + }, + onError: (error) => { + console.log('Error in saving notification settings', error); + } + } + ); + }; + + const handleOptOut = async () => { + const onCoreNetwork = chainId === appConfig.coreContractChain; + + const channelAddress = !onCoreNetwork ? (channelDetail.alias_address as string) : channelDetail.channel; + + const _signer = await provider.getSigner(account); + + unsubscribeChannel( + { + signer: _signer, + channelAddress: convertAddressToAddrCaip(channelAddress, chainId), + userAddress: convertAddressToAddrCaip(account, chainId), + env: appConfig.pushNodesEnv + }, + { + onSuccess: (response) => { + onSuccess(); + if (response.status === 'success') { + unsubscribeToast.showMessageToast({ + toastTitle: 'Success', + toastMessage: 'Successfully opted out of channel !', + toastType: 'SUCCESS', + getToastIcon: (size) => + }); + } else { + unsubscribeToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: `There was an error opting out of channel`, + toastType: 'ERROR', + getToastIcon: (size) => + }); + } + }, + onError: (error) => { + console.log('Error in the unsubcribe channel', error); + } + } + ); + }; + return ( - - {children} - + <> + {userSetting && channelSetting ? ( + + } + > + {children} + + ) : ( + + } onClick={handleOptOut} /> + + } + > + {children} + + )} + ); }; diff --git a/src/components/reusables/sliders/InputSlider.tsx b/src/components/reusables/sliders/InputSlider.tsx index 152d916459..74f7064a39 100644 --- a/src/components/reusables/sliders/InputSlider.tsx +++ b/src/components/reusables/sliders/InputSlider.tsx @@ -126,7 +126,7 @@ const Thumb = styled.div` const Active = styled.div` width: 100%; height: 4px; - background-color: ${(props) => props.theme.default.primaryPushThemeTextColor}; + background-color: #c742dd; border-top-left-radius: 8px; border-bottom-left-radius: 8px; `; @@ -134,7 +134,7 @@ const Active = styled.div` const Inactive = styled.div` width: 100%; height: 4px; - background-color: ${(props) => props.theme.snfBorder}; + background-color: #eaebf2; border-top-right-radius: 8px; border-bottom-right-radius: 8px; `; @@ -165,4 +165,4 @@ const PreviewContainer = styled.div` gap: 10px; `; -export default InputSlider; \ No newline at end of file +export default InputSlider; diff --git a/src/components/reusables/sliders/RangeSlider.tsx b/src/components/reusables/sliders/RangeSlider.tsx index 4815e48cd9..78e41becc1 100644 --- a/src/components/reusables/sliders/RangeSlider.tsx +++ b/src/components/reusables/sliders/RangeSlider.tsx @@ -11,7 +11,7 @@ interface RangeSliderProps extends Omit, 'childr defaultStartVal: number; defaultEndVal: number; preview?: boolean; - onChange: (value: { startVal: number, endVal: number }) => void; + onChange: (value: { startVal: number; endVal: number }) => void; onDragStart?: (e: React.MouseEvent | React.TouchEvent) => void; onDragEnd?: (e: React.MouseEvent | React.TouchEvent) => void; } @@ -132,15 +132,21 @@ const RangeSlider = ({ const showPreview = () => { previewSliderStartRef.current?.style.setProperty('display', 'flex'); previewSliderEndRef.current?.style.setProperty('display', 'flex'); - } + }; const hidePreview = () => { previewSliderStartRef.current?.style.setProperty('display', 'none'); previewSliderEndRef.current?.style.setProperty('display', 'none'); - } + }; useEffect(() => { - if (thumbStartRef.current && inactiveLeftRef.current && thumbEndRef.current && activeRef.current && inactiveRightRef.current) { + if ( + thumbStartRef.current && + inactiveLeftRef.current && + thumbEndRef.current && + activeRef.current && + inactiveRightRef.current + ) { thumbStartRef.current.style.left = `${((startVal - min) / (max - min)) * 98}%`; inactiveLeftRef.current.style.width = `${((startVal - min) / (max - min)) * 100}%`; activeRef.current.style.width = `${((endVal - startVal) / (max - min)) * 100}%`; @@ -185,8 +191,12 @@ const RangeSlider = ({ onMouseUp={handleMouseUpRightThumb} /> - {preview && !Number.isNaN(Number(startVal)) && {startVal}} - {preview && !Number.isNaN(Number(endVal)) && {endVal}} + {preview && !Number.isNaN(Number(startVal)) && ( + {startVal} + )} + {preview && !Number.isNaN(Number(endVal)) && ( + {endVal} + )} ); }; @@ -206,7 +216,7 @@ const Thumb = styled.div` const Active = styled.div` width: 100%; height: 4px; - background-color: ${(props) => props.theme.default.primaryPushThemeTextColor}; + background-color: #c742dd; border-top-left-radius: 8px; border-bottom-left-radius: 8px; `; @@ -214,7 +224,7 @@ const Active = styled.div` const Inactive = styled.div` width: 100%; height: 4px; - background-color: ${(props) => props.theme.snfBorder}; + background-color: #eaebf2; border-top-right-radius: 8px; border-bottom-right-radius: 8px; `; @@ -251,4 +261,4 @@ const SliderRange = styled.div` background-color: #999; `; -export default RangeSlider; \ No newline at end of file +export default RangeSlider; diff --git a/src/contexts/AppContext.tsx b/src/contexts/AppContext.tsx index 19aa1446b7..534e2c72fd 100644 --- a/src/contexts/AppContext.tsx +++ b/src/contexts/AppContext.tsx @@ -2,7 +2,7 @@ import { CONSTANTS, ProgressHookType, PushAPI } from '@pushprotocol/restapi'; import { ethers } from 'ethers'; import useModalBlur from 'hooks/useModalBlur'; -import { createContext, useContext, useEffect, useRef, useState } from 'react'; +import { createContext, ReactNode, useContext, useEffect, useRef, useState } from 'react'; // Internal Components import { LOADER_SPINNER_TYPE } from 'components/reusables/loaders/LoaderSpinner'; @@ -22,13 +22,14 @@ import { ConnectedPeerIDType, LocalPeerType, Web3NameListType, - onboardingProgressI, + handleConnectWalletAndEnableProfileProps, + onboardingProgressI } from 'types/context'; import { GlobalContext } from './GlobalContext'; export const AppContext = createContext(null); -const AppContextProvider = ({ children }) => { +const AppContextProvider = ({ children }: { children: ReactNode }) => { // To ensure intialize via [account] is not run on certain logic points const shouldInitializeRef = useRef(true); // Using a ref to control useEffect execution @@ -45,14 +46,14 @@ const AppContextProvider = ({ children }) => { const [connectedUser, setConnectedUser] = useState(); const [localPeer, setLocalPeer] = useState({ peer: '', - peerID: '', + peerID: '' }); const [connectedPeerID, setConnectedPeerID] = useState({ - peerID: '', + peerID: '' }); const [blockedLoading, setBlockedLoading] = useState({ enabled: false, - title: null, + title: null }); const [displayQR, setDisplayQR] = useState(false); @@ -65,7 +66,7 @@ const AppContextProvider = ({ children }) => { const { isModalOpen: isMetamaskPushSnapOpen, showModal: showMetamaskPushSnap, - ModalComponent: MetamaskPushSnapModalComponent, + ModalComponent: MetamaskPushSnapModalComponent } = useModalBlur(); const dispatch = useDispatch(); @@ -76,12 +77,7 @@ const AppContextProvider = ({ children }) => { toastMessage: toastMessage || 'Please connect your wallet to continue', toastTitle: 'Connect Wallet', toastType: 'ERROR', - getToastIcon: (size) => ( - - ), + getToastIcon: (size) => }); } @@ -100,13 +96,8 @@ const AppContextProvider = ({ children }) => { remember = false, showToast = false, toastMessage = undefined, - wallet, - }: { - wallet?: any; - remember?: any; - showToast?: boolean; - toastMessage?: string; - }) => { + wallet + }: handleConnectWalletAndEnableProfileProps) => { shouldInitializeRef.current = false; // Directly modify the ref to disable useEffect execution if (showToast) { @@ -114,12 +105,7 @@ const AppContextProvider = ({ children }) => { toastMessage: toastMessage || 'Please connect your wallet to continue', toastTitle: 'Connect Wallet', toastType: 'ERROR', - getToastIcon: (size) => ( - - ), + getToastIcon: (size) => }); } @@ -207,7 +193,7 @@ const AppContextProvider = ({ children }) => { userInstance = await PushAPI.initialize({ account: readOnlyWallet, env: appConfig.appEnv, - alpha: { feature: ['SCALABILITY_V2'] }, + alpha: { feature: ['SCALABILITY_V2'] } }); console.debug('src::contexts::AppContext::initializePushSdkGuestMode::User Instance Initialized', userInstance); @@ -235,7 +221,7 @@ const AppContextProvider = ({ children }) => { decryptedPGPPrivateKey: null, env: appConfig.appEnv, account: account, - alpha: { feature: ['SCALABILITY_V2'] }, + alpha: { feature: ['SCALABILITY_V2'] } }); console.debug('src::contexts::AppContext::initializePushSdkReadMode::User Instance Initialized', userInstance); @@ -269,14 +255,14 @@ const AppContextProvider = ({ children }) => { env: appConfig.appEnv, account: currentAddress, progressHook: onboardingProgressReformatter, - alpha: { feature: ['SCALABILITY_V2'] }, + alpha: { feature: ['SCALABILITY_V2'] } }); } else { userInstance = await PushAPI.initialize(librarySigner!, { env: appConfig.appEnv, account: currentAddress, progressHook: onboardingProgressReformatter, - alpha: { feature: ['SCALABILITY_V2'] }, + alpha: { feature: ['SCALABILITY_V2'] } }); } @@ -287,7 +273,7 @@ const AppContextProvider = ({ children }) => { title: 'Push Profile Setup Complete', spinnerType: LOADER_SPINNER_TYPE.COMPLETED, progressEnabled: false, - progress: 100, + progress: 100 }); } dispatch(setUserPushSDKInstance(userInstance)); @@ -310,7 +296,7 @@ const AppContextProvider = ({ children }) => { CONSTANTS.STREAM.CHAT, CONSTANTS.STREAM.CHAT_OPS, CONSTANTS.STREAM.NOTIF, - CONSTANTS.STREAM.VIDEO, + CONSTANTS.STREAM.VIDEO ]); stream.on(CONSTANTS.STREAM.CONNECT, () => { @@ -328,7 +314,7 @@ const AppContextProvider = ({ children }) => { hookInfo: progressHook, spinnerType: LOADER_SPINNER_TYPE.PROCESSING, progress: 0, - errorMessage: '', + errorMessage: '' }; if (progressHook) { @@ -431,7 +417,7 @@ const AppContextProvider = ({ children }) => { progressEnabled: onboardingProgress.progress ? true : false, progress: onboardingProgress.progress, progressNotice: onboardingProgress.hookInfo.progressInfo, - errorMessage: onboardingProgress.errorMessage, + errorMessage: onboardingProgress.errorMessage }); }; @@ -471,7 +457,7 @@ const AppContextProvider = ({ children }) => { sigType: '', signature: '', linkedListHash: '', - privateKey: '', + privateKey: '' }; } @@ -502,18 +488,18 @@ const AppContextProvider = ({ children }) => { account: account, env: appConfig.appEnv, signer: signer, - progressHook: onboardingProgressReformatter, + progressHook: onboardingProgressReformatter }); const createdUser = await PushAPI.user.get({ account: account, - env: appConfig.appEnv, + env: appConfig.appEnv }); const pvtkey = await PushAPI.chat.decryptPGPKey({ encryptedPGPPrivateKey: createdUser.encryptedPrivateKey, signer: signer, env: appConfig.appEnv, toUpgrade: true, - progressHook: onboardingProgressReformatter, + progressHook: onboardingProgressReformatter }); const createdConnectedUser = { ...createdUser, privateKey: pvtkey }; @@ -558,7 +544,7 @@ const AppContextProvider = ({ children }) => { removePGPKeyForUser, storePGPKeyForUser, isUserProfileUnlocked, - setUserProfileUnlocked, + setUserProfileUnlocked }} > {children} @@ -566,4 +552,12 @@ const AppContextProvider = ({ children }) => { ); }; +export const useAppContext = () => { + const context = useContext(AppContext); + if (context === null) { + throw new Error('useAppContext must be used within an AppContextProvider'); + } + return context; +}; + export default AppContextProvider; diff --git a/src/modules/dashboard/components/FeaturedChannelsListItem.tsx b/src/modules/dashboard/components/FeaturedChannelsListItem.tsx index 3e4e5844d1..113b5a044f 100644 --- a/src/modules/dashboard/components/FeaturedChannelsListItem.tsx +++ b/src/modules/dashboard/components/FeaturedChannelsListItem.tsx @@ -13,12 +13,10 @@ import { useAccount } from 'hooks'; import { formatSubscriberCount } from '../Dashboard.utils'; // Components -import { Box, Button, CaretDown, NotificationMobile, Skeleton, Text } from 'blocks'; +import { Box, Button, CaretDown, Ethereum, NotificationMobile, Skeleton, Text, TickDecoratedCircleFilled } from 'blocks'; import { SubscribeChannelDropdown } from 'common/components/SubscribeChannelDropdown'; import { UnsubscribeChannelDropdown } from 'common/components/UnsubscribeChannelDropdown'; -import TickDecoratedCircleFilled from 'blocks/icons/components/TickDecoratedCircleFilled'; import { VerifiedToolTipComponent } from './VerifiedToolTipComponent'; -import Ethereum from 'blocks/illustrations/components/Ethereum'; import { UserSetting } from 'helpers/channel/types'; // Internal Configs diff --git a/src/queries/hooks/user/index.ts b/src/queries/hooks/user/index.ts index 8cfe46aad4..fe7160ed77 100644 --- a/src/queries/hooks/user/index.ts +++ b/src/queries/hooks/user/index.ts @@ -1 +1,4 @@ export * from './useGetUserSubscriptions'; +export * from './useSubscribeChannel'; +export * from './useUnsubscribeChannel'; +export * from './useUpdateNotificationSettings'; diff --git a/src/queries/hooks/user/useSubscribeChannel.ts b/src/queries/hooks/user/useSubscribeChannel.ts new file mode 100644 index 0000000000..9097f13716 --- /dev/null +++ b/src/queries/hooks/user/useSubscribeChannel.ts @@ -0,0 +1,11 @@ +import { useMutation } from '@tanstack/react-query'; + +import { subscribe } from 'queries/queryKeys'; + +import { subscribeToChannel } from 'queries/services'; + +export const useSubscribeChannel = () => + useMutation({ + mutationKey: [subscribe], + mutationFn: subscribeToChannel, + }); diff --git a/src/queries/hooks/user/useUnsubscribeChannel.ts b/src/queries/hooks/user/useUnsubscribeChannel.ts new file mode 100644 index 0000000000..31ed22e1da --- /dev/null +++ b/src/queries/hooks/user/useUnsubscribeChannel.ts @@ -0,0 +1,11 @@ +import { useMutation } from '@tanstack/react-query'; + +import { unsubscribe } from 'queries/queryKeys'; + +import { unsubscribeChannel } from 'queries/services'; + +export const useUnsubscribeChannel = () => + useMutation({ + mutationKey: [unsubscribe], + mutationFn: unsubscribeChannel, + }); diff --git a/src/queries/hooks/user/useUpdateNotificationSettings.ts b/src/queries/hooks/user/useUpdateNotificationSettings.ts new file mode 100644 index 0000000000..997ddfaa53 --- /dev/null +++ b/src/queries/hooks/user/useUpdateNotificationSettings.ts @@ -0,0 +1,10 @@ +import { useMutation } from '@tanstack/react-query'; +import { updatingNotificationSetting } from 'queries/queryKeys'; +import { updateNotificationSettings } from 'queries/services'; + +export const useUpdateNotificationSettings = () => { + return useMutation({ + mutationKey: [updatingNotificationSetting], + mutationFn: updateNotificationSettings + }); +}; diff --git a/src/queries/queryKeys.ts b/src/queries/queryKeys.ts index 8bf4fb428b..fe7b098f84 100644 --- a/src/queries/queryKeys.ts +++ b/src/queries/queryKeys.ts @@ -25,3 +25,6 @@ export const rejectVaultUser = 'rejectVaultUser'; export const sentMessageCount = 'sentMessageCount'; export const sentNotificationCount = 'sentNotificationCount'; export const subscriberCount = 'subscriberCount'; +export const subscribe = 'subscribe'; +export const unsubscribe = 'unsubscribe'; +export const updatingNotificationSetting = 'updatingNotificationSetting'; diff --git a/src/queries/services/user/index.ts b/src/queries/services/user/index.ts index f471eb91a3..6618895a84 100644 --- a/src/queries/services/user/index.ts +++ b/src/queries/services/user/index.ts @@ -1 +1,4 @@ export * from './getUserSubscriptions'; +export * from './subscribeToChannel'; +export * from './unsubscribeChannel'; +export * from './updateNotificationSettings'; diff --git a/src/queries/services/user/subscribeToChannel.ts b/src/queries/services/user/subscribeToChannel.ts new file mode 100644 index 0000000000..235e746b68 --- /dev/null +++ b/src/queries/services/user/subscribeToChannel.ts @@ -0,0 +1,20 @@ +import * as PushAPI from '@pushprotocol/restapi'; + +import { SubscribeChannelParams, SubsribeChannelResponse } from 'queries/types'; + +export const subscribeToChannel = async ({ + signer, + channelAddress, + userAddress, + settings, + env +}: SubscribeChannelParams): Promise => { + const res = await PushAPI.channels.subscribeV2({ + signer: signer, + channelAddress: channelAddress, // channel address in CAIP + userAddress: userAddress, // user address in CAIP + settings: settings, + env + }); + return res; +}; diff --git a/src/queries/services/user/unsubscribeChannel.ts b/src/queries/services/user/unsubscribeChannel.ts new file mode 100644 index 0000000000..00d8b07c0e --- /dev/null +++ b/src/queries/services/user/unsubscribeChannel.ts @@ -0,0 +1,18 @@ +import * as PushAPI from '@pushprotocol/restapi'; + +import { UnsubscribeChannelParams, UnsubscribeChannelResponse } from 'queries/types'; + +export const unsubscribeChannel = async ({ + signer, + channelAddress, + userAddress, + env +}: UnsubscribeChannelParams): Promise => { + const res = await PushAPI.channels.unsubscribe({ + signer: signer, + channelAddress: channelAddress, // channel address in CAIP + userAddress: userAddress, // user address in CAIP + env + }); + return res; +}; diff --git a/src/queries/services/user/updateNotificationSettings.ts b/src/queries/services/user/updateNotificationSettings.ts new file mode 100644 index 0000000000..a905e987fd --- /dev/null +++ b/src/queries/services/user/updateNotificationSettings.ts @@ -0,0 +1,19 @@ +import { PushAPI } from '@pushprotocol/restapi'; + +type updateNotificationSettingsParams = { + userPushSDKInstance: PushAPI; + channelAddress: string; + settings: any; +}; + +export const updateNotificationSettings = async ({ + userPushSDKInstance, + channelAddress, + settings +}: updateNotificationSettingsParams) => { + const res = await userPushSDKInstance.notification.subscribe(channelAddress, { + settings + }); + + return res; +}; diff --git a/src/queries/types/user.ts b/src/queries/types/user.ts index 72dda589f0..dbdf8eec16 100644 --- a/src/queries/types/user.ts +++ b/src/queries/types/user.ts @@ -1,6 +1,34 @@ +import { Env, SignerType } from '@pushprotocol/restapi'; + export type UserSubscriptionData = { channel: string; user_settings: string; }; export type UserSubscriptionsResponse = Array; + +export type SubscribeChannelParams = { + signer: SignerType; + channelAddress: string; + userAddress: string; + settings?: string | null; + env: Env; +}; + +export type SubsribeChannelResponse = { + status: string; + message: string; +}; + +export type UnsubscribeChannelParams = { + signer: SignerType; + channelAddress: string; + userAddress: string; + settings?: string | null; + env: Env; +}; + +export type UnsubscribeChannelResponse = { + status: string; + message: string; +}; diff --git a/src/types/context.ts b/src/types/context.ts index e784ddc2bb..4cce932a2c 100644 --- a/src/types/context.ts +++ b/src/types/context.ts @@ -1,4 +1,4 @@ -import { ProgressHookType } from '@pushprotocol/restapi'; +import { ProgressHookType, PushAPI } from '@pushprotocol/restapi'; import { ConnectedUser } from './chat'; export interface Web3NameListType { @@ -34,6 +34,13 @@ export interface ConnectedPeerIDType { peerID: string; } +export type handleConnectWalletAndEnableProfileProps = { + showToast?: boolean; + toastMessage?: string; + wallet?: any; + remember?: boolean; +}; + export interface AppContextType { web3NameList: Web3NameListType; setWeb3NameList: (ens: Web3NameListType) => void; @@ -44,12 +51,7 @@ export interface AppContextType { initializePushSDK: () => Promise; snapInstalled: boolean; setSnapInstalled: (snapInstalled: boolean) => void; - handleConnectWalletAndEnableProfile: ( - showToast?: boolean, - toastMessage?: string, - wallet?: any, - remember?: any - ) => any; + handleConnectWalletAndEnableProfile: (props: handleConnectWalletAndEnableProfileProps) => Promise; connectWallet: (showToast?: boolean, toastMessage?: string) => any; setBlockedLoading: (blockedLoading: BlockedLoadingI) => void; blockedLoading: BlockedLoadingI;