Skip to content

Commit

Permalink
DAPP-2005: PGP Key feature in channels (#2009)
Browse files Browse the repository at this point in the history
* DAPP-2005: PGP Key Feature for Channels

- Added Unlock Profile modal to enforce profile unlocking before any channel action.
- Updated SDK function to allow channel actions without requiring signer approval each time.
- Fixed device config issue in Tooltip and Unlock Profile component.

* DAPP-2005: Code refactoring

* DAPP-2005: Revert Trending Channel List changes to static channels list

* DAPP-2005: Remove useCallback in Subscribe button

* DAPP-2005: Update @pushprotocol/restapi dependency to stable release
  • Loading branch information
meKushdeepSingh authored Jan 31, 2025
1 parent f87d312 commit 373b763
Show file tree
Hide file tree
Showing 20 changed files with 348 additions and 50 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@metamask/eth-sig-util": "^4.0.0",
"@mui/icons-material": "^5.8.4",
"@mui/material": "^5.5.0",
"@pushprotocol/restapi": "1.7.29",
"@pushprotocol/restapi": "^1.7.30",
"@pushprotocol/socket": "0.5.3",
"@pushprotocol/uiweb": "1.7.3",
"@radix-ui/react-dialog": "^1.1.1",
Expand Down
10 changes: 9 additions & 1 deletion src/common/components/ChannelDetailsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
Tooltip,
} from 'blocks';
import APP_PATHS from 'config/AppPaths';
import { SubscribeChannelDropdown } from 'common/components/SubscribeChannelDropdown';
import { ProfileModalVisibilityType, SubscribeChannelDropdown } from 'common/components/SubscribeChannelDropdown';
import { UnsubscribeChannelDropdown } from 'common/components/UnsubscribeChannelDropdown';
import { UserSetting } from 'helpers/channel/types';
import { appConfig } from 'config';
Expand All @@ -34,6 +34,8 @@ export type ChannelDetailsCardProps = {
userSettings: UserSetting[];
width?: ResponsiveProp<string>;
subscribeButtonVariant?: ButtonProps['variant'];
onChangeProfileModalVisibility?: (show: ProfileModalVisibilityType) => void; // Function prop to control modal visibility
profileModalVisibility?: ProfileModalVisibilityType;
};

