From 4f3fa50446d5a084b74fc42642ebee61a4223d46 Mon Sep 17 00:00:00 2001 From: Abhay Keyvalue <95466701+abhay-keyvalue@users.noreply.github.com> Date: Wed, 8 May 2024 10:36:03 +0530 Subject: [PATCH 01/10] refactor: Update realtime fetch logic and rename functions --- README.md | 4 -- package-lock.json | 14 ++--- package.json | 2 +- src/components/sirenInbox.tsx | 17 ++--- src/components/sirenInboxIcon.tsx | 5 +- src/components/sirenProvider.tsx | 63 +++++++++++++------ src/utils/constants.ts | 7 ++- src/utils/sirenHook.ts | 10 +-- .../components/sirenNotificationIcon.test.tsx | 16 +++-- tests/components/sirenProvider.test.tsx | 6 +- tests/utils/sirenHook.test.ts | 36 +++++------ 11 files changed, 100 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index 7645c49..ef4f162 100644 --- a/README.md +++ b/README.md @@ -246,10 +246,6 @@ function MyComponent() { markAsReadById(id); } - function handleDeleteNotification(id) { - deleteById(id); - } - return ( {/* Your component logic */} ); diff --git a/package-lock.json b/package-lock.json index b024c42..f77497c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@sirenapp/js-sdk": "^1.0.0", + "@sirenapp/js-sdk": "^1.1.0", "pubsub-js": "^1.9.4" }, "devDependencies": { @@ -4827,9 +4827,9 @@ } }, "node_modules/@sirenapp/js-sdk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sirenapp/js-sdk/-/js-sdk-1.0.0.tgz", - "integrity": "sha512-zypXxRdTNkWRzJhiIYPxlmrpIj2vMjh5H2u0Bbrw0uI+RPLap47HxKBMGMfaPwFP9UxdoZUuRze3Bizj4vt6AQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sirenapp/js-sdk/-/js-sdk-1.1.0.tgz", + "integrity": "sha512-4V6nltHLhPLOsPMQO7OqllUeFEELvgeb8RSPb/uuwZ1qr05pClQEgoCy5/AlX0glsfHCdoQUfAyWt3akJEenrw==", "dependencies": { "promise-polyfill": "^8.3.0", "tslib": "^2.6.2", @@ -19030,9 +19030,9 @@ } }, "@sirenapp/js-sdk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sirenapp/js-sdk/-/js-sdk-1.0.0.tgz", - "integrity": "sha512-zypXxRdTNkWRzJhiIYPxlmrpIj2vMjh5H2u0Bbrw0uI+RPLap47HxKBMGMfaPwFP9UxdoZUuRze3Bizj4vt6AQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sirenapp/js-sdk/-/js-sdk-1.1.0.tgz", + "integrity": "sha512-4V6nltHLhPLOsPMQO7OqllUeFEELvgeb8RSPb/uuwZ1qr05pClQEgoCy5/AlX0glsfHCdoQUfAyWt3akJEenrw==", "requires": { "promise-polyfill": "^8.3.0", "tslib": "^2.6.2", diff --git a/package.json b/package.json index ac91656..6ffad70 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,6 @@ ], "dependencies": { "pubsub-js": "^1.9.4", - "@sirenapp/js-sdk": "^1.0.0" + "@sirenapp/js-sdk": "^1.1.0" } } diff --git a/src/components/sirenInbox.tsx b/src/components/sirenInbox.tsx index aa7c6e7..c91565a 100644 --- a/src/components/sirenInbox.tsx +++ b/src/components/sirenInbox.tsx @@ -22,6 +22,7 @@ const { TOKEN_VERIFICATION_PENDING, MAXIMUM_ITEMS_PER_FETCH, VerificationStatus, + EventType, errorMap } = Constants; const { applyTheme, isNonEmptyArray, updateNotifications } = CommonUtils; @@ -149,7 +150,7 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { } }, [eventListenerData]); - const handleMarkNotificationsAsViewed = async (newNotifications = notifications) => { + const handleMarkAllAsViewed = async (newNotifications = notifications) => { const currentTimestamp = new Date().getTime(); const isoString = new Date(currentTimestamp).toISOString(); const response = await markAllAsViewed( @@ -168,10 +169,10 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { // Clean up - stop polling when component unmounts const cleanUp = () => () => { - siren?.stopRealTimeNotificationFetch(); + siren?.stopRealTimeFetch(EventType.NOTIFICATION); setNotifications([]); PubSub.unsubscribe(events.NOTIFICATION_LIST_EVENT); - handleMarkNotificationsAsViewed(); + handleMarkAllAsViewed(); }; const notificationSubscriber = async (type: string, dataString: string) => { @@ -183,7 +184,7 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { // Initialize Siren SDK and fetch notifications const initialize = async (): Promise => { if (siren) { - siren?.stopRealTimeNotificationFetch(); + siren?.stopRealTimeFetch(EventType.NOTIFICATION); const allNotifications = await fetchNotifications(siren, true); const notificationParams: fetchProps = { size: notificationsPerPage }; @@ -191,7 +192,7 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { notificationParams.start = allNotifications[0].createdAt; if (verificationStatus === VerificationStatus.SUCCESS) - siren?.startRealTimeNotificationFetch(notificationParams); + siren?.startRealTimeFetch({eventType: EventType.NOTIFICATION, params: notificationParams}); } }; @@ -214,7 +215,7 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { if (nonEmptyResponse) { const updatedNotifications = isResetList ? responseData : [...notifications, ...responseData]; - isResetList && handleMarkNotificationsAsViewed(updatedNotifications); + isResetList && handleMarkAllAsViewed(updatedNotifications); setNotifications(updatedNotifications); return updatedNotifications; @@ -273,7 +274,7 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { setNotifications([]); setIsLoading(true); - siren?.stopRealTimeNotificationFetch(); + siren?.stopRealTimeFetch(EventType.NOTIFICATION); const allNotifications = (await fetchNotifications(siren, true)) || []; const notificationParams: fetchProps = { size: notificationsPerPage }; @@ -281,7 +282,7 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { notificationParams.start = allNotifications[0].createdAt; if (verificationStatus === VerificationStatus.SUCCESS) - siren?.startRealTimeNotificationFetch(notificationParams); + siren?.startRealTimeFetch({eventType: EventType.NOTIFICATION, params:notificationParams}); } catch (err) { setIsLoading(false); setIsError(true); diff --git a/src/components/sirenInboxIcon.tsx b/src/components/sirenInboxIcon.tsx index 6481c8a..a73551c 100644 --- a/src/components/sirenInboxIcon.tsx +++ b/src/components/sirenInboxIcon.tsx @@ -14,6 +14,7 @@ const { eventTypes, events, defaultStyles, + EventType, VerificationStatus, errorMap } = Constants; @@ -67,7 +68,7 @@ const SirenInboxIcon = (props: SirenInboxIconProps) => { // Clean up - stop polling when component unmounts const cleanUp = () => () => { - siren?.stopRealTimeUnviewedCountFetch(); + siren?.stopRealTimeFetch(EventType.UNVIEWED_COUNT); PubSub.unsubscribe(events.NOTIFICATION_COUNT_EVENT); seUnviewedCount(0); }; @@ -102,7 +103,7 @@ const SirenInboxIcon = (props: SirenInboxIconProps) => { const unViewed: UnviewedCountReturnResponse | null = await siren.fetchUnviewedNotificationsCount(); - siren.startRealTimeUnviewedCountFetch(); + siren.startRealTimeFetch({eventType: EventType.UNVIEWED_COUNT}); if (unViewed?.data) seUnviewedCount(unViewed.data?.unviewedCount || 0); if (unViewed?.error) onError(unViewed?.error); } diff --git a/src/components/sirenProvider.tsx b/src/components/sirenProvider.tsx index e26df72..eecbbf8 100644 --- a/src/components/sirenProvider.tsx +++ b/src/components/sirenProvider.tsx @@ -7,7 +7,7 @@ import type { NotificationDataType, NotificationsApiResponse, SirenErrorType, - UnviewedCountApiResponse, + UnviewedCountApiResponse } from '@sirenapp/js-sdk/dist/esm/types'; import type { SirenProviderConfigProps } from '../types'; @@ -17,7 +17,8 @@ import { eventTypes, IN_APP_RECIPIENT_UNAUTHENTICATED, MAXIMUM_RETRY_COUNT, - VerificationStatus + VerificationStatus, + EventType } from '../utils/constants'; import { useSiren } from '../utils'; @@ -74,10 +75,11 @@ const SirenProvider: React.FC = ({ config, children }) => { let retryCount = 0; const { markAllAsViewed } = useSiren(); - + const [siren, setSiren] = useState(null); - const [verificationStatus, setVerificationStatus] = useState(VerificationStatus.PENDING); - + const [verificationStatus, setVerificationStatus] = useState( + VerificationStatus.PENDING + ); useEffect(() => { if (config?.recipientId && config?.userToken) { @@ -90,8 +92,8 @@ const SirenProvider: React.FC = ({ config, children }) => { }, [config]); const stopRealTimeFetch = (): void => { - siren?.stopRealTimeNotificationFetch(); - siren?.stopRealTimeUnviewedCountFetch(); + siren?.stopRealTimeFetch(EventType.NOTIFICATION); + siren?.stopRealTimeFetch(EventType.UNVIEWED_COUNT); }; const sendResetDataEvents = () => { @@ -106,9 +108,17 @@ const SirenProvider: React.FC = ({ config, children }) => { PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(updateNotificationPayload)); }; - const onUnViewedCountReceived = (response: UnviewedCountApiResponse): void => { - const totalUnviewed = response?.data?.totalUnviewed; + const onNewNotificationEvent = (responseData: NotificationDataType[]) => { + logger.info(`new notifications : ${JSON.stringify(responseData)}`); + + markAllAsViewed(responseData[0].createdAt); + const payload = { newNotifications: responseData, action: eventTypes.NEW_NOTIFICATIONS }; + + PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload)); + }; + const onTotalUnviewedCountEvent = (response: UnviewedCountApiResponse) => { + const totalUnviewed = response.data?.totalUnviewed; const payload = { unviewedCount: totalUnviewed, action: eventTypes.UPDATE_NOTIFICATIONS_COUNT @@ -117,25 +127,38 @@ const SirenProvider: React.FC = ({ config, children }) => { PubSub.publish(events.NOTIFICATION_COUNT_EVENT, JSON.stringify(payload)); }; - const onNotificationReceived = (response: NotificationsApiResponse): void => { - const responseData: NotificationDataType[] = response?.data || []; + const handleNotificationEvent = (response: NotificationsApiResponse) => { + const responseData = response?.data; - if (isNonEmptyArray(responseData)) { - logger.info(`new notifications : ${JSON.stringify(responseData)}`); - - markAllAsViewed(responseData[0].createdAt); - const payload = { newNotifications: response?.data, action: eventTypes.NEW_NOTIFICATIONS }; + if (Array.isArray(responseData) && isNonEmptyArray(responseData)) + onNewNotificationEvent(responseData); - PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload)); - } }; + const handleUnviewedCountEvent = (response: UnviewedCountApiResponse) => { + const responseData = response?.data; + if (responseData && 'totalUnviewed' in responseData) + onTotalUnviewedCountEvent(response); + + }; + const onEventReceive = ( + response: NotificationsApiResponse | UnviewedCountApiResponse = {}, + eventType: EventType + ) => { + switch (eventType) { + case EventType.NOTIFICATION: + handleNotificationEvent(response as NotificationsApiResponse); + break; + case EventType.UNVIEWED_COUNT: + handleUnviewedCountEvent(response as UnviewedCountApiResponse); + break; + } + }; const onStatusChange = (status: VerificationStatus) => { setVerificationStatus(status); }; - - const actionCallbacks = { onUnViewedCountReceived, onNotificationReceived, onStatusChange }; + const actionCallbacks = { onEventReceive, onStatusChange }; const getDataParams = () => { return { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 448b9ba..0c27b29 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -18,7 +18,7 @@ export const COLORS = { neutralColor: '#232326', borderColor: '#344054', dateColor: '#98A2B3', - deleteIcon: '#98A2B3', + deleteIcon: '#D0D5DD', timerIcon: '#98A2B3', clearAllIcon: '#D0D5DD', infiniteLoader: '#F56630' @@ -58,6 +58,11 @@ export const levelLogFns = { [LogLevel.ERROR]: console.error }; +export enum EventType { + NOTIFICATION = "NOTIFICATIONS", + UNVIEWED_COUNT = "UNVIEWED_COUNT" +} + export enum eventTypes { MARK_ITEM_AS_VIEWED = 'MARK_ITEM_AS_VIEWED', MARK_ITEM_AS_READ = 'MARK_ITEM_AS_READ', diff --git a/src/utils/sirenHook.ts b/src/utils/sirenHook.ts index f6c799e..d821a4a 100644 --- a/src/utils/sirenHook.ts +++ b/src/utils/sirenHook.ts @@ -9,7 +9,7 @@ const useSiren = () => { const markAsReadById = async (id: string) => { if (siren) if (id?.length > 0) { - const response = await siren?.markNotificationAsReadById(id); + const response = await siren?.markAsReadById(id); if (response?.data) { const payload = { id, action: eventTypes.MARK_ITEM_AS_READ }; @@ -27,7 +27,7 @@ const useSiren = () => { const markAsReadByDate = async (untilDate: string) => { if (siren && untilDate) { - const response = await siren?.markNotificationsAsReadByDate(untilDate); + const response = await siren?.markAsReadByDate(untilDate); if (response?.data) { const payload = { action: eventTypes.MARK_ALL_AS_READ }; @@ -44,7 +44,7 @@ const useSiren = () => { const deleteById = async (id: string, shouldUpdateList: boolean = true) => { if (siren) if (id?.length > 0) { - const response = await siren?.deleteNotificationById(id); + const response = await siren?.deleteById(id); if (response?.data && shouldUpdateList) { const payload = { id, action: eventTypes.DELETE_ITEM }; @@ -62,7 +62,7 @@ const useSiren = () => { const deleteByDate = async (untilDate: string) => { if (siren && untilDate) { - const response = await siren.deleteNotificationsByDate(untilDate); + const response = await siren.deleteByDate(untilDate); if (response?.data) { const payload = { action: eventTypes.DELETE_ALL_ITEM }; @@ -78,7 +78,7 @@ const useSiren = () => { const markAllAsViewed = async (untilDate: string) => { if (siren && untilDate) { - const response = await siren?.markNotificationsAsViewed(untilDate); + const response = await siren?.markAllAsViewed(untilDate); if (response?.data) { const payload = { unviewedCount: 0, action: eventTypes.UPDATE_NOTIFICATIONS_COUNT }; diff --git a/tests/components/sirenNotificationIcon.test.tsx b/tests/components/sirenNotificationIcon.test.tsx index 238f600..0e29724 100644 --- a/tests/components/sirenNotificationIcon.test.tsx +++ b/tests/components/sirenNotificationIcon.test.tsx @@ -34,18 +34,16 @@ describe('SirenInboxIcon', () => { const notificationIcon = ; const mockSiren:Pick = { - markNotificationAsReadById: jest.fn(), - markNotificationsAsReadByDate: jest.fn(), - deleteNotificationById: jest.fn(), - deleteNotificationsByDate: jest.fn(), - markNotificationsAsViewed: jest.fn(), + markAsReadById: jest.fn(), + markAsReadByDate: jest.fn(), + deleteById: jest.fn(), + deleteByDate: jest.fn(), + markAllAsViewed: jest.fn(), verifyToken: jest.fn(), fetchUnviewedNotificationsCount: jest.fn(async () => UnviewedCountReturnResponse), fetchAllNotifications: jest.fn(), - startRealTimeNotificationFetch: jest.fn(), - stopRealTimeNotificationFetch: jest.fn(), - startRealTimeUnviewedCountFetch: jest.fn(), - stopRealTimeUnviewedCountFetch: jest.fn(), + startRealTimeFetch: jest.fn(), + stopRealTimeFetch: jest.fn(), }; jest.spyOn(sirenProvider, 'useSirenContext').mockReturnValue({ diff --git a/tests/components/sirenProvider.test.tsx b/tests/components/sirenProvider.test.tsx index b155e4b..0c5b0c2 100644 --- a/tests/components/sirenProvider.test.tsx +++ b/tests/components/sirenProvider.test.tsx @@ -24,16 +24,14 @@ describe('SirenProvider', () => { ); const mocErrorFn = jest.fn(); - const mockNotificationHandler = jest.fn(); - const mockCountHandler = jest.fn(); + const mockEventHandler = jest.fn(); const sirenObject = new Siren({ token: 'user-token', recipientId: 'recipient-id', onError: mocErrorFn, actionCallbacks: { - onNotificationReceived: mockNotificationHandler, - onUnViewedCountReceived: mockCountHandler + onEventReceive: mockEventHandler, } }); diff --git a/tests/utils/sirenHook.test.ts b/tests/utils/sirenHook.test.ts index 848bb5a..979b2e7 100644 --- a/tests/utils/sirenHook.test.ts +++ b/tests/utils/sirenHook.test.ts @@ -73,21 +73,19 @@ const MarkAsViewedResponse = { describe('useSiren hook', () => { const mockSiren: Pick = { - markNotificationAsReadById: jest.fn(async () => Response), - markNotificationsAsReadByDate: jest.fn(async () => ActionResponse), - deleteNotificationById: jest.fn(async () => ActionResponse), - deleteNotificationsByDate: jest.fn(async () => ActionResponse), - markNotificationsAsViewed: jest.fn(async () => MarkAsViewedResponse), + markAsReadById: jest.fn(async () => Response), + markAsReadByDate: jest.fn(async () => ActionResponse), + deleteById: jest.fn(async () => ActionResponse), + deleteByDate: jest.fn(async () => ActionResponse), + markAllAsViewed: jest.fn(async () => MarkAsViewedResponse), verifyToken: jest.fn(), fetchUnviewedNotificationsCount: jest.fn(), fetchAllNotifications: jest.fn(), - startRealTimeNotificationFetch: jest.fn(), - stopRealTimeNotificationFetch: jest.fn(), - startRealTimeUnviewedCountFetch: jest.fn(), - stopRealTimeUnviewedCountFetch: jest.fn() + startRealTimeFetch: jest.fn(), + stopRealTimeFetch: jest.fn(), }; - it('should call siren.markNotificationAsReadById and update notifications list when siren exists and id is not empty', async () => { + it('should call siren.markAsReadById and update notifications list when siren exists and id is not empty', async () => { // Mock useSirenContext jest.spyOn(sirenProvider, 'useSirenContext').mockReturnValue({ @@ -98,11 +96,11 @@ describe('useSiren hook', () => { const { markAsReadById } = useSiren(); const response = await markAsReadById('xyz'); - expect(mockSiren.markNotificationAsReadById).toHaveBeenCalledWith('xyz'); + expect(mockSiren.markAsReadById).toHaveBeenCalledWith('xyz'); expect(response).toEqual(Response); }); - it('should call siren.markNotificationsAsReadByDate and update notifications list when siren exists and untilDate is provided', async () => { + it('should call siren.markAsReadByDate and update notifications list when siren exists and untilDate is provided', async () => { jest.spyOn(sirenProvider, 'useSirenContext').mockReturnValue({ siren: mockSiren as Siren, verificationStatus: VerificationStatus.SUCCESS @@ -112,11 +110,11 @@ describe('useSiren hook', () => { const untilDate = '2024-02-28T00:00:00Z'; const response = await markAsReadByDate(untilDate); - expect(mockSiren.markNotificationsAsReadByDate).toHaveBeenCalledWith(untilDate); + expect(mockSiren.markAsReadByDate).toHaveBeenCalledWith(untilDate); expect(response).toEqual(ActionResponse); }); - it('should call siren.deleteNotificationById and update notifications list when siren exists and id is not empty', async () => { + it('should call siren.deleteById and update notifications list when siren exists and id is not empty', async () => { jest.spyOn(sirenProvider, 'useSirenContext').mockReturnValue({ siren: mockSiren as Siren, @@ -126,11 +124,11 @@ describe('useSiren hook', () => { const { deleteById } = useSiren(); const response = await deleteById('xyz'); - expect(mockSiren.deleteNotificationById).toHaveBeenCalledWith('xyz'); + expect(mockSiren.deleteById).toHaveBeenCalledWith('xyz'); expect(response).toEqual(ActionResponse); }); - it('should call siren.deleteNotificationsByDate and update notifications list when siren exists and untilDate is provided', async () => {; + it('should call siren.deleteByDate and update notifications list when siren exists and untilDate is provided', async () => {; jest.spyOn(sirenProvider, 'useSirenContext').mockReturnValue({ siren: mockSiren as Siren, @@ -141,11 +139,11 @@ describe('useSiren hook', () => { const untilDate = '2024-02-28T00:00:00Z'; const response = await deleteByDate(untilDate); - expect(mockSiren.deleteNotificationsByDate).toHaveBeenCalledWith(untilDate); + expect(mockSiren.deleteByDate).toHaveBeenCalledWith(untilDate); expect(response).toEqual(ActionResponse); }); - it('should call siren.markNotificationsAsViewed and update notifications list when siren exists and untilDate is provided', async () => { + it('should call siren.markAllAsViewed and update notifications list when siren exists and untilDate is provided', async () => { jest.spyOn(sirenProvider, 'useSirenContext').mockReturnValue({ siren: mockSiren as Siren, @@ -156,7 +154,7 @@ describe('useSiren hook', () => { const untilDate = '2024-02-28T00:00:00Z'; const response = await markAllAsViewed(untilDate); - expect(mockSiren.markNotificationsAsViewed).toHaveBeenCalledWith(untilDate); + expect(mockSiren.markAllAsViewed).toHaveBeenCalledWith(untilDate); expect(response).toEqual(MarkAsViewedResponse); }); }); From 5254cdbd0c7449f47570733e5a3d5136ec9bea62 Mon Sep 17 00:00:00 2001 From: Abhay Keyvalue <95466701+abhay-keyvalue@users.noreply.github.com> Date: Thu, 9 May 2024 14:35:05 +0530 Subject: [PATCH 02/10] fix: Terminate retry verification api trigger if config changes while retrying --- src/components/sirenInbox.tsx | 5 +++-- src/components/sirenProvider.tsx | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/sirenInbox.tsx b/src/components/sirenInbox.tsx index c91565a..e6e1448 100644 --- a/src/components/sirenInbox.tsx +++ b/src/components/sirenInbox.tsx @@ -129,12 +129,13 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { useEffect(() => { // Initialize Siren SDK and start polling notifications - if (verificationStatus !== VerificationStatus.PENDING && siren) { + if (verificationStatus === VerificationStatus.SUCCESS && siren) { initialize(); } else if(verificationStatus === VerificationStatus.FAILED) { setIsError(true); setIsLoading(false); - if (onError) onError(errorMap.MISSING_PARAMETER); + setNotifications([]); + if (onError) onError(errorMap.INVALID_CREDENTIALS); } }, [siren, verificationStatus]); diff --git a/src/components/sirenProvider.tsx b/src/components/sirenProvider.tsx index eecbbf8..8bafcec 100644 --- a/src/components/sirenProvider.tsx +++ b/src/components/sirenProvider.tsx @@ -170,7 +170,11 @@ const SirenProvider: React.FC = ({ config, children }) => { }; const retryVerification = (error: SirenErrorType) => { - if (error.Code === IN_APP_RECIPIENT_UNAUTHENTICATED && retryCount < MAXIMUM_RETRY_COUNT) + if ( + error.Code === IN_APP_RECIPIENT_UNAUTHENTICATED && + retryCount < MAXIMUM_RETRY_COUNT && + verificationStatus === VerificationStatus.FAILED + ) setTimeout(() => { initialize(); retryCount++; @@ -181,6 +185,7 @@ const SirenProvider: React.FC = ({ config, children }) => { // Function to initialize the Siren SDK and fetch notifications const initialize = (): void => { + setVerificationStatus(VerificationStatus.PENDING); const dataParams: InitConfigType = getDataParams(); const siren = new Siren(dataParams); From 90952526b4568e6443b3d3f85a42f73be34019d4 Mon Sep 17 00:00:00 2001 From: Abhay Keyvalue <95466701+abhay-keyvalue@users.noreply.github.com> Date: Thu, 9 May 2024 14:36:33 +0530 Subject: [PATCH 03/10] refactor: Rename dateIcon to timerIcon --- README.md | 2 +- src/types.ts | 2 +- src/utils/commonUtils.ts | 22 +++++++++++----------- src/utils/constants.ts | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ef4f162..7376739 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ Here are the custom style options for the notification inbox: deleteIcon?:{ size?: number }; - dateIcon?:{ + timerIcon?:{ size?: number }; clearAllIcon?:{ diff --git a/src/types.ts b/src/types.ts index 5877fff..153c9aa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -182,7 +182,7 @@ export type CustomStyleProps = { deleteIcon?:{ size?: number }; - dateIcon?:{ + timerIcon?:{ size?: number }; clearAllIcon?:{ diff --git a/src/utils/commonUtils.ts b/src/utils/commonUtils.ts index 5718509..e834ae0 100644 --- a/src/utils/commonUtils.ts +++ b/src/utils/commonUtils.ts @@ -157,23 +157,23 @@ export const applyTheme = ( }, timerIcon: { borderColor: theme.colors?.timerIcon || DefaultTheme[mode].colors.timerIcon, - width: customStyles.dateIcon?.size || defaultStyles.dateIcon.size, - height: customStyles.dateIcon?.size || defaultStyles.dateIcon.size, - borderRadius: (customStyles.dateIcon?.size || defaultStyles.dateIcon.size) / 2, - borderWidth: (customStyles.dateIcon?.size || defaultStyles.dateIcon.size) * 0.1, - padding: (customStyles.dateIcon?.size || defaultStyles.dateIcon.size) * 0.1 + width: customStyles.timerIcon?.size || defaultStyles.timerIcon.size, + height: customStyles.timerIcon?.size || defaultStyles.timerIcon.size, + borderRadius: (customStyles.timerIcon?.size || defaultStyles.timerIcon.size) / 2, + borderWidth: (customStyles.timerIcon?.size || defaultStyles.timerIcon.size) * 0.1, + padding: (customStyles.timerIcon?.size || defaultStyles.timerIcon.size) * 0.1 }, timerIconLine: { backgroundColor: theme.colors?.timerIcon || DefaultTheme[mode].colors.timerIcon, - height: (customStyles.dateIcon?.size || defaultStyles.dateIcon.size) * 0.36, - width: (customStyles.dateIcon?.size || defaultStyles.dateIcon.size) * 0.1 + height: (customStyles.timerIcon?.size || defaultStyles.timerIcon.size) * 0.36, + width: (customStyles.timerIcon?.size || defaultStyles.timerIcon.size) * 0.1 }, timerIconLine2: { backgroundColor: theme.colors?.timerIcon || DefaultTheme[mode].colors.timerIcon, - height: (customStyles.dateIcon?.size || defaultStyles.dateIcon.size) * 0.28, - width: (customStyles.dateIcon?.size || defaultStyles.dateIcon.size) * 0.1, - marginLeft: (customStyles.dateIcon?.size || defaultStyles.dateIcon.size) * 0.22, - marginTop: -(customStyles.dateIcon?.size || defaultStyles.dateIcon.size) * 0.1 + height: (customStyles.timerIcon?.size || defaultStyles.timerIcon.size) * 0.28, + width: (customStyles.timerIcon?.size || defaultStyles.timerIcon.size) * 0.1, + marginLeft: (customStyles.timerIcon?.size || defaultStyles.timerIcon.size) * 0.22, + marginTop: -(customStyles.timerIcon?.size || defaultStyles.timerIcon.size) * 0.1 }, cardContainer: { padding: customStyles.notificationCard?.padding || defaultStyles.notificationCard.padding diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 0c27b29..e08cf10 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -147,7 +147,7 @@ export const defaultStyles = { deleteIcon: { size: 14 }, - dateIcon: { + timerIcon: { size: 12 }, clearAllIcon: { From 311bebc72e6a1cc38674c41f8916b7c489e01be6 Mon Sep 17 00:00:00 2001 From: Abhay Keyvalue <95466701+abhay-keyvalue@users.noreply.github.com> Date: Fri, 10 May 2024 11:53:38 +0530 Subject: [PATCH 04/10] feat: Add media thumbnail support --- README.md | 2 + src/assets/failedImageDark.png | Bin 0 -> 2728 bytes src/assets/failedImageLight.png | Bin 0 -> 2720 bytes src/components/card.tsx | 67 ++++++++++++++++++++++++++------ src/components/sirenInbox.tsx | 3 +- src/types.ts | 3 ++ src/utils/commonUtils.ts | 5 ++- src/utils/constants.ts | 6 ++- src/utils/defaultTheme.ts | 4 +- 9 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 src/assets/failedImageDark.png create mode 100644 src/assets/failedImageLight.png diff --git a/README.md b/README.md index 7376739..05bf074 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,8 @@ Here are the custom style options for the notification inbox: disableAutoMarkAsRead?: boolean; deleteIcon?: JSX.Element; hideDelete?: boolean; + hideMediaThumbnail?: boolean; + onMediaThumbnailClick?: (notification: NotificationDataType) => void; }; ``` diff --git a/src/assets/failedImageDark.png b/src/assets/failedImageDark.png new file mode 100644 index 0000000000000000000000000000000000000000..3f5f0567a6939ee0288d49c47b01bb07b74ff871 GIT binary patch literal 2728 zcmeHJ{Xf&|8~<#YvL;1CRC;v|=U{n0N5VWD+K4!t){=*D^2$Ril#Q`*B98~(67tYW z38ORmYRbkoM{+vFNH}DRl81?TT0+~-$A58tI6vI4`@Y}TeP8!=U9b0jz1}(gCo!6E z12_NxnqF8B8~{Mg006RWhdPL;GmIj^Nh20}<}v{28gC7V7w+3d5QJRDVcY?B@BUeE zv+cZ_uNwe7D%|-s3u zVFP%z{K2OKsIiWCy|c7E3X8q|u3;KK@6I`jJ8XRYMz)7HZnBViuqel*sLY|8ay1pb zvW_*0yAW_O?geFfiIq70JxSE`u&y%0}j2K$`Ol$F=)WGk_K;GsM~=A4)sRf zikEt*0XvKXfHa~z49Na(@oz2yXDH2$HgQdFw9NMWsCi@CYj!Yi$ReceZ=k@i3aXay z`O`={*7TAAUr%qbfaPc}T-<`o>r&Rl)Z}jCpYS@mYh;qc4y1k)_w7=rmdoRn zY0;A^)(h|8-RhY=5rIUgoEY!2^g-dt`-Db%4 zk2Bj7+w|GYFJ~TmkjcNRR5nuUnbFHO1vk0h*h;3MeWf|@MdGz*_f9k;t@BEr=MP%r z+kH>sqQlNQ`)+G8a)%|l&zqmwYmPjW7yh8&_7r{0S&t;hfG^(c1Rtu+)-6k5>5&dr zL0?_@Usr5Go9*0T*|&Hk$t44BHJoT2HkJhG z64+FZ!KLLjzm?SkbPeQP{BZbF_|0a)O~$An z$OLVkb-?BjN1ts8I?VHjFmZ+ZsI!5H;Zhd7DI47T;|uch^J^*9uU>rKLD_-0P?}>jSgA+NYk#%4TVJ z8zqP$jwPmr#ev-CG&isK9CT!aM00IpPAM6>ox$8IEJ&wG0O_uD;d!BpQlDpJ*g2JAZds9K{Lq-bh=_i9YOFVNcHO$7&l8pXsqAa z?3Vih?*{x_6mODPPtY>yU_&y-Lr(W=$m_7bIF^lm58c%bHWlrJwfmBS3Xm_tCWj5k z`<}CiX_k>bS_e7)cU^Z`Oj+;&n3zvbmSK15|5ydxZ{986vGb@t#szRiRY8Gg+GhVP z{@q1*r~~REJy6;(=XK1@%~eqV5OzHO|G^lSvznmJkxHd{4b#21c};x2a=L#rl?(v; z_+Oy0eF)%$k#AeRpOxIgS8mLyd>Nlq&*oPHDzScB!V>WEJn6x9 HCtdvqL$F)Q literal 0 HcmV?d00001 diff --git a/src/assets/failedImageLight.png b/src/assets/failedImageLight.png new file mode 100644 index 0000000000000000000000000000000000000000..74ab4a87966d23addd617c5cca472d732883165b GIT binary patch literal 2720 zcmeHJ`&ZK07XQLXCDHp?n7~|CnA4}ya3e49#|HQp(%@1dtv-kS!$Jy)bvp?5-y@|aVHurf=Wm#(Ukx+yhVKl z1)XR|FGm1qW^P`KgaH8D+TGbPAQLh_5zY*~px?JNsTa}~;e>JgZpS7svLD${`V5() zD|?nn{#2<%+=~ADJ7*rzlC66qg-=2rS4KB&A;dAxv`Z_}zoHc#QU8jxZHca#cvN90 zp&IujMV}OSDFze@+sRnL)~W-zdj>`F-1L_0X6!Tv3MAv(rbFKsrIsDYDap&*8g}<0 zM~{9mPH33(5O*q0D96Eo2m`E_ggaoVSzS)H37ypNH1)@hr^-~N^$#dD05Hd@*Y)61 zBxyH_9o0N2LGOeX%}QH4SQzBr0ERCC2K;Co05C{@9sbvgAaj6FKaa_@MI&t;uQ3(%C3)zz zsH2qMv!=PsY%YMpnwSkb#edc|>zfANxHB63u0&z442Iamcm~|hv+t6A)yK%unS9g@OD+5JTFQuiBE)3Su`@9tD2GP7k5#l1%}gm-<6v? zXiM1+J|k7PRRcU`hOO8|)bShNm!a8ZHb}N6s*nd{rY5cqgY=uU2;JeS3Mv9Se>pMG zX{OU3(_V9s;-fP%nUy_G9V_%?d24#6PkcGLYc~lx)h`&rbRU+0?cDPN0R8B#F4v-#+%YKyB6yA^$`gb)1Al!t&iiU~I_ zO<})z^3#dE&qrc72NbjT*E58b}{=y!nxS)eoWx%Zz1^L-p^xNcFPv< zn|p%SlPb$NH2z>;QlH01ad`Il&CM6B$NeBl_^!3Fk8P!XDXH;h{^4ckROdw=ig0nC zjNNayH^R@vtfxJtZdms;>}R2JX`27z?ms1x8)&``3%deeG=3O=&S#>)iW7fmoCgN9Aiy5i+jshBRL+eT zSb2d+N@*>?-C@>~SUA)qxdDsr9L4R!213#e0~m=@v4U!(nQTA%HNW>d2YxW$7t%g( zJr`+tq4H!lvtlCc`>Vnmf9h+z{Y^%4ljwKJs|ldj2{8V9@Nx}q?1E*mv~NkB!k5W4 zW<6K}>`v$?_Uz};4-Y`8zsGt=eOQ14dEHMd=Z#(`4zZ% zvrCO>q4?z(*j1Ay|Mg=8!V~yYn^>>aBt-NBsOL_zrWZj$K?45R?91c~N#^0fa4-~! zkAJi`vZ*9rB>#P2>m|*05EWNFY-^OY<3Lv1w+(3Zn_YH2=MR!|je{}YcK@I*tRH+l z+-Z0gExVjJIt)Hp+vP>k`E=O;NBb6OH(S!U5aJBkSb~Qz=2@u=vanlwtxq9v@z=&P z_bp5$J%Op74sDYTwwk^;<>?_mv0mQ}5mapqegQRMZuPEK{moXhJLheJ>{k*37}M|T z=(PIp9UAB4uNJGAsE^lkoX+CGwtt}?NwgF-l+)>zur!JNG6EG!fKl1B_uA(b+OZX# zx3pUSBBYn?0Jr29mCTYpqk&8BRvCP$q`FQ!E2|?vsMHjek*xn81yD66;66*dbbPt_ zRc~+ao6(tT#l^*qyYFkSz@;R0lvd)8`3|$q=Unb^(vv;U`P!$hLT)&Nmg)+-<2<1} zQE}HjbD4z}dU7kQye3#y@rOdy7vkOk;FTDeEppC3v*(tU=dMk`zF4~A9eA<%B5_|I z(=LQTJM0J9T134PIFOa5gGyMRZ_{5k{x$9X4<@~JfW;nwflWM$ixJ94puqN;SV3o0 z+O$C8WozTV&Tcw3xNdy@JWu=Y_OeEh#%w={v2R4=9nUYX;U1l$1-6J7JnoX0xWRb mLlZn! { disableAutoMarkAsRead, hideDelete = false, onAvatarClick, - deleteIcon = null + deleteIcon = null, + hideMediaThumbnail = false, + onMediaThumbnailClick } = cardProps; const { markAsReadById } = useSiren(); @@ -57,18 +59,26 @@ const Card = (props: NotificationCardProps): ReactElement => { return darkMode ? require('../assets/emptyDark.png') : require('../assets/emptyLight.png'); }; + const failedState = () => { + return darkMode + ? require('../assets/failedImageDark.png') + : require('../assets/failedImageLight.png'); + }; + + const avatarUrl = notification?.message?.avatar?.imageUrl || ''; + const thumbnailUrl = notification?.message?.thumbnailUrl || ''; + const [imageSource, setImageSource] = useState( - notification?.message?.avatar?.imageUrl?.length > 0 - ? { uri: notification.message?.avatar?.imageUrl } - : emptyState() + avatarUrl?.length > 0 ? { uri: avatarUrl } : emptyState() + ); + + const [mediaSource, setMediaSource] = useState( + thumbnailUrl?.length > 0 ? { uri: thumbnailUrl } : emptyState() ); useEffect(() => { - setImageSource( - notification?.message?.avatar?.imageUrl?.length > 0 - ? { uri: notification.message?.avatar?.imageUrl } - : emptyState() - ); + setImageSource(avatarUrl?.length > 0 ? { uri: avatarUrl } : emptyState()); + setMediaSource(thumbnailUrl?.length > 0 ? { uri: thumbnailUrl } : failedState()); }, [notification, darkMode]); const cardClick = (): void => { @@ -80,10 +90,18 @@ const Card = (props: NotificationCardProps): ReactElement => { setImageSource(emptyState()); }; + const onErrorMedia = (): void => { + setMediaSource(failedState()); + }; + const avatarClick = () => { if (onAvatarClick) onAvatarClick(notification); }; + const mediaClick = () => { + if (onMediaThumbnailClick) onMediaThumbnailClick(notification); + }; + const renderAvatar = useMemo((): JSX.Element => { return ( @@ -104,6 +122,18 @@ const Card = (props: NotificationCardProps): ReactElement => { ); }, [styles, darkMode, imageSource, onAvatarClick]); + const renderMediaThumbnail = useMemo((): JSX.Element => { + return ( + + + + ); + }, [darkMode, mediaSource, onMediaThumbnailClick]); + const onDeleteItem = async (): Promise => { const isSuccess = await onDelete(notification.id, false); @@ -151,9 +181,14 @@ const Card = (props: NotificationCardProps): ReactElement => { {notification.message?.subHeader} )} - - {notification.message?.body} - + {Boolean(notification.message?.body) && ( + + {notification.message?.body} + + )} + {!hideMediaThumbnail && + Boolean(notification.message?.thumbnailUrl) && + renderMediaThumbnail} @@ -228,6 +263,14 @@ const style = StyleSheet.create({ }, transparent: { backgroundColor: 'transparent' + }, + mediaContainer: { + width: '100%', + height: 130, + borderRadius: 6, + marginBottom: 10, + overflow: 'hidden', + backgroundColor: '#D3D3D3' } }); diff --git a/src/components/sirenInbox.tsx b/src/components/sirenInbox.tsx index e6e1448..032f34a 100644 --- a/src/components/sirenInbox.tsx +++ b/src/components/sirenInbox.tsx @@ -77,7 +77,8 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { cardProps = { hideAvatar: false, disableAutoMarkAsRead: false, - hideDelete: false + hideDelete: false, + hideMediaThumbnail: false, }, listEmptyComponent = null, headerProps = {}, diff --git a/src/types.ts b/src/types.ts index 153c9aa..15ae4c9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -85,6 +85,8 @@ type CardProps = { disableAutoMarkAsRead?: boolean; deleteIcon?: JSX.Element; hideDelete?: boolean; + hideMediaThumbnail?: boolean; + onMediaThumbnailClick?: (notification: NotificationDataType) => void; }; /** @@ -245,4 +247,5 @@ export type StyleProps = { skeltonLoaderColor: ViewStyle; highlighted: ViewStyle; backIcon: ViewStyle; + mediaContainer: ViewStyle; }; diff --git a/src/utils/commonUtils.ts b/src/utils/commonUtils.ts index e834ae0..f2da6d4 100644 --- a/src/utils/commonUtils.ts +++ b/src/utils/commonUtils.ts @@ -277,5 +277,8 @@ export const applyTheme = ( theme.windowHeader?.titleColor || theme.colors?.textColor || DefaultTheme[mode].windowHeader.titleColor - } + }, + mediaContainer: { + backgroundColor: DefaultTheme[mode].notificationCard.mediaContainerBackground + }, }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e08cf10..99210e5 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -9,7 +9,8 @@ export const COLORS = { deleteIcon: '#34405499', timerIcon: '#667185', clearAllIcon: '#667185', - infiniteLoader: '#F56630' + infiniteLoader: '#F56630', + imageBackground: '#F0F2F5' }, dark: { primaryColor: '#FA9874', @@ -21,7 +22,8 @@ export const COLORS = { deleteIcon: '#D0D5DD', timerIcon: '#98A2B3', clearAllIcon: '#D0D5DD', - infiniteLoader: '#F56630' + infiniteLoader: '#F56630', + imageBackground: '#4C4C4C' } }; diff --git a/src/utils/defaultTheme.ts b/src/utils/defaultTheme.ts index 106ded0..273e381 100644 --- a/src/utils/defaultTheme.ts +++ b/src/utils/defaultTheme.ts @@ -30,6 +30,7 @@ const defaultTheme = { subTitleColor: COLORS[ThemeMode.LIGHT].textColor, descriptionColor: COLORS[ThemeMode.LIGHT].textColor, dateColor: COLORS[ThemeMode.LIGHT].textColor, + mediaContainerBackground: COLORS[ThemeMode.LIGHT].imageBackground, } }, dark: { @@ -60,7 +61,8 @@ const defaultTheme = { titleColor: COLORS[ThemeMode.DARK].textColor, subTitleColor: COLORS[ThemeMode.DARK].textColor, descriptionColor: COLORS[ThemeMode.DARK].textColor, - dateColor: COLORS[ThemeMode.DARK].dateColor + dateColor: COLORS[ThemeMode.DARK].dateColor, + mediaContainerBackground: COLORS[ThemeMode.DARK].imageBackground, } } }; From f424331e07bd52d82ee60017fe708487d6ac4f6d Mon Sep 17 00:00:00 2001 From: Abhay Keyvalue <95466701+abhay-keyvalue@users.noreply.github.com> Date: Mon, 13 May 2024 14:44:16 +0530 Subject: [PATCH 05/10] fix: Add unique identifier for PubSub events in SirenProvider (#59) * fix: Add unique identifier for PubSub events in SirenProvider --- src/components/card.tsx | 4 +++- src/components/sirenInbox.tsx | 6 +++--- src/components/sirenInboxIcon.tsx | 6 +++--- src/components/sirenProvider.tsx | 18 +++++++++++++----- src/utils/sirenHook.ts | 12 ++++++------ 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/components/card.tsx b/src/components/card.tsx index c2255bc..4c26095 100644 --- a/src/components/card.tsx +++ b/src/components/card.tsx @@ -4,6 +4,7 @@ import { Animated, Image, StyleSheet, Text, TouchableOpacity, View } from 'react import type { NotificationCardProps } from '../types'; import { CommonUtils, useSiren } from '../utils'; import { eventTypes, events } from '../utils/constants'; +import { useSirenContext } from './sirenProvider'; import CloseIcon from './closeIcon'; import TimerIcon from './timerIcon'; @@ -52,6 +53,7 @@ const Card = (props: NotificationCardProps): ReactElement => { onMediaThumbnailClick } = cardProps; const { markAsReadById } = useSiren(); + const { id: providerId } = useSirenContext(); const opacity = useRef(new Animated.Value(1)).current; @@ -145,7 +147,7 @@ const Card = (props: NotificationCardProps): ReactElement => { }).start(() => { const payload = { id: notification.id, action: eventTypes.DELETE_ITEM }; - PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload)); + PubSub.publish(`${events.NOTIFICATION_LIST_EVENT}${providerId}`, JSON.stringify(payload)); }); }; diff --git a/src/components/sirenInbox.tsx b/src/components/sirenInbox.tsx index 032f34a..ecea810 100644 --- a/src/components/sirenInbox.tsx +++ b/src/components/sirenInbox.tsx @@ -105,7 +105,7 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { itemsPerFetch > MAXIMUM_ITEMS_PER_FETCH ? MAXIMUM_ITEMS_PER_FETCH : itemsPerFetch ); - const { siren, verificationStatus } = useSirenContext(); + const { siren, verificationStatus, id } = useSirenContext(); const { deleteById, deleteByDate, markAllAsViewed } = useSiren(); @@ -123,7 +123,7 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { const disableCardDelete = useRef(false); useEffect(() => { - PubSub.subscribe(events.NOTIFICATION_LIST_EVENT, notificationSubscriber); + PubSub.subscribe(`${events.NOTIFICATION_LIST_EVENT}${id}`, notificationSubscriber); return cleanUp(); }, []); @@ -173,7 +173,7 @@ const SirenInbox = (props: SirenInboxProps): ReactElement => { const cleanUp = () => () => { siren?.stopRealTimeFetch(EventType.NOTIFICATION); setNotifications([]); - PubSub.unsubscribe(events.NOTIFICATION_LIST_EVENT); + PubSub.unsubscribe(`${events.NOTIFICATION_LIST_EVENT}${id}`); handleMarkAllAsViewed(); }; diff --git a/src/components/sirenInboxIcon.tsx b/src/components/sirenInboxIcon.tsx index a73551c..355914c 100644 --- a/src/components/sirenInboxIcon.tsx +++ b/src/components/sirenInboxIcon.tsx @@ -51,7 +51,7 @@ const SirenInboxIcon = (props: SirenInboxIconProps) => { onError = () => null } = props; - const { siren, verificationStatus } = useSirenContext(); + const { siren, verificationStatus, id } = useSirenContext(); const [unviewedCount, seUnviewedCount] = useState(0); @@ -69,7 +69,7 @@ const SirenInboxIcon = (props: SirenInboxIconProps) => { // Clean up - stop polling when component unmounts const cleanUp = () => () => { siren?.stopRealTimeFetch(EventType.UNVIEWED_COUNT); - PubSub.unsubscribe(events.NOTIFICATION_COUNT_EVENT); + PubSub.unsubscribe(`${events.NOTIFICATION_COUNT_EVENT}${id}`); seUnviewedCount(0); }; @@ -82,7 +82,7 @@ const SirenInboxIcon = (props: SirenInboxIconProps) => { }; useEffect(() => { - PubSub.subscribe(events.NOTIFICATION_COUNT_EVENT, notificationSubscriber); + PubSub.subscribe(`${events.NOTIFICATION_COUNT_EVENT}${id}`, notificationSubscriber); return cleanUp(); }, []); diff --git a/src/components/sirenProvider.tsx b/src/components/sirenProvider.tsx index 8bafcec..99df603 100644 --- a/src/components/sirenProvider.tsx +++ b/src/components/sirenProvider.tsx @@ -25,6 +25,7 @@ import { useSiren } from '../utils'; type SirenContextProp = { siren: Siren | null; verificationStatus: VerificationStatus; + id: string; }; interface SirenProvider { @@ -34,7 +35,8 @@ interface SirenProvider { export const SirenContext = createContext({ siren: null, - verificationStatus: VerificationStatus.PENDING + verificationStatus: VerificationStatus.PENDING, + id: '' }); /** @@ -74,8 +76,13 @@ export const useSirenContext = (): SirenContextProp => useContext(SirenContext); const SirenProvider: React.FC = ({ config, children }) => { let retryCount = 0; + const generateUniqueId = (): string => { + return Math.random().toString(36).substring(2, 15); + }; + const { markAllAsViewed } = useSiren(); + const [id] = useState(generateUniqueId()); const [siren, setSiren] = useState(null); const [verificationStatus, setVerificationStatus] = useState( VerificationStatus.PENDING @@ -104,8 +111,8 @@ const SirenProvider: React.FC = ({ config, children }) => { action: eventTypes.RESET_NOTIFICATIONS }; - PubSub.publish(events.NOTIFICATION_COUNT_EVENT, JSON.stringify(updateCountPayload)); - PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(updateNotificationPayload)); + PubSub.publish(`${events.NOTIFICATION_COUNT_EVENT}${id}`, JSON.stringify(updateCountPayload)); + PubSub.publish(`${events.NOTIFICATION_LIST_EVENT}${id}`, JSON.stringify(updateNotificationPayload)); }; const onNewNotificationEvent = (responseData: NotificationDataType[]) => { @@ -114,7 +121,7 @@ const SirenProvider: React.FC = ({ config, children }) => { markAllAsViewed(responseData[0].createdAt); const payload = { newNotifications: responseData, action: eventTypes.NEW_NOTIFICATIONS }; - PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload)); + PubSub.publish(`${events.NOTIFICATION_LIST_EVENT}${id}`, JSON.stringify(payload)); }; const onTotalUnviewedCountEvent = (response: UnviewedCountApiResponse) => { @@ -124,7 +131,7 @@ const SirenProvider: React.FC = ({ config, children }) => { action: eventTypes.UPDATE_NOTIFICATIONS_COUNT }; - PubSub.publish(events.NOTIFICATION_COUNT_EVENT, JSON.stringify(payload)); + PubSub.publish(`${events.NOTIFICATION_COUNT_EVENT}${id}`, JSON.stringify(payload)); }; const handleNotificationEvent = (response: NotificationsApiResponse) => { @@ -195,6 +202,7 @@ const SirenProvider: React.FC = ({ config, children }) => { return ( { - const { siren } = useSirenContext(); + const { siren, id: providerId } = useSirenContext(); const markAsReadById = async (id: string) => { if (siren) @@ -14,7 +14,7 @@ const useSiren = () => { if (response?.data) { const payload = { id, action: eventTypes.MARK_ITEM_AS_READ }; - PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload)); + PubSub.publish(`${events.NOTIFICATION_LIST_EVENT}${providerId}`, JSON.stringify(payload)); } return response; @@ -32,7 +32,7 @@ const useSiren = () => { if (response?.data) { const payload = { action: eventTypes.MARK_ALL_AS_READ }; - PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload)); + PubSub.publish(`${events.NOTIFICATION_LIST_EVENT}${providerId}`, JSON.stringify(payload)); } return response; @@ -49,7 +49,7 @@ const useSiren = () => { if (response?.data && shouldUpdateList) { const payload = { id, action: eventTypes.DELETE_ITEM }; - PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload)); + PubSub.publish(`${events.NOTIFICATION_LIST_EVENT}${providerId}`, JSON.stringify(payload)); } return response; @@ -67,7 +67,7 @@ const useSiren = () => { if (response?.data) { const payload = { action: eventTypes.DELETE_ALL_ITEM }; - PubSub.publish(events.NOTIFICATION_LIST_EVENT, JSON.stringify(payload)); + PubSub.publish(`${events.NOTIFICATION_LIST_EVENT}${providerId}`, JSON.stringify(payload)); } return response; @@ -83,7 +83,7 @@ const useSiren = () => { if (response?.data) { const payload = { unviewedCount: 0, action: eventTypes.UPDATE_NOTIFICATIONS_COUNT }; - PubSub.publish(events.NOTIFICATION_COUNT_EVENT, JSON.stringify(payload)); + PubSub.publish(`${events.NOTIFICATION_COUNT_EVENT}${providerId}`, JSON.stringify(payload)); } return response; From eb9b05d2e61ed9674b7805bcb13c116fcc0c2f7b Mon Sep 17 00:00:00 2001 From: Abhay Date: Wed, 15 May 2024 12:37:00 +0530 Subject: [PATCH 06/10] fix: Fixed issue custom delete icon press is not working --- src/components/card.tsx | 12 ++++++++---- src/components/closeIcon.tsx | 4 +++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/card.tsx b/src/components/card.tsx index 4c26095..dc3e389 100644 --- a/src/components/card.tsx +++ b/src/components/card.tsx @@ -173,10 +173,14 @@ const Card = (props: NotificationCardProps): ReactElement => { {notification.message?.header} - {!hideDelete && - (deleteIcon || ( - - ))} + {!hideDelete && ( + + )} {Boolean(notification.message?.subHeader) && ( diff --git a/src/components/closeIcon.tsx b/src/components/closeIcon.tsx index d08bfde..f9da79a 100644 --- a/src/components/closeIcon.tsx +++ b/src/components/closeIcon.tsx @@ -8,10 +8,12 @@ import type { StyleProps } from '../types'; const CloseIcon = ({ notification, styles, + customIcon, onDelete }: { notification: NotificationDataType; styles: Partial; + customIcon?: ReactElement | null; onDelete: (id: string) => void; }): ReactElement => { const icon: JSX.Element[] = []; @@ -36,7 +38,7 @@ const CloseIcon = ({ testID='delete-button' accessibilityLabel={`siren-notification-delete${notification.id}`} > - <>{icon} + <>{customIcon || icon} ); }; From df82d3fc68d0090730efaab79a4e4be8a91af967 Mon Sep 17 00:00:00 2001 From: Abhay Date: Wed, 15 May 2024 12:37:12 +0530 Subject: [PATCH 07/10] refactor: Move generateUniqueId function to utils --- src/components/sirenProvider.tsx | 6 +----- src/utils/commonUtils.ts | 4 ++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/sirenProvider.tsx b/src/components/sirenProvider.tsx index 99df603..c9f8999 100644 --- a/src/components/sirenProvider.tsx +++ b/src/components/sirenProvider.tsx @@ -11,7 +11,7 @@ import type { } from '@sirenapp/js-sdk/dist/esm/types'; import type { SirenProviderConfigProps } from '../types'; -import { isNonEmptyArray, logger } from '../utils/commonUtils'; +import { generateUniqueId, isNonEmptyArray, logger } from '../utils/commonUtils'; import { events, eventTypes, @@ -76,10 +76,6 @@ export const useSirenContext = (): SirenContextProp => useContext(SirenContext); const SirenProvider: React.FC = ({ config, children }) => { let retryCount = 0; - const generateUniqueId = (): string => { - return Math.random().toString(36).substring(2, 15); - }; - const { markAllAsViewed } = useSiren(); const [id] = useState(generateUniqueId()); diff --git a/src/utils/commonUtils.ts b/src/utils/commonUtils.ts index f2da6d4..4ba25a7 100644 --- a/src/utils/commonUtils.ts +++ b/src/utils/commonUtils.ts @@ -8,6 +8,10 @@ import { DefaultTheme } from './index'; export const isNonEmptyArray = (arr?: unknown[] | null) => Boolean(arr && typeof arr === 'object' && arr instanceof Array && arr.length > 0); +export const generateUniqueId = (): string => { + return Math.random().toString(36).substring(2, 15); +}; + export const updateNotifications = ( eventData: { id?: string; From 71ebcf77c29106f6a63547ec30e8235fb9c855b9 Mon Sep 17 00:00:00 2001 From: Abhay Date: Wed, 15 May 2024 12:38:35 +0530 Subject: [PATCH 08/10] refactor: Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05bf074..14db430 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ theme | Object for custom themes | Theme | {} | customStyles | Object for custom styling | CustomStyleProps | {} | darkMode | Toggle to enable dark mode| boolean | false | itemsPerFetch | Number of notifications fetch per api request (have a max cap of 50) | number | 20 | -cardProps | Props for customizing the card | CardProps | { hideAvatar: false, disableAutoMarkAsRead: false, hideDelete: false, deleteIcon: JSX.Element, onAvatarClick: ()=> null } | +cardProps | Props for customizing the card | CardProps | { hideAvatar: false, disableAutoMarkAsRead: false, hideDelete: false, deleteIcon: JSX.Element, onAvatarClick: ()=> null, hideMediaThumbnailL: false, onMediaThumbnailClick: ()=> null} | customCard | Function for rendering custom card | (notification)=> JSX Element | null | onCardClick | Custom click handler for card | (notification)=> void | ()=>null | listEmptyComponent | Custom component for empty list | JSX Element | null | From 62596970736c9241981a5806f92db8848bea93bb Mon Sep 17 00:00:00 2001 From: Abhay Date: Wed, 15 May 2024 12:39:11 +0530 Subject: [PATCH 09/10] refactor:Update SDK version in example app's package.json --- example/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/package.json b/example/package.json index f119386..3940b67 100644 --- a/example/package.json +++ b/example/package.json @@ -13,7 +13,7 @@ "@react-navigation/bottom-tabs": "^6.5.14", "@react-navigation/native": "^6.1.12", "@react-navigation/native-stack": "^6.9.20", - "@sirenapp/react-native-inbox": "^1.0.0", + "@sirenapp/react-native-inbox": "^1.1.0", "react": "18.2.0", "react-native": "0.73.4", "react-native-modal": "^13.0.1", From d91d1d55786feea95e376f25780da2a66e2d8d97 Mon Sep 17 00:00:00 2001 From: Abhay Keyvalue <95466701+abhay-keyvalue@users.noreply.github.com> Date: Wed, 15 May 2024 15:52:20 +0530 Subject: [PATCH 10/10] docs: Update changelog --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d3557..7100124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,16 @@ All notable changes to this project will be documented in this file. -## [1.0.0] - 2024-03-26 +## 1.1.0 + +### Added +- Added support for custom delete icon and a flag to toggle the visibility of the delete icon. +- Added functionality to display thumbnail URL previews for media content. +- Exposed avatar click property. +- Implemented specific error code mapping. +- Enhanced style and theme customizations. + +## 1.0.0 ### Added