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.
- package/README.md +153 -0
- package/dist/components/bot-chat-modal.d.ts +12 -0
- package/dist/components/bot-chat-modal.d.ts.map +1 -0
- package/dist/components/bot-chat-modal.js +18 -0
- package/dist/components/chat-area-header.d.ts +11 -0
- package/dist/components/chat-area-header.d.ts.map +1 -0
- package/dist/components/chat-area-header.js +28 -0
- package/dist/components/chat-empty-state.d.ts +13 -0
- package/dist/components/chat-empty-state.d.ts.map +1 -0
- package/dist/components/chat-empty-state.js +12 -0
- package/dist/components/chat-input-form.d.ts +12 -0
- package/dist/components/chat-input-form.d.ts.map +1 -0
- package/dist/components/chat-input-form.js +7 -0
- package/dist/components/chat-message-list.d.ts +12 -0
- package/dist/components/chat-message-list.d.ts.map +1 -0
- package/dist/components/chat-message-list.js +38 -0
- package/dist/components/chat-page-header.d.ts +22 -0
- package/dist/components/chat-page-header.d.ts.map +1 -0
- package/dist/components/chat-page-header.js +10 -0
- package/dist/components/chat-sidebar.d.ts +18 -0
- package/dist/components/chat-sidebar.d.ts.map +1 -0
- package/dist/components/chat-sidebar.js +27 -0
- package/dist/components/chat-typing-status.d.ts +8 -0
- package/dist/components/chat-typing-status.d.ts.map +1 -0
- package/dist/components/chat-typing-status.js +11 -0
- package/dist/components/message-bubble.d.ts +7 -0
- package/dist/components/message-bubble.d.ts.map +1 -0
- package/dist/components/message-bubble.js +11 -0
- package/dist/components/types.d.ts +33 -0
- package/dist/components/types.d.ts.map +1 -0
- package/dist/components/types.js +2 -0
- package/dist/components/ui/avatar.d.ts +9 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +14 -0
- package/dist/components/ui/button.d.ts +11 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +35 -0
- package/dist/components/ui/card.d.ts +10 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +24 -0
- package/dist/components/ui/input.d.ts +4 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +6 -0
- package/dist/components/ui/scroll-area.d.ts +6 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +11 -0
- package/dist/hooks/use-chat-socket.d.ts +59 -0
- package/dist/hooks/use-chat-socket.d.ts.map +1 -0
- package/dist/hooks/use-chat-socket.js +175 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/lib/chat-api.d.ts +13 -0
- package/dist/lib/chat-api.d.ts.map +1 -0
- package/dist/lib/chat-api.js +57 -0
- package/dist/lib/chat-config.d.ts +7 -0
- package/dist/lib/chat-config.d.ts.map +1 -0
- package/dist/lib/chat-config.js +18 -0
- package/dist/lib/notification-sound.d.ts +6 -0
- package/dist/lib/notification-sound.d.ts.map +1 -0
- package/dist/lib/notification-sound.js +28 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +5 -0
- package/dist/provider/chat-provider.d.ts +70 -0
- package/dist/provider/chat-provider.d.ts.map +1 -0
- package/dist/provider/chat-provider.js +64 -0
- package/dist/provider/types.d.ts +6 -0
- package/dist/provider/types.d.ts.map +1 -0
- package/dist/provider/types.js +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# chat-ui-web
|
|
2
|
+
|
|
3
|
+
React bileşenleri ve provider’ları ile sohbet arayüzü. Chat Service backend’i (REST + WebSocket) ile çalışır.
|
|
4
|
+
|
|
5
|
+
## Kurulum
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install chat-ui-web
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Gereksinimler:** React 18+, Tailwind CSS (bileşen sınıfları Tailwind kullanır).
|
|
12
|
+
|
|
13
|
+
## Tailwind
|
|
14
|
+
|
|
15
|
+
Projenizde Tailwind kullanıyorsanız, paket sınıflarının derlenmesi için `content` listesine paketi ekleyin:
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
// tailwind.config.js
|
|
19
|
+
content: [
|
|
20
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
|
21
|
+
"./node_modules/chat-ui-web/dist/**/*.js",
|
|
22
|
+
],
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## API ve WebSocket URL’leri
|
|
26
|
+
|
|
27
|
+
- **Next.js:** `NEXT_PUBLIC_CHAT_API_URL` ve `NEXT_PUBLIC_CHAT_WS_URL` ortam değişkenleri kullanılır (varsayılan: `http://localhost:3000`).
|
|
28
|
+
- **Diğer projeler:** `ChatProvider`’a `apiBaseUrl` ve `wsUrl` prop’larını verin:
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
<ChatProvider
|
|
32
|
+
token={token}
|
|
33
|
+
userId={userId}
|
|
34
|
+
profile={profile}
|
|
35
|
+
onLogout={logout}
|
|
36
|
+
apiBaseUrl="https://api.example.com"
|
|
37
|
+
wsUrl="https://api.example.com"
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</ChatProvider>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Kullanım
|
|
44
|
+
|
|
45
|
+
Auth (token, kullanıcı bilgisi) kendi uygulamanızdan gelir; `ChatProvider` sadece token ve profile props alır.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import {
|
|
49
|
+
ChatProvider,
|
|
50
|
+
useChat,
|
|
51
|
+
ChatSidebar,
|
|
52
|
+
ChatMessageList,
|
|
53
|
+
ChatInputForm,
|
|
54
|
+
ChatAreaHeader,
|
|
55
|
+
getConversations,
|
|
56
|
+
getMessages,
|
|
57
|
+
getBlockedUsers,
|
|
58
|
+
blockUser,
|
|
59
|
+
unblockUser,
|
|
60
|
+
} from "chat-ui-web";
|
|
61
|
+
|
|
62
|
+
// Root’ta (kendi auth’ınızdan token/profile alın)
|
|
63
|
+
function App() {
|
|
64
|
+
const { token, userId, profile, logout } = useYourAuth();
|
|
65
|
+
return (
|
|
66
|
+
<ChatProvider
|
|
67
|
+
token={token}
|
|
68
|
+
userId={userId}
|
|
69
|
+
profile={profile}
|
|
70
|
+
onLogout={logout}
|
|
71
|
+
>
|
|
72
|
+
<ChatPage />
|
|
73
|
+
</ChatProvider>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Sohbet sayfası
|
|
78
|
+
function ChatPage() {
|
|
79
|
+
const { token, userId } = useChat();
|
|
80
|
+
const [conversations, setConversations] = useState([]);
|
|
81
|
+
const [messages, setMessages] = useState([]);
|
|
82
|
+
const [selectedId, setSelectedId] = useState(null);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!token) return;
|
|
86
|
+
getConversations(token).then(setConversations);
|
|
87
|
+
}, [token]);
|
|
88
|
+
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!token || !selectedId) return;
|
|
91
|
+
getMessages(token, selectedId).then(setMessages);
|
|
92
|
+
}, [token, selectedId]);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="flex">
|
|
96
|
+
<ChatSidebar
|
|
97
|
+
conversations={conversations}
|
|
98
|
+
selectedId={selectedId}
|
|
99
|
+
onSelectConversation={setSelectedId}
|
|
100
|
+
currentUserId={userId}
|
|
101
|
+
blockedUserIds={new Set()}
|
|
102
|
+
onlineUserIds={{}}
|
|
103
|
+
loading={false}
|
|
104
|
+
getOtherUserId={(c) => c.userAId === userId ? c.userBId : c.userAId}
|
|
105
|
+
getDisplayName={(c) => c.otherParticipant?.firstName ?? "Kullanıcı"}
|
|
106
|
+
getAvatarUrl={(c) => c.otherParticipant?.avatarUrl ?? null}
|
|
107
|
+
newChatUserId=""
|
|
108
|
+
onNewChatUserIdChange={() => {}}
|
|
109
|
+
/>
|
|
110
|
+
<main>
|
|
111
|
+
{selectedId && (
|
|
112
|
+
<>
|
|
113
|
+
<ChatAreaHeader otherUser={...} displayName="..." ... />
|
|
114
|
+
<ChatMessageList messages={messages} currentUserId={userId} />
|
|
115
|
+
<ChatInputForm value={input} onChange={...} onSubmit={...} />
|
|
116
|
+
</>
|
|
117
|
+
)}
|
|
118
|
+
</main>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Next.js Link
|
|
125
|
+
|
|
126
|
+
`ChatPageHeader` içindeki “Panel” / “Sohbet” linkleri varsayılan olarak `<a>` kullanır. Next.js’te `next/link` kullanmak için `LinkComponent` verin:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import Link from "next/link";
|
|
130
|
+
import { ChatPageHeader } from "chat-ui-web";
|
|
131
|
+
|
|
132
|
+
<ChatPageHeader
|
|
133
|
+
profile={profile}
|
|
134
|
+
currentUserId={currentUserId}
|
|
135
|
+
connected={connected}
|
|
136
|
+
onOpenBot={() => {}}
|
|
137
|
+
onLogout={logout}
|
|
138
|
+
LinkComponent={Link}
|
|
139
|
+
/>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Export’lar
|
|
143
|
+
|
|
144
|
+
- **Provider:** `ChatProvider`, `useChat`, `ChatProviderProps`, `Profile`, `ChatMessage`
|
|
145
|
+
- **Bileşenler:** `ChatAreaHeader`, `ChatPageHeader`, `ChatSidebar`, `ChatMessageList`, `ChatTypingStatus`, `ChatInputForm`, `ChatEmptyState`, `BotChatModal`, `MessageBubble`
|
|
146
|
+
- **Tipler:** `Conversation`, `MessageRow`, `OtherParticipant`, `LastMessage`, `BOT_USER_ID`
|
|
147
|
+
- **API:** `getConversations`, `getMessages`, `getBlockedUsers`, `blockUser`, `unblockUser`, `ChatApiError`
|
|
148
|
+
- **Config:** `setChatConfig`, `getApiBaseUrl`, `getWsUrl`, `cn`
|
|
149
|
+
- **UI:** `Button`, `Input`, `Card`, `Avatar`, `ScrollArea`, vb.
|
|
150
|
+
|
|
151
|
+
## Lisans
|
|
152
|
+
|
|
153
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MessageRow } from "./types";
|
|
2
|
+
export declare function BotChatModal({ open, onClose, botUserId, messages, currentUserId, inputValue, onInputChange, onSubmit, }: {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onClose: () => void;
|
|
5
|
+
botUserId: string;
|
|
6
|
+
messages: MessageRow[];
|
|
7
|
+
currentUserId: string | null;
|
|
8
|
+
inputValue: string;
|
|
9
|
+
onInputChange: (v: string) => void;
|
|
10
|
+
onSubmit: (e: React.FormEvent) => void;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
+
//# sourceMappingURL=bot-chat-modal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot-chat-modal.d.ts","sourceRoot":"","sources":["../../src/components/bot-chat-modal.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,OAAO,EACP,SAAS,EACT,QAAQ,EACR,aAAa,EACb,UAAU,EACV,aAAa,EACb,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;CACxC,kDA+DA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useEffect } from "react";
|
|
4
|
+
import { Button } from "./ui/button";
|
|
5
|
+
import { Input } from "./ui/input";
|
|
6
|
+
import { ScrollArea } from "./ui/scroll-area";
|
|
7
|
+
import { MessageBubble } from "./message-bubble";
|
|
8
|
+
export function BotChatModal({ open, onClose, botUserId, messages, currentUserId, inputValue, onInputChange, onSubmit, }) {
|
|
9
|
+
const endRef = useRef(null);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (open && messages.length > 0) {
|
|
12
|
+
queueMicrotask(() => endRef.current?.scrollIntoView({ behavior: "smooth" }));
|
|
13
|
+
}
|
|
14
|
+
}, [open, messages.length]);
|
|
15
|
+
if (!open)
|
|
16
|
+
return null;
|
|
17
|
+
return (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", onClick: onClose, role: "dialog", "aria-modal": "true", "aria-label": "Sohbet botu", children: _jsxs("div", { className: "flex max-h-[85vh] w-full max-w-md flex-col rounded-lg border bg-white shadow-lg dark:bg-zinc-900 dark:border-zinc-700", onClick: (e) => e.stopPropagation(), children: [_jsxs("div", { className: "flex shrink-0 items-center justify-between border-b px-4 py-2 dark:border-zinc-700", children: [_jsxs("h2", { className: "font-semibold text-sm", children: ["Sohbet Botu (", botUserId, ")"] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-8 w-8", "aria-label": "Kapat", onClick: onClose, children: "\u00D7" })] }), _jsx(ScrollArea, { className: "min-h-[200px] flex-1 p-3", children: _jsxs("div", { className: "flex flex-col gap-2", children: [messages.map((m) => (_jsx(MessageBubble, { message: m, isOwn: m.senderId === currentUserId, showTicks: false }, m.id))), _jsx("div", { ref: endRef, "aria-hidden": true })] }) }), _jsxs("form", { onSubmit: onSubmit, className: "flex shrink-0 gap-2 border-t p-2 dark:border-zinc-700", children: [_jsx(Input, { placeholder: "Mesaj yaz\u0131n\u2026", value: inputValue, onChange: (e) => onInputChange(e.target.value), className: "flex-1" }), _jsx(Button, { type: "submit", disabled: !inputValue.trim(), children: "G\u00F6nder" })] })] }) }));
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { OtherParticipant } from "./types";
|
|
2
|
+
export declare function ChatAreaHeader({ otherUser, displayName, avatarUrl, isOnline, isBlocked, onBlock, onUnblock, }: {
|
|
3
|
+
otherUser: OtherParticipant;
|
|
4
|
+
displayName: string;
|
|
5
|
+
avatarUrl: string | null;
|
|
6
|
+
isOnline?: boolean;
|
|
7
|
+
isBlocked?: boolean;
|
|
8
|
+
onBlock: () => void;
|
|
9
|
+
onUnblock?: () => void;
|
|
10
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=chat-area-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-area-header.d.ts","sourceRoot":"","sources":["../../src/components/chat-area-header.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,WAAW,EACX,SAAS,EACT,QAAQ,EACR,SAAiB,EACjB,OAAO,EACP,SAAS,GACV,EAAE;IACD,SAAS,EAAE,gBAAgB,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,2CA+FA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { Button } from "./ui/button";
|
|
5
|
+
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
|
6
|
+
export function ChatAreaHeader({ otherUser, displayName, avatarUrl, isOnline, isBlocked = false, onBlock, onUnblock, }) {
|
|
7
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
8
|
+
const menuRef = useRef(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!menuOpen)
|
|
11
|
+
return;
|
|
12
|
+
const close = (e) => {
|
|
13
|
+
if (menuRef.current && !menuRef.current.contains(e.target))
|
|
14
|
+
setMenuOpen(false);
|
|
15
|
+
};
|
|
16
|
+
document.addEventListener("click", close);
|
|
17
|
+
return () => document.removeEventListener("click", close);
|
|
18
|
+
}, [menuOpen]);
|
|
19
|
+
return (_jsxs("header", { className: "flex shrink-0 items-center justify-between border-b bg-white px-4 py-2 dark:bg-zinc-900", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs("div", { className: "relative", children: [_jsxs(Avatar, { className: "h-9 w-9", children: [avatarUrl ? _jsx(AvatarImage, { src: avatarUrl, alt: "" }) : null, _jsx(AvatarFallback, { className: "text-sm", children: (otherUser.firstName?.slice(0, 2) ?? otherUser.userId.slice(0, 2)).toUpperCase() })] }), isOnline !== undefined && (_jsx("span", { className: `absolute bottom-0 right-0 h-2.5 w-2.5 rounded-full border-2 border-white dark:border-zinc-900 ${isOnline ? "bg-green-500" : "bg-zinc-400"}`, "aria-hidden": true }))] }), _jsxs("div", { children: [_jsxs("p", { className: "font-medium text-sm flex items-center gap-2", children: [otherUser.firstName || otherUser.lastName
|
|
20
|
+
? [otherUser.firstName, otherUser.lastName].filter(Boolean).join(" ")
|
|
21
|
+
: displayName, isBlocked && (_jsx("span", { className: "rounded bg-muted px-1.5 py-0.5 text-[10px] font-normal text-muted-foreground", children: "Engellendi" }))] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [otherUser.userId, isOnline !== undefined && (_jsxs("span", { className: isOnline ? "text-green-600" : "text-muted-foreground", children: [" · ", isOnline ? "Çevrimiçi" : "Çevrimdışı"] }))] })] })] }), _jsxs("div", { className: "relative", ref: menuRef, children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-9 w-9", "aria-label": "Sohbet ayarlar\u0131", onClick: () => setMenuOpen((o) => !o), children: _jsx("span", { className: "text-lg", children: "\u22EE" }) }), menuOpen && (_jsx("div", { className: "absolute right-0 top-full z-10 mt-1 min-w-40 rounded-md border bg-white py-1 shadow-lg dark:border-zinc-700 dark:bg-zinc-900", children: isBlocked ? (_jsx("button", { type: "button", className: "flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-zinc-100 dark:hover:bg-zinc-800", onClick: () => {
|
|
22
|
+
onUnblock?.();
|
|
23
|
+
setMenuOpen(false);
|
|
24
|
+
}, children: "Engellemeyi kald\u0131r" })) : (_jsx("button", { type: "button", className: "flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-destructive hover:bg-destructive/10", onClick: () => {
|
|
25
|
+
onBlock();
|
|
26
|
+
setMenuOpen(false);
|
|
27
|
+
}, children: "Engelle" })) }))] })] }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function ChatEmptyState({ newChatUserId, inputValue, onInputChange, onInputBlur, onSubmit, showTyping, typingDisplayName, showStatus, statusDisplayName, statusValue, }: {
|
|
2
|
+
newChatUserId: string;
|
|
3
|
+
inputValue: string;
|
|
4
|
+
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
5
|
+
onInputBlur: () => void;
|
|
6
|
+
onSubmit: (e: React.FormEvent) => void;
|
|
7
|
+
showTyping: boolean;
|
|
8
|
+
typingDisplayName: string;
|
|
9
|
+
showStatus: boolean;
|
|
10
|
+
statusDisplayName: string;
|
|
11
|
+
statusValue: string | null;
|
|
12
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=chat-empty-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-empty-state.d.ts","sourceRoot":"","sources":["../../src/components/chat-empty-state.tsx"],"names":[],"mappings":"AAWA,wBAAgB,cAAc,CAAC,EAC7B,aAAa,EACb,UAAU,EACV,aAAa,EACb,WAAW,EACX,QAAQ,EACR,UAAU,EACV,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,WAAW,GACZ,EAAE;IACD,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAChE,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;IACvC,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,2CAsCA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Input } from "./ui/input";
|
|
4
|
+
import { Button } from "./ui/button";
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
|
|
6
|
+
const STATUS_LABEL = {
|
|
7
|
+
uploading_file: "Dosya gönderiyor…",
|
|
8
|
+
sending_message: "Mesaj gönderiyor…",
|
|
9
|
+
};
|
|
10
|
+
export function ChatEmptyState({ newChatUserId, inputValue, onInputChange, onInputBlur, onSubmit, showTyping, typingDisplayName, showStatus, statusDisplayName, statusValue, }) {
|
|
11
|
+
return (_jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-4 p-4 text-muted-foreground", children: [(showTyping || showStatus) && (_jsxs("div", { className: "w-full max-w-md rounded-lg bg-muted/50 px-4 py-2 text-sm", children: [showTyping && _jsxs("span", { children: [typingDisplayName, " yaz\u0131yor\u2026"] }), showStatus && statusValue && (_jsxs("span", { children: [statusDisplayName, " ", STATUS_LABEL[statusValue] ?? statusValue] }))] })), _jsx("p", { children: "Bir sohbet se\u00E7in veya yeni sohbet ba\u015Flat\u0131n." }), newChatUserId.trim() && (_jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-base", children: [newChatUserId, " ile yeni sohbet"] }), _jsx(CardDescription, { children: "Mesaj yaz\u0131p G\u00F6nder'e bas\u0131n." })] }), _jsx(CardContent, { children: _jsxs("form", { onSubmit: onSubmit, className: "flex gap-2", children: [_jsx(Input, { placeholder: "Mesaj\u2026", value: inputValue, onChange: onInputChange, onBlur: onInputBlur, className: "flex-1" }), _jsx(Button, { type: "submit", disabled: !inputValue.trim(), children: "G\u00F6nder" })] }) })] }))] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function ChatInputForm({ value, onChange, onBlur, onSubmit, onAttachClick, showAttach, disabled, placeholder, submitLabel, }: {
|
|
2
|
+
value: string;
|
|
3
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
4
|
+
onBlur?: () => void;
|
|
5
|
+
onSubmit: (e: React.FormEvent) => void;
|
|
6
|
+
onAttachClick?: () => void;
|
|
7
|
+
showAttach?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
submitLabel?: string;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=chat-input-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-input-form.d.ts","sourceRoot":"","sources":["../../src/components/chat-input-form.tsx"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,aAAa,EACb,UAAU,EACV,QAAgB,EAChB,WAAsB,EACtB,WAAsB,GACvB,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAC3D,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,2CA+BA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from "./ui/button";
|
|
4
|
+
import { Input } from "./ui/input";
|
|
5
|
+
export function ChatInputForm({ value, onChange, onBlur, onSubmit, onAttachClick, showAttach, disabled = false, placeholder = "Mesaj…", submitLabel = "Gönder", }) {
|
|
6
|
+
return (_jsxs("form", { onSubmit: onSubmit, className: "flex shrink-0 gap-2 border-t bg-white p-2 dark:bg-zinc-900", children: [_jsx(Input, { placeholder: disabled ? "Engelli kullanıcıya mesaj gönderilemez" : placeholder, value: value, onChange: onChange, onBlur: onBlur, className: "flex-1", disabled: disabled }), showAttach && (_jsx(Button, { type: "button", variant: "outline", size: "sm", title: "Di\u011Fer kullan\u0131c\u0131ya 'Dosya g\u00F6nderiyor' statusu g\u00F6nder", onClick: onAttachClick, disabled: disabled, children: "\uD83D\uDCCE" })), _jsx(Button, { type: "submit", disabled: disabled || !value.trim(), children: submitLabel })] }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
import type { MessageRow } from "./types";
|
|
3
|
+
export declare function ChatMessageList({ messages, currentUserId, showTicks, messagesEndRef, onLoadMoreOlder, hasMoreOlder, loadingOlder, }: {
|
|
4
|
+
messages: MessageRow[];
|
|
5
|
+
currentUserId: string | null;
|
|
6
|
+
showTicks?: boolean;
|
|
7
|
+
messagesEndRef?: RefObject<HTMLDivElement | null>;
|
|
8
|
+
onLoadMoreOlder?: () => void;
|
|
9
|
+
hasMoreOlder?: boolean;
|
|
10
|
+
loadingOlder?: boolean;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=chat-message-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-message-list.d.ts","sourceRoot":"","sources":["../../src/components/chat-message-list.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAqB,MAAM,OAAO,CAAC;AAI1D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,aAAa,EACb,SAAgB,EAChB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,YAAY,GACb,EAAE;IACD,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAClD,eAAe,CAAC,EAAE,MAAM,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,2CA8DA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useEffect } from "react";
|
|
4
|
+
import { Button } from "./ui/button";
|
|
5
|
+
import { ScrollArea } from "./ui/scroll-area";
|
|
6
|
+
import { MessageBubble } from "./message-bubble";
|
|
7
|
+
export function ChatMessageList({ messages, currentUserId, showTicks = true, messagesEndRef, onLoadMoreOlder, hasMoreOlder, loadingOlder, }) {
|
|
8
|
+
const contentRef = useRef(null);
|
|
9
|
+
const savedScrollRef = useRef(null);
|
|
10
|
+
const prevFirstIdRef = useRef(undefined);
|
|
11
|
+
const handleLoadMoreOlder = () => {
|
|
12
|
+
const viewport = contentRef.current?.parentElement;
|
|
13
|
+
if (viewport && onLoadMoreOlder) {
|
|
14
|
+
savedScrollRef.current = {
|
|
15
|
+
scrollHeight: viewport.scrollHeight,
|
|
16
|
+
scrollTop: viewport.scrollTop,
|
|
17
|
+
};
|
|
18
|
+
onLoadMoreOlder();
|
|
19
|
+
}
|
|
20
|
+
else if (onLoadMoreOlder) {
|
|
21
|
+
onLoadMoreOlder();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const firstId = messages[0]?.id;
|
|
26
|
+
const prevFirstId = prevFirstIdRef.current;
|
|
27
|
+
prevFirstIdRef.current = firstId;
|
|
28
|
+
if (savedScrollRef.current && prevFirstId !== undefined && firstId !== prevFirstId) {
|
|
29
|
+
const viewport = contentRef.current?.parentElement;
|
|
30
|
+
if (viewport) {
|
|
31
|
+
const { scrollHeight: oldH, scrollTop: oldT } = savedScrollRef.current;
|
|
32
|
+
viewport.scrollTop = oldT + (viewport.scrollHeight - oldH);
|
|
33
|
+
}
|
|
34
|
+
savedScrollRef.current = null;
|
|
35
|
+
}
|
|
36
|
+
}, [messages]);
|
|
37
|
+
return (_jsx(ScrollArea, { className: "min-h-0 flex-1 p-4", children: _jsxs("div", { ref: contentRef, className: "flex flex-col gap-2", children: [hasMoreOlder && onLoadMoreOlder && (_jsx("div", { className: "flex justify-center py-2", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "text-muted-foreground", disabled: loadingOlder, onClick: handleLoadMoreOlder, children: loadingOlder ? "Yükleniyor…" : "Daha eski mesajlar" }) })), messages.map((m) => (_jsx(MessageBubble, { message: m, isOwn: m.senderId === currentUserId, showTicks: showTicks }, m.id))), _jsx("div", { ref: messagesEndRef, "aria-hidden": true })] }) }));
|
|
38
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type Profile = {
|
|
3
|
+
firstName?: string;
|
|
4
|
+
lastName?: string;
|
|
5
|
+
avatarURL?: string;
|
|
6
|
+
};
|
|
7
|
+
type LinkComponentProps = {
|
|
8
|
+
href: string;
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
};
|
|
11
|
+
export declare function ChatPageHeader({ profile, currentUserId, connected, onOpenBot, onLogout,
|
|
12
|
+
/** Next.js: pass Link from "next/link". Otherwise links render as <a>. */
|
|
13
|
+
LinkComponent, }: {
|
|
14
|
+
profile: Profile | null;
|
|
15
|
+
currentUserId: string | null;
|
|
16
|
+
connected: boolean;
|
|
17
|
+
onOpenBot: () => void;
|
|
18
|
+
onLogout: () => void;
|
|
19
|
+
LinkComponent?: React.ComponentType<LinkComponentProps>;
|
|
20
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=chat-page-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-page-header.d.ts","sourceRoot":"","sources":["../../src/components/chat-page-header.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,KAAK,OAAO,GAAG;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,KAAK,kBAAkB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,CAAC;AAEtE,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,aAAa,EACb,SAAS,EACT,SAAS,EACT,QAAQ;AACR,0EAA0E;AAC1E,aAAa,GACd,EAAE;IACD,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC;CACzD,2CA8CA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from "./ui/button";
|
|
4
|
+
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
|
5
|
+
export function ChatPageHeader({ profile, currentUserId, connected, onOpenBot, onLogout,
|
|
6
|
+
/** Next.js: pass Link from "next/link". Otherwise links render as <a>. */
|
|
7
|
+
LinkComponent, }) {
|
|
8
|
+
const Link = LinkComponent ?? "a";
|
|
9
|
+
return (_jsxs("header", { className: "flex items-center justify-between border-b bg-white px-4 py-2 dark:bg-zinc-900", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Avatar, { className: "h-9 w-9", children: [profile?.avatarURL ? (_jsx(AvatarImage, { src: profile.avatarURL, alt: "" })) : null, _jsx(AvatarFallback, { className: "text-sm", children: profile?.firstName?.slice(0, 2).toUpperCase() ?? currentUserId?.slice(0, 2).toUpperCase() ?? "?" })] }), _jsxs("div", { children: [_jsxs("p", { className: "font-medium text-sm", children: [profile?.firstName, profile?.lastName ? ` ${profile.lastName}` : ""] }), _jsx("p", { className: "text-xs text-muted-foreground", children: currentUserId })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: `text-xs ${connected ? "text-green-600" : "text-amber-600"}`, children: connected ? "Bağlı" : "Bağlantı yok" }), _jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: onOpenBot, title: "Sohbet botu", children: "Bot" }), _jsx(Button, { variant: "outline", size: "sm", asChild: true, children: _jsx(Link, { href: "/dashboard", children: "Panel" }) }), _jsx(Button, { variant: "outline", size: "sm", onClick: onLogout, children: "\u00C7\u0131k\u0131\u015F" })] })] }));
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Conversation } from "./types";
|
|
2
|
+
export declare function ChatSidebar({ newChatUserId, onNewChatUserIdChange, conversations, selectedId, onSelectConversation, blockedUserIds, onlineUserIds, loading, getOtherUserId, getDisplayName, getAvatarUrl, currentUserId, typingConversationId, typingDisplayName, }: {
|
|
3
|
+
newChatUserId: string;
|
|
4
|
+
onNewChatUserIdChange: (v: string) => void;
|
|
5
|
+
conversations: Conversation[];
|
|
6
|
+
selectedId: string | null;
|
|
7
|
+
onSelectConversation: (id: string) => void;
|
|
8
|
+
blockedUserIds: Set<string>;
|
|
9
|
+
onlineUserIds: Record<string, boolean>;
|
|
10
|
+
loading: boolean;
|
|
11
|
+
getOtherUserId: (c: Conversation) => string;
|
|
12
|
+
getDisplayName: (c: Conversation) => string;
|
|
13
|
+
getAvatarUrl: (c: Conversation) => string | null;
|
|
14
|
+
currentUserId: string | null;
|
|
15
|
+
typingConversationId?: string | null;
|
|
16
|
+
typingDisplayName?: string;
|
|
17
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=chat-sidebar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-sidebar.d.ts","sourceRoot":"","sources":["../../src/components/chat-sidebar.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,wBAAgB,WAAW,CAAC,EAC1B,aAAa,EACb,qBAAqB,EACrB,aAAa,EACb,UAAU,EACV,oBAAoB,EACpB,cAAc,EACd,aAAa,EACb,OAAO,EACP,cAAc,EACd,cAAc,EACd,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,iBAAiB,GAClB,EAAE;IACD,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oBAAoB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,MAAM,CAAC;IAC5C,cAAc,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,MAAM,CAAC;IAC5C,YAAY,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,MAAM,GAAG,IAAI,CAAC;IACjD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,2CA8FA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Input } from "./ui/input";
|
|
4
|
+
import { ScrollArea } from "./ui/scroll-area";
|
|
5
|
+
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
|
6
|
+
export function ChatSidebar({ newChatUserId, onNewChatUserIdChange, conversations, selectedId, onSelectConversation, blockedUserIds, onlineUserIds, loading, getOtherUserId, getDisplayName, getAvatarUrl, currentUserId, typingConversationId, typingDisplayName, }) {
|
|
7
|
+
const lastMessagePreview = (c) => {
|
|
8
|
+
const last = c.lastMessage;
|
|
9
|
+
if (!last?.content)
|
|
10
|
+
return null;
|
|
11
|
+
const isOwn = last.senderId === currentUserId;
|
|
12
|
+
const snippet = last.content.length > 40 ? `${last.content.slice(0, 40)}…` : last.content;
|
|
13
|
+
return isOwn ? `Sen: ${snippet}` : snippet;
|
|
14
|
+
};
|
|
15
|
+
return (_jsxs("aside", { className: "flex min-h-0 w-64 flex-col border-r bg-white dark:bg-zinc-900", children: [_jsx("div", { className: "shrink-0 border-b p-2", children: _jsx(Input, { placeholder: "Yeni sohbet: kullan\u0131c\u0131 ID", value: newChatUserId, onChange: (e) => onNewChatUserIdChange(e.target.value), className: "text-sm" }) }), _jsx(ScrollArea, { className: "min-h-0 flex-1", children: loading ? (_jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Y\u00FCkleniyor\u2026" })) : (_jsx("ul", { className: "p-2", children: conversations.map((c) => {
|
|
16
|
+
const otherId = getOtherUserId(c);
|
|
17
|
+
const online = onlineUserIds[otherId];
|
|
18
|
+
const avatarUrl = getAvatarUrl(c);
|
|
19
|
+
const isTyping = typingConversationId === c.id;
|
|
20
|
+
const unread = (c.unreadCount ?? 0) > 0;
|
|
21
|
+
const preview = lastMessagePreview(c);
|
|
22
|
+
const isBlocked = blockedUserIds.has(otherId);
|
|
23
|
+
return (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => onSelectConversation(c.id), className: `flex w-full items-start gap-2 rounded-lg px-3 py-2 text-left text-sm transition-colors ${selectedId === c.id
|
|
24
|
+
? "bg-primary/10 text-primary"
|
|
25
|
+
: "hover:bg-zinc-100 dark:hover:bg-zinc-800"} ${unread ? "font-medium" : ""} ${isBlocked ? "opacity-80" : ""}`, children: [_jsxs("div", { className: "relative shrink-0", children: [_jsxs(Avatar, { className: "h-8 w-8", children: [avatarUrl ? (_jsx(AvatarImage, { src: avatarUrl, alt: "" })) : null, _jsx(AvatarFallback, { className: "text-xs", children: getDisplayName(c).slice(0, 2).toUpperCase() })] }), online !== undefined && !isBlocked && (_jsx("span", { className: `absolute bottom-0 right-0 h-2 w-2 rounded-full border-2 border-white dark:border-zinc-900 ${online ? "bg-green-500" : "bg-zinc-400"}`, "aria-hidden": true })), unread && (_jsx("span", { className: "absolute -right-1 -top-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-[10px] font-medium text-primary-foreground", children: c.unreadCount > 99 ? "99+" : c.unreadCount }))] }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center justify-between gap-1", children: [_jsx("span", { className: "truncate", children: getDisplayName(c) }), isBlocked && (_jsx("span", { className: "shrink-0 rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground", children: "Engelli" }))] }), isTyping && typingDisplayName ? (_jsxs("p", { className: "mt-0.5 truncate text-xs italic text-muted-foreground", children: [typingDisplayName, " yaz\u0131yor\u2026"] })) : preview ? (_jsx("p", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: preview })) : null] })] }) }, c.id));
|
|
26
|
+
}) })) })] }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function ChatTypingStatus({ showTyping, typingDisplayName, showStatus, statusDisplayName, statusValue, }: {
|
|
2
|
+
showTyping: boolean;
|
|
3
|
+
typingDisplayName: string;
|
|
4
|
+
showStatus: boolean;
|
|
5
|
+
statusDisplayName: string;
|
|
6
|
+
statusValue: string | null;
|
|
7
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
8
|
+
//# sourceMappingURL=chat-typing-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-typing-status.d.ts","sourceRoot":"","sources":["../../src/components/chat-typing-status.tsx"],"names":[],"mappings":"AAOA,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,iBAAiB,EACjB,UAAU,EACV,iBAAiB,EACjB,WAAW,GACZ,EAAE;IACD,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,kDAaA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
const STATUS_LABEL = {
|
|
4
|
+
uploading_file: "Dosya gönderiyor…",
|
|
5
|
+
sending_message: "Mesaj gönderiyor…",
|
|
6
|
+
};
|
|
7
|
+
export function ChatTypingStatus({ showTyping, typingDisplayName, showStatus, statusDisplayName, statusValue, }) {
|
|
8
|
+
if (!showTyping && !showStatus)
|
|
9
|
+
return null;
|
|
10
|
+
return (_jsxs("div", { className: "shrink-0 border-t bg-muted/50 px-4 py-1.5 text-sm text-muted-foreground", children: [showTyping && _jsxs("span", { children: [typingDisplayName, " yaz\u0131yor\u2026"] }), showStatus && statusValue && (_jsxs("span", { children: [statusDisplayName, " ", STATUS_LABEL[statusValue] ?? statusValue] }))] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MessageRow } from "./types";
|
|
2
|
+
export declare function MessageBubble({ message, isOwn, showTicks, }: {
|
|
3
|
+
message: MessageRow;
|
|
4
|
+
isOwn: boolean;
|
|
5
|
+
showTicks?: boolean;
|
|
6
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=message-bubble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message-bubble.d.ts","sourceRoot":"","sources":["../../src/components/message-bubble.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,KAAK,EACL,SAAS,GACV,EAAE;IACD,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,2CA0CA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
export function MessageBubble({ message, isOwn, showTicks, }) {
|
|
4
|
+
return (_jsx("div", { className: `flex ${isOwn ? "justify-end" : "justify-start"}`, children: _jsxs("div", { className: `max-w-[85%] rounded-2xl px-3 py-2 ${isOwn
|
|
5
|
+
? "bg-primary/90 text-primary-foreground"
|
|
6
|
+
: "bg-zinc-200/80 text-zinc-900 dark:bg-zinc-700/60 dark:text-zinc-100"}`, children: [_jsx("p", { className: "text-sm leading-snug", children: message.content }), _jsxs("div", { className: "mt-1 flex items-center justify-end gap-1.5", children: [_jsx("span", { className: "text-[11px] opacity-70", children: new Date(message.createdAt).toLocaleTimeString() }), showTicks && isOwn && (_jsx("span", { className: "inline-flex shrink-0 text-xs opacity-90", title: message.isRead
|
|
7
|
+
? "Okundu"
|
|
8
|
+
: message.deliveredAt
|
|
9
|
+
? "İletildi"
|
|
10
|
+
: "Gönderildi", "aria-hidden": true, children: message.isRead ? (_jsx("span", { className: "text-white", children: "\u2713\u2713" })) : message.deliveredAt ? (_jsx("span", { title: "\u0130letildi", children: "\u2713\u2713" })) : (_jsx("span", { title: "G\u00F6nderildi", children: "\u2713" })) }))] })] }) }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type OtherParticipant = {
|
|
2
|
+
userId: string;
|
|
3
|
+
firstName: string | null;
|
|
4
|
+
lastName: string | null;
|
|
5
|
+
avatarUrl: string | null;
|
|
6
|
+
};
|
|
7
|
+
export type LastMessage = {
|
|
8
|
+
content: string;
|
|
9
|
+
senderId: string;
|
|
10
|
+
createdAt: string;
|
|
11
|
+
};
|
|
12
|
+
export type Conversation = {
|
|
13
|
+
id: string;
|
|
14
|
+
userAId: string;
|
|
15
|
+
userBId: string;
|
|
16
|
+
lastMessageAt: string | null;
|
|
17
|
+
createdAt: string;
|
|
18
|
+
otherParticipant?: OtherParticipant;
|
|
19
|
+
lastMessage?: LastMessage | null;
|
|
20
|
+
unreadCount?: number;
|
|
21
|
+
};
|
|
22
|
+
export type MessageRow = {
|
|
23
|
+
id: string;
|
|
24
|
+
conversationId: string;
|
|
25
|
+
senderId: string;
|
|
26
|
+
content: string;
|
|
27
|
+
type: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
isRead?: boolean;
|
|
30
|
+
deliveredAt?: string | null;
|
|
31
|
+
};
|
|
32
|
+
export declare const BOT_USER_ID: string;
|
|
33
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/components/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,WAAW,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,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,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,CAAC;AAGF,eAAO,MAAM,WAAW,QAA6B,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Avatar as AvatarPrimitive } from "radix-ui";
|
|
3
|
+
declare function Avatar({ className, size, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
|
4
|
+
size?: "default" | "sm" | "lg";
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
declare function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
declare function AvatarFallback({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Fallback>): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export { Avatar, AvatarImage, AvatarFallback };
|
|
9
|
+
//# sourceMappingURL=avatar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar.d.ts","sourceRoot":"","sources":["../../../src/components/ui/avatar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,MAAM,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AAGrD,iBAAS,MAAM,CAAC,EACd,SAAS,EACT,IAAgB,EAChB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,IAAI,CAAC,GAAG;IACrD,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,CAAC;CAChC,2CAYA;AAED,iBAAS,WAAW,CAAC,EACnB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,2CAQpD;AAED,iBAAS,cAAc,CAAC,EACtB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,eAAe,CAAC,QAAQ,CAAC,2CAWvD;AAED,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Avatar as AvatarPrimitive } from "radix-ui";
|
|
4
|
+
import { cn } from "../../lib/utils";
|
|
5
|
+
function Avatar({ className, size = "default", ...props }) {
|
|
6
|
+
return (_jsx(AvatarPrimitive.Root, { "data-slot": "avatar", "data-size": size, className: cn("group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6", className), ...props }));
|
|
7
|
+
}
|
|
8
|
+
function AvatarImage({ className, ...props }) {
|
|
9
|
+
return (_jsx(AvatarPrimitive.Image, { "data-slot": "avatar-image", className: cn("aspect-square size-full", className), ...props }));
|
|
10
|
+
}
|
|
11
|
+
function AvatarFallback({ className, ...props }) {
|
|
12
|
+
return (_jsx(AvatarPrimitive.Fallback, { "data-slot": "avatar-fallback", className: cn("bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs", className), ...props }));
|
|
13
|
+
}
|
|
14
|
+
export { Avatar, AvatarImage, AvatarFallback };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type VariantProps } from "class-variance-authority";
|
|
3
|
+
declare const buttonVariants: (props?: ({
|
|
4
|
+
variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
5
|
+
size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
|
|
6
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
|
+
declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
|
|
8
|
+
asChild?: boolean;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export { Button, buttonVariants };
|
|
11
|
+
//# sourceMappingURL=button.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/ui/button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAIlE,QAAA,MAAM,cAAc;;;8EAgCnB,CAAC;AAEF,iBAAS,MAAM,CAAC,EACd,SAAS,EACT,OAAmB,EACnB,IAAgB,EAChB,OAAe,EACf,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GAC/B,YAAY,CAAC,OAAO,cAAc,CAAC,GAAG;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,2CAWF;AAED,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC"}
|