@myrjfa/ui 1.0.8 → 1.0.9
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/dist/chat/ChatMain.d.ts +9 -0
- package/dist/chat/ChatMain.d.ts.map +1 -0
- package/dist/chat/ChatMain.js +239 -0
- package/dist/chat/ChatSidebar.d.ts +13 -0
- package/dist/chat/ChatSidebar.d.ts.map +1 -0
- package/dist/chat/ChatSidebar.js +24 -0
- package/dist/chat/ConversationList.d.ts +12 -0
- package/dist/chat/ConversationList.d.ts.map +1 -0
- package/dist/chat/ConversationList.js +68 -0
- package/dist/chat/ExpensePanel.d.ts +8 -0
- package/dist/chat/ExpensePanel.d.ts.map +1 -0
- package/dist/chat/ExpensePanel.js +215 -0
- package/dist/chat/MessageBubble.d.ts +15 -0
- package/dist/chat/MessageBubble.d.ts.map +1 -0
- package/dist/chat/MessageBubble.js +197 -0
- package/dist/chat/MessageInput.d.ts +14 -0
- package/dist/chat/MessageInput.d.ts.map +1 -0
- package/dist/chat/MessageInput.js +235 -0
- package/dist/chat/MilestonePanel.d.ts +8 -0
- package/dist/chat/MilestonePanel.d.ts.map +1 -0
- package/dist/chat/MilestonePanel.js +173 -0
- package/dist/chat/dialogs/AddMemberDialog.d.ts +10 -0
- package/dist/chat/dialogs/AddMemberDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/AddMemberDialog.js +110 -0
- package/dist/chat/dialogs/CameraDialog.d.ts +8 -0
- package/dist/chat/dialogs/CameraDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/CameraDialog.js +128 -0
- package/dist/chat/dialogs/GroupInfoDialog.d.ts +10 -0
- package/dist/chat/dialogs/GroupInfoDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/GroupInfoDialog.js +108 -0
- package/dist/chat/dialogs/GroupInfoViewDialog.d.ts +9 -0
- package/dist/chat/dialogs/GroupInfoViewDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/GroupInfoViewDialog.js +16 -0
- package/dist/chat/dialogs/NewChatDialog.d.ts +2 -0
- package/dist/chat/dialogs/NewChatDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/NewChatDialog.js +127 -0
- package/dist/chat/dialogs/NewGroupDialog.d.ts +2 -0
- package/dist/chat/dialogs/NewGroupDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/NewGroupDialog.js +112 -0
- package/dist/chat/dialogs/ParticipantsDialog.d.ts +11 -0
- package/dist/chat/dialogs/ParticipantsDialog.d.ts.map +1 -0
- package/dist/chat/dialogs/ParticipantsDialog.js +143 -0
- package/dist/chat/index.d.ts +8 -0
- package/dist/chat/index.d.ts.map +1 -0
- package/dist/chat/index.js +7 -0
- package/dist/header/nav-item.d.ts +2 -1
- package/dist/header/nav-item.d.ts.map +1 -1
- package/dist/header/nav-item.js +2 -2
- package/dist/main/navbar.d.ts.map +1 -1
- package/dist/main/navbar.js +2 -2
- package/dist/misc/command.d.ts +3 -1
- package/dist/misc/command.d.ts.map +1 -1
- package/dist/misc/form.d.ts +1 -1
- package/dist/misc/form.d.ts.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Conversation } from "@myrjfa/state/dist/lib/models/chat";
|
|
2
|
+
interface ChatMainProps {
|
|
3
|
+
conversation: Conversation;
|
|
4
|
+
currentUserId: string;
|
|
5
|
+
onBack: () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function ChatMain({ conversation, currentUserId, onBack, }: ChatMainProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=ChatMain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatMain.d.ts","sourceRoot":"","sources":["../../src/chat/ChatMain.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAYvE,UAAU,aAAa;IACnB,YAAY,EAAE,YAAY,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,wBAAgB,QAAQ,CAAC,EACrB,YAAY,EACZ,aAAa,EACb,MAAM,GACT,EAAE,aAAa,2CAsYf"}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
39
|
+
import React, { useEffect, useRef, useCallback } from "react";
|
|
40
|
+
import { chatApi } from "@myrjfa/state/dist/lib/actions/chat";
|
|
41
|
+
import { useChatContext } from "@myrjfa/state/dist/lib/context/ChatContext";
|
|
42
|
+
import { MessageBubble } from "./MessageBubble";
|
|
43
|
+
import { MessageInput } from "./MessageInput";
|
|
44
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../misc/dropdown-menu";
|
|
45
|
+
import { ArrowLeft, Info, MoreVertical, Users } from "lucide-react";
|
|
46
|
+
import { useRouter } from "next/navigation";
|
|
47
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../misc/avatar";
|
|
48
|
+
import { MilestonePanel } from "./MilestonePanel";
|
|
49
|
+
import { ExpensePanel } from "./ExpensePanel";
|
|
50
|
+
import { ParticipantsDialog } from "./dialogs/ParticipantsDialog";
|
|
51
|
+
import { GroupInfoDialog } from "./dialogs/GroupInfoDialog";
|
|
52
|
+
import { GroupInfoViewDialog } from "./dialogs/GroupInfoViewDialog";
|
|
53
|
+
import { ListTodo, ReceiptText, MessageCircle, FilePenLine } from "lucide-react";
|
|
54
|
+
import { Button } from "../misc/button";
|
|
55
|
+
import { showAlert } from "../misc/alertdialogclaude";
|
|
56
|
+
export function ChatMain(_a) {
|
|
57
|
+
var _this = this;
|
|
58
|
+
var _b, _c;
|
|
59
|
+
var conversation = _a.conversation, currentUserId = _a.currentUserId, onBack = _a.onBack;
|
|
60
|
+
var _d = useChatContext(), socket = _d.socket, messages = _d.messages, setMessages = _d.setMessages, sendMessage = _d.sendMessage, typingUsers = _d.typingUsers, onlineUsers = _d.onlineUsers, editMessage = _d.editMessage, addMessageReaction = _d.addMessageReaction, deleteMessage = _d.deleteMessage, refreshConversations = _d.refreshConversations;
|
|
61
|
+
var messagesEndRef = useRef(null);
|
|
62
|
+
var messagesContainerRef = useRef(null);
|
|
63
|
+
var _e = React.useState("chat"), activePanel = _e[0], setActivePanel = _e[1];
|
|
64
|
+
var _f = React.useState(false), isParticipantsOpen = _f[0], setIsParticipantsOpen = _f[1];
|
|
65
|
+
var _g = React.useState(false), isGroupInfoOpen = _g[0], setIsGroupInfoOpen = _g[1];
|
|
66
|
+
var _h = React.useState(false), isGroupInfoViewOpen = _h[0], setIsGroupInfoViewOpen = _h[1];
|
|
67
|
+
var _j = React.useState(true), loading = _j[0], setLoading = _j[1];
|
|
68
|
+
var _k = React.useState(null), replyMessage = _k[0], setReplyMessage = _k[1];
|
|
69
|
+
var router = useRouter();
|
|
70
|
+
// Reset active panel when conversation changes
|
|
71
|
+
useEffect(function () {
|
|
72
|
+
setActivePanel("chat");
|
|
73
|
+
}, [conversation._id]);
|
|
74
|
+
useEffect(function () {
|
|
75
|
+
var cancelled = false;
|
|
76
|
+
setLoading(true);
|
|
77
|
+
chatApi
|
|
78
|
+
.getMessages(conversation._id, { limit: 50 })
|
|
79
|
+
.then(function (data) {
|
|
80
|
+
if (!cancelled) {
|
|
81
|
+
setMessages(data);
|
|
82
|
+
setLoading(false);
|
|
83
|
+
if (document.visibilityState === "visible") {
|
|
84
|
+
var unreadIds = data.filter(function (m) { var _a; return !((_a = m.readBy) === null || _a === void 0 ? void 0 : _a.some(function (r) { return r.userId === currentUserId; })) && m.senderId !== currentUserId; }).map(function (m) { return m._id; });
|
|
85
|
+
if (unreadIds.length > 0) {
|
|
86
|
+
chatApi.markRead(conversation._id, unreadIds).catch(console.error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
.catch(function (e) {
|
|
92
|
+
console.error(e);
|
|
93
|
+
if (!cancelled)
|
|
94
|
+
setLoading(false);
|
|
95
|
+
});
|
|
96
|
+
return function () {
|
|
97
|
+
cancelled = true;
|
|
98
|
+
};
|
|
99
|
+
}, [conversation._id, setMessages]);
|
|
100
|
+
useEffect(function () {
|
|
101
|
+
var _a;
|
|
102
|
+
(_a = messagesEndRef.current) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
|
|
103
|
+
}, [messages]);
|
|
104
|
+
var handleEdit = function (messageId, text) {
|
|
105
|
+
var newText = window.prompt("Edit message:", text);
|
|
106
|
+
if (newText !== null && newText.trim() !== "" && newText !== text) {
|
|
107
|
+
editMessage(messageId, newText);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var handleReact = function (messageId, emoji) {
|
|
111
|
+
if (emoji) {
|
|
112
|
+
addMessageReaction(messageId, emoji);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var handleDelete = function (messageId) {
|
|
116
|
+
if (window.confirm("Are you sure you want to unsend this message?")) {
|
|
117
|
+
deleteMessage(messageId);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var handleLeaveGroup = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
121
|
+
var err_1;
|
|
122
|
+
return __generator(this, function (_a) {
|
|
123
|
+
switch (_a.label) {
|
|
124
|
+
case 0:
|
|
125
|
+
if (!(isAdmin && conversation.participants.filter(function (p) { return p.chatRole == "admin"; }).length < 2)) return [3 /*break*/, 2];
|
|
126
|
+
return [4 /*yield*/, showAlert("Please assign someone else admin before leaving group")];
|
|
127
|
+
case 1:
|
|
128
|
+
_a.sent();
|
|
129
|
+
return [2 /*return*/];
|
|
130
|
+
case 2:
|
|
131
|
+
if (!window.confirm("Are you sure you want to leave this group?")) return [3 /*break*/, 6];
|
|
132
|
+
_a.label = 3;
|
|
133
|
+
case 3:
|
|
134
|
+
_a.trys.push([3, 5, , 6]);
|
|
135
|
+
return [4 /*yield*/, chatApi.removeParticipant(conversation._id, currentUserId)];
|
|
136
|
+
case 4:
|
|
137
|
+
_a.sent();
|
|
138
|
+
onBack();
|
|
139
|
+
return [3 /*break*/, 6];
|
|
140
|
+
case 5:
|
|
141
|
+
err_1 = _a.sent();
|
|
142
|
+
console.error("Failed to leave group:", err_1);
|
|
143
|
+
return [3 /*break*/, 6];
|
|
144
|
+
case 6: return [2 /*return*/];
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}); };
|
|
148
|
+
var handleDeleteConversation = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
149
|
+
var confirmMsg, err_2;
|
|
150
|
+
return __generator(this, function (_a) {
|
|
151
|
+
switch (_a.label) {
|
|
152
|
+
case 0:
|
|
153
|
+
confirmMsg = isGroup ? "Are you sure you want to delete this group?" : "Are you sure you want to delete this chat?";
|
|
154
|
+
if (!window.confirm(confirmMsg)) return [3 /*break*/, 4];
|
|
155
|
+
_a.label = 1;
|
|
156
|
+
case 1:
|
|
157
|
+
_a.trys.push([1, 3, , 4]);
|
|
158
|
+
return [4 /*yield*/, chatApi.deleteConversation(conversation._id)];
|
|
159
|
+
case 2:
|
|
160
|
+
_a.sent();
|
|
161
|
+
onBack();
|
|
162
|
+
return [3 /*break*/, 4];
|
|
163
|
+
case 3:
|
|
164
|
+
err_2 = _a.sent();
|
|
165
|
+
console.error("Failed to delete conversation:", err_2);
|
|
166
|
+
return [3 /*break*/, 4];
|
|
167
|
+
case 4: return [2 /*return*/];
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}); };
|
|
171
|
+
var handleSend = useCallback(function (text, files) {
|
|
172
|
+
sendMessage(conversation._id, text, files, replyMessage === null || replyMessage === void 0 ? void 0 : replyMessage._id);
|
|
173
|
+
setReplyMessage(null);
|
|
174
|
+
}, [conversation._id, sendMessage, replyMessage]);
|
|
175
|
+
var handleScrollToReply = function (messageId) {
|
|
176
|
+
var element = document.getElementById("message-".concat(messageId));
|
|
177
|
+
if (element) {
|
|
178
|
+
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
179
|
+
element.classList.add("ring-2", "ring-teal-500");
|
|
180
|
+
setTimeout(function () {
|
|
181
|
+
element.classList.remove("ring-2", "ring-teal-500");
|
|
182
|
+
}, 2000);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
var handleTypingStart = useCallback(function () {
|
|
186
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("typing:start", { conversationId: conversation._id });
|
|
187
|
+
}, [socket, conversation._id]);
|
|
188
|
+
var handleTypingStop = useCallback(function () {
|
|
189
|
+
socket === null || socket === void 0 ? void 0 : socket.emit("typing:stop", { conversationId: conversation._id });
|
|
190
|
+
}, [socket, conversation._id]);
|
|
191
|
+
function getDisplayName() {
|
|
192
|
+
if (conversation.type === "group")
|
|
193
|
+
return conversation.name || "Group Chat";
|
|
194
|
+
var other = conversation.participants.find(function (p) { return p.userId !== currentUserId; });
|
|
195
|
+
return (other === null || other === void 0 ? void 0 : other.userId) || "Chat";
|
|
196
|
+
}
|
|
197
|
+
function getSubtitle() {
|
|
198
|
+
if (conversation.type === "group") {
|
|
199
|
+
return "".concat(conversation.participants.length, " members");
|
|
200
|
+
}
|
|
201
|
+
var other = conversation.participants.find(function (p) { return p.userId !== currentUserId; });
|
|
202
|
+
if (other && onlineUsers.has(other.userId))
|
|
203
|
+
return "Online";
|
|
204
|
+
return "Offline";
|
|
205
|
+
}
|
|
206
|
+
var getSenderImage = function (senderId) {
|
|
207
|
+
var p = conversation.participants.find(function (p) { return p.userId === senderId; });
|
|
208
|
+
return (p === null || p === void 0 ? void 0 : p.profilePic) || "";
|
|
209
|
+
};
|
|
210
|
+
var handleInfo = function () {
|
|
211
|
+
if (conversation.type === "individual") {
|
|
212
|
+
var other = conversation.participants.find(function (p) { return p.userId !== currentUserId; });
|
|
213
|
+
if (other) {
|
|
214
|
+
router.push("/profile/".concat(other.role, "/").concat(other.userId));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (conversation.type === "group") {
|
|
218
|
+
setIsGroupInfoViewOpen(true);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
var typing = typingUsers.get(conversation._id);
|
|
222
|
+
var isGroup = conversation.type === "group";
|
|
223
|
+
var isAdmin = ((_b = conversation.participants.find(function (p) { return p.userId === currentUserId; })) === null || _b === void 0 ? void 0 : _b.chatRole) === "admin";
|
|
224
|
+
return (_jsxs("div", { className: "flex flex-col h-full bg-gray-50 dark:bg-gray-950", children: [_jsxs("div", { className: "flex items-center gap-3 px-4 py-3 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 shadow-sm flex-shrink-0", children: [_jsx(Button, { variant: "ghost", size: "icon", onClick: onBack, className: "md:hidden rounded-full", children: _jsx(ArrowLeft, { className: "w-5 h-5" }) }), _jsxs(Avatar, { className: "w-10 h-10 shadow-md flex-shrink-0", children: [_jsx(AvatarImage, { src: isGroup ? conversation.image : (_c = conversation.participants.find(function (p) { return p.userId !== currentUserId; })) === null || _c === void 0 ? void 0 : _c.profilePic }), _jsx(AvatarFallback, { className: isGroup
|
|
225
|
+
? "bg-gradient-to-br from-teal-500 to-cyan-600 text-white"
|
|
226
|
+
: "bg-gradient-to-br from-teal-400 to-cyan-500 text-white", children: isGroup ? _jsx(Users, { className: "w-5 h-5" }) : getDisplayName().charAt(0).toUpperCase() })] }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h2", { className: "font-semibold text-gray-900 dark:text-white truncate", children: getDisplayName() }), _jsx("p", { className: "text-xs text-gray-500", children: typing && typing.length > 0 ? (_jsx("span", { className: "text-teal-600 dark:text-teal-400 italic", children: typing.length === 1 ? "typing..." : "".concat(typing.length, " people typing...") })) : (getSubtitle()) })] }), _jsx(Button, { variant: "ghost", size: "icon", onClick: handleInfo, className: "rounded-full text-gray-400 flex-shrink-0", children: _jsx(Info, { className: "w-5 h-5" }) }), _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "rounded-full text-gray-400 flex-shrink-0", children: _jsx(MoreVertical, { className: "w-5 h-5" }) }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { onClick: handleInfo, children: "Info" }), isGroup && _jsxs(DropdownMenuItem, { onClick: function () { return setIsParticipantsOpen(true); }, children: [_jsx(Users, { className: "w-4 h-4 mr-2" }), isAdmin ? "Manage Members" : "View Members"] }), isAdmin && isGroup && (_jsxs(DropdownMenuItem, { onClick: function () { return setIsGroupInfoOpen(true); }, children: [_jsx(FilePenLine, { className: "w-4 h-4 mr-2" }), "Edit Group Info"] })), isAdmin && _jsx(DropdownMenuItem, { className: "text-red-500", onClick: handleDeleteConversation, children: isGroup ? "Delete Group" : "Delete Chat" }), isGroup && (_jsx(DropdownMenuItem, { className: "text-red-600", onClick: handleLeaveGroup, children: "Leave Group" }))] })] })] }), isGroup && (_jsxs("div", { className: "flex border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 flex-shrink-0", children: [_jsxs("button", { onClick: function () { return setActivePanel("chat"); }, className: "flex-1 py-3 px-4 text-center font-medium text-sm transition-colors border-b-2 ".concat(activePanel === "chat"
|
|
227
|
+
? "border-teal-500 text-teal-600 dark:text-teal-400"
|
|
228
|
+
: "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"), children: [_jsx(MessageCircle, { className: "w-4 h-4 inline mr-2" }), _jsx("span", { className: "hidden sm:inline", children: "Chat" })] }), _jsxs("button", { onClick: function () { return setActivePanel("milestones"); }, className: "flex-1 py-3 px-4 text-center font-medium text-sm transition-colors border-b-2 ".concat(activePanel === "milestones"
|
|
229
|
+
? "border-teal-500 text-teal-600 dark:text-teal-400"
|
|
230
|
+
: "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"), children: [_jsx(ListTodo, { className: "w-4 h-4 inline mr-2" }), _jsx("span", { className: "hidden sm:inline", children: "Milestones" })] }), _jsxs("button", { onClick: function () { return setActivePanel("expenses"); }, className: "flex-1 py-3 px-4 text-center font-medium text-sm transition-colors border-b-2 ".concat(activePanel === "expenses"
|
|
231
|
+
? "border-teal-500 text-teal-600 dark:text-teal-400"
|
|
232
|
+
: "border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"), children: [_jsx(ReceiptText, { className: "w-4 h-4 inline mr-2" }), _jsx("span", { className: "hidden sm:inline", children: "Expenses" })] })] })), _jsxs("div", { className: "flex-1 flex overflow-hidden gap-0", children: [_jsxs("div", { className: "flex flex-col min-w-0 ".concat(isGroup && activePanel !== "chat" ? "hidden md:flex" : "flex", " flex-1"), children: [_jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", ref: messagesContainerRef, children: loading ? (_jsx("div", { className: "flex items-center justify-center h-full", children: _jsx("div", { className: "animate-spin w-8 h-8 border-4 border-teal-500 border-t-transparent rounded-full" }) })) : messages.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center h-full text-gray-400", children: [_jsx("p", { className: "text-lg", children: "\uD83D\uDC4B Start the conversation!" }), _jsx("p", { className: "text-sm mt-1", children: "Send a message to get things going" })] })) : (_jsxs("div", { className: "max-w-4xl mx-auto space-y-0.5", children: [messages.map(function (msg, i) {
|
|
233
|
+
var prevMsg = messages[i - 1];
|
|
234
|
+
var showSender = isGroup &&
|
|
235
|
+
msg.senderId !== currentUserId &&
|
|
236
|
+
(!prevMsg || prevMsg.senderId !== msg.senderId);
|
|
237
|
+
return (_jsx("div", { id: "message-".concat(msg._id), className: "transition-all", children: _jsx(MessageBubble, { message: msg, isOwn: msg.senderId === currentUserId, showSender: showSender, senderProfilePic: getSenderImage(msg.senderId), onEdit: handleEdit, onDelete: handleDelete, onReply: function (msg) { return setReplyMessage(msg); }, onReactionAdd: handleReact, onScrollToReply: handleScrollToReply }) }, msg._id));
|
|
238
|
+
}), _jsx("div", { ref: messagesEndRef })] })) }), _jsx(MessageInput, { onSend: handleSend, onTypingStart: handleTypingStart, onTypingStop: handleTypingStop, replyTo: replyMessage, onCancelReply: function () { return setReplyMessage(null); }, onAddMilestone: function () { return setActivePanel("milestones"); }, onAddExpense: function () { return setActivePanel("expenses"); }, activePanel: activePanel })] }), isGroup && (_jsxs(_Fragment, { children: [activePanel === "milestones" && (_jsx("div", { className: "hidden md:flex md:flex-col w-80 border-l border-gray-200 dark:border-gray-700 min-w-0", children: _jsx(MilestonePanel, { conversationId: conversation._id, currentUserId: currentUserId }) })), activePanel === "expenses" && (_jsx("div", { className: "hidden md:flex md:flex-col w-80 border-l border-gray-200 dark:border-gray-700 min-w-0", children: _jsx(ExpensePanel, { conversationId: conversation._id, currentUserId: currentUserId }) }))] })), isGroup && activePanel === "milestones" && (_jsx("div", { className: "md:hidden w-full h-full overflow-hidden", children: _jsx(MilestonePanel, { conversationId: conversation._id, currentUserId: currentUserId, isFullScreen: true }) })), isGroup && activePanel === "expenses" && (_jsx("div", { className: "md:hidden w-full h-full overflow-hidden", children: _jsx(ExpensePanel, { conversationId: conversation._id, currentUserId: currentUserId, isFullScreen: true }) }))] }), _jsx(ParticipantsDialog, { open: isParticipantsOpen, onOpenChange: setIsParticipantsOpen, conversation: conversation, currentUserId: currentUserId, onUpdate: refreshConversations }), isGroup && (_jsx(GroupInfoDialog, { open: isGroupInfoOpen, onOpenChange: setIsGroupInfoOpen, conversation: conversation, onUpdate: refreshConversations })), isGroup && (_jsx(GroupInfoViewDialog, { open: isGroupInfoViewOpen, onOpenChange: setIsGroupInfoViewOpen, conversation: conversation }))] }));
|
|
239
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Conversation } from "@myrjfa/state/dist/lib/models/chat";
|
|
2
|
+
interface ChatSidebarProps {
|
|
3
|
+
conversations: Conversation[];
|
|
4
|
+
activeConversation: Conversation | null;
|
|
5
|
+
onSelect: (conv: Conversation) => void;
|
|
6
|
+
isConnected: boolean;
|
|
7
|
+
currentUserId: string;
|
|
8
|
+
onlineUsers?: Set<string>;
|
|
9
|
+
typingUsers?: Map<string, string[]>;
|
|
10
|
+
}
|
|
11
|
+
export declare function ChatSidebar({ conversations, activeConversation, onSelect, isConnected, currentUserId, onlineUsers, typingUsers }: ChatSidebarProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=ChatSidebar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatSidebar.d.ts","sourceRoot":"","sources":["../../src/chat/ChatSidebar.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAEvE,UAAU,gBAAgB;IACtB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,kBAAkB,EAAE,YAAY,GAAG,IAAI,CAAC;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACvC,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACvC;AAED,wBAAgB,WAAW,CAAC,EACxB,aAAa,EACb,kBAAkB,EAClB,QAAQ,EACR,WAAW,EACX,aAAa,EACb,WAAW,EACX,WAAW,EACd,EAAE,gBAAgB,2CAoElB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { MessageCircle, Plus, Wifi, WifiOff } from "lucide-react";
|
|
5
|
+
import { Input } from "../misc/input";
|
|
6
|
+
import { ScrollArea } from "../misc/scroll-area";
|
|
7
|
+
import { ConversationList } from "./ConversationList";
|
|
8
|
+
import { NewChatDialog } from "./dialogs/NewChatDialog";
|
|
9
|
+
import { NewGroupDialog } from "./dialogs/NewGroupDialog";
|
|
10
|
+
export function ChatSidebar(_a) {
|
|
11
|
+
var conversations = _a.conversations, activeConversation = _a.activeConversation, onSelect = _a.onSelect, isConnected = _a.isConnected, currentUserId = _a.currentUserId, onlineUsers = _a.onlineUsers, typingUsers = _a.typingUsers;
|
|
12
|
+
var _b = useState(false), showNewChat = _b[0], setShowNewChat = _b[1];
|
|
13
|
+
var _c = useState(""), search = _c[0], setSearch = _c[1];
|
|
14
|
+
var filteredConversations = Array.isArray(conversations)
|
|
15
|
+
? conversations.filter(function (c) {
|
|
16
|
+
var _a;
|
|
17
|
+
if (!search)
|
|
18
|
+
return true;
|
|
19
|
+
var name = c.name || ((_a = c.participants.find(function (p) { return p.userId !== currentUserId; })) === null || _a === void 0 ? void 0 : _a.userId) || "";
|
|
20
|
+
return name.toLowerCase().includes(search.toLowerCase());
|
|
21
|
+
})
|
|
22
|
+
: [];
|
|
23
|
+
return (_jsxs("div", { className: "w-full md:w-96 md:min-w-[384px] border-r border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 flex flex-col h-full ".concat(activeConversation ? "hidden md:flex" : "flex"), children: [_jsxs("div", { className: "flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(MessageCircle, { className: "w-6 h-6 text-teal-500" }), _jsx("h1", { className: "text-xl font-bold text-gray-900 dark:text-gray-100", children: "Messages" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex items-center gap-1", children: isConnected ? (_jsx(Wifi, { className: "w-4 h-4 text-green-500" })) : (_jsx(WifiOff, { className: "w-4 h-4 text-red-500" })) }), _jsx("button", { onClick: function () { return setShowNewChat(!showNewChat); }, className: "p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full transition-colors text-gray-600 dark:text-gray-400 cursor-pointer", children: _jsx(Plus, { className: "w-5 h-5" }) })] })] }), _jsx("div", { className: "p-3", children: _jsx(Input, { placeholder: "Search conversations...", value: search, onChange: function (e) { return setSearch(e.target.value); }, className: "bg-gray-100 dark:bg-gray-800 border-none" }) }), showNewChat && (_jsxs("div", { className: "px-3 pb-3 flex gap-2", children: [_jsx(NewChatDialog, {}), _jsx(NewGroupDialog, {})] })), _jsx(ScrollArea, { className: "flex-1", children: _jsx(ConversationList, { conversations: filteredConversations, activeConversation: activeConversation, onSelect: onSelect, currentUserId: currentUserId, onlineUsers: onlineUsers, typingUsers: typingUsers }) })] }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Conversation } from "@myrjfa/state/dist/lib/models/chat";
|
|
2
|
+
interface ChatListProps {
|
|
3
|
+
conversations: Conversation[];
|
|
4
|
+
activeConversation: Conversation | null;
|
|
5
|
+
onSelect: (conv: Conversation) => void;
|
|
6
|
+
currentUserId: string;
|
|
7
|
+
onlineUsers?: Set<string>;
|
|
8
|
+
typingUsers?: Map<string, string[]>;
|
|
9
|
+
}
|
|
10
|
+
export declare function ConversationList({ conversations, activeConversation, onSelect, currentUserId, onlineUsers, typingUsers }: ChatListProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=ConversationList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/chat/ConversationList.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAEvE,UAAU,aAAa;IACnB,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,kBAAkB,EAAE,YAAY,GAAG,IAAI,CAAC;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B,WAAW,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACvC;AAED,wBAAgB,gBAAgB,CAAC,EAC7B,aAAa,EACb,kBAAkB,EAClB,QAAQ,EACR,aAAa,EACb,WAAuB,EACvB,WAAuB,EAC1B,EAAE,aAAa,2CA6Gf"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Users, User, MessageCircle } from "lucide-react";
|
|
4
|
+
import { Avatar, AvatarFallback, AvatarImage } from "../misc/avatar";
|
|
5
|
+
import { cn } from "@myrjfa/state/dist/lib/utils";
|
|
6
|
+
export function ConversationList(_a) {
|
|
7
|
+
var conversations = _a.conversations, activeConversation = _a.activeConversation, onSelect = _a.onSelect, currentUserId = _a.currentUserId, _b = _a.onlineUsers, onlineUsers = _b === void 0 ? new Set() : _b, _c = _a.typingUsers, typingUsers = _c === void 0 ? new Map() : _c;
|
|
8
|
+
function getDisplayName(conv) {
|
|
9
|
+
if (conv.type === "group")
|
|
10
|
+
return conv.name || "Group Chat";
|
|
11
|
+
var other = conv.participants.find(function (p) { return p.userId !== currentUserId; });
|
|
12
|
+
// We might want to fetch user details if name is missing?
|
|
13
|
+
// For now, assume ID or fetched data.
|
|
14
|
+
// In real app, we should populate this.
|
|
15
|
+
return (other === null || other === void 0 ? void 0 : other.userId) || "Chat";
|
|
16
|
+
}
|
|
17
|
+
function getDisplayImage(conv) {
|
|
18
|
+
if (conv.type === "group")
|
|
19
|
+
return conv.image || "";
|
|
20
|
+
var other = conv.participants.find(function (p) { return p.userId !== currentUserId; });
|
|
21
|
+
return (other === null || other === void 0 ? void 0 : other.profilePic) || "";
|
|
22
|
+
}
|
|
23
|
+
// Identify the "other" user for avatar
|
|
24
|
+
function getOtherParticipantId(conv) {
|
|
25
|
+
if (conv.type === "group")
|
|
26
|
+
return null;
|
|
27
|
+
var other = conv.participants.find(function (p) { return p.userId !== currentUserId; });
|
|
28
|
+
return other === null || other === void 0 ? void 0 : other.userId;
|
|
29
|
+
}
|
|
30
|
+
function isOtherOnline(conv) {
|
|
31
|
+
if (conv.type !== "individual")
|
|
32
|
+
return false;
|
|
33
|
+
var otherAndId = getOtherParticipantId(conv);
|
|
34
|
+
return otherAndId ? onlineUsers.has(otherAndId) : false;
|
|
35
|
+
}
|
|
36
|
+
function formatTime(dateStr) {
|
|
37
|
+
if (!dateStr)
|
|
38
|
+
return "";
|
|
39
|
+
var date = new Date(dateStr);
|
|
40
|
+
var now = new Date();
|
|
41
|
+
var diff = now.getTime() - date.getTime();
|
|
42
|
+
if (diff < 60000)
|
|
43
|
+
return "Just now";
|
|
44
|
+
if (diff < 3600000)
|
|
45
|
+
return "".concat(Math.floor(diff / 60000), "m");
|
|
46
|
+
if (diff < 86400000)
|
|
47
|
+
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
48
|
+
return date.toLocaleDateString([], { month: "short", day: "numeric" });
|
|
49
|
+
}
|
|
50
|
+
function getTypingText(convId) {
|
|
51
|
+
var users = typingUsers.get(convId);
|
|
52
|
+
if (!users || users.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
if (users.length === 1)
|
|
55
|
+
return "typing...";
|
|
56
|
+
return "".concat(users.length, " people typing...");
|
|
57
|
+
}
|
|
58
|
+
if (conversations.length === 0) {
|
|
59
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center h-full text-gray-400 p-8", children: [_jsx(MessageCircle, { className: "w-16 h-16 mb-4 opacity-30" }), _jsx("p", { className: "text-lg font-medium", children: "No conversations yet" }), _jsx("p", { className: "text-sm mt-1", children: "Start a new chat to begin" })] }));
|
|
60
|
+
}
|
|
61
|
+
return (_jsx("div", { className: "flex-1 overflow-y-auto", children: conversations.map(function (conv) {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
var isActive = (activeConversation === null || activeConversation === void 0 ? void 0 : activeConversation._id) === conv._id;
|
|
64
|
+
var typing = getTypingText(conv._id);
|
|
65
|
+
var isOnline = isOtherOnline(conv);
|
|
66
|
+
return (_jsxs("button", { onClick: function () { return onSelect(conv); }, className: cn("w-full flex items-center gap-3 p-4 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-all duration-200 border-b border-gray-100 dark:border-gray-800", isActive ? "bg-indigo-50 dark:bg-indigo-950/30 border-l-4 border-l-indigo-500" : "border-l-4 border-l-transparent"), children: [_jsxs("div", { className: "relative", children: [_jsxs(Avatar, { children: [_jsx(AvatarImage, { src: getDisplayImage(conv) }), _jsx(AvatarFallback, { className: conv.type === "group" ? "bg-indigo-100 text-indigo-600" : "bg-teal-100 text-teal-600", children: conv.type === "group" ? _jsx(Users, { className: "w-4 h-4" }) : _jsx(User, { className: "w-4 h-4" }) })] }), isOnline && (_jsx("span", { className: "absolute bottom-0 right-0 w-3 h-3 bg-green-500 border-2 border-white dark:border-gray-900 rounded-full" }))] }), _jsxs("div", { className: "flex-1 min-w-0 text-left", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("span", { className: "font-semibold text-gray-900 dark:text-gray-100 truncate text-sm", children: getDisplayName(conv) }), _jsx("span", { className: "text-xs text-gray-400 ml-2 whitespace-nowrap", children: formatTime((_a = conv.lastMessage) === null || _a === void 0 ? void 0 : _a.sentAt) })] }), _jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 truncate mt-0.5", children: typing ? (_jsx("span", { className: "text-indigo-500 italic", children: typing })) : (((_b = conv.lastMessage) === null || _b === void 0 ? void 0 : _b.content) || "No messages yet") })] })] }, conv._id));
|
|
67
|
+
}) }));
|
|
68
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface ExpensePanelProps {
|
|
2
|
+
conversationId: string;
|
|
3
|
+
currentUserId: string;
|
|
4
|
+
isFullScreen?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function ExpensePanel({ conversationId, currentUserId, isFullScreen }: ExpensePanelProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=ExpensePanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExpensePanel.d.ts","sourceRoot":"","sources":["../../src/chat/ExpensePanel.tsx"],"names":[],"mappings":"AASA,UAAU,iBAAiB;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,YAAY,CAAC,EAAE,cAAc,EAAE,aAAa,EAAE,YAAoB,EAAE,EAAE,iBAAiB,2CAwStG"}
|