Skip to content

Commit

Permalink
In-App chat notifications (#1945)
Browse files Browse the repository at this point in the history
* chat notif changes

* added in-app chat notifs

* fixed minor issue

* fixed minor issue

* fixed refetch

* fixing stream

* issue with duplicate stream event

* fixed multiple connections

* fixed multiple connections

* Update config-dev.js

* updated sdk verion

* fixed gif

* updated uiweb to 1.7.2

* fixed review comments
  • Loading branch information
mishramonalisha76 authored Nov 7, 2024
1 parent b559c31 commit 66344c3
Show file tree
Hide file tree
Showing 31 changed files with 529 additions and 39 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@
"@metamask/eth-sig-util": "^4.0.0",
"@mui/icons-material": "^5.8.4",
"@mui/material": "^5.5.0",
"@pushprotocol/restapi": "1.7.25",
"@pushprotocol/restapi": "1.7.29",
"@pushprotocol/socket": "0.5.3",
"@pushprotocol/uiweb": "1.7.1",
"@pushprotocol/uiweb": "1.7.2",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-switch": "^1.1.0",
Expand Down
32 changes: 32 additions & 0 deletions src/blocks/icons/components/FillCircle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FC } from 'react';
import { IconWrapper } from '../IconWrapper';
import { IconProps } from '../Icons.types';

const FillCircle: FC<IconProps> = (allProps) => {
const { svgProps: props, ...restProps } = allProps;
return (
<IconWrapper
componentName="FillCircle"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="inherit"
height="inherit"
viewBox="0 0 20 20"
fill="none"
{...props}
>
<circle
cx="10"
cy="10"
r="10"
fill="currentColor"
/>
</svg>
}
{...restProps}
/>
);
};

export default FillCircle;
55 changes: 55 additions & 0 deletions src/blocks/icons/components/Image.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { FC } from 'react';
import { IconWrapper } from '../IconWrapper';
import { IconProps } from '../Icons.types';

const Image: FC<IconProps> = (allProps) => {
const { svgProps: props, ...restProps } = allProps;
return (
<IconWrapper
componentName="Image"
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
width="inherit"
height="inherit"
viewBox="0 0 24 22"
fill="none"
{...props}
>
<path
d="M15.2109 9.20528C15.97 9.20528 16.5854 8.58987 16.5854 7.83073C16.5854 7.07158 15.97 6.45618 15.2109 6.45618C14.4517 6.45618 13.8363 7.07158 13.8363 7.83073C13.8363 8.58987 14.4517 9.20528 15.2109 9.20528Z"
fill="currentColor"
/>
<path
d="M14.2155 15.1617L17.1581 12.2224C17.33 12.0507 17.563 11.9542 17.8059 11.9542C18.0488 11.9542 18.2818 12.0507 18.4537 12.2224L23 16.7722"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M1.0072 15.6989L7.23163 9.47329C7.31674 9.38809 7.41781 9.3205 7.52905 9.27438C7.6403 9.22827 7.75954 9.20453 7.87997 9.20453C8.00039 9.20453 8.11963 9.22827 8.23088 9.27438C8.34213 9.3205 8.44319 9.38809 8.5283 9.47329L19.2555 20.2017"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
<rect
x="1"
y="1.79828"
width="22"
height="18.4034"
rx="4"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
}
{...restProps}
/>
);
};

export default Image;
2 changes: 2 additions & 0 deletions src/blocks/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,12 @@ export { default as ErrorFilled } from './components/ErrorFilled';
export { default as ExternalLink } from './components/ExternalLink';

export { default as Front } from './components/Front';
export { default as FillCircle } from './components/FillCircle';

export { default as Gif } from './components/Gif';

export { default as InfoFilled } from './components/InfoFilled';
export { default as Image } from './components/Image';

export { default as Governance } from './components/Governance';
export { default as GovernanceFilled } from './components/GovernanceFilled';
Expand Down
6 changes: 4 additions & 2 deletions src/blocks/notification/Notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,14 @@ const toastIds: Array<string | number> = [];

