Skip to content

Commit 0941cde

Browse files
committed
feat(js,react,nextjs): subscription component
1 parent 5fe18df commit 0941cde

File tree

93 files changed

+2212
-477
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+2212
-477
lines changed

packages/js/src/cache/subscriptions-cache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ export class SubscriptionsCache {
269269
}
270270
}
271271

272-
clear(): void {
272+
clearAll(): void {
273273
this.#cache.clear();
274274
this.#itemCache.clear();
275275
}

packages/js/src/novu.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ export class Novu implements Pick<NovuEventEmitter, 'on'> {
107107
};
108108
}
109109

110+
private clearCache(): void {
111+
this.notifications.cache.clearAll();
112+
this.preferences.cache.clearAll();
113+
this.preferences.scheduleCache.clearAll();
114+
this.subscriptions.cache.clearAll();
115+
}
116+
117+
/**
118+
* @deprecated
119+
*/
110120
public async changeSubscriber(options: { subscriber: Subscriber; subscriberHash?: string }): Promise<void> {
111121
await this.#session.initialize({
112122
applicationIdentifier: this.#session.applicationIdentifier || '',
@@ -118,7 +128,7 @@ export class Novu implements Pick<NovuEventEmitter, 'on'> {
118128
});
119129

120130
// Clear cache and reconnect socket with new token
121-
this.notifications.cache.clearAll();
131+
this.clearCache();
122132

123133
// Disconnect and reconnect socket to use new JWT token
124134
const disconnectResult = await this.socket.disconnect();
@@ -127,6 +137,9 @@ export class Novu implements Pick<NovuEventEmitter, 'on'> {
127137
}
128138
}
129139

