yemowith-chat-web-ui 0.1.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.
Files changed (71) hide show
  1. package/README.md +153 -0
  2. package/dist/components/bot-chat-modal.d.ts +12 -0
  3. package/dist/components/bot-chat-modal.d.ts.map +1 -0
  4. package/dist/components/bot-chat-modal.js +18 -0
  5. package/dist/components/chat-area-header.d.ts +11 -0
  6. package/dist/components/chat-area-header.d.ts.map +1 -0
  7. package/dist/components/chat-area-header.js +28 -0
  8. package/dist/components/chat-empty-state.d.ts +13 -0
  9. package/dist/components/chat-empty-state.d.ts.map +1 -0
  10. package/dist/components/chat-empty-state.js +12 -0
  11. package/dist/components/chat-input-form.d.ts +12 -0
  12. package/dist/components/chat-input-form.d.ts.map +1 -0
  13. package/dist/components/chat-input-form.js +7 -0
  14. package/dist/components/chat-message-list.d.ts +12 -0
  15. package/dist/components/chat-message-list.d.ts.map +1 -0
  16. package/dist/components/chat-message-list.js +38 -0
  17. package/dist/components/chat-page-header.d.ts +22 -0
  18. package/dist/components/chat-page-header.d.ts.map +1 -0
  19. package/dist/components/chat-page-header.js +10 -0
  20. package/dist/components/chat-sidebar.d.ts +18 -0
  21. package/dist/components/chat-sidebar.d.ts.map +1 -0
  22. package/dist/components/chat-sidebar.js +27 -0
  23. package/dist/components/chat-typing-status.d.ts +8 -0
  24. package/dist/components/chat-typing-status.d.ts.map +1 -0
  25. package/dist/components/chat-typing-status.js +11 -0
  26. package/dist/components/message-bubble.d.ts +7 -0
  27. package/dist/components/message-bubble.d.ts.map +1 -0
  28. package/dist/components/message-bubble.js +11 -0
  29. package/dist/components/types.d.ts +33 -0
  30. package/dist/components/types.d.ts.map +1 -0
  31. package/dist/components/types.js +2 -0
  32. package/dist/components/ui/avatar.d.ts +9 -0
  33. package/dist/components/ui/avatar.d.ts.map +1 -0
  34. package/dist/components/ui/avatar.js +14 -0
  35. package/dist/components/ui/button.d.ts +11 -0
  36. package/dist/components/ui/button.d.ts.map +1 -0
  37. package/dist/components/ui/button.js +35 -0
  38. package/dist/components/ui/card.d.ts +10 -0
  39. package/dist/components/ui/card.d.ts.map +1 -0
  40. package/dist/components/ui/card.js +24 -0
  41. package/dist/components/ui/input.d.ts +4 -0
  42. package/dist/components/ui/input.d.ts.map +1 -0
  43. package/dist/components/ui/input.js +6 -0
  44. package/dist/components/ui/scroll-area.d.ts +6 -0
  45. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  46. package/dist/components/ui/scroll-area.js +11 -0
  47. package/dist/hooks/use-chat-socket.d.ts +59 -0
  48. package/dist/hooks/use-chat-socket.d.ts.map +1 -0
  49. package/dist/hooks/use-chat-socket.js +175 -0
  50. package/dist/index.d.ts +21 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +19 -0
  53. package/dist/lib/chat-api.d.ts +13 -0
  54. package/dist/lib/chat-api.d.ts.map +1 -0
  55. package/dist/lib/chat-api.js +57 -0
  56. package/dist/lib/chat-config.d.ts +7 -0
  57. package/dist/lib/chat-config.d.ts.map +1 -0
  58. package/dist/lib/chat-config.js +18 -0
  59. package/dist/lib/notification-sound.d.ts +6 -0
  60. package/dist/lib/notification-sound.d.ts.map +1 -0
  61. package/dist/lib/notification-sound.js +28 -0
  62. package/dist/lib/utils.d.ts +3 -0
  63. package/dist/lib/utils.d.ts.map +1 -0
  64. package/dist/lib/utils.js +5 -0
  65. package/dist/provider/chat-provider.d.ts +70 -0
  66. package/dist/provider/chat-provider.d.ts.map +1 -0
  67. package/dist/provider/chat-provider.js +64 -0
  68. package/dist/provider/types.d.ts +6 -0
  69. package/dist/provider/types.d.ts.map +1 -0
  70. package/dist/provider/types.js +1 -0
  71. package/package.json +54 -0
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cva } from "class-variance-authority";
3
+ import { Slot } from "radix-ui";
4
+ import { cn } from "../../lib/utils";
5
+ const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", {
6
+ variants: {
7
+ variant: {
8
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
9
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
10
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
11
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
12
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
13
+ link: "text-primary underline-offset-4 hover:underline",
14
+ },
15
+ size: {
16
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
17
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
18
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
19
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
20
+ icon: "size-9",
21
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
22
+ "icon-sm": "size-8",
23
+ "icon-lg": "size-10",
24
+ },
25
+ },
26
+ defaultVariants: {
27
+ variant: "default",
28
+ size: "default",
29
+ },
30
+ });
31
+ function Button({ className, variant = "default", size = "default", asChild = false, ...props }) {
32
+ const Comp = asChild ? Slot.Root : "button";
33
+ return (_jsx(Comp, { "data-slot": "button", "data-variant": variant, "data-size": size, className: cn(buttonVariants({ variant, size, className })), ...props }));
34
+ }
35
+ export { Button, buttonVariants };
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ declare function Card({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
3
+ declare function CardHeader({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
4
+ declare function CardTitle({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
5
+ declare function CardDescription({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
6
+ declare function CardAction({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
7
+ declare function CardContent({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
8
+ declare function CardFooter({ className, ...props }: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
9
+ export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, };
10
+ //# sourceMappingURL=card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../../src/components/ui/card.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,iBAAS,IAAI,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,2CAWjE;AAED,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,2CAWvE;AAED,iBAAS,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,2CAQtE;AAED,iBAAS,eAAe,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,2CAQ5E;AAED,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,2CAWvE;AAED,iBAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,2CAQxE;AAED,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,2CAQvE;AAED,OAAO,EACL,IAAI,EACJ,UAAU,EACV,UAAU,EACV,SAAS,EACT,UAAU,EACV,eAAe,EACf,WAAW,GACZ,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/utils";
3
+ function Card({ className, ...props }) {
4
+ return (_jsx("div", { "data-slot": "card", className: cn("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", className), ...props }));
5
+ }
6
+ function CardHeader({ className, ...props }) {
7
+ return (_jsx("div", { "data-slot": "card-header", className: cn("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", className), ...props }));
8
+ }
9
+ function CardTitle({ className, ...props }) {
10
+ return (_jsx("div", { "data-slot": "card-title", className: cn("leading-none font-semibold", className), ...props }));
11
+ }
12
+ function CardDescription({ className, ...props }) {
13
+ return (_jsx("div", { "data-slot": "card-description", className: cn("text-muted-foreground text-sm", className), ...props }));
14
+ }
15
+ function CardAction({ className, ...props }) {
16
+ return (_jsx("div", { "data-slot": "card-action", className: cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className), ...props }));
17
+ }
18
+ function CardContent({ className, ...props }) {
19
+ return (_jsx("div", { "data-slot": "card-content", className: cn("px-6", className), ...props }));
20
+ }
21
+ function CardFooter({ className, ...props }) {
22
+ return (_jsx("div", { "data-slot": "card-footer", className: cn("flex items-center px-6 [.border-t]:pt-6", className), ...props }));
23
+ }
24
+ export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, };
@@ -0,0 +1,4 @@
1
+ import * as React from "react";
2
+ declare function Input({ className, type, ...props }: React.ComponentProps<"input">): import("react/jsx-runtime").JSX.Element;
3
+ export { Input };
4
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/ui/input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,iBAAS,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,2CAc1E;AAED,OAAO,EAAE,KAAK,EAAE,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/utils";
3
+ function Input({ className, type, ...props }) {
4
+ return (_jsx("input", { type: type, "data-slot": "input", className: cn("file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", className), ...props }));
5
+ }
6
+ export { Input };
@@ -0,0 +1,6 @@
1
+ import * as React from "react";
2
+ import { ScrollArea as ScrollAreaPrimitive } from "radix-ui";
3
+ declare function ScrollArea({ className, children, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>): import("react/jsx-runtime").JSX.Element;
4
+ declare function ScrollBar({ className, orientation, ...props }: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>): import("react/jsx-runtime").JSX.Element;
5
+ export { ScrollArea, ScrollBar };
6
+ //# sourceMappingURL=scroll-area.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll-area.d.ts","sourceRoot":"","sources":["../../../src/components/ui/scroll-area.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,UAAU,IAAI,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAG7D,iBAAS,UAAU,CAAC,EAClB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,mBAAmB,CAAC,IAAI,CAAC,2CAiBvD;AAED,iBAAS,SAAS,CAAC,EACjB,SAAS,EACT,WAAwB,EACxB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,mBAAmB,CAAC,mBAAmB,CAAC,2CAmBtE;AAED,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ScrollArea as ScrollAreaPrimitive } from "radix-ui";
4
+ import { cn } from "../../lib/utils";
5
+ function ScrollArea({ className, children, ...props }) {
6
+ return (_jsxs(ScrollAreaPrimitive.Root, { "data-slot": "scroll-area", className: cn("relative", className), ...props, children: [_jsx(ScrollAreaPrimitive.Viewport, { "data-slot": "scroll-area-viewport", className: "focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1", children: children }), _jsx(ScrollBar, {}), _jsx(ScrollAreaPrimitive.Corner, {})] }));
7
+ }
8
+ function ScrollBar({ className, orientation = "vertical", ...props }) {
9
+ return (_jsx(ScrollAreaPrimitive.ScrollAreaScrollbar, { "data-slot": "scroll-area-scrollbar", orientation: orientation, className: cn("flex touch-none p-px transition-colors select-none", orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent", orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent", className), ...props, children: _jsx(ScrollAreaPrimitive.ScrollAreaThumb, { "data-slot": "scroll-area-thumb", className: "bg-border relative flex-1 rounded-full" }) }));
10
+ }
11
+ export { ScrollArea, ScrollBar };
@@ -0,0 +1,59 @@
1
+ export type ChatMessage = {
2
+ messageId: string;
3
+ conversationId: string;
4
+ senderId: string;
5
+ content: string;
6
+ type: string;
7
+ createdAt: string;
8
+ isRead: boolean;
9
+ deliveredAt?: string | null;
10
+ };
11
+ export type SocketAuthProfile = {
12
+ firstName?: string;
13
+ lastName?: string;
14
+ avatarURL?: string;
15
+ };
16
+ export type TypingContext = {
17
+ userId: string;
18
+ conversationId: string | null;
19
+ receiverUserId: string | null;
20
+ };
21
+ export type UserStatusContext = {
22
+ userId: string;
23
+ conversationId: string | null;
24
+ receiverUserId: string | null;
25
+ status: string;
26
+ };
27
+ export declare function useChatSocket(token: string | null, profile?: SocketAuthProfile | null, onUnauthorized?: () => void, currentUserId?: string | null): {
28
+ connected: boolean;
29
+ lastError: string | null;
30
+ typing: TypingContext | null;
31
+ userStatus: UserStatusContext | null;
32
+ onlineUserIds: Record<string, boolean>;
33
+ requestPresence: (userIds: string[]) => void;
34
+ sendTyping: (opts: {
35
+ conversationId?: string;
36
+ receiverUserId?: string;
37
+ }) => void;
38
+ sendTypingStop: (opts: {
39
+ conversationId?: string;
40
+ receiverUserId?: string;
41
+ }) => void;
42
+ sendStatus: (opts: {
43
+ conversationId?: string;
44
+ receiverUserId?: string;
45
+ status: string;
46
+ }) => void;
47
+ sendMessage: (params: {
48
+ receiverUserId?: string;
49
+ conversationId?: string;
50
+ content: string;
51
+ type?: string;
52
+ }) => void;
53
+ markRead: (conversationId: string) => void;
54
+ setOnNewMessage: (cb: (msg: ChatMessage) => void) => void;
55
+ setCurrentConversationId: (id: string | null) => void;
56
+ setOnMessageDelivered: (cb: (messageId: string) => void) => void;
57
+ setOnMessageRead: (cb: (messageIds: string[]) => void) => void;
58
+ };
59
+ //# sourceMappingURL=use-chat-socket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-chat-socket.d.ts","sourceRoot":"","sources":["../../src/hooks/use-chat-socket.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,WAAW,GAAG;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAYF,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAAG,IAAI,EAClC,cAAc,CAAC,EAAE,MAAM,IAAI,EAC3B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI;;;;;;+BAiKiB,MAAM,EAAE;uBA/C7C;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE;2BAapD;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE;uBAYpD;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;0BAQlE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;+BAUjD,MAAM;0BA7IX,CAAC,GAAG,EAAE,WAAW,KAAK,IAAI;mCAUjB,MAAM,GAAG,IAAI;gCAPhB,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI;2BAGhC,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI;EAiKzE"}
@@ -0,0 +1,175 @@
1
+ "use client";
2
+ import { useEffect, useRef, useCallback, useState } from "react";
3
+ import { io } from "socket.io-client";
4
+ import { playNotificationSound } from "../lib/notification-sound";
5
+ import { getWsUrl } from "../lib/chat-config";
6
+ const HEARTBEAT_MS = 25000;
7
+ function isAuthError(e) {
8
+ const code = (e?.code ?? "").toUpperCase();
9
+ const msg = (e?.message ?? "").toLowerCase();
10
+ return (code === "UNAUTHORIZED" ||
11
+ msg.includes("invalid token") ||
12
+ msg.includes("missing token"));
13
+ }
14
+ export function useChatSocket(token, profile, onUnauthorized, currentUserId) {
15
+ const [connected, setConnected] = useState(false);
16
+ const [lastError, setLastError] = useState(null);
17
+ const [typing, setTyping] = useState(null);
18
+ const [userStatus, setUserStatus] = useState(null);
19
+ const [onlineUserIds, setOnlineUserIds] = useState({});
20
+ const socketRef = useRef(null);
21
+ const heartbeatRef = useRef(null);
22
+ const typingTimeoutRef = useRef(null);
23
+ const onNewMessage = useRef(() => { });
24
+ const onMessageDelivered = useRef(() => { });
25
+ const onMessageRead = useRef(() => { });
26
+ const currentConversationIdRef = useRef(null);
27
+ const setOnNewMessage = useCallback((cb) => {
28
+ onNewMessage.current = cb;
29
+ }, []);
30
+ const setOnMessageDelivered = useCallback((cb) => {
31
+ onMessageDelivered.current = cb;
32
+ }, []);
33
+ const setOnMessageRead = useCallback((cb) => {
34
+ onMessageRead.current = cb;
35
+ }, []);
36
+ const setCurrentConversationId = useCallback((id) => {
37
+ currentConversationIdRef.current = id;
38
+ }, []);
39
+ useEffect(() => {
40
+ if (!token)
41
+ return;
42
+ const wsUrl = getWsUrl();
43
+ const auth = { token };
44
+ if (profile?.firstName)
45
+ auth.firstName = profile.firstName;
46
+ if (profile?.lastName)
47
+ auth.lastName = profile.lastName;
48
+ if (profile?.avatarURL)
49
+ auth.avatarURL = profile.avatarURL;
50
+ const socket = io(wsUrl, {
51
+ auth,
52
+ transports: ["websocket", "polling"],
53
+ });
54
+ socketRef.current = socket;
55
+ socket.on("connect", () => setConnected(true));
56
+ socket.on("disconnect", () => setConnected(false));
57
+ socket.on("chat:error", (e) => {
58
+ const err = e?.message ?? e?.code ?? "Hata";
59
+ setLastError(err);
60
+ if (onUnauthorized && isAuthError(e))
61
+ onUnauthorized();
62
+ });
63
+ socket.on("chat:new", (msg) => {
64
+ onNewMessage.current(msg);
65
+ const isFromOther = currentUserId != null && msg.senderId !== currentUserId;
66
+ const isNotViewingThisChat = msg.conversationId !== currentConversationIdRef.current;
67
+ if (isFromOther && isNotViewingThisChat) {
68
+ playNotificationSound();
69
+ }
70
+ });
71
+ socket.on("chat:user_typing", (payload) => {
72
+ setTyping(payload);
73
+ setUserStatus(null);
74
+ });
75
+ socket.on("chat:user_typing_stop", () => setTyping(null));
76
+ socket.on("chat:user_status", (payload) => {
77
+ setUserStatus(payload);
78
+ });
79
+ socket.on("chat:user_online", (payload) => {
80
+ const uid = payload?.userId;
81
+ if (uid)
82
+ setOnlineUserIds((prev) => ({ ...prev, [uid]: true }));
83
+ });
84
+ socket.on("chat:user_offline", (payload) => {
85
+ const uid = payload?.userId;
86
+ if (uid)
87
+ setOnlineUserIds((prev) => ({ ...prev, [uid]: false }));
88
+ });
89
+ socket.on("chat:presence", (payload) => {
90
+ const o = payload?.online;
91
+ if (o && typeof o === "object")
92
+ setOnlineUserIds((prev) => ({ ...prev, ...o }));
93
+ });
94
+ socket.on("chat:message_delivered", (payload) => {
95
+ if (payload?.messageId)
96
+ onMessageDelivered.current(payload.messageId);
97
+ });
98
+ socket.on("chat:message_read", (payload) => {
99
+ if (Array.isArray(payload?.messageIds) && payload.messageIds.length > 0)
100
+ onMessageRead.current(payload.messageIds);
101
+ });
102
+ heartbeatRef.current = setInterval(() => {
103
+ if (socket.connected)
104
+ socket.emit("chat:heartbeat");
105
+ }, HEARTBEAT_MS);
106
+ return () => {
107
+ if (heartbeatRef.current)
108
+ clearInterval(heartbeatRef.current);
109
+ if (typingTimeoutRef.current)
110
+ clearTimeout(typingTimeoutRef.current);
111
+ socket.disconnect();
112
+ socketRef.current = null;
113
+ setConnected(false);
114
+ setTyping(null);
115
+ setUserStatus(null);
116
+ };
117
+ }, [token, profile?.firstName, profile?.lastName, profile?.avatarURL, onUnauthorized, currentUserId]);
118
+ const sendTyping = useCallback((opts) => {
119
+ if (!socketRef.current?.connected)
120
+ return;
121
+ socketRef.current.emit("chat:typing", opts);
122
+ if (typingTimeoutRef.current)
123
+ clearTimeout(typingTimeoutRef.current);
124
+ typingTimeoutRef.current = setTimeout(() => {
125
+ socketRef.current?.emit("chat:typing_stop", opts);
126
+ typingTimeoutRef.current = null;
127
+ }, 2000);
128
+ }, []);
129
+ const sendTypingStop = useCallback((opts) => {
130
+ if (typingTimeoutRef.current) {
131
+ clearTimeout(typingTimeoutRef.current);
132
+ typingTimeoutRef.current = null;
133
+ }
134
+ socketRef.current?.emit("chat:typing_stop", opts);
135
+ setTyping(null);
136
+ }, []);
137
+ const sendStatus = useCallback((opts) => {
138
+ if (!socketRef.current?.connected)
139
+ return;
140
+ socketRef.current.emit("chat:status", opts);
141
+ }, []);
142
+ const sendMessage = useCallback((params) => {
143
+ if (!socketRef.current?.connected)
144
+ return;
145
+ socketRef.current.emit("chat:send", {
146
+ ...params,
147
+ type: params.type ?? "text",
148
+ });
149
+ }, []);
150
+ const markRead = useCallback((conversationId) => {
151
+ socketRef.current?.emit("chat:read", { conversationId });
152
+ }, []);
153
+ const requestPresence = useCallback((userIds) => {
154
+ if (!socketRef.current?.connected || userIds.length === 0)
155
+ return;
156
+ socketRef.current.emit("chat:presence", { userIds });
157
+ }, []);
158
+ return {
159
+ connected,
160
+ lastError,
161
+ typing,
162
+ userStatus,
163
+ onlineUserIds,
164
+ requestPresence,
165
+ sendTyping,
166
+ sendTypingStop,
167
+ sendStatus,
168
+ sendMessage,
169
+ markRead,
170
+ setOnNewMessage,
171
+ setCurrentConversationId,
172
+ setOnMessageDelivered,
173
+ setOnMessageRead,
174
+ };
175
+ }
@@ -0,0 +1,21 @@
1
+ export { ChatProvider, useChat, type ChatProviderProps, type Profile, type ChatMessage } from "./provider/chat-provider";
2
+ export { ChatAreaHeader } from "./components/chat-area-header";
3
+ export { ChatPageHeader } from "./components/chat-page-header";
4
+ export { ChatSidebar } from "./components/chat-sidebar";
5
+ export { ChatMessageList } from "./components/chat-message-list";
6
+ export { ChatTypingStatus } from "./components/chat-typing-status";
7
+ export { ChatInputForm } from "./components/chat-input-form";
8
+ export { ChatEmptyState } from "./components/chat-empty-state";
9
+ export { BotChatModal } from "./components/bot-chat-modal";
10
+ export { MessageBubble } from "./components/message-bubble";
11
+ export type { Conversation, MessageRow, OtherParticipant, LastMessage } from "./components/types";
12
+ export { BOT_USER_ID } from "./components/types";
13
+ export { getConversations, getMessages, getBlockedUsers, blockUser, unblockUser, ChatApiError } from "./lib/chat-api";
14
+ export { setChatConfig, getApiBaseUrl, getWsUrl } from "./lib/chat-config";
15
+ export { cn } from "./lib/utils";
16
+ export { Button, buttonVariants } from "./components/ui/button";
17
+ export { Input } from "./components/ui/input";
18
+ export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction, } from "./components/ui/card";
19
+ export { Avatar, AvatarImage, AvatarFallback } from "./components/ui/avatar";
20
+ export { ScrollArea, ScrollBar } from "./components/ui/scroll-area";
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACzH,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,eAAe,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACtH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEjC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,OAAO,EACL,IAAI,EACJ,UAAU,EACV,SAAS,EACT,eAAe,EACf,WAAW,EACX,UAAU,EACV,UAAU,GACX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ export { ChatProvider, useChat } from "./provider/chat-provider";
2
+ export { ChatAreaHeader } from "./components/chat-area-header";
3
+ export { ChatPageHeader } from "./components/chat-page-header";
4
+ export { ChatSidebar } from "./components/chat-sidebar";
5
+ export { ChatMessageList } from "./components/chat-message-list";
6
+ export { ChatTypingStatus } from "./components/chat-typing-status";
7
+ export { ChatInputForm } from "./components/chat-input-form";
8
+ export { ChatEmptyState } from "./components/chat-empty-state";
9
+ export { BotChatModal } from "./components/bot-chat-modal";
10
+ export { MessageBubble } from "./components/message-bubble";
11
+ export { BOT_USER_ID } from "./components/types";
12
+ export { getConversations, getMessages, getBlockedUsers, blockUser, unblockUser, ChatApiError } from "./lib/chat-api";
13
+ export { setChatConfig, getApiBaseUrl, getWsUrl } from "./lib/chat-config";
14
+ export { cn } from "./lib/utils";
15
+ export { Button, buttonVariants } from "./components/ui/button";
16
+ export { Input } from "./components/ui/input";
17
+ export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction, } from "./components/ui/card";
18
+ export { Avatar, AvatarImage, AvatarFallback } from "./components/ui/avatar";
19
+ export { ScrollArea, ScrollBar } from "./components/ui/scroll-area";
@@ -0,0 +1,13 @@
1
+ export declare class ChatApiError extends Error {
2
+ status: number;
3
+ constructor(message: string, status: number);
4
+ }
5
+ export declare function getConversations(token: string): Promise<any>;
6
+ export declare function getMessages(token: string, conversationId: string, opts?: {
7
+ cursor?: string;
8
+ limit?: number;
9
+ }): Promise<any>;
10
+ export declare function getBlockedUsers(token: string): Promise<string[]>;
11
+ export declare function blockUser(token: string, blockedUserId: string): Promise<void>;
12
+ export declare function unblockUser(token: string, blockedUserId: string): Promise<void>;
13
+ //# sourceMappingURL=chat-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-api.d.ts","sourceRoot":"","sources":["../../src/lib/chat-api.ts"],"names":[],"mappings":"AAMA,qBAAa,YAAa,SAAQ,KAAK;IAG5B,MAAM,EAAE,MAAM;gBADrB,OAAO,EAAE,MAAM,EACR,MAAM,EAAE,MAAM;CAKxB;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,gBAMnD;AAID,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,gBAU3C;AAED,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOtE;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnF;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMrF"}
@@ -0,0 +1,57 @@
1
+ import { getApiBaseUrl } from "./chat-config";
2
+ function getBase() {
3
+ return getApiBaseUrl();
4
+ }
5
+ export class ChatApiError extends Error {
6
+ constructor(message, status) {
7
+ super(message);
8
+ this.status = status;
9
+ this.name = "ChatApiError";
10
+ }
11
+ }
12
+ export async function getConversations(token) {
13
+ const res = await fetch(`${getBase()}/conversations`, {
14
+ headers: { Authorization: `Bearer ${token}` },
15
+ });
16
+ if (!res.ok)
17
+ throw new ChatApiError(await res.text(), res.status);
18
+ return res.json();
19
+ }
20
+ const DEFAULT_MESSAGE_LIMIT = 30;
21
+ export async function getMessages(token, conversationId, opts) {
22
+ const url = new URL(`${getBase()}/conversations/${conversationId}/messages`);
23
+ if (opts?.cursor)
24
+ url.searchParams.set("cursor", opts.cursor);
25
+ url.searchParams.set("limit", String(opts?.limit ?? DEFAULT_MESSAGE_LIMIT));
26
+ const res = await fetch(url.toString(), {
27
+ headers: { Authorization: `Bearer ${token}` },
28
+ });
29
+ if (!res.ok)
30
+ throw new ChatApiError(await res.text(), res.status);
31
+ return res.json();
32
+ }
33
+ export async function getBlockedUsers(token) {
34
+ const res = await fetch(`${getBase()}/block`, {
35
+ headers: { Authorization: `Bearer ${token}` },
36
+ });
37
+ if (!res.ok)
38
+ throw new ChatApiError(await res.text(), res.status);
39
+ const data = await res.json();
40
+ return Array.isArray(data) ? data : [];
41
+ }
42
+ export async function blockUser(token, blockedUserId) {
43
+ const res = await fetch(`${getBase()}/block/${encodeURIComponent(blockedUserId)}`, {
44
+ method: "POST",
45
+ headers: { Authorization: `Bearer ${token}` },
46
+ });
47
+ if (!res.ok)
48
+ throw new ChatApiError(await res.text(), res.status);
49
+ }
50
+ export async function unblockUser(token, blockedUserId) {
51
+ const res = await fetch(`${getBase()}/block/${encodeURIComponent(blockedUserId)}`, {
52
+ method: "DELETE",
53
+ headers: { Authorization: `Bearer ${token}` },
54
+ });
55
+ if (!res.ok)
56
+ throw new ChatApiError(await res.text(), res.status);
57
+ }
@@ -0,0 +1,7 @@
1
+ export declare function setChatConfig(options: {
2
+ apiBaseUrl?: string;
3
+ wsUrl?: string;
4
+ }): void;
5
+ export declare function getApiBaseUrl(): string;
6
+ export declare function getWsUrl(): string;
7
+ //# sourceMappingURL=chat-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-config.d.ts","sourceRoot":"","sources":["../../src/lib/chat-config.ts"],"names":[],"mappings":"AAMA,wBAAgB,aAAa,CAAC,OAAO,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,QAG7E;AAED,wBAAgB,aAAa,IAAI,MAAM,CAGtC;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAGjC"}
@@ -0,0 +1,18 @@
1
+ const DEFAULT_API = "http://localhost:3000";
2
+ const DEFAULT_WS = "http://localhost:3000";
3
+ let apiBaseUrl;
4
+ let wsUrl;
5
+ export function setChatConfig(options) {
6
+ if (options.apiBaseUrl !== undefined)
7
+ apiBaseUrl = options.apiBaseUrl;
8
+ if (options.wsUrl !== undefined)
9
+ wsUrl = options.wsUrl;
10
+ }
11
+ export function getApiBaseUrl() {
12
+ const fromEnv = typeof process !== "undefined" ? process.env?.NEXT_PUBLIC_CHAT_API_URL : undefined;
13
+ return apiBaseUrl ?? fromEnv ?? DEFAULT_API;
14
+ }
15
+ export function getWsUrl() {
16
+ const fromEnv = typeof process !== "undefined" ? process.env?.NEXT_PUBLIC_CHAT_WS_URL : undefined;
17
+ return wsUrl ?? fromEnv ?? DEFAULT_WS;
18
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Kısa, bildirim tarzı iki tonlu ses çalar (Web Audio API).
3
+ * Tarayıcı politikası gereği ilk çalma öncesi kullanıcı etkileşimi gerekebilir.
4
+ */
5
+ export declare function playNotificationSound(): void;
6
+ //# sourceMappingURL=notification-sound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-sound.d.ts","sourceRoot":"","sources":["../../src/lib/notification-sound.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAqB5C"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Kısa, bildirim tarzı iki tonlu ses çalar (Web Audio API).
3
+ * Tarayıcı politikası gereği ilk çalma öncesi kullanıcı etkileşimi gerekebilir.
4
+ */
5
+ export function playNotificationSound() {
6
+ if (typeof window === "undefined")
7
+ return;
8
+ try {
9
+ const ctx = new (window.AudioContext || window.webkitAudioContext)();
10
+ const playTone = (frequency, startTime, duration) => {
11
+ const osc = ctx.createOscillator();
12
+ const gain = ctx.createGain();
13
+ osc.connect(gain);
14
+ gain.connect(ctx.destination);
15
+ osc.frequency.value = frequency;
16
+ osc.type = "sine";
17
+ gain.gain.setValueAtTime(0.15, startTime);
18
+ gain.gain.exponentialRampToValueAtTime(0.01, startTime + duration);
19
+ osc.start(startTime);
20
+ osc.stop(startTime + duration);
21
+ };
22
+ playTone(523.25, 0, 0.08);
23
+ playTone(659.25, 0.1, 0.12);
24
+ }
25
+ catch {
26
+ // Ses API yok veya engelliyse sessizce geç
27
+ }
28
+ }
@@ -0,0 +1,3 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): string;
3
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;AAG7C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
@@ -0,0 +1,5 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ export function cn(...inputs) {
4
+ return twMerge(clsx(inputs));
5
+ }
@@ -0,0 +1,70 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Profile } from "./types";
3
+ import { type ChatMessage } from "../hooks/use-chat-socket";
4
+ export type { Profile };
5
+ export type { ChatMessage };
6
+ type TypingContext = {
7
+ userId: string;
8
+ conversationId: string | null;
9
+ receiverUserId: string | null;
10
+ };
11
+ type UserStatusContext = {
12
+ userId: string;
13
+ conversationId: string | null;
14
+ receiverUserId: string | null;
15
+ status: string;
16
+ };
17
+ type ChatContextValue = {
18
+ token: string | null;
19
+ userId: string | null;
20
+ profile: Profile | null;
21
+ isReady: boolean;
22
+ connected: boolean;
23
+ lastError: string | null;
24
+ typing: TypingContext | null;
25
+ userStatus: UserStatusContext | null;
26
+ sendMessage: (params: {
27
+ receiverUserId?: string;
28
+ conversationId?: string;
29
+ content: string;
30
+ type?: string;
31
+ }) => void;
32
+ sendTyping: (opts: {
33
+ conversationId?: string;
34
+ receiverUserId?: string;
35
+ }) => void;
36
+ sendTypingStop: (opts: {
37
+ conversationId?: string;
38
+ receiverUserId?: string;
39
+ }) => void;
40
+ sendStatus: (opts: {
41
+ conversationId?: string;
42
+ receiverUserId?: string;
43
+ status: string;
44
+ }) => void;
45
+ markRead: (conversationId: string) => void;
46
+ setOnNewMessage: (cb: (msg: ChatMessage) => void) => void;
47
+ setOnMessageDelivered: (cb: (messageId: string) => void) => void;
48
+ setOnMessageRead: (cb: (messageIds: string[]) => void) => void;
49
+ setCurrentConversationId: (id: string | null) => void;
50
+ onlineUserIds: Record<string, boolean>;
51
+ requestPresence: (userIds: string[]) => void;
52
+ logout: () => void;
53
+ };
54
+ export type ChatProviderProps = {
55
+ children: ReactNode;
56
+ token: string | null;
57
+ userId: string | null;
58
+ profile: Profile | null;
59
+ isReady?: boolean;
60
+ onLogout: () => void;
61
+ userIdOverride?: string | null;
62
+ metadataOverride?: Profile | null;
63
+ /** REST API base URL (optional; falls back to env NEXT_PUBLIC_CHAT_API_URL) */
64
+ apiBaseUrl?: string;
65
+ /** WebSocket URL (optional; falls back to env NEXT_PUBLIC_CHAT_WS_URL) */
66
+ wsUrl?: string;
67
+ };
68
+ export declare function ChatProvider({ children, token, userId, profile, isReady, onLogout, userIdOverride, metadataOverride, apiBaseUrl, wsUrl, }: ChatProviderProps): import("react/jsx-runtime").JSX.Element;
69
+ export declare function useChat(): ChatContextValue;
70
+ //# sourceMappingURL=chat-provider.d.ts.map