// Export the notification object with show and hide methods
const notification = {
show: (config: NotificationProps) => {
show: (config: NotificationProps, id?: string) => {
const toastId = toast.custom(() => <NotificationItem {...config} />, {
id: id,
duration: config.duration || Infinity,
position: config.position || 'bottom-right',
onAutoClose: config.onAutoClose,
});
toastIds.push(toastId);
if (!toastIds.find((toastId) => toastId === id)) toastIds.push(toastId);
},
hide: () => {
if (toastIds.length > 0) {
Expand Down
2 changes: 2 additions & 0 deletions src/blocks/notification/Notification.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@ export type NotificationProps = {
position?: 'bottom-right' | 'bottom-left' | 'top-center';
/* Optional duration of the notification component */
duration?: number;
/* Optional onAutoClose event for the notification called after it's timeout */
onAutoClose?: () => void;
};
17 changes: 17 additions & 0 deletions src/common/Common.utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { appConfig } from 'config';
import { LOGO_ALIAS_CHAIN } from './Common.constants';
import { networkName } from 'helpers/UtilityHelper';
import { EnvType } from './Common.types';
import moment from 'moment';

export const allowedNetworks = appConfig.allowedNetworks.filter(
(chain: number) => chain != appConfig.coreContractChain
Expand Down Expand Up @@ -53,3 +54,19 @@ export const isValidURL = (str: string | undefined) => {
export const getCurrentEnv = (): EnvType => {
return appConfig.appEnv;
};

export function convertTimeStamp(timestamp: string) {
const date = moment.unix(Number(timestamp));
const now = moment();

const diffInSeconds = now.diff(date, 'seconds');
const diffInMinutes = now.diff(date, 'minutes');

if (diffInSeconds < 60) {
return 'now';
} else if (diffInMinutes < 60) {
return `${diffInMinutes} minutes ago`;
} else {
return date.format('hh:mm A');
}
}
222 changes: 222 additions & 0 deletions src/common/components/InAppChatNotifications.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { FC, useContext } from 'react';

import { useNavigate } from 'react-router-dom';
import { css } from 'styled-components';

import { Box, Cross, Pin, Text, Image, FillCircle, ChatFilled, EditProfile } from 'blocks';

import { AppContextType } from 'types/context';
import { AppContext } from 'contexts/AppContext';

import { convertTimeStamp } from 'common/Common.utils';
import { shortenText } from 'helpers/UtilityHelper';
import { caip10ToWallet } from 'helpers/w2w';

import { useResolveWeb3Name } from 'hooks/useResolveWeb3Name';
import { useGetGroupInfo, useGetUserProfileDetails } from 'queries';

type InAppChatNotificationsProps = {
chatDetails: Array<any>;
onClose: () => void;
};

const getContentText = (chatDetail: any) => {
if (chatDetail.message.type === 'Text') return chatDetail.message.content;
if (chatDetail.message.type === 'Image') return 'Image';
if (chatDetail.message.type === 'File') return 'File';
if (chatDetail.message.type === 'MediaEmbed' || chatDetail.message.type === 'GIF') return 'GIF';
};
const getContentImage = (chatDetail: any) => {
if (
chatDetail.message.type === 'Image' ||
chatDetail.message.type === 'MediaEmbed' ||
chatDetail.message.type === 'GIF'
)
return (
<Image
size={16}
color="icon-tertiary"
/>
);
if (chatDetail.message.type === 'File')
return (
<Pin
size={16}
color="icon-tertiary"
/>
);
};

const InAppChatNotifications: FC<InAppChatNotificationsProps> = ({ chatDetails, onClose }) => {
const { web3NameList }: AppContextType = useContext(AppContext)!;
const fromAddress = caip10ToWallet(chatDetails[0]?.from);
const { data: userProfileDetails } = useGetUserProfileDetails(fromAddress, {
refetchOnWindowFocus: false,
staleTime: Infinity,
refetchInterval: 3600000, // 1 hour,
});
const { data: groupInfo } = useGetGroupInfo(chatDetails[0]?.meta?.group ? chatDetails[0].chatId : '', {
refetchOnWindowFocus: false,
staleTime: Infinity,
refetchInterval: 3600000, // 1 hour,
});

const navigate = useNavigate();

useResolveWeb3Name(fromAddress);
const web3Name = web3NameList[fromAddress];
const sender = web3Name ? web3Name : shortenText(fromAddress, 6);
const displayName = chatDetails[0]?.meta?.group
? groupInfo?.groupName || shortenText(chatDetails[0]?.chatId, 6)
: web3Name || shortenText(fromAddress, 6);

const latestTimestamp = convertTimeStamp(chatDetails[chatDetails.length - 1]?.timestamp);

//optimise it and fix the close button z-index
return (
<Box
width="397px"
display="flex"
>
{chatDetails && userProfileDetails && (
<Box
padding="spacing-sm"
display="flex"
borderRadius="radius-sm"
flexDirection="column"
gap="spacing-xxs"
border="border-sm solid stroke-tertiary"
backgroundColor="surface-primary"
width="inherit"
cursor="pointer"
onClick={() => navigate(`/chat/chatid:${chatDetails[0].chatId}`)}
>
<Box
display="flex"
justifyContent="space-between"
alignItems="center"
>
<Box
display="flex"
gap="spacing-xxs"
alignItems="center"
>
<Box
width="24px"
height="24px"
overflow="hidden"
borderRadius="radius-round"
css={css`
flex-shrink: 0;
`}
>
{chatDetails[0].event === 'chat.request' ? (
<ChatFilled
size={24}
color="icon-brand-medium"
/>
) : (
<img
width="100%"
height="100%"
src={userProfileDetails?.picture || groupInfo?.groupImage || ''}
alt={displayName}
/>
)}
</Box>
<Text
color="text-primary"
variant="bes-semibold"
>
{chatDetails[0].event === 'chat.request' ? 'Push Chat' : displayName}
</Text>
<FillCircle
color="icon-tertiary"
size={4}
/>
<Text
color="text-tertiary"
variant="c-semibold"
>
{latestTimestamp}
</Text>
</Box>
<Box
onClick={(e) => {
e.stopPropagation();
onClose();
}}
cursor="pointer"
>
<Cross
color="icon-primary"
size={16}
/>
</Box>
</Box>
{chatDetails.map((chatDetail: any) =>
chatDetail.event === 'chat.request' ? (
<Box
display="flex"
gap="spacing-xxxs"
alignItems="center"
>
<EditProfile
size={16}
color="icon-tertiary"
/>
<Box>
<Text
color="text-primary"
variant="bes-bold"
as="span"
>
{displayName}{' '}
</Text>
<Text
color="text-secondary"
variant="bes-regular"
as="span"
>
has sent you a chat request
</Text>
</Box>
</Box>
) : (
<Box
display="flex"
flexDirection="column"
>
<Box
display="flex"
gap="spacing-xxxs"
alignItems="center"
>
{chatDetails[0]?.meta?.group && (
<Text
color="text-primary"
variant="bes-bold"
as="span"
>
{sender}{' '}
</Text>
)}
{chatDetail.message.type !== 'Text' ? <Box>{getContentImage(chatDetail)}</Box> : null}
<Text
color="text-secondary"
variant="bes-regular"
numberOfLines={2}
>
{getContentText(chatDetail)}
</Text>
</Box>
</Box>
)
)}
</Box>
)}
</Box>
);
};

export { InAppChatNotifications };
1 change: 1 addition & 0 deletions src/common/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './TokenFaucet';
export * from './CopyButton';
export * from './VerifiedChannelTooltipContent';
export * from './InAppChannelNotifications';
export * from './InAppChatNotifications';
Loading

0 comments on commit 66344c3

Please sign in to comment.