140+
/**
141+
* @deprecated
142+
*/
130143
public async changeContext(options: { context: Context; contextHash?: string }): Promise<void> {
131144
const currentSubscriber = this.#session.subscriber;
132145
if (!currentSubscriber) {
@@ -143,7 +156,7 @@ export class Novu implements Pick<NovuEventEmitter, 'on'> {
143156
});
144157

145158
// Clear cache and reconnect socket with new token
146-
this.notifications.cache.clearAll();
159+
this.clearCache();
147160

148161
// Disconnect and reconnect socket to use new JWT token
149162
const disconnectResult = await this.socket.disconnect();

packages/js/src/ui/api/hooks/useArchiveAll.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { NotificationFilter } from '../../../types';
22
import { useNovu } from '../../context';
33

44
export const useArchiveAll = (props?: { onSuccess?: () => void; onError?: (err: unknown) => void }) => {
5-
const novu = useNovu();
5+
const novuAccessor = useNovu();
66

77
const archiveAll = async ({
88
tags,
@@ -12,7 +12,7 @@ export const useArchiveAll = (props?: { onSuccess?: () => void; onError?: (err:
1212
data?: Record<string, unknown>;
1313
} = {}) => {
1414
try {
15-
await novu.notifications.archiveAll({ tags, data });
15+
await novuAccessor().notifications.archiveAll({ tags, data });
1616
props?.onSuccess?.();
1717
} catch (error) {
1818
props?.onError?.(error);

packages/js/src/ui/api/hooks/useArchiveAllRead.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { NotificationFilter } from '../../../types';
22
import { useNovu } from '../../context';
33

44
export const useArchiveAllRead = (props?: { onSuccess?: () => void; onError?: (err: unknown) => void }) => {
5-
const novu = useNovu();
5+
const novuAccessor = useNovu();
66

77
const archiveAllRead = async ({
88
tags,
@@ -12,7 +12,7 @@ export const useArchiveAllRead = (props?: { onSuccess?: () => void; onError?: (e
1212
data?: NotificationFilter['data'];
1313
} = {}) => {
1414
try {
15-
await novu.notifications.archiveAllRead({ tags, data });
15+
await novuAccessor().notifications.archiveAllRead({ tags, data });
1616
props?.onSuccess?.();
1717
} catch (error) {
1818
props?.onError?.(error);

packages/js/src/ui/api/hooks/useDeleteAll.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { NotificationFilter } from '../../../types';
22
import { useNovu } from '../../context';
33

44
export const useDeleteAll = (props?: { onSuccess?: () => void; onError?: (err: unknown) => void }) => {
5-
const novu = useNovu();
5+
const novuAccessor = useNovu();
66

77
const deleteAll = async ({
88
tags,
@@ -12,7 +12,7 @@ export const useDeleteAll = (props?: { onSuccess?: () => void; onError?: (err: u
1212
data?: Record<string, unknown>;
1313
} = {}) => {
1414
try {
15-
await novu.notifications.deleteAll({ tags, data });
15+
await novuAccessor().notifications.deleteAll({ tags, data });
1616
props?.onSuccess?.();
1717
} catch (error) {
1818
props?.onError?.(error);

packages/js/src/ui/api/hooks/useNotifications.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Accessor, createEffect, onCleanup, onMount } from 'solid-js';
1+
import { Accessor, createEffect, onCleanup } from 'solid-js';
22
import { ListNotificationsArgs, ListNotificationsResponse } from '../../../notifications';
33
import type { NotificationFilter } from '../../../types';
44
import { isSameFilter } from '../../../utils/notification-utils';
@@ -10,21 +10,22 @@ type UseNotificationsInfiniteScrollProps = {
1010
};
1111

1212
export const useNotificationsInfiniteScroll = (props: UseNotificationsInfiniteScrollProps) => {
13-
const novu = useNovu();
13+
const novuAccessor = useNovu();
1414
let filter = { ...props.options() };
1515

1616
const [data, { initialLoading, setEl, end, mutate, reset }] = createInfiniteScroll(
1717
async (after) => {
18-
const { data } = await novu.notifications.list({ ...(props.options() || {}), after });
18+
const { data } = await novuAccessor().notifications.list({ ...(props.options() || {}), after });
1919

2020
return { data: data?.notifications ?? [], hasMore: data?.hasMore ?? false };
2121
},
2222
{
2323
paginationField: 'id',
24+
dependency: novuAccessor,
2425
}
2526
);
2627

27-
onMount(() => {
28+
createEffect(() => {
2829
const listener = ({ data }: { data: ListNotificationsResponse }) => {
2930
if (!data || !isSameFilter(filter, data.filter)) {
3031
return;
@@ -33,7 +34,7 @@ export const useNotificationsInfiniteScroll = (props: UseNotificationsInfiniteSc
3334
mutate({ data: data.notifications, hasMore: data.hasMore });
3435
};
3536

36-
const cleanup = novu.on('notifications.list.updated', listener);
37+
const cleanup = novuAccessor().on('notifications.list.updated', listener);
3738

3839
onCleanup(() => cleanup());
3940
});
@@ -44,13 +45,13 @@ export const useNotificationsInfiniteScroll = (props: UseNotificationsInfiniteSc
4445
return;
4546
}
4647

47-
novu.notifications.clearCache();
48+
novuAccessor().notifications.clearCache();
4849
await reset();
4950
filter = newFilter;
5051
});
5152

5253
const refetch = async ({ filter }: { filter?: NotificationFilter }) => {
53-
novu.notifications.clearCache({ filter });
54+
novuAccessor().notifications.clearCache({ filter });
5455
await reset();
5556
};
5657

packages/js/src/ui/api/hooks/usePreferences.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
1-
import { createEffect, createResource, createSignal, onCleanup, onMount } from 'solid-js';
1+
import { createEffect, createResource, createSignal, onCleanup } from 'solid-js';
22
import { Preference } from '../../../preferences/preference';
33
import { FetchPreferencesArgs } from '../../../preferences/types';
44
import { useNovu } from '../../context';
55

66
export const usePreferences = (options?: FetchPreferencesArgs) => {
7-
const novu = useNovu();
7+
const novuAccessor = useNovu();
88

99
const [loading, setLoading] = createSignal(true);
10-
const [preferences, { mutate, refetch }] = createResource(options || {}, async ({ tags, severity, criticality }) => {
11-
try {
12-
const response = await novu.preferences.list({ tags, severity, criticality });
13-
14-
return response.data;
15-
} catch (error) {
16-
console.error('Error fetching preferences:', error);
17-
throw error;
10+
const [preferences, { mutate, refetch }] = createResource(
11+
() => ({ ...options, dependency: novuAccessor() }),
12+
async ({ tags, severity, criticality }) => {
13+
try {
14+
const response = await novuAccessor().preferences.list({ tags, severity, criticality });
15+
16+
return response.data;
17+
} catch (error) {
18+
console.error('Error fetching preferences:', error);
19+
throw error;
20+
}
1821
}
19-
});
22+
);
2023

21-
onMount(() => {
24+
createEffect(() => {
2225
const listener = ({ data }: { data: Preference[] }) => {
2326
if (!data) {
2427
return;
@@ -27,7 +30,7 @@ export const usePreferences = (options?: FetchPreferencesArgs) => {
2730
mutate(data);
2831
};
2932

30-
const cleanup = novu.on('preferences.list.updated', listener);
33+
const cleanup = novuAccessor().on('preferences.list.updated', listener);
3134

3235
onCleanup(() => cleanup());
3336
});

packages/js/src/ui/api/hooks/useReadAll.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { NotificationFilter } from '../../../types';
22
import { useNovu } from '../../context';
33

44
export const useReadAll = (props?: { onSuccess?: () => void; onError?: (err: unknown) => void }) => {
5-
const novu = useNovu();
5+
const novuAccessor = useNovu();
66

77
const readAll = async ({
88
tags,
@@ -12,7 +12,7 @@ export const useReadAll = (props?: { onSuccess?: () => void; onError?: (err: unk
1212
data?: Record<string, unknown>;
1313
} = {}) => {
1414
try {
15-
await novu.notifications.readAll({ tags, data });
15+
await novuAccessor().notifications.readAll({ tags, data });
1616
props?.onSuccess?.();
1717
} catch (error) {
1818
props?.onError?.(error);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { createEffect, createResource, createSignal, onCleanup, onMount } from 'solid-js';
2+
import {
3+
CreateSubscriptionArgs,
4+
DeleteSubscriptionArgs,
5+
GetSubscriptionArgs,
6+
TopicSubscription,
7+
} from '../../../subscriptions';
8+
import { useNovu } from '../../context';
9+
10+
export const useSubscription = (options: GetSubscriptionArgs) => {
11+
const novuAccessor = useNovu();
12+
13+
const [loading, setLoading] = createSignal(true);
14+
const [subscription, { mutate, refetch }] = createResource(options || {}, async ({ topicKey, identifier }) => {
15+
try {
16+
const response = await novuAccessor().subscriptions.get({ topicKey, identifier });
17+
18+
return response.data;
19+
} catch (error) {
20+
console.error('Error fetching subscription:', error);
21+
throw error;
22+
}
23+
});
24+
25+
const create = async (args: CreateSubscriptionArgs) => {
26+
try {
27+
setLoading(true);
28+
const response = await novuAccessor().subscriptions.create(args);
29+
30+
if (response.data) {
31+
mutate(response.data);
32+
}
33+
34+
return response;
35+
} catch (error) {
36+
console.error('Error creating subscription:', error);
37+
throw error;
38+
} finally {
39+
setLoading(false);
40+
}
41+
};
42+
43+
const remove = async (args: DeleteSubscriptionArgs) => {
44+
try {
45+
setLoading(true);
46+
const response =
47+
'subscription' in args
48+
? await novuAccessor().subscriptions.delete({ subscription: args.subscription })
49+
: await novuAccessor().subscriptions.delete({ subscriptionId: args.subscriptionId });
50+
51+
mutate(null);
52+
53+
return response;
54+
} catch (error) {
55+
console.error('Error deleting subscription:', error);
56+
throw error;
57+
} finally {
58+
setLoading(false);
59+
}
60+
};
61+
62+
onMount(() => {
63+
const listener = ({ data }: { data?: TopicSubscription }) => {
64+
if (!data || data.topicKey !== options.topicKey || data.identifier !== options.identifier) {
65+
return;
66+
}
67+
68+
mutate(data);
69+
};
70+
71+
const currentNovu = novuAccessor();
72+
const cleanupCreate = currentNovu.on('subscription.create.resolved', listener);
73+
const cleanupUpdate = currentNovu.on('subscription.update.resolved', listener);
74+
const cleanupDelete = currentNovu.on('subscription.delete.resolved', () => {
75+
mutate(null);
76+
});
77+
78+
onCleanup(() => {
79+
cleanupCreate();
80+
cleanupUpdate();
81+
cleanupDelete();
82+
});
83+
});
84+
85+
createEffect(() => {
86+
setLoading(subscription.loading);
87+
});
88+
89+
return { subscription, loading, mutate, refetch, create, remove };
90+
};

0 commit comments

Comments
 (0)