const ChannelDetailsCard: FC<ChannelDetailsCardProps> = ({
Expand All @@ -45,6 +47,8 @@ const ChannelDetailsCard: FC<ChannelDetailsCardProps> = ({
userSettings,
width,
subscribeButtonVariant = 'tertiary',
onChangeProfileModalVisibility,
profileModalVisibility,
}) => {
let verifiedAliasChainIds = [
appConfig.coreContractChain,
Expand Down Expand Up @@ -104,6 +108,8 @@ const ChannelDetailsCard: FC<ChannelDetailsCardProps> = ({
height="40px"
>
<SubscribeChannelDropdown
onChangeProfileModalVisibility={onChangeProfileModalVisibility}
profileModalVisibility={profileModalVisibility}
channelDetails={channelDetails!}
onSuccess={handleRefetch}
>
Expand All @@ -125,6 +131,8 @@ const ChannelDetailsCard: FC<ChannelDetailsCardProps> = ({
height="40px"
>
<UnsubscribeChannelDropdown
onChangeProfileModalVisibility={onChangeProfileModalVisibility}
profileModalVisibility={profileModalVisibility}
channelDetail={channelDetails!}
onSuccess={handleRefetch}
userSetting={userSettings}
Expand Down
54 changes: 46 additions & 8 deletions src/common/components/SubscribeChannelDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// React and other libraries
import { FC, ReactNode } from 'react';
import { FC, memo, ReactNode, useEffect, useState } from 'react';
import { MdCheckCircle, MdError } from 'react-icons/md';
import { useSelector } from 'react-redux';

// Utility functions
import { ChannelDetails } from 'queries';
Expand All @@ -12,6 +13,8 @@ import { useAppContext } from 'contexts/AppContext';

import { useAccount } from 'hooks';

import { UserStoreType } from 'types';

import { ChannelSetting } from 'helpers/channel/types';
import { getMinimalUserSetting, notifChannelSettingFormatString } from 'helpers/channel/notifSetting';
import { convertAddressToAddrCaip } from 'helpers/CaipHelper';
Expand All @@ -20,30 +23,63 @@ import useToast from 'hooks/useToast';
import { useSubscribeChannel } from 'queries';

import { NotificationSettingsDropdown } from './NotificationSettingsDropdown';
import { retrieveUserPGPKeyFromStorage } from 'helpers/connectWalletHelper';

export type ProfileModalVisibilityType = {
isVisible: boolean;
channel_id: number | null;
};

export type SubscribeChannelDropdownProps = {
children: ReactNode;
channelDetails: ChannelDetails;
onSuccess: () => void;
onChangeProfileModalVisibility?: (show: ProfileModalVisibilityType) => void; // Function prop to control modal visibility
profileModalVisibility?: ProfileModalVisibilityType;
};

const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) => {
const { children, channelDetails, onSuccess } = options;
const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = memo((options) => {
const { children, channelDetails, onSuccess, onChangeProfileModalVisibility, profileModalVisibility } = options;
const { account, provider, wallet, chainId } = useAccount();

const { connectWallet } = useAppContext();

// State to handle the temporary channel setting
const [tempChannelSetting, setTempChannelSettings] = useState<ChannelSetting[] | undefined>(undefined);
// Get the userPushSDKInstance from the store
const { userPushSDKInstance } = useSelector((state: UserStoreType) => state.user);

const channelSettings =
channelDetails && channelDetails?.channel_settings ? JSON.parse(channelDetails?.channel_settings) : null;

const { mutate: subscribeChannel, isPending } = useSubscribeChannel();
const subscribeToast = useToast();

useEffect(() => {
// If the user has account and the profile is unlocked, then run the optInHandler
if (
profileModalVisibility?.isVisible &&
profileModalVisibility?.channel_id === channelDetails.id &&
userPushSDKInstance &&
!userPushSDKInstance?.readmode()
) {
onChangeProfileModalVisibility?.({ isVisible: false, channel_id: null });
optInHandler(tempChannelSetting);
}
}, [profileModalVisibility, userPushSDKInstance]);

const optInHandler = async (channelSetting?: ChannelSetting[]) => {
const hasAccount = wallet?.accounts?.length > 0;

const connectedWallet = !hasAccount ? await connectWallet() : null;

// If the user has account or the wallet is connected, and the profile is locked, then show the profile modal and return
if ((hasAccount || connectedWallet) && userPushSDKInstance && userPushSDKInstance?.readmode()) {
onChangeProfileModalVisibility?.({ isVisible: true, channel_id: channelDetails.id });
channelSetting && setTempChannelSettings(channelSetting);
return;
}

const walletAddress = hasAccount ? account : connectedWallet.accounts[0].address;
const web3Provider = hasAccount ? provider : new ethers.providers.Web3Provider(connectedWallet.provider, 'any');
const onCoreNetwork = chainId === appConfig.coreContractChain;
Expand All @@ -56,17 +92,19 @@ const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) =>
? getMinimalUserSetting(notifChannelSettingFormatString({ settings: channelSetting }))
: null;

const decryptedPGPKeys = userPushSDKInstance?.decryptedPgpPvtKey ?? retrieveUserPGPKeyFromStorage(account);

subscribeChannel(
{
signer: _signer,
channelAddress: convertAddressToAddrCaip(channelAddress, chainId),
userAddress: convertAddressToAddrCaip(walletAddress, chainId),
settings: minimalNotifSettings,
env: appConfig.pushNodesEnv,
decryptedPGPKeys,
},
{
onSuccess: (response) => {
console.log('Response on the channels apge', response);
if (response.status == '204') {
onSuccess();
subscribeToast.showMessageToast({
Expand All @@ -81,7 +119,6 @@ const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) =>
),
});
} else {
console.log('Error in the response >>', response);
subscribeToast.showMessageToast({
toastTitle: 'Error',
toastMessage: `There was an error opting into channel`,
Expand All @@ -100,10 +137,11 @@ const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) =>
},
}
);
tempChannelSetting && setTempChannelSettings(undefined);
};

return (
<>
<Box>
{channelSettings && channelSettings.length ? (
<Dropdown
overlay={(setIsOpen) => (
Expand All @@ -126,8 +164,8 @@ const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) =>
{children}
</Box>
)}
</>
</Box>
);
};
});

export { SubscribeChannelDropdown };
46 changes: 43 additions & 3 deletions src/common/components/UnsubscribeChannelDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// React and other libraries
import { FC, ReactNode } from 'react';
import { FC, ReactNode, useEffect, useState } from 'react';
import { MdCheckCircle, MdError } from 'react-icons/md';

import { useSelector } from 'react-redux';
Expand All @@ -23,25 +23,34 @@ import { useUnsubscribeChannel, useUpdateNotificationSettings } from 'queries';
// Components
import { ManageSettingsDropdown } from './ManageSettingsDropdown';
import { UserStoreType } from 'types';
import { ProfileModalVisibilityType } from './SubscribeChannelDropdown';
import { retrieveUserPGPKeyFromStorage } from 'helpers/connectWalletHelper';

export type UnsubscribeChannelDropdownProps = {
children: ReactNode;
channelDetail: ChannelDetails;
centeronMobile?: boolean;
onSuccess: () => void;
userSetting?: UserSetting[] | undefined;
onChangeProfileModalVisibility?: (show: ProfileModalVisibilityType) => void; // Function prop to control modal visibility
profileModalVisibility?: ProfileModalVisibilityType;
};

const UnsubscribeChannelDropdown: FC<UnsubscribeChannelDropdownProps> = ({
children,
channelDetail,
onSuccess,
userSetting,
profileModalVisibility,
onChangeProfileModalVisibility,
}) => {
const { account, chainId, provider, wallet } = useAccount();

const { handleConnectWalletAndEnableProfile } = useAppContext();

// State to handle the temporary channel setting
const [tempSetting, setTempSettings] = useState<UserSetting[] | undefined>(undefined);
// Get the userPushSDKInstance from the store
const { userPushSDKInstance } = useSelector((state: UserStoreType) => state.user);

const channelSetting =
Expand All @@ -53,7 +62,30 @@ const UnsubscribeChannelDropdown: FC<UnsubscribeChannelDropdownProps> = ({
// This will get changed when new toast is made
const unsubscribeToast = useToast();

useEffect(() => {
// If the user has account and the profile is unlocked, then run the optInHandler
if (
profileModalVisibility?.isVisible &&
profileModalVisibility?.channel_id === channelDetail.id &&
userPushSDKInstance &&
!userPushSDKInstance?.readmode()
) {
onChangeProfileModalVisibility?.({ isVisible: false, channel_id: null });
if (tempSetting) {
handleSaveNotificationSettings(tempSetting);
} else {
handleOptOut();
}
}
}, [profileModalVisibility, userPushSDKInstance]);

const handleSaveNotificationSettings = async (settings: UserSetting[]) => {
// If the profile is locked, then show the profile modal and return
if (userPushSDKInstance && userPushSDKInstance?.readmode()) {
onChangeProfileModalVisibility?.({ isVisible: true, channel_id: channelDetail.id });
setTempSettings(settings);
return;
}
const onCoreNetwork = chainId === appConfig.coreContractChain;

const channelAddress = !onCoreNetwork ? (channelDetail.alias_address as string) : channelDetail.channel;
Expand Down Expand Up @@ -84,7 +116,6 @@ const UnsubscribeChannelDropdown: FC<UnsubscribeChannelDropdownProps> = ({
),
});
} else {
console.log('Error in Saving notification settings', response);
unsubscribeToast.showMessageToast({
toastTitle: 'Error',
toastMessage: `There was an error in saving the settings`,
Expand All @@ -103,26 +134,35 @@ const UnsubscribeChannelDropdown: FC<UnsubscribeChannelDropdownProps> = ({
},
}
);
setTempSettings(undefined);
};

const handleOptOut = async () => {
// If the profile is locked, then show the profile modal and return
if (userPushSDKInstance && userPushSDKInstance?.readmode()) {
onChangeProfileModalVisibility?.({ isVisible: true, channel_id: channelDetail.id });
return;
}
const onCoreNetwork = chainId === appConfig.coreContractChain;

const channelAddress = !onCoreNetwork ? (channelDetail.alias_address as string) : channelDetail.channel;

const _signer = await provider.getSigner(account);

const decryptedPGPKeys = userPushSDKInstance?.decryptedPgpPvtKey ?? retrieveUserPGPKeyFromStorage(account);

unsubscribeChannel(
{
signer: _signer,
channelAddress: convertAddressToAddrCaip(channelAddress, chainId),
userAddress: convertAddressToAddrCaip(account, chainId),
env: appConfig.pushNodesEnv,
decryptedPGPKeys,
},
{
onSuccess: (response) => {
onSuccess();
if (response.status === 'success') {
if (response.status === 204) {
unsubscribeToast.showMessageToast({
toastTitle: 'Success',
toastMessage: 'Successfully opted out of channel !',
Expand Down
8 changes: 4 additions & 4 deletions src/components/chat/unlockProfile/UnlockProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useAccount, useDeviceWidthCheck } from 'hooks';
import { retrieveUserPGPKeyFromStorage } from 'helpers/connectWalletHelper';

// Internal Configs
import { device, size } from 'config/Globals';
import { size } from 'config/Globals';

// Assets
import Tooltip from 'components/reusables/tooltip/Tooltip';
Expand Down Expand Up @@ -246,7 +246,7 @@ const UnlockProfile = ({ InnerComponentProps, onClose }: UnlockProfileModalProps
{activeStatus.status === PROFILESTATE.UNLOCK_PROFILE && (
<>
{!isLoading ? (
<RenderToolTip type={type}>
<RenderToolTip>
<ItemHV2
gap="8px"
justifyContent={type === UNLOCK_PROFILE_TYPE.MODAL ? 'center' : 'end'}
Expand Down Expand Up @@ -344,7 +344,7 @@ const SubContainer = styled(ItemVV2)`
flex-direction: ${(props) => (props.type === UNLOCK_PROFILE_TYPE.MODAL ? 'column' : 'row')};
justify-content: space-between;
@media ${device.tablet} {
@media ${deviceMediaQ.tablet} {
align-items: center;
flex-direction: column;
gap: 24px;
Expand Down Expand Up @@ -383,7 +383,7 @@ const HorizontalBar = styled.div`
? `linear-gradient(to right, ${colorBrands['primary-500']}, ${props.theme.btn.disabledBg})`
: colorBrands['primary-500']};
@media ${device.tablet} {
@media ${deviceMediaQ.tablet} {
width: 2px;
height: 40px;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/reusables/tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { device } from 'config/Globals';
import { deviceMediaQ } from 'blocks';
import * as React from 'react';
import styled from 'styled-components';

Expand Down Expand Up @@ -74,7 +74,7 @@ const Wrapper = styled.div`
height: fit-content;
display: inline-block;
position: relative;
@media ${device.tablet} {
@media ${deviceMediaQ.tablet} {
width: 100%;
min-width: 100%;
max-width: 100%;
Expand Down
Loading

0 comments on commit 373b763

Please sign in to comment.