Skip to content

Commit 373b763

Browse files
DAPP-2005: PGP Key feature in channels (#2009)
* 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
1 parent f87d312 commit 373b763

20 files changed

+348
-50
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"@metamask/eth-sig-util": "^4.0.0",
3535
"@mui/icons-material": "^5.8.4",
3636
"@mui/material": "^5.5.0",
37-
"@pushprotocol/restapi": "1.7.29",
37+
"@pushprotocol/restapi": "^1.7.30",
3838
"@pushprotocol/socket": "0.5.3",
3939
"@pushprotocol/uiweb": "1.7.3",
4040
"@radix-ui/react-dialog": "^1.1.1",

src/common/components/ChannelDetailsCard.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
Tooltip,
2121
} from 'blocks';
2222
import APP_PATHS from 'config/AppPaths';
23-
import { SubscribeChannelDropdown } from 'common/components/SubscribeChannelDropdown';
23+
import { ProfileModalVisibilityType, SubscribeChannelDropdown } from 'common/components/SubscribeChannelDropdown';
2424
import { UnsubscribeChannelDropdown } from 'common/components/UnsubscribeChannelDropdown';
2525
import { UserSetting } from 'helpers/channel/types';
2626
import { appConfig } from 'config';
@@ -34,6 +34,8 @@ export type ChannelDetailsCardProps = {
3434
userSettings: UserSetting[];
3535
width?: ResponsiveProp<string>;
3636
subscribeButtonVariant?: ButtonProps['variant'];
37+
onChangeProfileModalVisibility?: (show: ProfileModalVisibilityType) => void; // Function prop to control modal visibility
38+
profileModalVisibility?: ProfileModalVisibilityType;
3739
};
3840

