@cstar.help/react 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  React hooks for the [cStar](https://cstar.help) customer support platform. Build custom chat widgets and knowledge base UIs with type-safe, SSR-compatible hooks.
4
4
 
5
5
  - **React 18+** — hooks with automatic loading states and error handling
6
- - **Two modules** — Chat (real-time messaging) and Library (knowledge base)
6
+ - **Three modules** — Chat (real-time messaging), Library (knowledge base), and Community (forum)
7
7
  - **TypeScript-first** — full type definitions included
8
8
  - **Tiny** — ~18 KB, tree-shakeable, only peer deps
9
9
 
@@ -182,6 +182,87 @@ function SearchBar() {
182
182
  }
183
183
  ```
184
184
 
185
+ ## Community
186
+
187
+ Wrap in `<CStarCommunityProvider>` for public community forum access — no auth required.
188
+
189
+ ### Setup
190
+
191
+ ```tsx
192
+ import { CStarCommunityProvider } from '@cstar.help/react';
193
+
194
+ function App() {
195
+ return (
196
+ <CStarCommunityProvider teamSlug="acme">
197
+ <CommunityForum />
198
+ </CStarCommunityProvider>
199
+ );
200
+ }
201
+ ```
202
+
203
+ ### Topics + Posts
204
+
205
+ ```tsx
206
+ import { useTopics, usePosts, usePost } from '@cstar.help/react';
207
+
208
+ function CommunityForum() {
209
+ const { topics, isLoading: topicsLoading } = useTopics();
210
+ const { posts, count, isLoading: postsLoading } = usePosts({ sort: 'votes' });
211
+
212
+ return (
213
+ <>
214
+ <nav>
215
+ {topics.map((topic) => (
216
+ <a key={topic.id}>{topic.name}</a>
217
+ ))}
218
+ </nav>
219
+ <p>{count} posts</p>
220
+ <ul>
221
+ {posts.map((post) => (
222
+ <li key={post.id}>
223
+ {post.title} — {post.voteCount} votes
224
+ </li>
225
+ ))}
226
+ </ul>
227
+ </>
228
+ );
229
+ }
230
+
231
+ // Single post with comments
232
+ function PostPage({ slug }) {
233
+ const { data, isLoading, error } = usePost(slug);
234
+ if (isLoading) return <Spinner />;
235
+ if (!data) return <NotFound />;
236
+ return (
237
+ <>
238
+ <h1>{data.post.title}</h1>
239
+ {data.comments.map((c) => (
240
+ <p key={c.id}>{c.body}</p>
241
+ ))}
242
+ </>
243
+ );
244
+ }
245
+ ```
246
+
247
+ ### Search
248
+
249
+ ```tsx
250
+ import { useCommunitySearch } from '@cstar.help/react';
251
+
252
+ function CommunitySearch() {
253
+ const { results, isLoading, search } = useCommunitySearch();
254
+
255
+ return (
256
+ <>
257
+ <input onChange={(e) => search(e.target.value)} placeholder="Search posts..." />
258
+ {results?.data.map((post) => (
259
+ <a key={post.id}>{post.title}</a>
260
+ ))}
261
+ </>
262
+ );
263
+ }
264
+ ```
265
+
185
266
  ## API Reference
186
267
 
187
268
  ### Chat Hooks
@@ -202,12 +283,22 @@ function SearchBar() {
202
283
  | `useArticle(slug)` | `{ article, isLoading, error, refresh }` | Single article by slug |
203
284
  | `useArticleSearch(query)` | `{ results, totalCount, isLoading, error }` | Search with 300ms debounce |
204
285
 
286
+ ### Community Hooks
287
+
288
+ | Hook | Returns | Description |
289
+ | ---------------------- | --------------------------------------------- | ----------------------------------------------------- |
290
+ | `useTopics()` | `{ topics, isLoading, error, refresh }` | Community discussion topics |
291
+ | `usePosts(params?)` | `{ posts, count, isLoading, error, refresh }` | Posts with optional filters (topicSlug, status, sort) |
292
+ | `usePost(slug)` | `{ data, isLoading, error, refresh }` | Single post with comments |
293
+ | `useCommunitySearch()` | `{ results, isLoading, error, search }` | On-demand search across posts |
294
+
205
295
  ### Context Accessors
206
296
 
207
- | Function | Returns | Description |
208
- | -------------------- | --------------- | ---------------------------------------------- |
209
- | `useChatClient()` | `ChatClient` | Raw client from nearest `CStarChatProvider` |
210
- | `useLibraryClient()` | `LibraryClient` | Raw client from nearest `CStarLibraryProvider` |
297
+ | Function | Returns | Description |
298
+ | ---------------------- | ----------------- | ------------------------------------------------ |
299
+ | `useChatClient()` | `ChatClient` | Raw client from nearest `CStarChatProvider` |
300
+ | `useLibraryClient()` | `LibraryClient` | Raw client from nearest `CStarLibraryProvider` |
301
+ | `useCommunityClient()` | `CommunityClient` | Raw client from nearest `CStarCommunityProvider` |
211
302
 
212
303
  ## Requirements
213
304
 
package/dist/index.cjs CHANGED
@@ -22,6 +22,8 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  CStarChatContext: () => CStarChatContext,
24
24
  CStarChatProvider: () => CStarChatProvider,
25
+ CStarCommunityContext: () => CStarCommunityContext,
26
+ CStarCommunityProvider: () => CStarCommunityProvider,
25
27
  CStarLibraryContext: () => CStarLibraryContext,
26
28
  CStarLibraryProvider: () => CStarLibraryProvider,
27
29
  useArticle: () => useArticle,
@@ -30,9 +32,14 @@ __export(src_exports, {
30
32
  useCategories: () => useCategories,
31
33
  useChat: () => useChat,
32
34
  useChatClient: () => useChatClient,
35
+ useCommunityClient: () => useCommunityClient,
36
+ useCommunitySearch: () => useCommunitySearch,
33
37
  useConversations: () => useConversations,
34
38
  useLibraryClient: () => useLibraryClient,
35
39
  useMessages: () => useMessages,
40
+ usePost: () => usePost,
41
+ usePosts: () => usePosts,
42
+ useTopics: () => useTopics,
36
43
  useTyping: () => useTyping
37
44
  });
38
45
  module.exports = __toCommonJS(src_exports);
@@ -41,6 +48,7 @@ module.exports = __toCommonJS(src_exports);
41
48
  var import_react = require("react");
42
49
  var CStarChatContext = (0, import_react.createContext)(null);
43
50
  var CStarLibraryContext = (0, import_react.createContext)(null);
51
+ var CStarCommunityContext = (0, import_react.createContext)(null);
44
52
  function useChatClient() {
45
53
  const client = (0, import_react.useContext)(CStarChatContext);
46
54
  if (!client) {
@@ -55,6 +63,13 @@ function useLibraryClient() {
55
63
  }
56
64
  return client;
57
65
  }
66
+ function useCommunityClient() {
67
+ const client = (0, import_react.useContext)(CStarCommunityContext);
68
+ if (!client) {
69
+ throw new Error("useCommunityClient must be used within a <CStarCommunityProvider>");
70
+ }
71
+ return client;
72
+ }
58
73
 
59
74
  // src/chat/CStarChatProvider.tsx
60
75
  var import_react2 = require("react");
@@ -412,6 +427,13 @@ function useArticle(slug) {
412
427
  }
413
428
  }
414
429
  }, [client, slug]);
430
+ const recordView = (0, import_react10.useCallback)(async () => {
431
+ if (!slug) return;
432
+ try {
433
+ await client.recordView(slug);
434
+ } catch {
435
+ }
436
+ }, [client, slug]);
415
437
  (0, import_react10.useEffect)(() => {
416
438
  mountedRef.current = true;
417
439
  if (!slug) {
@@ -424,7 +446,7 @@ function useArticle(slug) {
424
446
  mountedRef.current = false;
425
447
  };
426
448
  }, [slug, refresh]);
427
- return { article, isLoading, error, refresh };
449
+ return { article, isLoading, error, refresh, recordView };
428
450
  }
429
451
 
430
452
  // src/library/useArticleSearch.ts
@@ -473,10 +495,181 @@ function useArticleSearch(query) {
473
495
  }, [client, query]);
474
496
  return { results, totalCount, isLoading, error };
475
497
  }
498
+
499
+ // src/community/CStarCommunityProvider.tsx
500
+ var import_react12 = require("react");
501
+ var import_community = require("@cstar.help/js/community");
502
+ var import_jsx_runtime3 = require("react/jsx-runtime");
503
+ function CStarCommunityProvider({ children, ...config }) {
504
+ const clientRef = (0, import_react12.useRef)(null);
505
+ if (!clientRef.current) {
506
+ clientRef.current = new import_community.CommunityClient(config);
507
+ }
508
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CStarCommunityContext.Provider, { value: clientRef.current, children });
509
+ }
510
+
511
+ // src/community/useTopics.ts
512
+ var import_react13 = require("react");
513
+ function useTopics() {
514
+ const client = useCommunityClient();
515
+ const [topics, setTopics] = (0, import_react13.useState)([]);
516
+ const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
517
+ const [error, setError] = (0, import_react13.useState)(null);
518
+ const mountedRef = (0, import_react13.useRef)(true);
519
+ const refresh = (0, import_react13.useCallback)(async () => {
520
+ setIsLoading(true);
521
+ setError(null);
522
+ try {
523
+ const data = await client.topics();
524
+ if (mountedRef.current) {
525
+ setTopics(data);
526
+ }
527
+ } catch (err) {
528
+ if (mountedRef.current) {
529
+ setError(err instanceof Error ? err : new Error(String(err)));
530
+ }
531
+ } finally {
532
+ if (mountedRef.current) {
533
+ setIsLoading(false);
534
+ }
535
+ }
536
+ }, [client]);
537
+ (0, import_react13.useEffect)(() => {
538
+ mountedRef.current = true;
539
+ refresh();
540
+ return () => {
541
+ mountedRef.current = false;
542
+ };
543
+ }, [refresh]);
544
+ return { topics, isLoading, error, refresh };
545
+ }
546
+
547
+ // src/community/usePosts.ts
548
+ var import_react14 = require("react");
549
+ function usePosts(params) {
550
+ const client = useCommunityClient();
551
+ const [posts, setPosts] = (0, import_react14.useState)([]);
552
+ const [count, setCount] = (0, import_react14.useState)(0);
553
+ const [isLoading, setIsLoading] = (0, import_react14.useState)(true);
554
+ const [error, setError] = (0, import_react14.useState)(null);
555
+ const mountedRef = (0, import_react14.useRef)(true);
556
+ const topicSlug = params?.topicSlug;
557
+ const status = params?.status;
558
+ const sort = params?.sort;
559
+ const limit = params?.limit;
560
+ const offset = params?.offset;
561
+ const refresh = (0, import_react14.useCallback)(async () => {
562
+ setIsLoading(true);
563
+ setError(null);
564
+ try {
565
+ const result = await client.posts({ topicSlug, status, sort, limit, offset });
566
+ if (mountedRef.current) {
567
+ setPosts(result.posts);
568
+ setCount(result.count);
569
+ }
570
+ } catch (err) {
571
+ if (mountedRef.current) {
572
+ setError(err instanceof Error ? err : new Error(String(err)));
573
+ }
574
+ } finally {
575
+ if (mountedRef.current) {
576
+ setIsLoading(false);
577
+ }
578
+ }
579
+ }, [client, topicSlug, status, sort, limit, offset]);
580
+ (0, import_react14.useEffect)(() => {
581
+ mountedRef.current = true;
582
+ refresh();
583
+ return () => {
584
+ mountedRef.current = false;
585
+ };
586
+ }, [refresh]);
587
+ return { posts, count, isLoading, error, refresh };
588
+ }
589
+
590
+ // src/community/usePost.ts
591
+ var import_react15 = require("react");
592
+ function usePost(slug) {
593
+ const client = useCommunityClient();
594
+ const [data, setData] = (0, import_react15.useState)(null);
595
+ const [isLoading, setIsLoading] = (0, import_react15.useState)(true);
596
+ const [error, setError] = (0, import_react15.useState)(null);
597
+ const mountedRef = (0, import_react15.useRef)(true);
598
+ const refresh = (0, import_react15.useCallback)(async () => {
599
+ setIsLoading(true);
600
+ setError(null);
601
+ try {
602
+ const result = await client.post(slug);
603
+ if (mountedRef.current) {
604
+ setData(result);
605
+ }
606
+ } catch (err) {
607
+ if (mountedRef.current) {
608
+ setError(err instanceof Error ? err : new Error(String(err)));
609
+ }
610
+ } finally {
611
+ if (mountedRef.current) {
612
+ setIsLoading(false);
613
+ }
614
+ }
615
+ }, [client, slug]);
616
+ (0, import_react15.useEffect)(() => {
617
+ mountedRef.current = true;
618
+ refresh();
619
+ return () => {
620
+ mountedRef.current = false;
621
+ };
622
+ }, [refresh]);
623
+ return { data, isLoading, error, refresh };
624
+ }
625
+
626
+ // src/community/useCommunitySearch.ts
627
+ var import_react16 = require("react");
628
+ function useCommunitySearch() {
629
+ const client = useCommunityClient();
630
+ const [results, setResults] = (0, import_react16.useState)(null);
631
+ const [isLoading, setIsLoading] = (0, import_react16.useState)(false);
632
+ const [error, setError] = (0, import_react16.useState)(null);
633
+ const mountedRef = (0, import_react16.useRef)(true);
634
+ (0, import_react16.useEffect)(() => {
635
+ mountedRef.current = true;
636
+ return () => {
637
+ mountedRef.current = false;
638
+ };
639
+ }, []);
640
+ const search = (0, import_react16.useCallback)(
641
+ async (query) => {
642
+ if (!query.trim()) {
643
+ setResults(null);
644
+ return;
645
+ }
646
+ setIsLoading(true);
647
+ setError(null);
648
+ try {
649
+ const data = await client.search(query);
650
+ if (mountedRef.current) {
651
+ setResults(data);
652
+ }
653
+ } catch (err) {
654
+ if (mountedRef.current) {
655
+ setError(err instanceof Error ? err : new Error(String(err)));
656
+ }
657
+ } finally {
658
+ if (mountedRef.current) {
659
+ setIsLoading(false);
660
+ }
661
+ }
662
+ },
663
+ [client]
664
+ );
665
+ return { results, isLoading, error, search };
666
+ }
476
667
  // Annotate the CommonJS export names for ESM import in node:
477
668
  0 && (module.exports = {
478
669
  CStarChatContext,
479
670
  CStarChatProvider,
671
+ CStarCommunityContext,
672
+ CStarCommunityProvider,
480
673
  CStarLibraryContext,
481
674
  CStarLibraryProvider,
482
675
  useArticle,
@@ -485,8 +678,13 @@ function useArticleSearch(query) {
485
678
  useCategories,
486
679
  useChat,
487
680
  useChatClient,
681
+ useCommunityClient,
682
+ useCommunitySearch,
488
683
  useConversations,
489
684
  useLibraryClient,
490
685
  useMessages,
686
+ usePost,
687
+ usePosts,
688
+ useTopics,
491
689
  useTyping
492
690
  });
package/dist/index.d.cts CHANGED
@@ -4,10 +4,13 @@ import { ChatClient, ChatConfig, IdentifyParams, IdentifyResult, Conversation, C
4
4
  export { ChatConfig, Conversation, ConversationCreateParams, ConversationListResponse, IdentifyParams, IdentifyResult, TypingPayload, WidgetMessage } from '@cstar.help/js/chat';
5
5
  import { LibraryClient, LibraryConfig, Category, LibraryArticle, LibraryArticleListParams } from '@cstar.help/js/library';
6
6
  export { Category, LibraryArticle, LibraryArticleListParams, LibraryConfig, LibrarySearchParams, LibrarySearchResult } from '@cstar.help/js/library';
7
+ import { CommunityClient, CommunityConfig, CommunityTopic, CommunityPost, CommunityPostListParams, CommunityPostDetail, CommunitySearchResult } from '@cstar.help/js/community';
8
+ export { CommunityComment, CommunityConfig, CommunityPost, CommunityPostDetail, CommunityPostListParams, CommunitySearchResult, CommunityTopic } from '@cstar.help/js/community';
7
9
  import * as react_jsx_runtime from 'react/jsx-runtime';
8
10
 
9
11
  declare const CStarChatContext: react.Context<ChatClient | null>;
10
12
  declare const CStarLibraryContext: react.Context<LibraryClient | null>;
13
+ declare const CStarCommunityContext: react.Context<CommunityClient | null>;
11
14
  /**
12
15
  * Access the ChatClient from the nearest CStarChatProvider.
13
16
  * Throws if used outside a provider.
@@ -18,6 +21,11 @@ declare function useChatClient(): ChatClient;
18
21
  * Throws if used outside a provider.
19
22
  */
20
23
  declare function useLibraryClient(): LibraryClient;
24
+ /**
25
+ * Access the CommunityClient from the nearest CStarCommunityProvider.
26
+ * Throws if used outside a provider.
27
+ */
28
+ declare function useCommunityClient(): CommunityClient;
21
29
 
22
30
  interface CStarChatProviderProps extends ChatConfig {
23
31
  children: ReactNode;
@@ -185,6 +193,8 @@ interface UseArticleReturn {
185
193
  error: Error | null;
186
194
  /** Re-fetch the article. */
187
195
  refresh: () => Promise<void>;
196
+ /** Record a view for this article. Call once when the article page mounts. */
197
+ recordView: () => Promise<void>;
188
198
  }
189
199
  /**
190
200
  * Fetch a single article by slug.
@@ -215,4 +225,99 @@ interface UseArticleSearchReturn {
215
225
  */
216
226
  declare function useArticleSearch(query: string): UseArticleSearchReturn;
217
227
 
218
- export { CStarChatContext, CStarChatProvider, type CStarChatProviderProps, CStarLibraryContext, CStarLibraryProvider, type CStarLibraryProviderProps, type TypingAgent, type UseArticleReturn, type UseArticleSearchReturn, type UseArticlesReturn, type UseCategoriesReturn, type UseChatReturn, type UseConversationsReturn, type UseMessagesReturn, type UseTypingReturn, useArticle, useArticleSearch, useArticles, useCategories, useChat, useChatClient, useConversations, useLibraryClient, useMessages, useTyping };
228
+ interface CStarCommunityProviderProps extends CommunityConfig {
229
+ children: ReactNode;
230
+ }
231
+ /**
232
+ * Provides a CommunityClient instance to all child components.
233
+ *
234
+ * ```tsx
235
+ * <CStarCommunityProvider teamSlug="acme">
236
+ * <CommunityForum />
237
+ * </CStarCommunityProvider>
238
+ * ```
239
+ */
240
+ declare function CStarCommunityProvider({ children, ...config }: CStarCommunityProviderProps): react_jsx_runtime.JSX.Element;
241
+
242
+ interface UseTopicsReturn {
243
+ /** Community discussion topics. */
244
+ topics: CommunityTopic[];
245
+ /** Whether the fetch is in progress. */
246
+ isLoading: boolean;
247
+ /** The last error, or null. */
248
+ error: Error | null;
249
+ /** Re-fetch topics. */
250
+ refresh: () => Promise<void>;
251
+ }
252
+ /**
253
+ * Fetch community discussion topics.
254
+ *
255
+ * ```tsx
256
+ * const { topics, isLoading } = useTopics();
257
+ * ```
258
+ */
259
+ declare function useTopics(): UseTopicsReturn;
260
+
261
+ interface UsePostsReturn {
262
+ /** Community posts. */
263
+ posts: CommunityPost[];
264
+ /** Total count of matching posts (for pagination). */
265
+ count: number;
266
+ /** Whether the fetch is in progress. */
267
+ isLoading: boolean;
268
+ /** The last error, or null. */
269
+ error: Error | null;
270
+ /** Re-fetch posts with the current params. */
271
+ refresh: () => Promise<void>;
272
+ }
273
+ /**
274
+ * Fetch community posts with optional filters.
275
+ *
276
+ * ```tsx
277
+ * const { posts, isLoading } = usePosts({ topicSlug: 'feedback', sort: 'votes' });
278
+ * ```
279
+ */
280
+ declare function usePosts(params?: CommunityPostListParams): UsePostsReturn;
281
+
282
+ interface UsePostReturn {
283
+ /** The post with comments, or null if not loaded. */
284
+ data: CommunityPostDetail | null;
285
+ /** Whether the fetch is in progress. */
286
+ isLoading: boolean;
287
+ /** The last error, or null. */
288
+ error: Error | null;
289
+ /** Re-fetch the post. */
290
+ refresh: () => Promise<void>;
291
+ }
292
+ /**
293
+ * Fetch a single community post by slug, including all comments.
294
+ *
295
+ * ```tsx
296
+ * const { data, isLoading } = usePost('dark-mode-support');
297
+ * ```
298
+ */
299
+ declare function usePost(slug: string): UsePostReturn;
300
+
301
+ interface UseCommunitySearchReturn {
302
+ /** Search results (null until first search). */
303
+ results: CommunitySearchResult | null;
304
+ /** Whether a search is in progress. */
305
+ isLoading: boolean;
306
+ /** The last error, or null. */
307
+ error: Error | null;
308
+ /** Execute a search query. */
309
+ search: (query: string) => Promise<void>;
310
+ }
311
+ /**
312
+ * Search community posts by title and body content.
313
+ *
314
+ * ```tsx
315
+ * const { results, isLoading, search } = useCommunitySearch();
316
+ *
317
+ * <input onChange={(e) => search(e.target.value)} />
318
+ * {results?.data.map(post => <PostCard key={post.id} post={post} />)}
319
+ * ```
320
+ */
321
+ declare function useCommunitySearch(): UseCommunitySearchReturn;
322
+
323
+ export { CStarChatContext, CStarChatProvider, type CStarChatProviderProps, CStarCommunityContext, CStarCommunityProvider, type CStarCommunityProviderProps, CStarLibraryContext, CStarLibraryProvider, type CStarLibraryProviderProps, type TypingAgent, type UseArticleReturn, type UseArticleSearchReturn, type UseArticlesReturn, type UseCategoriesReturn, type UseChatReturn, type UseCommunitySearchReturn, type UseConversationsReturn, type UseMessagesReturn, type UsePostReturn, type UsePostsReturn, type UseTopicsReturn, type UseTypingReturn, useArticle, useArticleSearch, useArticles, useCategories, useChat, useChatClient, useCommunityClient, useCommunitySearch, useConversations, useLibraryClient, useMessages, usePost, usePosts, useTopics, useTyping };
package/dist/index.d.ts CHANGED
@@ -4,10 +4,13 @@ import { ChatClient, ChatConfig, IdentifyParams, IdentifyResult, Conversation, C
4
4
  export { ChatConfig, Conversation, ConversationCreateParams, ConversationListResponse, IdentifyParams, IdentifyResult, TypingPayload, WidgetMessage } from '@cstar.help/js/chat';
5
5
  import { LibraryClient, LibraryConfig, Category, LibraryArticle, LibraryArticleListParams } from '@cstar.help/js/library';
6
6
  export { Category, LibraryArticle, LibraryArticleListParams, LibraryConfig, LibrarySearchParams, LibrarySearchResult } from '@cstar.help/js/library';
7
+ import { CommunityClient, CommunityConfig, CommunityTopic, CommunityPost, CommunityPostListParams, CommunityPostDetail, CommunitySearchResult } from '@cstar.help/js/community';
8
+ export { CommunityComment, CommunityConfig, CommunityPost, CommunityPostDetail, CommunityPostListParams, CommunitySearchResult, CommunityTopic } from '@cstar.help/js/community';
7
9
  import * as react_jsx_runtime from 'react/jsx-runtime';
8
10
 
9
11
  declare const CStarChatContext: react.Context<ChatClient | null>;
10
12
  declare const CStarLibraryContext: react.Context<LibraryClient | null>;
13
+ declare const CStarCommunityContext: react.Context<CommunityClient | null>;
11
14
  /**
12
15
  * Access the ChatClient from the nearest CStarChatProvider.
13
16
  * Throws if used outside a provider.
@@ -18,6 +21,11 @@ declare function useChatClient(): ChatClient;
18
21
  * Throws if used outside a provider.
19
22
  */
20
23
  declare function useLibraryClient(): LibraryClient;
24
+ /**
25
+ * Access the CommunityClient from the nearest CStarCommunityProvider.
26
+ * Throws if used outside a provider.
27
+ */
28
+ declare function useCommunityClient(): CommunityClient;
21
29
 
22
30
  interface CStarChatProviderProps extends ChatConfig {
23
31
  children: ReactNode;
@@ -185,6 +193,8 @@ interface UseArticleReturn {
185
193
  error: Error | null;
186
194
  /** Re-fetch the article. */
187
195
  refresh: () => Promise<void>;
196
+ /** Record a view for this article. Call once when the article page mounts. */
197
+ recordView: () => Promise<void>;
188
198
  }
189
199
  /**
190
200
  * Fetch a single article by slug.
@@ -215,4 +225,99 @@ interface UseArticleSearchReturn {
215
225
  */
216
226
  declare function useArticleSearch(query: string): UseArticleSearchReturn;
217
227
 
218
- export { CStarChatContext, CStarChatProvider, type CStarChatProviderProps, CStarLibraryContext, CStarLibraryProvider, type CStarLibraryProviderProps, type TypingAgent, type UseArticleReturn, type UseArticleSearchReturn, type UseArticlesReturn, type UseCategoriesReturn, type UseChatReturn, type UseConversationsReturn, type UseMessagesReturn, type UseTypingReturn, useArticle, useArticleSearch, useArticles, useCategories, useChat, useChatClient, useConversations, useLibraryClient, useMessages, useTyping };
228
+ interface CStarCommunityProviderProps extends CommunityConfig {
229
+ children: ReactNode;
230
+ }
231
+ /**
232
+ * Provides a CommunityClient instance to all child components.
233
+ *
234
+ * ```tsx
235
+ * <CStarCommunityProvider teamSlug="acme">
236
+ * <CommunityForum />
237
+ * </CStarCommunityProvider>
238
+ * ```
239
+ */
240
+ declare function CStarCommunityProvider({ children, ...config }: CStarCommunityProviderProps): react_jsx_runtime.JSX.Element;
241
+
242
+ interface UseTopicsReturn {
243
+ /** Community discussion topics. */
244
+ topics: CommunityTopic[];
245
+ /** Whether the fetch is in progress. */
246
+ isLoading: boolean;
247
+ /** The last error, or null. */
248
+ error: Error | null;
249
+ /** Re-fetch topics. */
250
+ refresh: () => Promise<void>;
251
+ }
252
+ /**
253
+ * Fetch community discussion topics.
254
+ *
255
+ * ```tsx
256
+ * const { topics, isLoading } = useTopics();
257
+ * ```
258
+ */
259
+ declare function useTopics(): UseTopicsReturn;
260
+
261
+ interface UsePostsReturn {
262
+ /** Community posts. */
263
+ posts: CommunityPost[];
264
+ /** Total count of matching posts (for pagination). */
265
+ count: number;
266
+ /** Whether the fetch is in progress. */
267
+ isLoading: boolean;
268
+ /** The last error, or null. */
269
+ error: Error | null;
270
+ /** Re-fetch posts with the current params. */
271
+ refresh: () => Promise<void>;
272
+ }
273
+ /**
274
+ * Fetch community posts with optional filters.
275
+ *
276
+ * ```tsx
277
+ * const { posts, isLoading } = usePosts({ topicSlug: 'feedback', sort: 'votes' });
278
+ * ```
279
+ */
280
+ declare function usePosts(params?: CommunityPostListParams): UsePostsReturn;
281
+
282
+ interface UsePostReturn {
283
+ /** The post with comments, or null if not loaded. */
284
+ data: CommunityPostDetail | null;
285
+ /** Whether the fetch is in progress. */
286
+ isLoading: boolean;
287
+ /** The last error, or null. */
288
+ error: Error | null;
289
+ /** Re-fetch the post. */
290
+ refresh: () => Promise<void>;
291
+ }
292
+ /**
293
+ * Fetch a single community post by slug, including all comments.
294
+ *
295
+ * ```tsx
296
+ * const { data, isLoading } = usePost('dark-mode-support');
297
+ * ```
298
+ */
299
+ declare function usePost(slug: string): UsePostReturn;
300
+
301
+ interface UseCommunitySearchReturn {
302
+ /** Search results (null until first search). */
303
+ results: CommunitySearchResult | null;
304
+ /** Whether a search is in progress. */
305
+ isLoading: boolean;
306
+ /** The last error, or null. */
307
+ error: Error | null;
308
+ /** Execute a search query. */
309
+ search: (query: string) => Promise<void>;
310
+ }
311
+ /**
312
+ * Search community posts by title and body content.
313
+ *
314
+ * ```tsx
315
+ * const { results, isLoading, search } = useCommunitySearch();
316
+ *
317
+ * <input onChange={(e) => search(e.target.value)} />
318
+ * {results?.data.map(post => <PostCard key={post.id} post={post} />)}
319
+ * ```
320
+ */
321
+ declare function useCommunitySearch(): UseCommunitySearchReturn;
322
+
323
+ export { CStarChatContext, CStarChatProvider, type CStarChatProviderProps, CStarCommunityContext, CStarCommunityProvider, type CStarCommunityProviderProps, CStarLibraryContext, CStarLibraryProvider, type CStarLibraryProviderProps, type TypingAgent, type UseArticleReturn, type UseArticleSearchReturn, type UseArticlesReturn, type UseCategoriesReturn, type UseChatReturn, type UseCommunitySearchReturn, type UseConversationsReturn, type UseMessagesReturn, type UsePostReturn, type UsePostsReturn, type UseTopicsReturn, type UseTypingReturn, useArticle, useArticleSearch, useArticles, useCategories, useChat, useChatClient, useCommunityClient, useCommunitySearch, useConversations, useLibraryClient, useMessages, usePost, usePosts, useTopics, useTyping };
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { createContext, useContext } from "react";
3
3
  var CStarChatContext = createContext(null);
4
4
  var CStarLibraryContext = createContext(null);
5
+ var CStarCommunityContext = createContext(null);
5
6
  function useChatClient() {
6
7
  const client = useContext(CStarChatContext);
7
8
  if (!client) {
@@ -16,6 +17,13 @@ function useLibraryClient() {
16
17
  }
17
18
  return client;
18
19
  }
20
+ function useCommunityClient() {
21
+ const client = useContext(CStarCommunityContext);
22
+ if (!client) {
23
+ throw new Error("useCommunityClient must be used within a <CStarCommunityProvider>");
24
+ }
25
+ return client;
26
+ }
19
27
 
20
28
  // src/chat/CStarChatProvider.tsx
21
29
  import { useEffect, useRef } from "react";
@@ -373,6 +381,13 @@ function useArticle(slug) {
373
381
  }
374
382
  }
375
383
  }, [client, slug]);
384
+ const recordView = useCallback7(async () => {
385
+ if (!slug) return;
386
+ try {
387
+ await client.recordView(slug);
388
+ } catch {
389
+ }
390
+ }, [client, slug]);
376
391
  useEffect7(() => {
377
392
  mountedRef.current = true;
378
393
  if (!slug) {
@@ -385,7 +400,7 @@ function useArticle(slug) {
385
400
  mountedRef.current = false;
386
401
  };
387
402
  }, [slug, refresh]);
388
- return { article, isLoading, error, refresh };
403
+ return { article, isLoading, error, refresh, recordView };
389
404
  }
390
405
 
391
406
  // src/library/useArticleSearch.ts
@@ -434,9 +449,180 @@ function useArticleSearch(query) {
434
449
  }, [client, query]);
435
450
  return { results, totalCount, isLoading, error };
436
451
  }
452
+
453
+ // src/community/CStarCommunityProvider.tsx
454
+ import { useRef as useRef10 } from "react";
455
+ import { CommunityClient } from "@cstar.help/js/community";
456
+ import { jsx as jsx3 } from "react/jsx-runtime";
457
+ function CStarCommunityProvider({ children, ...config }) {
458
+ const clientRef = useRef10(null);
459
+ if (!clientRef.current) {
460
+ clientRef.current = new CommunityClient(config);
461
+ }
462
+ return /* @__PURE__ */ jsx3(CStarCommunityContext.Provider, { value: clientRef.current, children });
463
+ }
464
+
465
+ // src/community/useTopics.ts
466
+ import { useState as useState9, useEffect as useEffect9, useCallback as useCallback8, useRef as useRef11 } from "react";
467
+ function useTopics() {
468
+ const client = useCommunityClient();
469
+ const [topics, setTopics] = useState9([]);
470
+ const [isLoading, setIsLoading] = useState9(true);
471
+ const [error, setError] = useState9(null);
472
+ const mountedRef = useRef11(true);
473
+ const refresh = useCallback8(async () => {
474
+ setIsLoading(true);
475
+ setError(null);
476
+ try {
477
+ const data = await client.topics();
478
+ if (mountedRef.current) {
479
+ setTopics(data);
480
+ }
481
+ } catch (err) {
482
+ if (mountedRef.current) {
483
+ setError(err instanceof Error ? err : new Error(String(err)));
484
+ }
485
+ } finally {
486
+ if (mountedRef.current) {
487
+ setIsLoading(false);
488
+ }
489
+ }
490
+ }, [client]);
491
+ useEffect9(() => {
492
+ mountedRef.current = true;
493
+ refresh();
494
+ return () => {
495
+ mountedRef.current = false;
496
+ };
497
+ }, [refresh]);
498
+ return { topics, isLoading, error, refresh };
499
+ }
500
+
501
+ // src/community/usePosts.ts
502
+ import { useState as useState10, useEffect as useEffect10, useCallback as useCallback9, useRef as useRef12 } from "react";
503
+ function usePosts(params) {
504
+ const client = useCommunityClient();
505
+ const [posts, setPosts] = useState10([]);
506
+ const [count, setCount] = useState10(0);
507
+ const [isLoading, setIsLoading] = useState10(true);
508
+ const [error, setError] = useState10(null);
509
+ const mountedRef = useRef12(true);
510
+ const topicSlug = params?.topicSlug;
511
+ const status = params?.status;
512
+ const sort = params?.sort;
513
+ const limit = params?.limit;
514
+ const offset = params?.offset;
515
+ const refresh = useCallback9(async () => {
516
+ setIsLoading(true);
517
+ setError(null);
518
+ try {
519
+ const result = await client.posts({ topicSlug, status, sort, limit, offset });
520
+ if (mountedRef.current) {
521
+ setPosts(result.posts);
522
+ setCount(result.count);
523
+ }
524
+ } catch (err) {
525
+ if (mountedRef.current) {
526
+ setError(err instanceof Error ? err : new Error(String(err)));
527
+ }
528
+ } finally {
529
+ if (mountedRef.current) {
530
+ setIsLoading(false);
531
+ }
532
+ }
533
+ }, [client, topicSlug, status, sort, limit, offset]);
534
+ useEffect10(() => {
535
+ mountedRef.current = true;
536
+ refresh();
537
+ return () => {
538
+ mountedRef.current = false;
539
+ };
540
+ }, [refresh]);
541
+ return { posts, count, isLoading, error, refresh };
542
+ }
543
+
544
+ // src/community/usePost.ts
545
+ import { useState as useState11, useEffect as useEffect11, useCallback as useCallback10, useRef as useRef13 } from "react";
546
+ function usePost(slug) {
547
+ const client = useCommunityClient();
548
+ const [data, setData] = useState11(null);
549
+ const [isLoading, setIsLoading] = useState11(true);
550
+ const [error, setError] = useState11(null);
551
+ const mountedRef = useRef13(true);
552
+ const refresh = useCallback10(async () => {
553
+ setIsLoading(true);
554
+ setError(null);
555
+ try {
556
+ const result = await client.post(slug);
557
+ if (mountedRef.current) {
558
+ setData(result);
559
+ }
560
+ } catch (err) {
561
+ if (mountedRef.current) {
562
+ setError(err instanceof Error ? err : new Error(String(err)));
563
+ }
564
+ } finally {
565
+ if (mountedRef.current) {
566
+ setIsLoading(false);
567
+ }
568
+ }
569
+ }, [client, slug]);
570
+ useEffect11(() => {
571
+ mountedRef.current = true;
572
+ refresh();
573
+ return () => {
574
+ mountedRef.current = false;
575
+ };
576
+ }, [refresh]);
577
+ return { data, isLoading, error, refresh };
578
+ }
579
+
580
+ // src/community/useCommunitySearch.ts
581
+ import { useState as useState12, useEffect as useEffect12, useCallback as useCallback11, useRef as useRef14 } from "react";
582
+ function useCommunitySearch() {
583
+ const client = useCommunityClient();
584
+ const [results, setResults] = useState12(null);
585
+ const [isLoading, setIsLoading] = useState12(false);
586
+ const [error, setError] = useState12(null);
587
+ const mountedRef = useRef14(true);
588
+ useEffect12(() => {
589
+ mountedRef.current = true;
590
+ return () => {
591
+ mountedRef.current = false;
592
+ };
593
+ }, []);
594
+ const search = useCallback11(
595
+ async (query) => {
596
+ if (!query.trim()) {
597
+ setResults(null);
598
+ return;
599
+ }
600
+ setIsLoading(true);
601
+ setError(null);
602
+ try {
603
+ const data = await client.search(query);
604
+ if (mountedRef.current) {
605
+ setResults(data);
606
+ }
607
+ } catch (err) {
608
+ if (mountedRef.current) {
609
+ setError(err instanceof Error ? err : new Error(String(err)));
610
+ }
611
+ } finally {
612
+ if (mountedRef.current) {
613
+ setIsLoading(false);
614
+ }
615
+ }
616
+ },
617
+ [client]
618
+ );
619
+ return { results, isLoading, error, search };
620
+ }
437
621
  export {
438
622
  CStarChatContext,
439
623
  CStarChatProvider,
624
+ CStarCommunityContext,
625
+ CStarCommunityProvider,
440
626
  CStarLibraryContext,
441
627
  CStarLibraryProvider,
442
628
  useArticle,
@@ -445,8 +631,13 @@ export {
445
631
  useCategories,
446
632
  useChat,
447
633
  useChatClient,
634
+ useCommunityClient,
635
+ useCommunitySearch,
448
636
  useConversations,
449
637
  useLibraryClient,
450
638
  useMessages,
639
+ usePost,
640
+ usePosts,
641
+ useTopics,
451
642
  useTyping
452
643
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cstar.help/react",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "React hooks for the cStar customer support platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",