3941
const ChannelDetailsCard: FC<ChannelDetailsCardProps> = ({
@@ -45,6 +47,8 @@ const ChannelDetailsCard: FC<ChannelDetailsCardProps> = ({
4547
userSettings,
4648
width,
4749
subscribeButtonVariant = 'tertiary',
50+
onChangeProfileModalVisibility,
51+
profileModalVisibility,
4852
}) => {
4953
let verifiedAliasChainIds = [
5054
appConfig.coreContractChain,
@@ -104,6 +108,8 @@ const ChannelDetailsCard: FC<ChannelDetailsCardProps> = ({
104108
height="40px"
105109
>
106110
<SubscribeChannelDropdown
111+
onChangeProfileModalVisibility={onChangeProfileModalVisibility}
112+
profileModalVisibility={profileModalVisibility}
107113
channelDetails={channelDetails!}
108114
onSuccess={handleRefetch}
109115
>
@@ -125,6 +131,8 @@ const ChannelDetailsCard: FC<ChannelDetailsCardProps> = ({
125131
height="40px"
126132
>
127133
<UnsubscribeChannelDropdown
134+
onChangeProfileModalVisibility={onChangeProfileModalVisibility}
135+
profileModalVisibility={profileModalVisibility}
128136
channelDetail={channelDetails!}
129137
onSuccess={handleRefetch}
130138
userSetting={userSettings}

src/common/components/SubscribeChannelDropdown.tsx

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// React and other libraries
2-
import { FC, ReactNode } from 'react';
2+
import { FC, memo, ReactNode, useEffect, useState } from 'react';
33
import { MdCheckCircle, MdError } from 'react-icons/md';
4+
import { useSelector } from 'react-redux';
45

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

1314
import { useAccount } from 'hooks';
1415

16+
import { UserStoreType } from 'types';
17+
1518
import { ChannelSetting } from 'helpers/channel/types';
1619
import { getMinimalUserSetting, notifChannelSettingFormatString } from 'helpers/channel/notifSetting';
1720
import { convertAddressToAddrCaip } from 'helpers/CaipHelper';
@@ -20,30 +23,63 @@ import useToast from 'hooks/useToast';
2023
import { useSubscribeChannel } from 'queries';
2124

2225
import { NotificationSettingsDropdown } from './NotificationSettingsDropdown';
26+
import { retrieveUserPGPKeyFromStorage } from 'helpers/connectWalletHelper';
27+
28+
export type ProfileModalVisibilityType = {
29+
isVisible: boolean;
30+
channel_id: number | null;
31+
};
2332

2433
export type SubscribeChannelDropdownProps = {
2534
children: ReactNode;
2635
channelDetails: ChannelDetails;
2736
onSuccess: () => void;
37+
onChangeProfileModalVisibility?: (show: ProfileModalVisibilityType) => void; // Function prop to control modal visibility
38+
profileModalVisibility?: ProfileModalVisibilityType;
2839
};
2940

30-
const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) => {
31-
const { children, channelDetails, onSuccess } = options;
41+
const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = memo((options) => {
42+
const { children, channelDetails, onSuccess, onChangeProfileModalVisibility, profileModalVisibility } = options;
3243
const { account, provider, wallet, chainId } = useAccount();
3344

3445
const { connectWallet } = useAppContext();
3546

47+
// State to handle the temporary channel setting
48+
const [tempChannelSetting, setTempChannelSettings] = useState<ChannelSetting[] | undefined>(undefined);
49+
// Get the userPushSDKInstance from the store
50+
const { userPushSDKInstance } = useSelector((state: UserStoreType) => state.user);
51+
3652
const channelSettings =
3753
channelDetails && channelDetails?.channel_settings ? JSON.parse(channelDetails?.channel_settings) : null;
3854

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

58+
useEffect(() => {
59+
// If the user has account and the profile is unlocked, then run the optInHandler
60+
if (
61+
profileModalVisibility?.isVisible &&
62+
profileModalVisibility?.channel_id === channelDetails.id &&
63+
userPushSDKInstance &&
64+
!userPushSDKInstance?.readmode()
65+
) {
66+
onChangeProfileModalVisibility?.({ isVisible: false, channel_id: null });
67+
optInHandler(tempChannelSetting);
68+
}
69+
}, [profileModalVisibility, userPushSDKInstance]);
70+
4271
const optInHandler = async (channelSetting?: ChannelSetting[]) => {
4372
const hasAccount = wallet?.accounts?.length > 0;
4473

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

76+
// If the user has account or the wallet is connected, and the profile is locked, then show the profile modal and return
77+
if ((hasAccount || connectedWallet) && userPushSDKInstance && userPushSDKInstance?.readmode()) {
78+
onChangeProfileModalVisibility?.({ isVisible: true, channel_id: channelDetails.id });
79+
channelSetting && setTempChannelSettings(channelSetting);
80+
return;
81+
}
82+
4783
const walletAddress = hasAccount ? account : connectedWallet.accounts[0].address;
4884
const web3Provider = hasAccount ? provider : new ethers.providers.Web3Provider(connectedWallet.provider, 'any');
4985
const onCoreNetwork = chainId === appConfig.coreContractChain;
@@ -56,17 +92,19 @@ const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) =>
5692
? getMinimalUserSetting(notifChannelSettingFormatString({ settings: channelSetting }))
5793
: null;
5894

95+
const decryptedPGPKeys = userPushSDKInstance?.decryptedPgpPvtKey ?? retrieveUserPGPKeyFromStorage(account);
96+
5997
subscribeChannel(
6098
{
6199
signer: _signer,
62100
channelAddress: convertAddressToAddrCaip(channelAddress, chainId),
63101
userAddress: convertAddressToAddrCaip(walletAddress, chainId),
64102
settings: minimalNotifSettings,
65103
env: appConfig.pushNodesEnv,
104+
decryptedPGPKeys,
66105
},
67106
{
68107
onSuccess: (response) => {
69-
console.log('Response on the channels apge', response);
70108
if (response.status == '204') {
71109
onSuccess();
72110
subscribeToast.showMessageToast({
@@ -81,7 +119,6 @@ const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) =>
81119
),
82120
});
83121
} else {
84-
console.log('Error in the response >>', response);
85122
subscribeToast.showMessageToast({
86123
toastTitle: 'Error',
87124
toastMessage: `There was an error opting into channel`,
@@ -100,10 +137,11 @@ const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) =>
100137
},
101138
}
102139
);
140+
tempChannelSetting && setTempChannelSettings(undefined);
103141
};
104142

105143
return (
106-
<>
144+
<Box>
107145
{channelSettings && channelSettings.length ? (
108146
<Dropdown
109147
overlay={(setIsOpen) => (
@@ -126,8 +164,8 @@ const SubscribeChannelDropdown: FC<SubscribeChannelDropdownProps> = (options) =>
126164
{children}
127165
</Box>
128166
)}
129-
</>
167+
</Box>
130168
);
131-
};
169+
});
132170

133171
export { SubscribeChannelDropdown };

src/common/components/UnsubscribeChannelDropdown.tsx

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// React and other libraries
2-
import { FC, ReactNode } from 'react';
2+
import { FC, ReactNode, useEffect, useState } from 'react';
33
import { MdCheckCircle, MdError } from 'react-icons/md';
44

55
import { useSelector } from 'react-redux';
@@ -23,25 +23,34 @@ import { useUnsubscribeChannel, useUpdateNotificationSettings } from 'queries';
2323
// Components
2424
import { ManageSettingsDropdown } from './ManageSettingsDropdown';
2525
import { UserStoreType } from 'types';
26+
import { ProfileModalVisibilityType } from './SubscribeChannelDropdown';
27+
import { retrieveUserPGPKeyFromStorage } from 'helpers/connectWalletHelper';
2628

2729
export type UnsubscribeChannelDropdownProps = {
2830
children: ReactNode;
2931
channelDetail: ChannelDetails;
3032
centeronMobile?: boolean;
3133
onSuccess: () => void;
3234
userSetting?: UserSetting[] | undefined;
35+
onChangeProfileModalVisibility?: (show: ProfileModalVisibilityType) => void; // Function prop to control modal visibility
36+
profileModalVisibility?: ProfileModalVisibilityType;
3337
};
3438

3539
const UnsubscribeChannelDropdown: FC<UnsubscribeChannelDropdownProps> = ({
3640
children,
3741
channelDetail,
3842
onSuccess,
3943
userSetting,
44+
profileModalVisibility,
45+
onChangeProfileModalVisibility,
4046
}) => {
4147
const { account, chainId, provider, wallet } = useAccount();
4248

4349
const { handleConnectWalletAndEnableProfile } = useAppContext();
4450

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

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

65+
useEffect(() => {
66+
// If the user has account and the profile is unlocked, then run the optInHandler
67+
if (
68+
profileModalVisibility?.isVisible &&
69+
profileModalVisibility?.channel_id === channelDetail.id &&
70+
userPushSDKInstance &&
71+
!userPushSDKInstance?.readmode()
72+
) {
73+
onChangeProfileModalVisibility?.({ isVisible: false, channel_id: null });
74+
if (tempSetting) {
75+
handleSaveNotificationSettings(tempSetting);
76+
} else {
77+
handleOptOut();
78+
}
79+
}
80+
}, [profileModalVisibility, userPushSDKInstance]);
81+
5682
const handleSaveNotificationSettings = async (settings: UserSetting[]) => {
83+
// If the profile is locked, then show the profile modal and return
84+
if (userPushSDKInstance && userPushSDKInstance?.readmode()) {
85+
onChangeProfileModalVisibility?.({ isVisible: true, channel_id: channelDetail.id });
86+
setTempSettings(settings);
87+
return;
88+
}
5789
const onCoreNetwork = chainId === appConfig.coreContractChain;
5890

5991
const channelAddress = !onCoreNetwork ? (channelDetail.alias_address as string) : channelDetail.channel;
@@ -84,7 +116,6 @@ const UnsubscribeChannelDropdown: FC<UnsubscribeChannelDropdownProps> = ({
84116
),
85117
});
86118
} else {
87-
console.log('Error in Saving notification settings', response);
88119
unsubscribeToast.showMessageToast({
89120
toastTitle: 'Error',
90121
toastMessage: `There was an error in saving the settings`,
@@ -103,26 +134,35 @@ const UnsubscribeChannelDropdown: FC<UnsubscribeChannelDropdownProps> = ({
103134
},
104135
}
105136
);
137+
setTempSettings(undefined);
106138
};
107139

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

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

113150
const _signer = await provider.getSigner(account);
114151

152+
const decryptedPGPKeys = userPushSDKInstance?.decryptedPgpPvtKey ?? retrieveUserPGPKeyFromStorage(account);
153+
115154
unsubscribeChannel(
116155
{
117156
signer: _signer,
118157
channelAddress: convertAddressToAddrCaip(channelAddress, chainId),
119158
userAddress: convertAddressToAddrCaip(account, chainId),
120159
env: appConfig.pushNodesEnv,
160+
decryptedPGPKeys,
121161
},
122162
{
123163
onSuccess: (response) => {
124164
onSuccess();
125-
if (response.status === 'success') {
165+
if (response.status === 204) {
126166
unsubscribeToast.showMessageToast({
127167
toastTitle: 'Success',
128168
toastMessage: 'Successfully opted out of channel !',

src/components/chat/unlockProfile/UnlockProfile.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { useAccount, useDeviceWidthCheck } from 'hooks';
1111
import { retrieveUserPGPKeyFromStorage } from 'helpers/connectWalletHelper';
1212

1313
// Internal Configs
14-
import { device, size } from 'config/Globals';
14+
import { size } from 'config/Globals';
1515

1616
// Assets
1717
import Tooltip from 'components/reusables/tooltip/Tooltip';
@@ -246,7 +246,7 @@ const UnlockProfile = ({ InnerComponentProps, onClose }: UnlockProfileModalProps
246246
{activeStatus.status === PROFILESTATE.UNLOCK_PROFILE && (
247247
<>
248248
{!isLoading ? (
249-
<RenderToolTip type={type}>
249+
<RenderToolTip>
250250
<ItemHV2
251251
gap="8px"
252252
justifyContent={type === UNLOCK_PROFILE_TYPE.MODAL ? 'center' : 'end'}
@@ -344,7 +344,7 @@ const SubContainer = styled(ItemVV2)`
344344
flex-direction: ${(props) => (props.type === UNLOCK_PROFILE_TYPE.MODAL ? 'column' : 'row')};
345345
justify-content: space-between;
346346
347-
@media ${device.tablet} {
347+
@media ${deviceMediaQ.tablet} {
348348
align-items: center;
349349
flex-direction: column;
350350
gap: 24px;
@@ -383,7 +383,7 @@ const HorizontalBar = styled.div`
383383
? `linear-gradient(to right, ${colorBrands['primary-500']}, ${props.theme.btn.disabledBg})`
384384
: colorBrands['primary-500']};
385385
386-
@media ${device.tablet} {
386+
@media ${deviceMediaQ.tablet} {
387387
width: 2px;
388388
height: 40px;
389389
}

src/components/reusables/tooltip/Tooltip.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { device } from 'config/Globals';
1+
import { deviceMediaQ } from 'blocks';
22
import * as React from 'react';
33
import styled from 'styled-components';
44

@@ -74,7 +74,7 @@ const Wrapper = styled.div`
7474
height: fit-content;
7575
display: inline-block;
7676
position: relative;
77-
@media ${device.tablet} {
77+
@media ${deviceMediaQ.tablet} {
7878
width: 100%;
7979
min-width: 100%;
8080
max-width: 100%;

0 commit comments

Comments
 (0)